From ce6d95201dbd6c9f198d95b068f3eb9644358302 Mon Sep 17 00:00:00 2001 From: Yordan Atanasov Date: Tue, 27 Aug 2024 21:17:00 +0300 Subject: [PATCH 1/5] feat: implement functionality to make rpc call to other nodes. --- .../java/com/limechain/config/HostConfig.java | 18 ++++++-- .../com/limechain/constants/RpcConstants.java | 18 ++++++++ .../com/limechain/rpc/BlockRpcClient.java | 27 +++++++++++ .../java/com/limechain/rpc/LoadBalancer.java | 22 +++++++++ .../java/com/limechain/rpc/RpcClient.java | 45 +++++++++++++++++++ .../rpc/dto/ChainGetHeaderResult.java | 15 +++++++ .../java/com/limechain/rpc/dto/Digest.java | 13 ++++++ .../rpc/dto/GrandpaRoundStateResult.java | 15 +++++++ .../com/limechain/rpc/dto/RoundState.java | 14 ++++++ .../java/com/limechain/rpc/dto/RpcError.java | 13 ++++++ .../java/com/limechain/rpc/dto/RpcMethod.java | 17 +++++++ .../com/limechain/rpc/dto/RpcRequest.java | 20 +++++++++ .../com/limechain/rpc/dto/RpcResponse.java | 16 +++++++ .../java/com/limechain/rpc/dto/Votes.java | 14 ++++++ .../limechain/rpc/server/CommonConfig.java | 3 +- .../limechain/storage/block/SyncState.java | 1 - .../java/com/limechain/teavm/HttpRequest.java | 2 +- .../com/limechain/utils/json/JsonUtil.java | 6 +-- .../limechain/utils/json/ObjectMapper.java | 12 ++--- 19 files changed, 275 insertions(+), 16 deletions(-) create mode 100644 src/main/java/com/limechain/rpc/BlockRpcClient.java create mode 100644 src/main/java/com/limechain/rpc/LoadBalancer.java create mode 100644 src/main/java/com/limechain/rpc/RpcClient.java create mode 100644 src/main/java/com/limechain/rpc/dto/ChainGetHeaderResult.java create mode 100644 src/main/java/com/limechain/rpc/dto/Digest.java create mode 100644 src/main/java/com/limechain/rpc/dto/GrandpaRoundStateResult.java create mode 100644 src/main/java/com/limechain/rpc/dto/RoundState.java create mode 100644 src/main/java/com/limechain/rpc/dto/RpcError.java create mode 100644 src/main/java/com/limechain/rpc/dto/RpcMethod.java create mode 100644 src/main/java/com/limechain/rpc/dto/RpcRequest.java create mode 100644 src/main/java/com/limechain/rpc/dto/RpcResponse.java create mode 100644 src/main/java/com/limechain/rpc/dto/Votes.java diff --git a/src/main/java/com/limechain/config/HostConfig.java b/src/main/java/com/limechain/config/HostConfig.java index a0dccf528..d273cff16 100644 --- a/src/main/java/com/limechain/config/HostConfig.java +++ b/src/main/java/com/limechain/config/HostConfig.java @@ -5,6 +5,7 @@ import com.limechain.utils.DivLogger; import lombok.Getter; +import java.util.List; import java.util.logging.Level; /** @@ -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(); @@ -66,4 +67,13 @@ public String getGenesisPath() { case LOCAL -> localGenesisPath; }; } + + public List 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(); + }; + } } diff --git a/src/main/java/com/limechain/constants/RpcConstants.java b/src/main/java/com/limechain/constants/RpcConstants.java index 002e966c3..c4a066f7a 100644 --- a/src/main/java/com/limechain/constants/RpcConstants.java +++ b/src/main/java/com/limechain/constants/RpcConstants.java @@ -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 POLKADOT_HTTPS_RPC = List.of( + "https://rpc.ibp.network/polkadot", + "https://polkadot-rpc.dwellir.com", + "https://rpc.polkadot.io" + ); + public static final List KUSAMA_HTTPS_RPC = List.of( + "https://rpc.ibp.network/kusama", + "https://kusama-rpc.dwellir.com", + "https://kusama-rpc.polkadot.io" + ); + public static final List WESTEND_HTTPS_RPC = List.of( + "https://rpc.ibp.network/westend", + "https://westend-rpc.dwellir.com", + "https://westend-rpc.polkadot.io" + ); } \ No newline at end of file diff --git a/src/main/java/com/limechain/rpc/BlockRpcClient.java b/src/main/java/com/limechain/rpc/BlockRpcClient.java new file mode 100644 index 000000000..0686dcad9 --- /dev/null +++ b/src/main/java/com/limechain/rpc/BlockRpcClient.java @@ -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); + } +} diff --git a/src/main/java/com/limechain/rpc/LoadBalancer.java b/src/main/java/com/limechain/rpc/LoadBalancer.java new file mode 100644 index 000000000..28ad3001c --- /dev/null +++ b/src/main/java/com/limechain/rpc/LoadBalancer.java @@ -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 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); + } +} diff --git a/src/main/java/com/limechain/rpc/RpcClient.java b/src/main/java/com/limechain/rpc/RpcClient.java new file mode 100644 index 000000000..62fbbc890 --- /dev/null +++ b/src/main/java/com/limechain/rpc/RpcClient.java @@ -0,0 +1,45 @@ +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 params) { + RpcRequest request = new RpcRequest(ID_COUNTER.getAndAdd(1), method, params); + return JsonUtil.stringify(request); + } + + protected static RpcResponse sendRpcRequest(RpcMethod method, List params) { + String jsonResult = HttpRequest.asyncHttpRequest(POST, LOAD_BALANCER.getNextEndpoint(), + createRpcRequestJson(method.getMethod(), params)); + return OBJECT_MAPPER.mapToClass(jsonResult, RpcResponse.class); + } + + protected static T getResult(RpcResponse response, Class 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); + } +} diff --git a/src/main/java/com/limechain/rpc/dto/ChainGetHeaderResult.java b/src/main/java/com/limechain/rpc/dto/ChainGetHeaderResult.java new file mode 100644 index 000000000..c95d274d7 --- /dev/null +++ b/src/main/java/com/limechain/rpc/dto/ChainGetHeaderResult.java @@ -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; +} \ No newline at end of file diff --git a/src/main/java/com/limechain/rpc/dto/Digest.java b/src/main/java/com/limechain/rpc/dto/Digest.java new file mode 100644 index 000000000..ef3ee55d5 --- /dev/null +++ b/src/main/java/com/limechain/rpc/dto/Digest.java @@ -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 logs; +} diff --git a/src/main/java/com/limechain/rpc/dto/GrandpaRoundStateResult.java b/src/main/java/com/limechain/rpc/dto/GrandpaRoundStateResult.java new file mode 100644 index 000000000..e65799e10 --- /dev/null +++ b/src/main/java/com/limechain/rpc/dto/GrandpaRoundStateResult.java @@ -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 background; +} \ No newline at end of file diff --git a/src/main/java/com/limechain/rpc/dto/RoundState.java b/src/main/java/com/limechain/rpc/dto/RoundState.java new file mode 100644 index 000000000..d0c66a7c6 --- /dev/null +++ b/src/main/java/com/limechain/rpc/dto/RoundState.java @@ -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; +} diff --git a/src/main/java/com/limechain/rpc/dto/RpcError.java b/src/main/java/com/limechain/rpc/dto/RpcError.java new file mode 100644 index 000000000..0e6778a48 --- /dev/null +++ b/src/main/java/com/limechain/rpc/dto/RpcError.java @@ -0,0 +1,13 @@ +package com.limechain.rpc.dto; + +import com.limechain.teavm.annotation.Reflectable; +import lombok.Data; + +@Data +@Reflectable +public class RpcError { + + private int code; + private String message; + private String data; +} diff --git a/src/main/java/com/limechain/rpc/dto/RpcMethod.java b/src/main/java/com/limechain/rpc/dto/RpcMethod.java new file mode 100644 index 000000000..19280f489 --- /dev/null +++ b/src/main/java/com/limechain/rpc/dto/RpcMethod.java @@ -0,0 +1,17 @@ +package com.limechain.rpc.dto; + +import lombok.Getter; + +@Getter +public enum RpcMethod { + + CHAIN_GET_FINALIZED_HEAD("chain_getFinalizedHead"), + CHAIN_GET_HEADER("chain_getHeader"), + GRANDPA_ROUND_STATE("grandpa_roundState"); + + RpcMethod(String method) { + this.method = method; + } + + private final String method; +} diff --git a/src/main/java/com/limechain/rpc/dto/RpcRequest.java b/src/main/java/com/limechain/rpc/dto/RpcRequest.java new file mode 100644 index 000000000..d37f85262 --- /dev/null +++ b/src/main/java/com/limechain/rpc/dto/RpcRequest.java @@ -0,0 +1,20 @@ +package com.limechain.rpc.dto; + +import com.limechain.teavm.annotation.Reflectable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@Reflectable +@NoArgsConstructor +@AllArgsConstructor +public class RpcRequest { + + private int id; + private final String jsonrpc = "2.0"; + private String method; + private List params; +} diff --git a/src/main/java/com/limechain/rpc/dto/RpcResponse.java b/src/main/java/com/limechain/rpc/dto/RpcResponse.java new file mode 100644 index 000000000..34b8173d3 --- /dev/null +++ b/src/main/java/com/limechain/rpc/dto/RpcResponse.java @@ -0,0 +1,16 @@ +package com.limechain.rpc.dto; + +import com.limechain.teavm.annotation.Reflectable; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Reflectable +@NoArgsConstructor +public class RpcResponse { + + private int id; + private final String jsonrpc = "2.0"; + private Object result; + private RpcError error; +} diff --git a/src/main/java/com/limechain/rpc/dto/Votes.java b/src/main/java/com/limechain/rpc/dto/Votes.java new file mode 100644 index 000000000..9b19b10ad --- /dev/null +++ b/src/main/java/com/limechain/rpc/dto/Votes.java @@ -0,0 +1,14 @@ +package com.limechain.rpc.dto; + +import com.limechain.teavm.annotation.Reflectable; +import lombok.Data; + +import java.util.List; + +@Data +@Reflectable +public class Votes { + + private int currentWeight; + private List missing; +} diff --git a/src/main/java/com/limechain/rpc/server/CommonConfig.java b/src/main/java/com/limechain/rpc/server/CommonConfig.java index 45ae13095..c9dc28d85 100644 --- a/src/main/java/com/limechain/rpc/server/CommonConfig.java +++ b/src/main/java/com/limechain/rpc/server/CommonConfig.java @@ -16,7 +16,7 @@ */ public class CommonConfig { - private static Map, Object> beans = new HashMap<>(); + private static final Map, Object> beans = new HashMap<>(); public static void start() { getBean(SystemInfo.class); @@ -95,5 +95,4 @@ private static WarpSyncMachine warpSyncMachine(Network network, ChainService cha WarpSyncState warpSyncState) { return new WarpSyncMachine(network, chainService, syncState, warpSyncState); } - } diff --git a/src/main/java/com/limechain/storage/block/SyncState.java b/src/main/java/com/limechain/storage/block/SyncState.java index 7022d3536..9e86ed28f 100644 --- a/src/main/java/com/limechain/storage/block/SyncState.java +++ b/src/main/java/com/limechain/storage/block/SyncState.java @@ -14,7 +14,6 @@ import lombok.extern.java.Log; import java.math.BigInteger; -import java.util.Arrays; @Getter @Log diff --git a/src/main/java/com/limechain/teavm/HttpRequest.java b/src/main/java/com/limechain/teavm/HttpRequest.java index a8c51b2b0..2b49430e1 100644 --- a/src/main/java/com/limechain/teavm/HttpRequest.java +++ b/src/main/java/com/limechain/teavm/HttpRequest.java @@ -27,7 +27,7 @@ private static void asyncHttpRequest(String method, String url, String body, Asy } @JSBody(params = {"method", "url", "body", "callback"}, script = "return asyncHttpRequest(method, url, body, callback);") - public static native void createAsyncHttpRequest(String method, String url, String body, HttpRequestCallback callback); + private static native void createAsyncHttpRequest(String method, String url, String body, HttpRequestCallback callback); @JSFunctor private interface HttpRequestCallback extends JSObject { diff --git a/src/main/java/com/limechain/utils/json/JsonUtil.java b/src/main/java/com/limechain/utils/json/JsonUtil.java index 92e7aaba5..a7f2435c4 100644 --- a/src/main/java/com/limechain/utils/json/JsonUtil.java +++ b/src/main/java/com/limechain/utils/json/JsonUtil.java @@ -1,12 +1,12 @@ package com.limechain.utils.json; import com.limechain.teavm.HttpRequest; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +@NoArgsConstructor(access = AccessLevel.PRIVATE) public class JsonUtil { - // Prevents instantiation - private JsonUtil() {} - public static Object parseJson(String jsonString) { return new JsonParser(jsonString).parse(); } diff --git a/src/main/java/com/limechain/utils/json/ObjectMapper.java b/src/main/java/com/limechain/utils/json/ObjectMapper.java index e1f85fa0c..175d09ce5 100644 --- a/src/main/java/com/limechain/utils/json/ObjectMapper.java +++ b/src/main/java/com/limechain/utils/json/ObjectMapper.java @@ -67,18 +67,18 @@ private T convertValue(Class type, Object value) { if (type.isInstance(value)) { return (T) value; } else if (type == Integer.class || type == int.class) { - return handleWholeNumber(value, (long) Integer.MIN_VALUE, (long) Integer.MIN_VALUE); + return handleWholeNumber(value, (long) Integer.MIN_VALUE, (long) Integer.MAX_VALUE); } else if (type == Long.class || type == long.class) { return handleWholeNumber(value, Long.MIN_VALUE, Long.MAX_VALUE); } else if (type == Double.class || type == double.class) { - BigDecimal bigDecimalValue = new BigDecimal((String) value); + BigDecimal bigDecimalValue = new BigDecimal(value.toString()); double doubleValue = bigDecimalValue.doubleValue(); if (doubleValue == Double.POSITIVE_INFINITY || doubleValue == Double.NEGATIVE_INFINITY) { throw new ArithmeticException("Value out of range for Double: " + value); } return (T) Double.valueOf(doubleValue); } else if (type == BigInteger.class) { - return (T) new BigInteger((String) value); + return (T) new BigInteger(value.toString()); } else if (type == Boolean.class || type == boolean.class) { return (T) value; } else if (type == String.class) { @@ -91,6 +91,8 @@ private T convertValue(Class type, Object value) { } } else if (type.isArray()) { return (T) convertArray(type.getComponentType(), (List) value); + } else if (Map.class.isAssignableFrom(value.getClass())) { + return mapToClass(JsonUtil.stringify(value), type); } throw new RuntimeException("Unsupported field type: " + type); @@ -98,10 +100,10 @@ private T convertValue(Class type, Object value) { @SuppressWarnings("unchecked") private T handleWholeNumber(Object value, Long min, Long max) { - BigInteger bigIntValue = new BigInteger((String) value); + BigInteger bigIntValue = new BigInteger(value.toString()); if (bigIntValue.compareTo(BigInteger.valueOf(min)) < 0 || bigIntValue.compareTo(BigInteger.valueOf(max)) > 0) { - throw new ArithmeticException("Value out of range number type: " + value); + throw new ArithmeticException("Value out of range for number type: " + value); } return (T) Integer.valueOf(bigIntValue.intValue()); } From d566d928703869adef40b58f3bb50a0820b0cdc1 Mon Sep 17 00:00:00 2001 From: Yordan Atanasov Date: Wed, 28 Aug 2024 14:18:58 +0300 Subject: [PATCH 2/5] feat: expose RPC calls --- src/main/java/com/limechain/Main.java | 10 ++++++++-- src/main/java/com/limechain/rpc/Function.java | 8 ++++++++ src/main/java/com/limechain/rpc/RpcClient.java | 5 +++++ .../java/com/limechain/teavm/HttpRequest.java | 7 +++++-- src/main/webapp/js/http.js | 18 +++++++++++++++++- 5 files changed, 43 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/limechain/rpc/Function.java diff --git a/src/main/java/com/limechain/Main.java b/src/main/java/com/limechain/Main.java index 585a87d24..7c7d829c8 100644 --- a/src/main/java/com/limechain/Main.java +++ b/src/main/java/com/limechain/Main.java @@ -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; @@ -20,9 +22,13 @@ public static void main(String[] args) { HostNode client = new LightClient(); + exportAPI(RpcClient::sendRpcRequest, JSString.valueOf("rpc")); // Start the client // NOTE: This starts the beans the client would need - mutates the global context client.start(); log.log(Level.INFO, "\uD83D\uDE80Started light client!"); } + + @JSBody(params = {"f", "apiName"}, script = "window[apiName] = f") + private static native void exportAPI(Function f, JSString apiName); } \ No newline at end of file diff --git a/src/main/java/com/limechain/rpc/Function.java b/src/main/java/com/limechain/rpc/Function.java new file mode 100644 index 000000000..07d69daaa --- /dev/null +++ b/src/main/java/com/limechain/rpc/Function.java @@ -0,0 +1,8 @@ +package com.limechain.rpc; + +import org.teavm.jso.JSObject; + +public interface Function extends JSObject { + + String sendRequest(String method, String[] params); +} diff --git a/src/main/java/com/limechain/rpc/RpcClient.java b/src/main/java/com/limechain/rpc/RpcClient.java index 62fbbc890..35ee552bf 100644 --- a/src/main/java/com/limechain/rpc/RpcClient.java +++ b/src/main/java/com/limechain/rpc/RpcClient.java @@ -28,6 +28,11 @@ private static String createRpcRequestJson(String method, List params) { return JsonUtil.stringify(request); } + public static String sendRpcRequest(String method, String[] params) { + return HttpRequest.createHttpRequest(POST, LOAD_BALANCER.getNextEndpoint(), + createRpcRequestJson(method, List.of(params))); + } + protected static RpcResponse sendRpcRequest(RpcMethod method, List params) { String jsonResult = HttpRequest.asyncHttpRequest(POST, LOAD_BALANCER.getNextEndpoint(), createRpcRequestJson(method.getMethod(), params)); diff --git a/src/main/java/com/limechain/teavm/HttpRequest.java b/src/main/java/com/limechain/teavm/HttpRequest.java index 2b49430e1..80bb15c2e 100644 --- a/src/main/java/com/limechain/teavm/HttpRequest.java +++ b/src/main/java/com/limechain/teavm/HttpRequest.java @@ -17,7 +17,7 @@ public class HttpRequest { public static native String asyncHttpRequest(String method, String url, String body); private static void asyncHttpRequest(String method, String url, String body, AsyncCallback callback) { - createAsyncHttpRequest(method, url, body, (error, response) -> { + createHttpRequest(method, url, body, (error, response) -> { if (error != null) { log.log(Level.WARNING, error.getMessage()); } else { @@ -27,7 +27,10 @@ private static void asyncHttpRequest(String method, String url, String body, Asy } @JSBody(params = {"method", "url", "body", "callback"}, script = "return asyncHttpRequest(method, url, body, callback);") - private static native void createAsyncHttpRequest(String method, String url, String body, HttpRequestCallback callback); + private static native void createHttpRequest(String method, String url, String body, HttpRequestCallback callback); + + @JSBody(params = {"method", "url", "body"}, script = "return httpRequestSync(method, url, body);") + public static native String createHttpRequest(String method, String url, String body); @JSFunctor private interface HttpRequestCallback extends JSObject { diff --git a/src/main/webapp/js/http.js b/src/main/webapp/js/http.js index fe84da78a..3b5942339 100644 --- a/src/main/webapp/js/http.js +++ b/src/main/webapp/js/http.js @@ -19,4 +19,20 @@ async function asyncHttpRequest(method = 'GET', url, body = null, callback) { } catch (error) { callback(new Error(`Error during sending request: ${error?.message}`), null); } -} \ No newline at end of file +} + +function httpRequestSync(method, url, body) { + var xhr = new XMLHttpRequest(); + xhr.open(method, url, false); // false for synchronous request + xhr.setRequestHeader('Content-Type', 'application/json'); + if (method === 'POST' && body) { + xhr.send(body); + } else { + xhr.send(); + } + if (xhr.status === 200) { + return xhr.responseText; + } else { + throw new Error('Request failed with status ' + xhr.status); + } +} From 23aaff7c5d823e67fea6b422ef3cc24df04a480e Mon Sep 17 00:00:00 2001 From: Yordan Atanasov Date: Wed, 28 Aug 2024 21:54:46 +0300 Subject: [PATCH 3/5] feat: implement rpc call sync state populate logic. --- src/main/java/com/limechain/Main.java | 2 + .../com/limechain/client/LightClient.java | 30 +++++--- .../java/com/limechain/config/SystemInfo.java | 12 +-- .../limechain/rpc/server/CommonConfig.java | 6 +- .../limechain/storage/block/SyncState.java | 5 +- .../limechain/sync/JustificationVerifier.java | 18 ++--- .../sync/warpsync/WarpSyncMachine.java | 50 +++++++----- .../warpsync/action/RpcFallbackAction.java | 76 +++++++++++++++++++ .../action/VerifyJustificationAction.java | 16 ++-- 9 files changed, 147 insertions(+), 68 deletions(-) create mode 100644 src/main/java/com/limechain/sync/warpsync/action/RpcFallbackAction.java diff --git a/src/main/java/com/limechain/Main.java b/src/main/java/com/limechain/Main.java index 7c7d829c8..6119b2363 100644 --- a/src/main/java/com/limechain/Main.java +++ b/src/main/java/com/limechain/Main.java @@ -5,6 +5,7 @@ import com.limechain.rpc.Function; import com.limechain.rpc.RpcClient; import com.limechain.rpc.server.RpcApp; +import com.limechain.storage.LocalStorage; import com.limechain.utils.DivLogger; import org.teavm.jso.JSBody; import org.teavm.jso.core.JSString; @@ -17,6 +18,7 @@ public class Main { public static void main(String[] args) { log.log("Starting LimeChain node..."); + LocalStorage.clear(); RpcApp rpcApp = new RpcApp(); rpcApp.start(); diff --git a/src/main/java/com/limechain/client/LightClient.java b/src/main/java/com/limechain/client/LightClient.java index 281f6bdec..31b9170bb 100644 --- a/src/main/java/com/limechain/client/LightClient.java +++ b/src/main/java/com/limechain/client/LightClient.java @@ -3,8 +3,8 @@ 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; @@ -12,12 +12,12 @@ * 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, @@ -33,16 +33,26 @@ 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(); - break; + System.out.println("Node successfully connected to a peer! Sync will use warp protocol!"); + warpSyncMachine.start(false); + return; + } else { + retryCount++; + System.out.println("Waiting to retry peer connection..."); + Thread.sleep(1000); } - log.log(Level.INFO, "Waiting for peer connection..."); - Thread.sleep(10000); } + + System.out.println("Node failed to connect to peer! Sync will use RPC calls!!"); + warpSyncMachine.start(true); } } diff --git a/src/main/java/com/limechain/config/SystemInfo.java b/src/main/java/com/limechain/config/SystemInfo.java index b2fafd50b..c5dd4e81b 100644 --- a/src/main/java/com/limechain/config/SystemInfo.java +++ b/src/main/java/com/limechain/config/SystemInfo.java @@ -1,12 +1,9 @@ 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; /** @@ -14,20 +11,18 @@ */ @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(); } @@ -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); } } diff --git a/src/main/java/com/limechain/rpc/server/CommonConfig.java b/src/main/java/com/limechain/rpc/server/CommonConfig.java index c9dc28d85..dc4a6fc35 100644 --- a/src/main/java/com/limechain/rpc/server/CommonConfig.java +++ b/src/main/java/com/limechain/rpc/server/CommonConfig.java @@ -42,7 +42,7 @@ protected static Object getBean(Class beanClass) { beans.put(beanClass, syncState); return syncState; case "SystemInfo": - SystemInfo systemInfo = systemInfo((HostConfig) getBean(HostConfig.class), (SyncState) getBean(SyncState.class)); + SystemInfo systemInfo = systemInfo((HostConfig) getBean(HostConfig.class)); beans.put(beanClass, systemInfo); return systemInfo; case "Network": @@ -79,8 +79,8 @@ private static SyncState syncState() { return new SyncState(); } - private static SystemInfo systemInfo(HostConfig hostConfig, SyncState syncState) { - return new SystemInfo(hostConfig, syncState); + private static SystemInfo systemInfo(HostConfig hostConfig) { + return new SystemInfo(hostConfig); } private static Network network(ChainService chainService, HostConfig hostConfig) { diff --git a/src/main/java/com/limechain/storage/block/SyncState.java b/src/main/java/com/limechain/storage/block/SyncState.java index a3778b31e..334d481be 100644 --- a/src/main/java/com/limechain/storage/block/SyncState.java +++ b/src/main/java/com/limechain/storage/block/SyncState.java @@ -27,6 +27,7 @@ public class SyncState { @Setter private Authority[] authoritySet; private BigInteger latestRound; + @Setter private BigInteger setId; public SyncState() { @@ -88,8 +89,4 @@ public void setLightSyncState(LightSyncState initState) { setAuthoritySet(initState.getGrandpaAuthoritySet().getCurrentAuthorities()); finalizeHeader(initState.getFinalizedBlockHeader()); } - - public String getStateRoot() { - return null; - } } diff --git a/src/main/java/com/limechain/sync/JustificationVerifier.java b/src/main/java/com/limechain/sync/JustificationVerifier.java index 497a9203a..e6a37cdf9 100644 --- a/src/main/java/com/limechain/sync/JustificationVerifier.java +++ b/src/main/java/com/limechain/sync/JustificationVerifier.java @@ -17,14 +17,6 @@ import java.math.BigInteger; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.security.InvalidKeyException; -import java.security.KeyFactory; -import java.security.NoSuchAlgorithmException; -import java.security.PublicKey; -import java.security.Signature; -import java.security.SignatureException; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.X509EncodedKeySpec; import java.util.Arrays; import java.util.HashSet; import java.util.Set; @@ -49,7 +41,7 @@ public static boolean verify(Precommit[] precommits, BigInteger round) { Set seenPublicKeys = new HashSet<>(); Set authorityKeys = - Arrays.stream(authorities).map(Authority::getPublicKey).map(Hash256::new).collect(Collectors.toSet()); + Arrays.stream(authorities).map(Authority::getPublicKey).map(Hash256::new).collect(Collectors.toSet()); for (Precommit precommit : precommits) { if (!authorityKeys.contains(precommit.getAuthorityPublicKey())) { @@ -68,9 +60,9 @@ public static boolean verify(Precommit[] precommits, BigInteger round) { byte[] data = getDataToVerify(precommit, authoritiesSetId, round); boolean isValid = verifySignature( - StringUtils.toHex(precommit.getAuthorityPublicKey().getBytes()), - StringUtils.toHex(precommit.getSignature().getBytes()), - StringUtils.toHex(data)); + StringUtils.toHex(precommit.getAuthorityPublicKey().getBytes()), + StringUtils.toHex(precommit.getSignature().getBytes()), + StringUtils.toHex(data)); if (!isValid) { log.log(Level.WARNING, "Failed to verify signature"); @@ -142,7 +134,7 @@ private static byte[] getDataToVerify(Precommit precommit, BigInteger authoritie } @JSBody(params = {"publicKeyHex", "signatureHex", - "messageHex"}, script = "return verifyAsync(signatureHex, messageHex, publicKeyHex);") + "messageHex"}, script = "return verifyAsync(signatureHex, messageHex, publicKeyHex);") public static native JSPromise verifyAsync(String publicKeyHex, String signatureHex, String messageHex); } diff --git a/src/main/java/com/limechain/sync/warpsync/WarpSyncMachine.java b/src/main/java/com/limechain/sync/warpsync/WarpSyncMachine.java index c281bf243..25b207f86 100644 --- a/src/main/java/com/limechain/sync/warpsync/WarpSyncMachine.java +++ b/src/main/java/com/limechain/sync/warpsync/WarpSyncMachine.java @@ -9,11 +9,12 @@ import com.limechain.storage.block.SyncState; import com.limechain.sync.warpsync.action.FinishedAction; import com.limechain.sync.warpsync.action.RequestFragmentsAction; +import com.limechain.sync.warpsync.action.RpcFallbackAction; import com.limechain.sync.warpsync.action.WarpSyncAction; +import com.limechain.tuple.Pair; +import com.limechain.utils.DivLogger; import lombok.Getter; import lombok.Setter; -import lombok.extern.java.Log; -import com.limechain.tuple.Pair; import java.math.BigInteger; import java.util.ArrayList; @@ -23,11 +24,12 @@ import java.util.Queue; import java.util.logging.Level; -@Log @Getter @Setter public class WarpSyncMachine { + private static final DivLogger log = new DivLogger(); + private final PriorityQueue> scheduledAuthorityChanges; private final ChainInformation chainInformation; private Queue fragmentsQueue; @@ -62,22 +64,27 @@ public boolean isSyncing() { return this.warpSyncAction.getClass() != FinishedAction.class; } - public void start() { - LightSyncState initState = LightSyncState.decode(this.chainService.getChainSpec().getLightSyncState()); - - if (this.syncState.getLastFinalizedBlockNumber() - .compareTo(initState.getFinalizedBlockHeader().getBlockNumber()) < 0) { - this.syncState.setLightSyncState(initState); + public void start(boolean useRpc) { + if (!useRpc) { + LightSyncState initState = LightSyncState.decode(this.chainService.getChainSpec().getLightSyncState()); + + if (this.syncState.getLastFinalizedBlockNumber() + .compareTo(initState.getFinalizedBlockHeader().getBlockNumber()) < 0) { + this.syncState.setLightSyncState(initState); + } + System.out.println(this.syncState.getLastFinalizedBlockHash()); + System.out.println(this.syncState.getLastFinalizedBlockNumber()); + + final Hash256 initStateHash = this.syncState.getLastFinalizedBlockHash(); + // Always start with requesting fragments + System.out.println("Requesting fragments... " + initStateHash); + this.networkService.updateCurrentSelectedPeerWithNextBootnode(); + this.warpSyncAction = new RequestFragmentsAction(initStateHash); + } else { + System.out.println("Warping via RPC... "); + this.networkService.updateCurrentSelectedPeerWithNextBootnode(); + this.warpSyncAction = new RpcFallbackAction(); } - System.out.println(this.syncState.getLastFinalizedBlockHash()); - System.out.println(this.syncState.getLastFinalizedBlockNumber()); - - final Hash256 initStateHash = this.syncState.getLastFinalizedBlockHash(); - - // Always start with requesting fragments - log.log(Level.INFO, "Requesting fragments... " + initStateHash); - this.networkService.updateCurrentSelectedPeerWithNextBootnode(); - this.warpSyncAction = new RequestFragmentsAction(initStateHash); // new Thread(() -> { while (this.warpSyncAction.getClass() != FinishedAction.class) { @@ -90,16 +97,17 @@ public void start() { } public void stop() { - log.info("Stopping warp sync machine"); + System.out.println("Stopping warp sync machine"); this.warpSyncAction = null; - log.info("Warp sync machine stopped."); + System.out.println("Warp sync machine stopped."); } private void finishWarpSync() { this.warpState.setWarpSyncFinished(true); // this.networkService.handshakeBootNodes(); this.syncState.persistState(); - log.info("Warp sync finished."); + System.out.println("Warp sync finished."); + log.log(Level.INFO, "Highest known block at #" + syncState.getLastFinalizedBlockNumber()); this.onFinishCallbacks.forEach(Runnable::run); } diff --git a/src/main/java/com/limechain/sync/warpsync/action/RpcFallbackAction.java b/src/main/java/com/limechain/sync/warpsync/action/RpcFallbackAction.java new file mode 100644 index 000000000..0f788a56b --- /dev/null +++ b/src/main/java/com/limechain/sync/warpsync/action/RpcFallbackAction.java @@ -0,0 +1,76 @@ +package com.limechain.sync.warpsync.action; + +import com.limechain.network.protocol.warp.dto.BlockHeader; +import com.limechain.network.protocol.warp.dto.HeaderDigest; +import com.limechain.network.protocol.warp.scale.reader.HeaderDigestReader; +import com.limechain.polkaj.Hash256; +import com.limechain.polkaj.reader.ScaleCodecReader; +import com.limechain.rpc.BlockRpcClient; +import com.limechain.rpc.dto.ChainGetHeaderResult; +import com.limechain.rpc.dto.GrandpaRoundStateResult; +import com.limechain.rpc.server.AppBean; +import com.limechain.storage.block.SyncState; +import com.limechain.sync.warpsync.WarpSyncMachine; +import com.limechain.utils.StringUtils; +import lombok.extern.java.Log; + +import java.math.BigInteger; +import java.util.List; +import java.util.logging.Level; + +@Log +public class RpcFallbackAction implements WarpSyncAction { + private final SyncState syncState; + private Exception error; + + public RpcFallbackAction() { + this.syncState = AppBean.getBean(SyncState.class); + } + + @Override + public void next(WarpSyncMachine sync) { + if (this.error != null) { + sync.setWarpSyncAction(new RequestFragmentsAction(syncState.getLastFinalizedBlockHash())); + return; + } + + log.log(Level.INFO, "Populated sync state from RPC results. Block hash is now at #" + + syncState.getLastFinalizedBlockNumber() + ": " + + syncState.getLastFinalizedBlockHash().toString() + + " with state root " + syncState.getStateRoot()); + + sync.setWarpSyncAction(new FinishedAction()); + } + + @Override + public void handle(WarpSyncMachine sync) { + try { + Hash256 latestFinalizedHashResult = BlockRpcClient.getLastFinalizedBlockHash(); + ChainGetHeaderResult headerResult = BlockRpcClient.getHeader(latestFinalizedHashResult.toString()); + GrandpaRoundStateResult roundStateResult = BlockRpcClient.getGrandpaRoundState(); + + BlockHeader latestFinalizedHeader = new BlockHeader(); + latestFinalizedHeader.setBlockNumber(new BigInteger( + StringUtils.remove0xPrefix(headerResult.getNumber()), 16)); + latestFinalizedHeader.setParentHash(Hash256.from(headerResult.getParentHash())); + latestFinalizedHeader.setStateRoot(Hash256.from(headerResult.getStateRoot())); + latestFinalizedHeader.setExtrinsicsRoot(Hash256.from(headerResult.getExtrinsicsRoot())); + + List digestHexes = headerResult.getDigest().getLogs(); + HeaderDigest[] digests = new HeaderDigest[digestHexes.size()]; + for (int i = 0; i < digestHexes.size(); i++) { + digests[i] = new HeaderDigestReader().read( + new ScaleCodecReader(StringUtils.hexToBytes(digestHexes.get(i)))); + } + latestFinalizedHeader.setDigest(digests); + + syncState.finalizeHeader(latestFinalizedHeader); + syncState.setSetId(BigInteger.valueOf(roundStateResult.getSetId())); + syncState.resetRound(); + + } catch (Exception e) { + log.log(Level.WARNING, "Error while calling rpc endpoints: " + e.getMessage()); + this.error = e; + } + } +} diff --git a/src/main/java/com/limechain/sync/warpsync/action/VerifyJustificationAction.java b/src/main/java/com/limechain/sync/warpsync/action/VerifyJustificationAction.java index 7c07f4e4f..1e7c93011 100644 --- a/src/main/java/com/limechain/sync/warpsync/action/VerifyJustificationAction.java +++ b/src/main/java/com/limechain/sync/warpsync/action/VerifyJustificationAction.java @@ -28,7 +28,7 @@ public VerifyJustificationAction() { public void next(WarpSyncMachine sync) { if (this.error != null) { // Not sure what state we should transition to here. - sync.setWarpSyncAction(new FinishedAction()); + sync.setWarpSyncAction(new RpcFallbackAction()); return; } @@ -52,8 +52,8 @@ public void handle(WarpSyncMachine sync) { throw new JustificationVerificationException("No such fragment"); } boolean verified = JustificationVerifier.verify( - fragment.getJustification().getPrecommits(), - fragment.getJustification().getRound()); + fragment.getJustification().getPrecommits(), + fragment.getJustification().getRound()); if (!verified) { throw new JustificationVerificationException("Justification could not be verified."); } @@ -69,12 +69,12 @@ public void handle(WarpSyncMachine sync) { private void handleAuthorityChanges(WarpSyncFragment fragment) { try { warpSyncState.handleAuthorityChanges( - fragment.getHeader().getDigest(), - fragment.getJustification().getTargetBlock()); + fragment.getHeader().getDigest(), + fragment.getJustification().getTargetBlock()); log.log(Level.INFO, "Verified justification. Block hash is now at #" - + syncState.getLastFinalizedBlockNumber() + ": " - + syncState.getLastFinalizedBlockHash().toString() - + " with state root " + syncState.getStateRoot()); + + syncState.getLastFinalizedBlockNumber() + ": " + + syncState.getLastFinalizedBlockHash().toString() + + " with state root " + syncState.getStateRoot()); } catch (Exception e) { this.error = e; } From c9d5ac2995416379f34451ace1e091b7ae2e74c4 Mon Sep 17 00:00:00 2001 From: Yordan Atanasov Date: Thu, 29 Aug 2024 16:47:37 +0300 Subject: [PATCH 4/5] bug: Fix issue where RPC warp sync results in consecutive warp syncs always failing and falling back to RPC. --- src/main/java/com/limechain/Main.java | 2 -- .../java/com/limechain/client/LightClient.java | 10 ++++------ .../java/com/limechain/storage/DBConstants.java | 1 + .../com/limechain/storage/block/SyncState.java | 12 ++++++++++++ .../limechain/sync/warpsync/WarpSyncMachine.java | 14 ++++++++------ .../warpsync/action/RequestFragmentsAction.java | 8 ++++---- .../sync/warpsync/action/RpcFallbackAction.java | 1 + 7 files changed, 30 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/limechain/Main.java b/src/main/java/com/limechain/Main.java index 6119b2363..7c7d829c8 100644 --- a/src/main/java/com/limechain/Main.java +++ b/src/main/java/com/limechain/Main.java @@ -5,7 +5,6 @@ import com.limechain.rpc.Function; import com.limechain.rpc.RpcClient; import com.limechain.rpc.server.RpcApp; -import com.limechain.storage.LocalStorage; import com.limechain.utils.DivLogger; import org.teavm.jso.JSBody; import org.teavm.jso.core.JSString; @@ -18,7 +17,6 @@ public class Main { public static void main(String[] args) { log.log("Starting LimeChain node..."); - LocalStorage.clear(); RpcApp rpcApp = new RpcApp(); rpcApp.start(); diff --git a/src/main/java/com/limechain/client/LightClient.java b/src/main/java/com/limechain/client/LightClient.java index 31b9170bb..7e2fffca0 100644 --- a/src/main/java/com/limechain/client/LightClient.java +++ b/src/main/java/com/limechain/client/LightClient.java @@ -42,17 +42,15 @@ public void start() { this.network.updateCurrentSelectedPeer(); if (this.network.getKademliaService().getSuccessfulBootNodes() > 0) { - System.out.println("Node successfully connected to a peer! Sync will use warp protocol!"); - warpSyncMachine.start(false); - return; + warpSyncMachine.setProtocolSync(true); + break; } else { retryCount++; System.out.println("Waiting to retry peer connection..."); - Thread.sleep(1000); + Thread.sleep(2000); } } - System.out.println("Node failed to connect to peer! Sync will use RPC calls!!"); - warpSyncMachine.start(true); + warpSyncMachine.start(); } } diff --git a/src/main/java/com/limechain/storage/DBConstants.java b/src/main/java/com/limechain/storage/DBConstants.java index 9fd4bb725..fe1e0541c 100644 --- a/src/main/java/com/limechain/storage/DBConstants.java +++ b/src/main/java/com/limechain/storage/DBConstants.java @@ -22,6 +22,7 @@ public class DBConstants { public static final String LATEST_ROUND = "ss::latestRound"; public static final String SET_ID = "ss::setId"; public static final String STATE_ROOT = "ss::stateRoot"; + public static final String IS_PROTOCOL_SYNC = "ss::isProtocolSync"; // } diff --git a/src/main/java/com/limechain/storage/block/SyncState.java b/src/main/java/com/limechain/storage/block/SyncState.java index 334d481be..f832bfe7c 100644 --- a/src/main/java/com/limechain/storage/block/SyncState.java +++ b/src/main/java/com/limechain/storage/block/SyncState.java @@ -33,6 +33,7 @@ public class SyncState { public SyncState() { this.genesisBlockHash = GenesisBlockHash.POLKADOT; + clearStoredStateIfNeeded(); loadState(); this.startingBlock = this.lastFinalizedBlockNumber; } @@ -89,4 +90,15 @@ public void setLightSyncState(LightSyncState initState) { setAuthoritySet(initState.getGrandpaAuthoritySet().getCurrentAuthorities()); finalizeHeader(initState.getFinalizedBlockHeader()); } + + public void saveIsProtocolSync(boolean isProtocolSync) { + LocalStorage.save(DBConstants.IS_PROTOCOL_SYNC, isProtocolSync); + } + + private void clearStoredStateIfNeeded() { + boolean isProtocolSync = LocalStorage.find(DBConstants.IS_PROTOCOL_SYNC, boolean.class).orElse(false); + if (!isProtocolSync) { + LocalStorage.clear(); + } + } } diff --git a/src/main/java/com/limechain/sync/warpsync/WarpSyncMachine.java b/src/main/java/com/limechain/sync/warpsync/WarpSyncMachine.java index 25b207f86..c4599452b 100644 --- a/src/main/java/com/limechain/sync/warpsync/WarpSyncMachine.java +++ b/src/main/java/com/limechain/sync/warpsync/WarpSyncMachine.java @@ -30,11 +30,12 @@ public class WarpSyncMachine { private static final DivLogger log = new DivLogger(); + private Queue fragmentsQueue; + private WarpSyncAction warpSyncAction; + private boolean isProtocolSync; private final PriorityQueue> scheduledAuthorityChanges; private final ChainInformation chainInformation; - private Queue fragmentsQueue; private final ChainService chainService; - private WarpSyncAction warpSyncAction; private final WarpSyncState warpState; private final Network networkService; private final SyncState syncState; @@ -50,6 +51,7 @@ public WarpSyncMachine(Network network, ChainService chainService, SyncState syn this.scheduledAuthorityChanges = new PriorityQueue<>(Comparator.comparing(Pair::getValue0)); this.chainInformation = new ChainInformation(); this.onFinishCallbacks = new ArrayList<>(); + this.isProtocolSync = false; } public void nextState() { @@ -64,16 +66,15 @@ public boolean isSyncing() { return this.warpSyncAction.getClass() != FinishedAction.class; } - public void start(boolean useRpc) { - if (!useRpc) { + public void start() { + if (isProtocolSync) { + System.out.println("Warping via warp protocol... "); LightSyncState initState = LightSyncState.decode(this.chainService.getChainSpec().getLightSyncState()); if (this.syncState.getLastFinalizedBlockNumber() .compareTo(initState.getFinalizedBlockHeader().getBlockNumber()) < 0) { this.syncState.setLightSyncState(initState); } - System.out.println(this.syncState.getLastFinalizedBlockHash()); - System.out.println(this.syncState.getLastFinalizedBlockNumber()); final Hash256 initStateHash = this.syncState.getLastFinalizedBlockHash(); // Always start with requesting fragments @@ -106,6 +107,7 @@ private void finishWarpSync() { this.warpState.setWarpSyncFinished(true); // this.networkService.handshakeBootNodes(); this.syncState.persistState(); + syncState.saveIsProtocolSync(isProtocolSync); System.out.println("Warp sync finished."); log.log(Level.INFO, "Highest known block at #" + syncState.getLastFinalizedBlockNumber()); this.onFinishCallbacks.forEach(Runnable::run); diff --git a/src/main/java/com/limechain/sync/warpsync/action/RequestFragmentsAction.java b/src/main/java/com/limechain/sync/warpsync/action/RequestFragmentsAction.java index f5df404e3..7c716347f 100644 --- a/src/main/java/com/limechain/sync/warpsync/action/RequestFragmentsAction.java +++ b/src/main/java/com/limechain/sync/warpsync/action/RequestFragmentsAction.java @@ -10,7 +10,6 @@ import java.util.ArrayDeque; import java.util.Arrays; -import java.util.concurrent.LinkedBlockingQueue; import java.util.logging.Level; @Log @@ -38,7 +37,8 @@ public void next(WarpSyncMachine sync) { } catch (InterruptedException e) { Thread.currentThread().interrupt(); log.log(Level.SEVERE, "Retry warp sync request fragment exception: " - + e.getMessage(), e.getStackTrace()); + + e.getMessage(), e.getStackTrace()); + sync.setWarpSyncAction(new RpcFallbackAction()); } } if (this.result != null) { @@ -50,8 +50,8 @@ public void next(WarpSyncMachine sync) { @Override public void handle(WarpSyncMachine sync) { - WarpSyncResponse resp = sync.getNetworkService().makeWarpSyncRequest(blockHash.toString()); try { + WarpSyncResponse resp = sync.getNetworkService().makeWarpSyncRequest(blockHash.toString()); if (resp == null) { throw new MissingObjectException("No response received."); } @@ -63,7 +63,7 @@ public void handle(WarpSyncMachine sync) { } warpSyncState.setWarpSyncFragmentsFinished(resp.isFinished()); sync.setFragmentsQueue(new ArrayDeque<>( - Arrays.stream(resp.getFragments()).toList()) + Arrays.stream(resp.getFragments()).toList()) ); this.result = resp; diff --git a/src/main/java/com/limechain/sync/warpsync/action/RpcFallbackAction.java b/src/main/java/com/limechain/sync/warpsync/action/RpcFallbackAction.java index 0f788a56b..18607a79a 100644 --- a/src/main/java/com/limechain/sync/warpsync/action/RpcFallbackAction.java +++ b/src/main/java/com/limechain/sync/warpsync/action/RpcFallbackAction.java @@ -68,6 +68,7 @@ public void handle(WarpSyncMachine sync) { syncState.setSetId(BigInteger.valueOf(roundStateResult.getSetId())); syncState.resetRound(); + sync.setProtocolSync(false); } catch (Exception e) { log.log(Level.WARNING, "Error while calling rpc endpoints: " + e.getMessage()); this.error = e; From 7faeed850a44c5426c9b4c36713bf50cc5639125 Mon Sep 17 00:00:00 2001 From: Yordan Atanasov Date: Thu, 29 Aug 2024 17:59:43 +0300 Subject: [PATCH 5/5] feat: export rpc client as js function sendRpcRequest(String, Object[]) --- src/main/java/com/limechain/Main.java | 6 ++++-- src/main/java/com/limechain/rpc/RpcClient.java | 2 +- src/main/webapp/index.html | 4 ++++ src/main/webapp/js/http.js | 10 ++++++++++ 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/limechain/Main.java b/src/main/java/com/limechain/Main.java index 7c7d829c8..1b5de9df9 100644 --- a/src/main/java/com/limechain/Main.java +++ b/src/main/java/com/limechain/Main.java @@ -17,18 +17,20 @@ 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(); HostNode client = new LightClient(); - exportAPI(RpcClient::sendRpcRequest, JSString.valueOf("rpc")); // Start the client // NOTE: This starts the beans the client would need - mutates the global context client.start(); log.log(Level.INFO, "\uD83D\uDE80Started light client!"); } - @JSBody(params = {"f", "apiName"}, script = "window[apiName] = f") + @JSBody(params = {"f", "apiName"}, script = "window[apiName] = f;" + + "isRpcExported = true;") private static native void exportAPI(Function f, JSString apiName); } \ No newline at end of file diff --git a/src/main/java/com/limechain/rpc/RpcClient.java b/src/main/java/com/limechain/rpc/RpcClient.java index 35ee552bf..827136293 100644 --- a/src/main/java/com/limechain/rpc/RpcClient.java +++ b/src/main/java/com/limechain/rpc/RpcClient.java @@ -28,7 +28,7 @@ private static String createRpcRequestJson(String method, List params) { return JsonUtil.stringify(request); } - public static String sendRpcRequest(String method, String[] params) { + public static String sendRpcRequest(String method, Object[] params) { return HttpRequest.createHttpRequest(POST, LOAD_BALANCER.getNextEndpoint(), createRpcRequestJson(method, List.of(params))); } diff --git a/src/main/webapp/index.html b/src/main/webapp/index.html index 1cb2bad07..241c71813 100644 --- a/src/main/webapp/index.html +++ b/src/main/webapp/index.html @@ -68,7 +68,11 @@ // stream.end(); // }); } + // Test + sendRpcRequest('system_name', []); main(); + // Test 2 + sendRpcRequest('chain_getBlock', []); \ No newline at end of file diff --git a/src/main/webapp/js/http.js b/src/main/webapp/js/http.js index 3b5942339..e68f3829f 100644 --- a/src/main/webapp/js/http.js +++ b/src/main/webapp/js/http.js @@ -36,3 +36,13 @@ function httpRequestSync(method, url, body) { throw new Error('Request failed with status ' + xhr.status); } } + +var isRpcExported = false; + +function sendRpcRequest(method, params) { + if (isRpcExported === false) { + window.setTimeout(() => sendRpcRequest(method, params), 10); + } else { + console.log(rpc.sendRequest(method, params)); + } +}