diff --git a/lc_proxy/lc_proxy.nim b/lc_proxy/lc_proxy.nim index e1ba6c6f8..6b607a56f 100644 --- a/lc_proxy/lc_proxy.nim +++ b/lc_proxy/lc_proxy.nim @@ -14,31 +14,20 @@ import std/[os, strutils], chronicles, chronicles/chronos_tools, chronos, eth/keys, - json_rpc/[rpcserver, rpcclient], + json_rpc/rpcproxy, beacon_chain/eth1/eth1_monitor, beacon_chain/gossip_processing/optimistic_processor, beacon_chain/networking/topic_params, beacon_chain/spec/beaconstate, beacon_chain/spec/datatypes/[phase0, altair, bellatrix], beacon_chain/[light_client, nimbus_binary_common, version], + ../nimbus/rpc/cors, ./rpc/rpc_eth_lc_api, ./lc_proxy_conf -from beacon_chain/consensus_object_pools/consensus_manager import runForkchoiceUpdated from beacon_chain/gossip_processing/block_processor import newExecutionPayload from beacon_chain/gossip_processing/eth2_processor import toValidationResult -proc initRpcClient(config: LcProxyConf): Future[RpcClient] {.async.} = - case config.web3ClientConfig.kind - of WsClient: - let wssClient = newRpcWebSocketClient() - await wssClient.connect(config.web3ClientConfig.url) - return wssClient - of HttpClient: - let httpClient = newRpcHttpClient() - await httpClient.connect(config.web3ClientConfig.url) - return httpClient - func getConfiguredChainId(networkMetadata: Eth2NetworkMetadata): Quantity = if networkMetadata.eth1Network.isSome(): let @@ -105,13 +94,18 @@ proc run() {.raises: [Exception, Defect].} = forkDigests, getBeaconTime, genesis_validators_root ) - rpcClient = waitFor initRpcClient(config) + # TODO: for now we serve all cross origin requests + authHooks = @[httpCors(@[])] - rpcHttpServer = newRpcHttpServer( - [initTAddress(config.rpcAddress, config.rpcPort)] + clientConfig = config.web3url.asClientConfig() + + rpcProxy = RpcProxy.new( + [initTAddress(config.rpcAddress, config.rpcPort)], + clientConfig, + authHooks ) - lcProxy = LightClientRpcProxy.new(rpcHttpServer, rpcClient, chainId) + lcProxy = LightClientRpcProxy.new(rpcProxy, chainId) optimisticHandler = proc(signedBlock: ForkedMsgTrustedSignedBeaconBlock): Future[void] {.async.} = @@ -156,7 +150,7 @@ proc run() {.raises: [Exception, Defect].} = waitFor network.startListening() waitFor network.start() - rpcHttpServer.start() + waitFor rpcProxy.start() waitFor lcProxy.verifyChaindId() proc onFinalizedHeader( diff --git a/lc_proxy/lc_proxy_conf.nim b/lc_proxy/lc_proxy_conf.nim index 661667266..ba22c2626 100644 --- a/lc_proxy/lc_proxy_conf.nim +++ b/lc_proxy/lc_proxy_conf.nim @@ -14,7 +14,8 @@ import std/os, json_serialization/std/net, beacon_chain/light_client, - beacon_chain/conf + beacon_chain/conf, + json_rpc/[rpcproxy] export net, conf @@ -28,19 +29,17 @@ proc defaultLCPDataDir*(): string = getHomeDir() / dataDir - -type Web3ClientType* = enum - WsClient, HttpClient - -type Web3ClientConfig* = object - kind*: Web3ClientType - url*: string - const - defaultWeb3Address* = (static "http://127.0.0.1:8546") - defaultWeb3ClientConfig* = Web3ClientConfig(url: defaultWeb3Address, kind: HttpClient) defaultDataLCPDirDesc* = defaultLCPDataDir() +type + Web3UrlKind* = enum + HttpUrl, WsUrl + + ValidatedWeb3Url* = object + kind*: Web3UrlKind + web3Url*: string + type LcProxyConf* = object # Config configFile* {. @@ -164,25 +163,25 @@ type LcProxyConf* = object defaultValueDesc: $defaultAdminListenAddressDesc name: "rpc-address" .}: ValidIpAddress - web3ClientConfig* {. + # There is no default as its need to be provided by the user + web3url* {. desc: "url of web3 data provider" - defaultValue: defaultWeb3ClientConfig - name: "web3-url" .}: Web3ClientConfig + name: "web3-url" .}: ValidatedWeb3Url -proc parseCmdArg*(T: type Web3ClientConfig, p: TaintedString): T +proc parseCmdArg*(T: type ValidatedWeb3Url, p: TaintedString): T {.raises: [Defect, ConfigurationError].} = let url = parseUri(p) let normalizedScheme = url.scheme.toLowerAscii() if (normalizedScheme == "http" or normalizedScheme == "https"): - Web3ClientConfig(kind: HttpClient, url: p) + ValidatedWeb3Url(kind: HttpUrl, web3Url: p) elif (normalizedScheme == "ws" or normalizedScheme == "wss"): - Web3ClientConfig(kind: WsClient, url: p) + ValidatedWeb3Url(kind: WsUrl, web3Url: p) else: raise newException( - ConfigurationError, "Web3 client uri should have defined scheme (http/https/ws/wss)" + ConfigurationError, "Web3 url should have defined scheme (http/https/ws/wss)" ) -proc completeCmdArg*(T: type Web3ClientConfig, val: TaintedString): seq[string] = +proc completeCmdArg*(T: type ValidatedWeb3Url, val: TaintedString): seq[string] = return @[] func asLightClientConf*(pc: LcProxyConf): LightClientConf = @@ -210,3 +209,13 @@ func asLightClientConf*(pc: LcProxyConf): LightClientConf = jwtSecret: none(string), stopAtEpoch: 0 ) + +# TODO: Cannot use ClientConfig in LcProxyConf due to the fact that +# it contain `set[TLSFlags]` which does not have proper toml serialization +func asClientConfig*(url: ValidatedWeb3Url): ClientConfig = + case url.kind + of HttpUrl: + getHttpClientConfig(url.web3Url) + of WsUrl: + getWebSocketClientConfig(url.web3Url, flags = {}) + diff --git a/lc_proxy/rpc/rpc_eth_lc_api.nim b/lc_proxy/rpc/rpc_eth_lc_api.nim index 39e548be1..64448bb6f 100644 --- a/lc_proxy/rpc/rpc_eth_lc_api.nim +++ b/lc_proxy/rpc/rpc_eth_lc_api.nim @@ -11,7 +11,7 @@ import stint, stew/byteutils, chronicles, - json_rpc/[rpcserver, rpcclient], + json_rpc/[rpcproxy, rpcserver, rpcclient], web3, web3/[ethhexstrings, ethtypes], beacon_chain/eth1/eth1_monitor, @@ -36,8 +36,7 @@ template encodeQuantity(value: Quantity): HexQuantityStr = hexQuantityStr(encodeQuantity(value.uint64)) type LightClientRpcProxy* = ref object - client*: RpcClient - server*: RpcServer + proxy: RpcProxy executionPayload*: Opt[ExecutionPayloadV1] chainId: Quantity @@ -54,13 +53,15 @@ template checkPreconditions(payload: Opt[ExecutionPayloadV1], quantityTag: strin # fetched on demand. raise newException(ValueError, "Only latest block is supported") +template rpcClient(lcProxy: LightClientRpcProxy): RpcClient = lcProxy.proxy.getClient() + proc installEthApiHandlers*(lcProxy: LightClientRpcProxy) = template payload(): Opt[ExecutionPayloadV1] = lcProxy.executionPayload - lcProxy.server.rpc("eth_chainId") do() -> HexQuantityStr: + lcProxy.proxy.rpc("eth_chainId") do() -> HexQuantityStr: return encodeQuantity(lcProxy.chainId) - lcProxy.server.rpc("eth_blockNumber") do() -> HexQuantityStr: + lcProxy.proxy.rpc("eth_blockNumber") do() -> HexQuantityStr: ## Returns the number of most recent block. if payload.isNone: raise newException(ValueError, "Syncing") @@ -68,7 +69,7 @@ proc installEthApiHandlers*(lcProxy: LightClientRpcProxy) = return encodeQuantity(payload.get.blockNumber) # TODO quantity tag should be better typed - lcProxy.server.rpc("eth_getBalance") do(address: Address, quantityTag: string) -> HexQuantityStr: + lcProxy.proxy.rpc("eth_getBalance") do(address: Address, quantityTag: string) -> HexQuantityStr: checkPreconditions(payload, quantityTag) # When requesting state for `latest` block number, we need to translate @@ -81,7 +82,7 @@ proc installEthApiHandlers*(lcProxy: LightClientRpcProxy) = info "Forwarding get_Balance", executionBn = blockNumber - let proof = await lcProxy.client.eth_getProof(address, @[], blockId(blockNumber)) + let proof = await lcProxy.rpcClient.eth_getProof(address, @[], blockId(blockNumber)) let accountResult = getAccountFromProof( executionPayload.stateRoot, @@ -98,7 +99,7 @@ proc installEthApiHandlers*(lcProxy: LightClientRpcProxy) = else: raise newException(ValueError, accountResult.error) - lcProxy.server.rpc("eth_getStorageAt") do(address: Address, slot: HexDataStr, quantityTag: string) -> HexDataStr: + lcProxy.proxy.rpc("eth_getStorageAt") do(address: Address, slot: HexDataStr, quantityTag: string) -> HexDataStr: checkPreconditions(payload, quantityTag) let @@ -108,7 +109,7 @@ proc installEthApiHandlers*(lcProxy: LightClientRpcProxy) = info "Forwarding eth_getStorageAt", executionBn = blockNumber - let proof = await lcProxy.client.eth_getProof(address, @[uslot], blockId(blockNumber)) + let proof = await lcProxy.rpcClient.eth_getProof(address, @[uslot], blockId(blockNumber)) let dataResult = getStorageData(executionPayload.stateRoot, uslot, proof) @@ -118,15 +119,23 @@ proc installEthApiHandlers*(lcProxy: LightClientRpcProxy) = else: raise newException(ValueError, dataResult.error) + # TODO This methods are forwarded directly to provider therefore thay are not + # validated in any way + lcProxy.proxy.registerProxyMethod("net_version") + lcProxy.proxy.registerProxyMethod("eth_call") + + # TODO cache blocks received from light client, and respond using them in this + # call. It would also enable handling of numerical `quantityTag` for the + # set of cached blocks + lcProxy.proxy.registerProxyMethod("eth_getBlockByNumber") + proc new*( T: type LightClientRpcProxy, - server: RpcServer, - client: RpcClient, + proxy: RpcProxy, chainId: Quantity): T = return LightClientRpcProxy( - client: client, - server: server, + proxy: proxy, chainId: chainId ) @@ -136,7 +145,7 @@ proc verifyChaindId*(p: LightClientRpcProxy): Future[void] {.async.} = # retry 2 times, if the data provider will fail despite re-tries, propagate # exception to the caller. let providerId = awaitWithRetries( - p.client.eth_chainId(), + p.rpcClient.eth_chainId(), retries = 2, timeout = seconds(30) ) diff --git a/nimbus/rpc/cors.nim b/nimbus/rpc/cors.nim index b4a264b30..bd4c0e13e 100644 --- a/nimbus/rpc/cors.nim +++ b/nimbus/rpc/cors.nim @@ -14,8 +14,7 @@ import chronos/apps/http/[httptable, httpserver], json_rpc/rpcserver, httputils, - websock/websock as ws, - ../config + websock/websock as ws proc sameOrigin(a, b: Uri): bool = a.hostname == b.hostname and