Skip to content

Commit

Permalink
Merge pull request #246 from LimeChain/feat/242-implement-trie-structure
Browse files Browse the repository at this point in the history
Introduce FullClient
  • Loading branch information
David-Petrov authored Nov 24, 2023
2 parents b5b3aef + c15b6d3 commit 7743804
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 98 deletions.
50 changes: 47 additions & 3 deletions src/main/java/com/limechain/Main.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,59 @@
package com.limechain;

import com.limechain.lightclient.LightClient;
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.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();
LightClient client = new LightClient(args, 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;

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();
Signal.handle(new Signal("INT"), signal -> client.stop());
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);
});
}
}
2 changes: 2 additions & 0 deletions src/main/java/com/limechain/cli/Cli.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ 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);
Expand Down
41 changes: 41 additions & 0 deletions src/main/java/com/limechain/client/FullNode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
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();
}
}
12 changes: 12 additions & 0 deletions src/main/java/com/limechain/client/HostNode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
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();
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package com.limechain.lightclient;
package com.limechain.client;

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;
Expand All @@ -15,28 +14,26 @@
* the client and hold references to dependencies
*/
@Log
public class LightClient {
public class LightClient implements HostNode {
// TODO: Add service dependencies i.e rpc, sync, network, etc.
private final String[] cliArgs;
private final RpcApp rpcApp;
// TODO: Do we need those as fields here...?
private final ConnectionManager connectionManager = ConnectionManager.getInstance();
private Network network;
private final Network network;
private WarpSyncMachine warpSyncMachine;

public LightClient(String[] cliArgs, RpcApp rpcApp) {
this.cliArgs = cliArgs;
this.rpcApp = rpcApp;
/**
* @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);
}

/**
* Starts the light client by instantiating all dependencies and services
*/
@SneakyThrows
public void start() {
// TODO: Add business logic
this.rpcApp.start(cliArgs);

this.network = AppBean.getBean(Network.class);
this.network.start();

while (true) {
Expand All @@ -45,7 +42,6 @@ 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();
Expand All @@ -55,20 +51,4 @@ 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);
}
}
34 changes: 26 additions & 8 deletions src/main/java/com/limechain/rpc/server/RpcApp.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package com.limechain.rpc.server;

import com.limechain.config.SystemInfo;
import org.springframework.boot.ApplicationArguments;
import com.limechain.network.Network;
import com.limechain.sync.warpsync.WarpSyncMachine;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
Expand All @@ -14,10 +15,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 {

Expand All @@ -26,6 +27,16 @@ 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
*/
Expand All @@ -36,11 +47,9 @@ public class RpcApp {
*
* @param cliArgs arguments that will be passed as
* ApplicationArguments to {@link com.limechain.rpc.config.CommonConfig}.
* @see com.limechain.rpc.config.CommonConfig#hostConfig(ApplicationArguments)
* @see com.limechain.rpc.config.CommonConfig#hostConfig(com.limechain.cli.CliArguments)
*/
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;
Expand All @@ -50,6 +59,15 @@ 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();
}
Expand Down
54 changes: 0 additions & 54 deletions src/test/java/com/limechain/lightclient/LightClientTest.java

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ public void stopNode() {
}

@Test
@Disabled("Integration test")
@Disabled("No response is received")
//TODO: See https://github.com/orgs/LimeChain/projects/16?pane=issue&itemId=40022251
void remoteBlockRequest_returnCorrectBlock_ifGivenBlockHash() {
var peerId = PeerId.fromBase58(PEER_ID);
//CHECKSTYLE.OFF
Expand All @@ -77,7 +78,8 @@ void remoteBlockRequest_returnCorrectBlock_ifGivenBlockHash() {
}

@Test
@Disabled("Integration test")
@Disabled("No response is received")
//TODO: See https://github.com/orgs/LimeChain/projects/16?pane=issue&itemId=40022251
void remoteBlockRequest_returnCorrectBlock_ifGivenBlockNumber() {
var peerId = PeerId.fromBase58(PEER_ID);
//CHECKSTYLE.OFF
Expand All @@ -99,7 +101,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};
Expand Down

0 comments on commit 7743804

Please sign in to comment.