Skip to content

Commit

Permalink
feat: Make Fruzhin configurable for different chains. (#23)
Browse files Browse the repository at this point in the history
# Description
This PR allows Fruzhin web to connect to polkadot, kusama or westend
chains via providing a chain name or a chain spec string as a parameter
for the main method.

- What does this PR do?
Enables configuration for chains different from polkadot.
- Why are these changes needed?
To cover light client requirements.
- How were these changes implemented and what do they affect?
The main method now requires a single parameter. It can be one of two:

1: Name of a well known chain (polkadot, kusama, westend).
2: A chain spec string of a well known chain.

Based on the provided a ChainService with the chain context is built and
provided to the host to use.

Fixes LimeChain#531

## Checklist:
- [X] I have read the [contributing
guidelines](https://github.com/LimeChain/Fruzhin/blob/dev/CONTRIBUTING.md).
- [X] My PR title matches the [Conventional Commits
spec](https://www.conventionalcommits.org/).
- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
- [ ] I have added tests to cover my changes.
- [X] All new and existing tests passed.
  • Loading branch information
Zurcusa authored Sep 24, 2024
1 parent 79262ae commit 102d398
Show file tree
Hide file tree
Showing 36 changed files with 308 additions and 417 deletions.
26 changes: 19 additions & 7 deletions src/main/java/com/limechain/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

import com.limechain.client.HostNode;
import com.limechain.client.LightClient;
import com.limechain.config.AppBean;
import com.limechain.config.ChainService;
import com.limechain.config.CommonConfig;
import com.limechain.rpc.WsRpcClient;
import com.limechain.rpc.WsRpcClientImpl;
import com.limechain.rpc.server.RpcApp;
import com.limechain.utils.DivLogger;
import org.teavm.jso.JSBody;
import org.teavm.jso.core.JSString;
Expand All @@ -18,21 +20,31 @@ public class Main {
private static final DivLogger log = new DivLogger();

public static void main(String[] args) {
exportWsRpc(new WsRpcClientImpl(), JSString.valueOf(WS_RPC));
if (args.length != 1) {
throw new IllegalStateException(
"Please provide a valid chain spec string or one of the supported chain names.");
}

log.log("Starting LimeChain node...");

RpcApp rpcApp = new RpcApp();
rpcApp.start();
String chainString = args[0];
initContext(chainString);

HostNode client = new LightClient();

// 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!");
}

private static void initContext(String chainString) {
ChainService chainService = AppBean.getBean(ChainService.class);
chainService.init(chainString);

CommonConfig.start();

exportWsRpc(new WsRpcClientImpl(), JSString.valueOf(WS_RPC));
}

@JSBody(params = {"c", "apiName"}, script = "window[apiName] = c;")
private static native void exportWsRpc(WsRpcClient c, JSString apiName);
}
46 changes: 39 additions & 7 deletions src/main/java/com/limechain/chain/Chain.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,40 @@

import lombok.Getter;

// TODO: Cleanup

/**
* Stores the Polkadot chain the Host is running on.
* Stores data for the supported chains.
*/
@Getter
public enum Chain {
POLKADOT("polkadot"),
KUSAMA("kusama"),
LOCAL("local"),
WESTEND("westend");
POLKADOT("polkadot",
"polkadot",
"91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3",
"rpc.polkadot.io"),
KUSAMA("kusama",
"ksmcc3",
"b0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe",
"kusama-rpc.polkadot.io"),
WESTEND("westend",
"westend2",
"e143f23803ac50e8f6f8e62695d1ce9e4e1d68aa36c1cd2cfd15340213f3423e",
"westend-rpc.polkadot.io"),
LOCAL("local", "local", null, null);

/**
* Holds the name of the chain
*/
private final String value;
private final String id;
private final String genesisBlockHash;
private final String rpcEndpoint;

Chain(String value) {
Chain(String value, String id, String genesisBlockHash, String rpcEndpoint) {
this.value = value;
this.id = id;
this.genesisBlockHash = genesisBlockHash;
this.rpcEndpoint = rpcEndpoint;
}

/**
Expand All @@ -29,7 +46,22 @@ public enum Chain {
*/
public static Chain fromString(String chain) {
for (Chain type : values()) {
if (type.getValue().equals(chain)) {
if (type.value.equals(chain)) {
return type;
}
}
return null;
}

/**
* Tries to map chain id string parameter to an enum based on its id field.
*
* @param id name of the enum id to map
* @return {@link Chain} or null if mapping is unsuccessful
*/
public static Chain fromChainId(String id) {
for (Chain type : values()) {
if (type.id.equals(id)) {
return type;
}
}
Expand Down
63 changes: 0 additions & 63 deletions src/main/java/com/limechain/chain/ChainService.java

This file was deleted.

4 changes: 1 addition & 3 deletions src/main/java/com/limechain/chain/spec/ChainSpec.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import lombok.Getter;
import lombok.Setter;

import java.io.IOException;
import java.io.Serializable;
import java.util.Map;

Expand All @@ -28,9 +27,8 @@ public class ChainSpec implements Serializable {
*
* @param pathToChainSpecJSON path to the chain specification json file
* @return class instance mapped to the json file
* @throws IOException If path is invalid
*/
public static ChainSpec newFromJSON(String pathToChainSpecJSON) throws IOException {
public static ChainSpec newFromJSON(String pathToChainSpecJSON) {
ObjectMapper mapper = new ObjectMapper(false);
String jsonChainSpec = JsonUtil.readJsonFromFile(pathToChainSpecJSON);
return mapper.mapToClass(jsonChainSpec, ChainSpec.class);
Expand Down
1 change: 1 addition & 0 deletions src/main/java/com/limechain/chain/spec/ChainType.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.io.Serializable;

// TODO: Cleanup
/**
* An enum modelling all possible chain types
* (as per <a href="https://spec.polkadot.network/id-cryptography-encoding#section-chainspec">the spec</a>).
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/limechain/client/LightClient.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.limechain.client;

import com.limechain.config.AppBean;
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;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.limechain.rpc.server;
package com.limechain.config;

import lombok.extern.java.Log;

Expand All @@ -16,7 +16,7 @@ public class AppBean {
* Returns the Spring managed bean instance of the given class type if it exists.
* Returns null otherwise.
*/
public static <T extends Object> T getBean(Class<T> beanClass) {
return (T) CommonConfig.getBean(beanClass);
public static <T> T getBean(Class<T> beanClass) {
return CommonConfig.getBean(beanClass);
}
}
74 changes: 74 additions & 0 deletions src/main/java/com/limechain/config/ChainService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package com.limechain.config;

import com.limechain.chain.Chain;
import com.limechain.chain.spec.ChainSpec;
import com.limechain.polkaj.Hash256;
import com.limechain.utils.DivLogger;
import com.limechain.utils.json.ObjectMapper;
import lombok.Getter;

import java.util.logging.Level;

// TODO: Cleanup

/**
* Configuration class used to store any Host specific information
*/
@Getter
public class ChainService {

private static final String GENESIS_DIR_FORMAT = "genesis/%s.json";
private static final String WSS_PROTOCOL = "wss://";
private static final String HTTPS_PROTOCOL = "https://";

/**
* The chain that the light client is communicating with.
*/
private Chain chain;
/**
* The chain spec of the chain.
*/
private ChainSpec chainSpec;

private static final DivLogger log = new DivLogger();

public void init(String chainString) {
log.log(Level.INFO, "Loading chain context...");

Chain cliChain = Chain.fromString(chainString);
try {
if (cliChain != null) {
chain = cliChain;
chainSpec = ChainSpec.newFromJSON(getGenesisPath());
} else {
chainSpec = new ObjectMapper(false).mapToClass(chainString, ChainSpec.class);
chain = Chain.fromChainId(chainSpec.getId());
}
} catch (Exception e) {
System.out.println("Something went wrong while loading chain data. " + e.getMessage());
}

log.log(Level.INFO, "✅️Loaded chain context for the " + chain.getValue() + " chain.");
}

/**
* Gets the genesis file path based on the chain the node is configured
*
* @return genesis(chain spec) file path
*/
public String getGenesisPath() {
return String.format(GENESIS_DIR_FORMAT, chain.getId());
}

public String getWsRpcEndpoint() {
return WSS_PROTOCOL + chain.getRpcEndpoint();
}

public String getHttpsEndpoint() {
return HTTPS_PROTOCOL + chain.getRpcEndpoint();
}

public Hash256 getGenesisBlockHash() {
return Hash256.from(chain.getGenesisBlockHash());
}
}
58 changes: 58 additions & 0 deletions src/main/java/com/limechain/config/CommonConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.limechain.config;

import com.limechain.network.Network;
import com.limechain.storage.block.SyncState;
import com.limechain.sync.warpsync.WarpSyncMachine;
import com.limechain.sync.warpsync.WarpSyncState;

import java.util.HashMap;
import java.util.Map;

/**
* Spring configuration class used to instantiate beans.
*/
public class CommonConfig {

private static final Map<Class<?>, Object> beans = new HashMap<>();

public static void start() {
getBean(SystemInfo.class);
getBean(WarpSyncMachine.class);
}

@SuppressWarnings("unchecked")
protected static <T> T getBean(Class<T> beanClass) {
if (beans.containsKey(beanClass)) {
return (T) beans.get(beanClass);
} else {
Object bean;
switch (beanClass.getSimpleName()) {
case "ChainService":
bean = new ChainService();
break;
case "SyncState":
bean = new SyncState(getBean(ChainService.class));
break;
case "SystemInfo":
bean = new SystemInfo(getBean(ChainService.class));
break;
case "Network":
bean = new Network(getBean(ChainService.class));
break;
case "WarpSyncState":
bean = new WarpSyncState(getBean(SyncState.class), getBean(Network.class));
break;
case "WarpSyncMachine":
bean = new WarpSyncMachine(getBean(Network.class),
getBean(ChainService.class),
getBean(SyncState.class),
getBean(WarpSyncState.class));
break;
default:
return null;
}
beans.put(beanClass, bean);
return (T) bean;
}
}
}
Loading

0 comments on commit 102d398

Please sign in to comment.