Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Fallback warp sync via RPC. #9

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions src/main/java/com/limechain/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

import com.limechain.client.HostNode;
import com.limechain.client.LightClient;
import com.limechain.config.HostConfig;
import com.limechain.rpc.server.AppBean;
import com.limechain.rpc.Function;
import com.limechain.rpc.RpcClient;
import com.limechain.rpc.server.RpcApp;
import com.limechain.utils.DivLogger;
import org.teavm.jso.JSBody;
import org.teavm.jso.core.JSString;

import java.util.logging.Level;

Expand All @@ -15,6 +17,8 @@ public class Main {

public static void main(String[] args) {
log.log("Starting LimeChain node...");
exportAPI(RpcClient::sendRpcRequest, JSString.valueOf("rpc"));

RpcApp rpcApp = new RpcApp();
rpcApp.start();

Expand All @@ -25,4 +29,8 @@ public static void main(String[] args) {
client.start();
log.log(Level.INFO, "\uD83D\uDE80Started light client!");
}

@JSBody(params = {"f", "apiName"}, script = "window[apiName] = f;" +
"isRpcExported = true;")
private static native void exportAPI(Function f, JSString apiName);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import lombok.Getter;
import lombok.ToString;

import java.util.Arrays;
import java.util.Map;

/**
Expand Down Expand Up @@ -51,14 +50,14 @@ public static LightSyncState decode(Map<String, String> lightSyncStateMap) {
LightSyncState lightSyncState = new LightSyncState();
byte[] bytes = StringUtils.hexToBytes(header);
lightSyncState.finalizedBlockHeader = new BlockHeaderReader()
.read(new ScaleCodecReader(bytes));
.read(new ScaleCodecReader(bytes));

byte[] bytes1 = StringUtils.hexToBytes(epochChanges);
lightSyncState.epochChanges = new EpochChangesReader()
.read(new ScaleCodecReader(bytes1));
.read(new ScaleCodecReader(bytes1));

lightSyncState.grandpaAuthoritySet = new AuthoritySetReader()
.read(new ScaleCodecReader(StringUtils.hexToBytes(grandpaAuthoritySet)));
.read(new ScaleCodecReader(StringUtils.hexToBytes(grandpaAuthoritySet)));

return lightSyncState;
}
Expand Down
26 changes: 17 additions & 9 deletions src/main/java/com/limechain/client/LightClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,21 @@
import com.limechain.network.Network;
import com.limechain.rpc.server.AppBean;
import com.limechain.sync.warpsync.WarpSyncMachine;
import com.limechain.utils.DivLogger;
import lombok.SneakyThrows;
import lombok.extern.java.Log;

import java.util.logging.Level;

/**
* Main light client class that starts and stops execution of
* the client and hold references to dependencies
*/
@Log
public class LightClient implements HostNode {
// TODO: Add service dependencies i.e rpc, sync, network, etc.
// TODO: Do we need those as fields here...?
private final Network network;
private WarpSyncMachine warpSyncMachine;

private static final DivLogger log = new DivLogger();

/**
* @implNote the RpcApp is assumed to have been started before constructing the client,
Expand All @@ -33,16 +33,24 @@ public LightClient() {
@SneakyThrows
public void start() {
this.network.start();
while (true) {
WarpSyncMachine warpSyncMachine = AppBean.getBean(WarpSyncMachine.class);

log.log(Level.INFO, "Syncing to latest finalized block state...");

int retryCount = 0;
while (retryCount < 3) {
this.network.updateCurrentSelectedPeer();

if (this.network.getKademliaService().getSuccessfulBootNodes() > 0) {
log.log(Level.INFO, "Node successfully connected to a peer! Sync can start!");
this.warpSyncMachine = AppBean.getBean(WarpSyncMachine.class);
this.warpSyncMachine.start();
warpSyncMachine.setProtocolSync(true);
break;
} else {
retryCount++;
System.out.println("Waiting to retry peer connection...");
Thread.sleep(2000);
}
log.log(Level.INFO, "Waiting for peer connection...");
Thread.sleep(10000);
}

warpSyncMachine.start();
}
}
18 changes: 14 additions & 4 deletions src/main/java/com/limechain/config/HostConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.limechain.utils.DivLogger;
import lombok.Getter;

import java.util.List;
import java.util.logging.Level;

/**
Expand All @@ -19,10 +20,10 @@ public class HostConfig {
// private final NodeRole nodeRole;
private final String rpcNodeAddress;

private String polkadotGenesisPath = "genesis/polkadot.json";
private String kusamaGenesisPath = "genesis/ksmcc3.json";
private String westendGenesisPath = "genesis/westend2.json";
private String localGenesisPath = "genesis/westend-local.json";
private final String polkadotGenesisPath = "genesis/polkadot.json";
private final String kusamaGenesisPath = "genesis/ksmcc3.json";
private final String westendGenesisPath = "genesis/westend2.json";
private final String localGenesisPath = "genesis/westend-local.json";

private static final DivLogger log = new DivLogger();

Expand Down Expand Up @@ -66,4 +67,13 @@ public String getGenesisPath() {
case LOCAL -> localGenesisPath;
};
}

public List<String> getHttpsRpcEndpoints() {
return switch (chain) {
case POLKADOT -> RpcConstants.POLKADOT_HTTPS_RPC;
case KUSAMA -> RpcConstants.KUSAMA_HTTPS_RPC;
case WESTEND -> RpcConstants.WESTEND_HTTPS_RPC;
case LOCAL -> List.of();
};
}
}
12 changes: 3 additions & 9 deletions src/main/java/com/limechain/config/SystemInfo.java
Original file line number Diff line number Diff line change
@@ -1,33 +1,28 @@
package com.limechain.config;

import com.limechain.chain.Chain;
import com.limechain.storage.block.SyncState;
import com.limechain.utils.DivLogger;
import lombok.Getter;

import java.math.BigInteger;
import java.util.Map;
import java.util.logging.Level;

/**
* Configuration class used to hold and information used by the system rpc methods
*/
@Getter
public class SystemInfo {
// private final String role;
// private final String role;
private final Chain chain;
// private final String hostIdentity;
// private final String hostIdentity;
private String hostName = "Fruzhin";
private String hostVersion = "0.0.1";
private final BigInteger highestBlock;

private static final DivLogger log = new DivLogger();

public SystemInfo(HostConfig hostConfig, SyncState syncState) {
public SystemInfo(HostConfig hostConfig) {
// this.role = network.getNodeRole().name();
this.chain = hostConfig.getChain();
// this.hostIdentity = network.getHost().getPeerId().toString();
this.highestBlock = syncState.getLastFinalizedBlockNumber();
logSystemInfo();
}

Expand All @@ -48,6 +43,5 @@ public void logSystemInfo() {
// log.log(Level.INFO, authEmoji + "Role: " + role);
// log.log(Level.INFO, "Local node identity is: " + hostIdentity);
log.log(Level.INFO, "Operating System: " + System.getProperty("os.name"));
log.log(Level.INFO, "Highest known block at #" + highestBlock);
}
}
18 changes: 18 additions & 0 deletions src/main/java/com/limechain/constants/RpcConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,27 @@
import lombok.AccessLevel;
import lombok.NoArgsConstructor;

import java.util.List;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class RpcConstants {
public static final String POLKADOT_WS_RPC = "wss://rpc.polkadot.io";
public static final String KUSAMA_WS_RPC = "wss://kusama-rpc.polkadot.io";
public static final String WESTEND_WS_RPC = "wss://westend-rpc.polkadot.io";

public static final List<String> POLKADOT_HTTPS_RPC = List.of(
"https://rpc.ibp.network/polkadot",
"https://polkadot-rpc.dwellir.com",
"https://rpc.polkadot.io"
);
public static final List<String> KUSAMA_HTTPS_RPC = List.of(
"https://rpc.ibp.network/kusama",
"https://kusama-rpc.dwellir.com",
"https://kusama-rpc.polkadot.io"
);
public static final List<String> WESTEND_HTTPS_RPC = List.of(
"https://rpc.ibp.network/westend",
"https://westend-rpc.dwellir.com",
"https://westend-rpc.polkadot.io"
);
}
27 changes: 27 additions & 0 deletions src/main/java/com/limechain/rpc/BlockRpcClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.limechain.rpc;

import com.limechain.polkaj.Hash256;
import com.limechain.rpc.dto.ChainGetHeaderResult;
import com.limechain.rpc.dto.GrandpaRoundStateResult;
import com.limechain.rpc.dto.RpcMethod;
import com.limechain.rpc.dto.RpcResponse;

import java.util.List;

public final class BlockRpcClient extends RpcClient {

public static Hash256 getLastFinalizedBlockHash() {
RpcResponse response = sendRpcRequest(RpcMethod.CHAIN_GET_FINALIZED_HEAD, List.of());
return Hash256.from(getResult(response, String.class));
}

public static ChainGetHeaderResult getHeader(String blockHash) {
RpcResponse response = sendRpcRequest(RpcMethod.CHAIN_GET_HEADER, List.of(blockHash));
return getResult(response, ChainGetHeaderResult.class);
}

public static GrandpaRoundStateResult getGrandpaRoundState() {
RpcResponse response = sendRpcRequest(RpcMethod.GRANDPA_ROUND_STATE, List.of());
return getResult(response, GrandpaRoundStateResult.class);
}
}
8 changes: 8 additions & 0 deletions src/main/java/com/limechain/rpc/Function.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.limechain.rpc;

import org.teavm.jso.JSObject;

public interface Function extends JSObject {

String sendRequest(String method, String[] params);
}
22 changes: 22 additions & 0 deletions src/main/java/com/limechain/rpc/LoadBalancer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.limechain.rpc;

import com.limechain.config.HostConfig;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

public class LoadBalancer {

private final List<String> endpoints;
private final AtomicInteger index;

public LoadBalancer(HostConfig hostConfig) {
this.endpoints = hostConfig.getHttpsRpcEndpoints();
this.index = new AtomicInteger(0);
}

public String getNextEndpoint() {
int currentIndex = index.getAndUpdate(i -> (i + 1) % endpoints.size());
return endpoints.get(currentIndex);
}
}
50 changes: 50 additions & 0 deletions src/main/java/com/limechain/rpc/RpcClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.limechain.rpc;

import com.limechain.config.HostConfig;
import com.limechain.rpc.dto.RpcMethod;
import com.limechain.rpc.dto.RpcRequest;
import com.limechain.rpc.dto.RpcResponse;
import com.limechain.rpc.server.AppBean;
import com.limechain.teavm.HttpRequest;
import com.limechain.utils.json.JsonUtil;
import com.limechain.utils.json.ObjectMapper;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

@NoArgsConstructor(access = AccessLevel.PROTECTED)
public sealed class RpcClient permits BlockRpcClient {

private static final String POST = "POST";

private static final AtomicInteger ID_COUNTER = new AtomicInteger(1);
private static final LoadBalancer LOAD_BALANCER = new LoadBalancer(AppBean.getBean(HostConfig.class));
protected static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(false);

private static String createRpcRequestJson(String method, List<Object> params) {
RpcRequest request = new RpcRequest(ID_COUNTER.getAndAdd(1), method, params);
return JsonUtil.stringify(request);
}

public static String sendRpcRequest(String method, Object[] params) {
return HttpRequest.createHttpRequest(POST, LOAD_BALANCER.getNextEndpoint(),
createRpcRequestJson(method, List.of(params)));
}

protected static RpcResponse sendRpcRequest(RpcMethod method, List<Object> params) {
String jsonResult = HttpRequest.asyncHttpRequest(POST, LOAD_BALANCER.getNextEndpoint(),
createRpcRequestJson(method.getMethod(), params));
return OBJECT_MAPPER.mapToClass(jsonResult, RpcResponse.class);
}

protected static <T> T getResult(RpcResponse response, Class<T> type) {
if (response.getError() != null) {
throw new IllegalStateException("RPC request resulted in an error with code:" + response.getError().getCode()
+ " and message:" + response.getError().getMessage());
}

return OBJECT_MAPPER.mapToClass(JsonUtil.stringify(response.getResult()), type);
}
}
15 changes: 15 additions & 0 deletions src/main/java/com/limechain/rpc/dto/ChainGetHeaderResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.limechain.rpc.dto;

import com.limechain.teavm.annotation.Reflectable;
import lombok.Data;

@Data
@Reflectable
public class ChainGetHeaderResult {

private String parentHash;
private String number;
private String stateRoot;
private String extrinsicsRoot;
private Digest digest;
}
13 changes: 13 additions & 0 deletions src/main/java/com/limechain/rpc/dto/Digest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.limechain.rpc.dto;

import com.limechain.teavm.annotation.Reflectable;
import lombok.Data;

import java.util.List;

@Data
@Reflectable
public class Digest {

private List<String> logs;
}
15 changes: 15 additions & 0 deletions src/main/java/com/limechain/rpc/dto/GrandpaRoundStateResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.limechain.rpc.dto;

import com.limechain.teavm.annotation.Reflectable;
import lombok.Data;

import java.util.List;

@Data
@Reflectable
public class GrandpaRoundStateResult {

private int setId;
private RoundState best;
private List<RoundState> background;
}
14 changes: 14 additions & 0 deletions src/main/java/com/limechain/rpc/dto/RoundState.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.limechain.rpc.dto;

import com.limechain.teavm.annotation.Reflectable;
import lombok.Data;

@Data
@Reflectable
public class RoundState {
private int round;
private int totalWeight;
private int thresholdWeight;
private Votes prevotes;
private Votes precommits;
}
Loading
Loading