From 369694ce743a9d121e1b3c7d4a5fade269e802d0 Mon Sep 17 00:00:00 2001 From: Kare Nuorteva Date: Sat, 26 Feb 2022 19:28:20 +0200 Subject: [PATCH 1/2] feat: replace rpc{Http,Ws}Url variables with RpcEndpoint[] --- packages/config/src/index.ts | 30 ++++++++++++++-- packages/config/src/networks.json | 55 +++++++++++++++++++++++------- packages/config/test/index.test.ts | 10 ++++-- 3 files changed, 79 insertions(+), 16 deletions(-) diff --git a/packages/config/src/index.ts b/packages/config/src/index.ts index 88a632390..82e9d264e 100644 --- a/packages/config/src/index.ts +++ b/packages/config/src/index.ts @@ -4,10 +4,20 @@ export interface Contracts { [name: string]: string } +export enum RpcProtocol { + HTTP, + WEBSOCKET +} + +export interface RpcEndpoint { + url: string + //readTimeoutSecond: int + //writeTimeoutSecond: int +} + export interface Chain { id: number - rpcHttpUrl: string - rpcWsUrl: string + rpcEndpoints: RpcEndpoint[] contracts: Contracts } @@ -21,6 +31,22 @@ export type Networks = { [env in Environment]: Chains } +export const getRpcEndpointsByProtocol = (rpcEndpoints: RpcEndpoint[], protocol: RpcProtocol): RpcEndpoint[] => { + const endpoints = new Array() + for (const rpcEndpoint of rpcEndpoints) { + if (protocol === RpcProtocol.HTTP) { + if (rpcEndpoint.url.startsWith("https://") || rpcEndpoint.url.startsWith("http://")) { + endpoints.push(rpcEndpoint) + } + } else if (protocol === RpcProtocol.WEBSOCKET) { + if (rpcEndpoint.url.startsWith("wss://") || rpcEndpoint.url.startsWith("ws://")) { + endpoints.push(rpcEndpoint) + } + } + } + return endpoints +} + export const loadConfig = (env: Environment): Chains => { const networks: Networks = networksAsJSON const chain: Chains = networks[env] diff --git a/packages/config/src/networks.json b/packages/config/src/networks.json index 2745f0f7d..86ecf7f5d 100644 --- a/packages/config/src/networks.json +++ b/packages/config/src/networks.json @@ -2,8 +2,14 @@ "development": { "ethereum": { "id": 8995, - "rpcHttpUrl": "http://10.200.10.1:8545", - "rpcWsUrl": "ws://10.200.10.1:8545", + "rpcEndpoints": [ + { + "url": "http://10.200.10.1:8545" + }, + { + "url": "ws://10.200.10.1:8545" + } + ], "contracts": { "DATA-token": "0xbAA81A0179015bE47Ad439566374F2Bae098686F", "XDATA-token": "0x6d0F3bF9aD2455b4F62f22fFD21317e1E3eEFE5C", @@ -24,8 +30,14 @@ }, "streamr": { "id": 8997, - "rpcHttpUrl": "http://10.200.10.1:8546", - "rpcWsUrl": "ws://10.200.10.1:8546", + "rpcEndpoints": [ + { + "url": "http://10.200.10.1:8546" + }, + { + "url": "ws://10.200.10.1:8546" + } + ], "contracts": { "Token": "0x73Be21733CC5D08e1a14Ea9a399fb27DB3BEf8fF", "Mediator": "0xedD2aa644a6843F2e5133Fe3d6BD3F4080d97D9F", @@ -42,8 +54,7 @@ "production": { "ethereum": { "id": 1, - "rpcHttpUrl": "", - "rpcWsUrl": "", + "rpcEndpoints": [], "contracts": { "DATA-token": "0x8f693ca8D21b157107184d29D398A8D082b38b76", "XDATA-token": "0x0cf0ee63788a0849fe5297f3407f701e122cc023", @@ -62,8 +73,14 @@ }, "gnosis": { "id": 100, - "rpcHttpUrl": "https://rpc.gnosischain.com", - "rpcWsUrl": "wss://rpc.gnosischain.com/wss", + "rpcEndpoints": [ + { + "url": "https://rpc.gnosischain.com" + }, + { + "url": "wss://rpc.gnosischain.com/wss" + } + ], "contracts": { "XDATA-token": "0xE4a2620edE1058D61BEe5F45F6414314fdf10548", "DATA-token": "0x256eb8a51f382650B2A1e946b8811953640ee47D", @@ -80,8 +97,16 @@ }, "binance": { "id": 56, - "rpcHttpUrl": "https://bsc-dataseed.binance.org", - "rpcWsUrl": "wss://bsc-dataseed.binance.org", + "rpcHttpUrl": "", + "rpcWsUrl": "", + "rpcEndpoints": [ + { + "url": "https://bsc-dataseed.binance.org" + }, + { + "url": "wss://bsc-dataseed.binance.org" + } + ], "contracts": { "DATA-token": "0x0864c156b3c5f69824564dec60c629ae6401bf2a", "xdaiBridge": "0xa93ee7b4a7215f7e725437a6b6d7a4e7fe1dd8f0" @@ -89,8 +114,14 @@ }, "polygon": { "id": 137, - "rpcHttpUrl": "https://polygon-rpc.com", - "rpcWsUrl": "wss://polygon-rpc.com", + "rpcEndpoints": [ + { + "url": "https://polygon-rpc.com" + }, + { + "url": "wss://polygon-rpc.com" + } + ], "contracts": { "DATA-token": "0x3a9A81d576d83FF21f26f325066054540720fC34", "StorageNodeRegistry": "0x080F34fec2bc33928999Ea9e39ADc798bEF3E0d6", diff --git a/packages/config/test/index.test.ts b/packages/config/test/index.test.ts index 708f3a9df..f3b494b72 100644 --- a/packages/config/test/index.test.ts +++ b/packages/config/test/index.test.ts @@ -1,6 +1,6 @@ import { describe, it } from "mocha" import { assert } from "chai" -import { Chains, loadConfig } from "../src/index" +import { Chains, getRpcEndpointsByProtocol, loadConfig, RpcProtocol } from "../src/index" describe("Load configuration from JSON file", () => { it("ethereum chain id is 1", () => { @@ -23,8 +23,14 @@ describe("Load configuration from JSON file", () => { }) it("reads prod Polygon RPC URL", () => { const config: Chains = loadConfig("production") - const rpcHttpUrl = config.polygon.rpcHttpUrl + const rpcHttpUrl = config.polygon.rpcEndpoints[0].url const expected = "https://polygon-rpc.com" assert.equal(rpcHttpUrl, expected, `Expecting prod polygon RPC URL to equal ${expected}, got '${rpcHttpUrl}'`) }) + it("finds RPC endpoints by protocol", () => { + const config: Chains = loadConfig("production") + const endpoints = getRpcEndpointsByProtocol(config.binance.rpcEndpoints, RpcProtocol.HTTP) + assert.equal(endpoints.length, 1) + assert.equal(endpoints[0].url, "https://bsc-dataseed.binance.org") + }) }) \ No newline at end of file From f9f39fef538132ec3c1f954f366304ca45e62934 Mon Sep 17 00:00:00 2001 From: Kare Nuorteva Date: Mon, 28 Feb 2022 21:16:01 +0200 Subject: [PATCH 2/2] refactor: Separate JSON interfaces from implementation - Rename: loadConfig(env) to Chains.load(env) - Rename: use capital case for acronym classes - RpcEndpoint -> RPCEndpoint - RpcProtocol -> RPCProtocol - RpcEndpoint -> RPCEndpointJSON - Add Chains.getRPCEndpointsByProtocol(RPCProtocol): RPCEndpoint[] - Network -> NetworkJSON - Chain -> ChainJSON - Add classes: - Chain - Network - ChainsFactory - RPCEndpoint - Use concrete classes that implement JSON interfaces in public API - Use interfaces for parsing JSON file --- packages/config/Makefile | 2 +- packages/config/README.md | 4 +- packages/config/src/index.ts | 116 +++++++++++++++++++++-------- packages/config/test/index.test.ts | 22 +++--- 4 files changed, 97 insertions(+), 47 deletions(-) diff --git a/packages/config/Makefile b/packages/config/Makefile index 0ff3ab666..52e3c2099 100644 --- a/packages/config/Makefile +++ b/packages/config/Makefile @@ -26,8 +26,8 @@ test: ## Run tests lint: ## Run lint $(call npm, run lint) -build: NODE_ENV = "production" .PHONY: build +build: NODE_ENV = "production" build: ## Run build $(call npm, run build) diff --git a/packages/config/README.md b/packages/config/README.md index 712c73265..a0e4de3d5 100644 --- a/packages/config/README.md +++ b/packages/config/README.md @@ -13,9 +13,9 @@ npm install --save @streamr/config ## Examples Import DATA token production Ethereum address as a variable in a Typescript project: ```typescript -import { Chains, loadConfig } from "index" +import { Chains } from "index" -const config: Chains = loadConfig("production") +const config: Chains = Chains.load("production") const contractAddress: string = config.ethereum.contracts["DATA-token"] const chainId: number = config.ethereum.id const rpcHttpUrl: string = config.ethereum.rpcHttpUrl diff --git a/packages/config/src/index.ts b/packages/config/src/index.ts index 82e9d264e..6d0a5bc9d 100644 --- a/packages/config/src/index.ts +++ b/packages/config/src/index.ts @@ -1,54 +1,104 @@ import networksAsJSON from "./networks.json" -export interface Contracts { - [name: string]: string +interface ContractsJSON { + readonly [name: string]: string +} +export class Contracts implements ContractsJSON { + [name: string]: string +} + +export enum RPCProtocol { + HTTP, + WEBSOCKET } -export enum RpcProtocol { - HTTP, - WEBSOCKET +interface RPCEndpointJSON { + readonly url: string } -export interface RpcEndpoint { - url: string - //readTimeoutSecond: int - //writeTimeoutSecond: int +export class RPCEndpoint implements RPCEndpointJSON { + constructor( + readonly url: string, + //readonly readTimeoutSecond: int, + //readonly writeTimeoutSecond: int, + ) {} } -export interface Chain { - id: number - rpcEndpoints: RpcEndpoint[] - contracts: Contracts +interface ChainJSON { + readonly id: number + readonly rpcEndpoints: RPCEndpointJSON[] + readonly contracts: ContractsJSON } -export interface Chains { - [name: string]: Chain +export class Chain implements ChainJSON { + constructor( + public readonly id: number, + public rpcEndpoints: RPCEndpoint[], + public contracts: Contracts, + ) { + this.id = id + this.rpcEndpoints = new Array() + for (const rpcEndpoint of rpcEndpoints) { + this.rpcEndpoints.push(new RPCEndpoint(rpcEndpoint.url)) + } + this.contracts = new Contracts() + for (const key of Object.keys(contracts)) { + this.contracts[key] = contracts[key] + } + } + getRPCEndpointsByProtocol(protocol: RPCProtocol): RPCEndpoint[] { + const endpoints = new Array() + for (const rpcEndpoint of this.rpcEndpoints) { + if (protocol === RPCProtocol.HTTP) { + if (rpcEndpoint.url.startsWith("https://") || rpcEndpoint.url.startsWith("http://")) { + endpoints.push(new RPCEndpoint(rpcEndpoint.url)) + } + } else if (protocol === RPCProtocol.WEBSOCKET) { + if (rpcEndpoint.url.startsWith("wss://") || rpcEndpoint.url.startsWith("ws://")) { + endpoints.push(new RPCEndpoint(rpcEndpoint.url)) + } + } + } + return endpoints + } } export type Environment = "development" | "production" -export type Networks = { - [env in Environment]: Chains +type NetworksJSON = { + readonly [env in Environment]: ChainsJSON +} + +interface ChainsJSON { + readonly [name: string]: ChainJSON +} + +export class Chains implements ChainsJSON { + [name: string]: Chain + public static load(env: Environment): Chains { + const networks: NetworksJSON = networksAsJSON + const chainsJson: ChainsJSON = networks[env] + const chains: Chains = ChainsFactory.create(chainsJson) + return chains + } } -export const getRpcEndpointsByProtocol = (rpcEndpoints: RpcEndpoint[], protocol: RpcProtocol): RpcEndpoint[] => { - const endpoints = new Array() - for (const rpcEndpoint of rpcEndpoints) { - if (protocol === RpcProtocol.HTTP) { - if (rpcEndpoint.url.startsWith("https://") || rpcEndpoint.url.startsWith("http://")) { - endpoints.push(rpcEndpoint) +class ChainsFactory { + private constructor() {} + static create(chainsJson: ChainsJSON): Chains { + const chains = new Chains() + for (const key in chainsJson) { + const chainJson: ChainJSON = chainsJson[key] + const rpcEndpoints = new Array() + for (const rpcEndpoint of chainJson.rpcEndpoints) { + rpcEndpoints.push(new RPCEndpoint(rpcEndpoint.url)) } - } else if (protocol === RpcProtocol.WEBSOCKET) { - if (rpcEndpoint.url.startsWith("wss://") || rpcEndpoint.url.startsWith("ws://")) { - endpoints.push(rpcEndpoint) + const contracts = new Contracts() + for (const key of Object.keys(chainJson.contracts)) { + contracts[key] = chainJson.contracts[key] } + chains[key] = new Chain(chainJson.id, rpcEndpoints, contracts) } + return chains } - return endpoints -} - -export const loadConfig = (env: Environment): Chains => { - const networks: Networks = networksAsJSON - const chain: Chains = networks[env] - return chain } diff --git a/packages/config/test/index.test.ts b/packages/config/test/index.test.ts index f3b494b72..e788f07aa 100644 --- a/packages/config/test/index.test.ts +++ b/packages/config/test/index.test.ts @@ -1,35 +1,35 @@ import { describe, it } from "mocha" import { assert } from "chai" -import { Chains, getRpcEndpointsByProtocol, loadConfig, RpcProtocol } from "../src/index" +import { Chains, RPCProtocol } from "../src/index" describe("Load configuration from JSON file", () => { it("ethereum chain id is 1", () => { - const config: Chains = loadConfig("production") - const chainId: number = config.ethereum.id + const chains: Chains = Chains.load("production") + const chainId: number = chains.ethereum.id const expected = 1 assert.equal(chainId, expected, `Expecting ethereum prod chain id to equal ${expected}, got '${chainId}'`) }) it("development chain id is 8995", () => { - const config: Chains = loadConfig("development") - const chainId: number = config.ethereum.id + const chains: Chains = Chains.load("development") + const chainId: number = chains.ethereum.id const expected = 8995 assert.equal(chainId, expected, `Expecting ethereum dev chain id to equal ${expected}, got '${chainId}'`) }) it("reads DATA token dev address from JSON", () => { - const config: Chains = loadConfig("development") - const address = config.ethereum.contracts["DATA-token"] + const chains: Chains = Chains.load("development") + const address = chains.ethereum.contracts["DATA-token"] const expected = "0xbAA81A0179015bE47Ad439566374F2Bae098686F" assert.equal(address, expected, `Expecting ethereum DATA token to equal ${expected}, got '${address}'`) }) it("reads prod Polygon RPC URL", () => { - const config: Chains = loadConfig("production") - const rpcHttpUrl = config.polygon.rpcEndpoints[0].url + const chains: Chains = Chains.load("production") + const rpcHttpUrl = chains.polygon.rpcEndpoints[0].url const expected = "https://polygon-rpc.com" assert.equal(rpcHttpUrl, expected, `Expecting prod polygon RPC URL to equal ${expected}, got '${rpcHttpUrl}'`) }) it("finds RPC endpoints by protocol", () => { - const config: Chains = loadConfig("production") - const endpoints = getRpcEndpointsByProtocol(config.binance.rpcEndpoints, RpcProtocol.HTTP) + const chains: Chains = Chains.load("production") + const endpoints = chains.binance.getRPCEndpointsByProtocol(RPCProtocol.HTTP) assert.equal(endpoints.length, 1) assert.equal(endpoints[0].url, "https://bsc-dataseed.binance.org") })