diff --git a/src/main/java/com/limechain/Main.java b/src/main/java/com/limechain/Main.java index 4076c91b6..91835e820 100644 --- a/src/main/java/com/limechain/Main.java +++ b/src/main/java/com/limechain/Main.java @@ -1,59 +1,15 @@ package com.limechain; -import com.limechain.config.HostConfig; -import com.limechain.client.FullNode; -import com.limechain.client.LightClient; -import com.limechain.client.HostNode; -import com.limechain.network.protocol.blockannounce.NodeRole; -import com.limechain.rpc.server.AppBean; +import com.limechain.lightclient.LightClient; import com.limechain.rpc.server.RpcApp; -import lombok.extern.java.Log; import sun.misc.Signal; -import java.util.logging.Level; - -@Log public class Main { - public static void main(String[] args) { - // Instantiate and start the spring application, so we get the global context RpcApp rpcApp = new RpcApp(); - rpcApp.start(args); - - // Figure out what client role we want to start - HostConfig hostConfig = AppBean.getBean(HostConfig.class); - final NodeRole nodeRole = hostConfig.getNodeRole(); - HostNode client; + LightClient client = new LightClient(args, rpcApp); - switch (nodeRole) { - case FULL -> { - client = new FullNode(); - } - case LIGHT -> { - client = new LightClient(); - } - case NONE -> { - // This shouldn't happen. - // TODO: don't use this enum for the CLI NodeRole option - return; - } - default -> { - log.log(Level.SEVERE, "Node role {0} not yet implemented.", nodeRole); - return; - } - } - - // Start the client - // NOTE: This starts the beans the client would need - mutates the global context client.start(); - log.log(Level.INFO, "\uD83D\uDE80Started {0} client!", nodeRole); - - Signal.handle(new Signal("INT"), signal -> { - rpcApp.stop(); // NOTE: rpcApp is responsible for stopping everything that could've been started - - // TODO: Maybe think of another place to hold the logic below - log.log(Level.INFO, "\uD83D\uDED1Stopped {0} client!", nodeRole); - System.exit(0); - }); + Signal.handle(new Signal("INT"), signal -> client.stop()); } } \ No newline at end of file diff --git a/src/main/java/com/limechain/cli/Cli.java b/src/main/java/com/limechain/cli/Cli.java index b3b76e50d..f7e28212d 100644 --- a/src/main/java/com/limechain/cli/Cli.java +++ b/src/main/java/com/limechain/cli/Cli.java @@ -55,8 +55,6 @@ public CliArguments parseArgs(String[] args) { String dbPath = cmd.getOptionValue(DB_PATH, DBInitializer.DEFAULT_DIRECTORY); boolean dbRecreate = cmd.hasOption(DB_RECREATE); String nodeKey = cmd.getOptionValue(NODE_KEY); - // TODO: separation of enums; this NodeRole enum is used for blockannounce - // what does running the node in NodeMode NONE mean? String nodeMode = cmd.getOptionValue(NODE_MODE, NodeRole.FULL.toString()); return new CliArguments(network, dbPath, dbRecreate, nodeKey, nodeMode); diff --git a/src/main/java/com/limechain/client/FullNode.java b/src/main/java/com/limechain/client/FullNode.java deleted file mode 100644 index 73a16c7cc..000000000 --- a/src/main/java/com/limechain/client/FullNode.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.limechain.client; - -import com.limechain.network.Network; -import com.limechain.rpc.server.AppBean; -import com.limechain.sync.warpsync.WarpSyncMachine; -import lombok.SneakyThrows; -import lombok.extern.java.Log; - -import java.util.logging.Level; - -@Log -public class FullNode implements HostNode { - /** - * Starts the light client by instantiating all dependencies and services - * - * @implNote the RpcApp is assumed to have been started before starting the client, - * as it relies on the application context - */ - @SneakyThrows - public void start() { - // TODO: Initialize storage here (temporally) - - final Network network = AppBean.getBean(Network.class); - network.start(); - - while (true) { - if (!network.kademliaService.getBootNodePeerIds().isEmpty()) { - if (network.kademliaService.getSuccessfulBootNodes() > 0) { - break; - } - network.updateCurrentSelectedPeer(); - } - - log.log(Level.INFO, "Waiting for peer connection..."); - Thread.sleep(10000); // TODO: Maybe extract this number into an application.property as it's duplicated - } - - log.log(Level.INFO, "Node successfully connected to a peer! Sync can start!"); - AppBean.getBean(WarpSyncMachine.class).start(); - } -} diff --git a/src/main/java/com/limechain/client/HostNode.java b/src/main/java/com/limechain/client/HostNode.java deleted file mode 100644 index e8c86af82..000000000 --- a/src/main/java/com/limechain/client/HostNode.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.limechain.client; - -public interface HostNode { - /** - * Starts the client by assigning all dependencies and services from the spring boot application's context - * - * @apiNote the RpcApp is assumed to have been started - * before constructing the clients in our current implementations, - * as starting the clients relies on the application context - */ - void start(); -} diff --git a/src/main/java/com/limechain/client/LightClient.java b/src/main/java/com/limechain/lightclient/LightClient.java similarity index 64% rename from src/main/java/com/limechain/client/LightClient.java rename to src/main/java/com/limechain/lightclient/LightClient.java index 506f95cf1..e18d82fa5 100644 --- a/src/main/java/com/limechain/client/LightClient.java +++ b/src/main/java/com/limechain/lightclient/LightClient.java @@ -1,8 +1,9 @@ -package com.limechain.client; +package com.limechain.lightclient; import com.limechain.network.ConnectionManager; import com.limechain.network.Network; import com.limechain.rpc.server.AppBean; +import com.limechain.rpc.server.RpcApp; import com.limechain.sync.warpsync.WarpSyncMachine; import lombok.SneakyThrows; import lombok.extern.java.Log; @@ -14,19 +15,17 @@ * the client and hold references to dependencies */ @Log -public class LightClient implements HostNode { +public class LightClient { // TODO: Add service dependencies i.e rpc, sync, network, etc. - // TODO: Do we need those as fields here...? + private final String[] cliArgs; + private final RpcApp rpcApp; private final ConnectionManager connectionManager = ConnectionManager.getInstance(); - private final Network network; + private Network network; private WarpSyncMachine warpSyncMachine; - /** - * @implNote the RpcApp is assumed to have been started before constructing the client, - * as it relies on the application context - */ - public LightClient() { - this.network = AppBean.getBean(Network.class); + public LightClient(String[] cliArgs, RpcApp rpcApp) { + this.cliArgs = cliArgs; + this.rpcApp = rpcApp; } /** @@ -34,6 +33,10 @@ public LightClient() { */ @SneakyThrows public void start() { + // TODO: Add business logic + this.rpcApp.start(cliArgs); + + this.network = AppBean.getBean(Network.class); this.network.start(); while (true) { @@ -42,6 +45,7 @@ public void start() { log.log(Level.INFO, "Node successfully connected to a peer! Sync can start!"); this.warpSyncMachine = AppBean.getBean(WarpSyncMachine.class); this.warpSyncMachine.start(); + log.log(Level.INFO, "\uD83D\uDE80Started light client!"); break; } else { this.network.updateCurrentSelectedPeer(); @@ -51,4 +55,20 @@ public void start() { Thread.sleep(10000); } } + + /** + * Stops the light client by shutting down all running services + */ + public void stop() { + // TODO: Stop running services + this.warpSyncMachine.stop(); + this.network.stop(); + this.rpcApp.stop(); + log.log(Level.INFO, "\uD83D\uDED1Stopped light client!"); + doExit(); + } + + protected void doExit() { + System.exit(0); + } } diff --git a/src/main/java/com/limechain/rpc/server/RpcApp.java b/src/main/java/com/limechain/rpc/server/RpcApp.java index bf8114a85..f323c55dc 100644 --- a/src/main/java/com/limechain/rpc/server/RpcApp.java +++ b/src/main/java/com/limechain/rpc/server/RpcApp.java @@ -1,8 +1,7 @@ package com.limechain.rpc.server; import com.limechain.config.SystemInfo; -import com.limechain.network.Network; -import com.limechain.sync.warpsync.WarpSyncMachine; +import org.springframework.boot.ApplicationArguments; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; @@ -15,10 +14,10 @@ */ @SpringBootApplication @ComponentScan(basePackages = { - "com.limechain.rpc.config", - "com.limechain.rpc.methods", - "com.limechain.rpc.server", - "com.limechain.storage" + "com.limechain.rpc.config", + "com.limechain.rpc.methods", + "com.limechain.rpc.server", + "com.limechain.storage" }) public class RpcApp { @@ -27,16 +26,6 @@ public class RpcApp { */ private final String serverPort = "9922"; - /** - * The reference to the underlying SpringApplication - */ - private final SpringApplication app; - - public RpcApp() { - this.app = new SpringApplication(RpcApp.class); - app.setDefaultProperties(Collections.singletonMap("server.port", serverPort)); - } - /** * Spring application context */ @@ -47,9 +36,11 @@ public RpcApp() { * * @param cliArgs arguments that will be passed as * ApplicationArguments to {@link com.limechain.rpc.config.CommonConfig}. - * @see com.limechain.rpc.config.CommonConfig#hostConfig(com.limechain.cli.CliArguments) + * @see com.limechain.rpc.config.CommonConfig#hostConfig(ApplicationArguments) */ public void start(String[] cliArgs) { + SpringApplication app = new SpringApplication(RpcApp.class); + app.setDefaultProperties(Collections.singletonMap("server.port", serverPort)); ConfigurableApplicationContext ctx = app.run(cliArgs); ctx.getBean(SystemInfo.class).logSystemInfo(); this.springCtx = ctx; @@ -59,15 +50,6 @@ public void start(String[] cliArgs) { * Shuts down the spring application as well as any services that it's using */ public void stop() { - // TODO: This is untestable with our current design... but do we need to test it really? - // (I mean verifying that everything necessary has been stopped) - - // TODO: Think of a way to make those beans lifecycle-aware so that stopping the context would propagate - // to stopping the necessary instances - // Perhaps Spring can handle this for us instead of us manually stopping the beans? - AppBean.getBean(WarpSyncMachine.class).stop(); - AppBean.getBean(Network.class).stop(); - if (this.springCtx != null) { this.springCtx.stop(); } diff --git a/src/test/java/com/limechain/lightclient/LightClientTest.java b/src/test/java/com/limechain/lightclient/LightClientTest.java new file mode 100644 index 000000000..6bafb1bac --- /dev/null +++ b/src/test/java/com/limechain/lightclient/LightClientTest.java @@ -0,0 +1,54 @@ +package com.limechain.lightclient; + +import com.limechain.network.Network; +import com.limechain.rpc.server.RpcApp; +import com.limechain.sync.warpsync.WarpSyncMachine; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Field; + +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +class LightClientTest { + private LightClient lightClient; + + private RpcApp rpcApp; + private String[] args; + + // Setting private fields. Not a good idea in general + private void setPrivateField(String fieldName, Object value) + throws NoSuchFieldException, IllegalAccessException { + Field privateField = LightClient.class.getDeclaredField(fieldName); + privateField.setAccessible(true); + + privateField.set(lightClient, value); + } + + @BeforeEach + public void setup() { + rpcApp = mock(RpcApp.class); + args = new String[]{"some args"}; + + lightClient = spy(new LightClient(args, rpcApp)); + } + + @Test + void lightClient_stop_invokesStopFunctions() throws NoSuchFieldException, IllegalAccessException { + Network network = mock(Network.class); + WarpSyncMachine warpSync = mock(WarpSyncMachine.class); + + setPrivateField("network", network); + setPrivateField("warpSyncMachine", warpSync); + doNothing().when(lightClient).doExit(); + + lightClient.stop(); + + verify(network, times(1)).stop(); + verify(rpcApp, times(1)).stop(); + } +} diff --git a/src/test/java/com/limechain/network/protocol/sync/SyncTest.java b/src/test/java/com/limechain/network/protocol/sync/SyncTest.java index c6733a9f3..ff70ff907 100644 --- a/src/test/java/com/limechain/network/protocol/sync/SyncTest.java +++ b/src/test/java/com/limechain/network/protocol/sync/SyncTest.java @@ -56,8 +56,7 @@ public void stopNode() { } @Test - @Disabled("No response is received") - //TODO: See https://github.com/orgs/LimeChain/projects/16?pane=issue&itemId=40022251 + @Disabled("Integration test") void remoteBlockRequest_returnCorrectBlock_ifGivenBlockHash() { var peerId = PeerId.fromBase58(PEER_ID); //CHECKSTYLE.OFF @@ -78,8 +77,7 @@ void remoteBlockRequest_returnCorrectBlock_ifGivenBlockHash() { } @Test - @Disabled("No response is received") - //TODO: See https://github.com/orgs/LimeChain/projects/16?pane=issue&itemId=40022251 + @Disabled("Integration test") void remoteBlockRequest_returnCorrectBlock_ifGivenBlockNumber() { var peerId = PeerId.fromBase58(PEER_ID); //CHECKSTYLE.OFF @@ -101,7 +99,7 @@ void remoteBlockRequest_returnCorrectBlock_ifGivenBlockNumber() { @Test @Disabled("No response is received") - //TODO: See https://github.com/orgs/LimeChain/projects/16?pane=issue&itemId=40022251 + //TODO: See https://github.com/orgs/LimeChain/projects/16?pane=issue&itemId=40022251 void remoteFunctions_return_correctData() { var peerId = PeerId.fromBase58(PEER_ID); var receivers = new String[]{"/dns/p2p.4.polkadot.network/tcp/30333/p2p/" + PEER_ID};