diff --git a/fluffy/tools/portal_bridge/portal_bridge_beacon.nim b/fluffy/tools/portal_bridge/portal_bridge_beacon.nim index 8f0d33333..0187e00cc 100644 --- a/fluffy/tools/portal_bridge/portal_bridge_beacon.nim +++ b/fluffy/tools/portal_bridge/portal_bridge_beacon.nim @@ -19,7 +19,7 @@ import ../../network/beacon/beacon_content, ../../rpc/portal_rpc_client, ../eth_data_exporter/cl_data_exporter, - ./portal_bridge_conf + ./[portal_bridge_conf, portal_bridge_common] const restRequestsTimeout = 30.seconds @@ -29,7 +29,7 @@ proc sleepAsync(t: TimeDiff): Future[void] = proc gossipLCBootstrapUpdate( restClient: RestClientRef, - portalRpcClient: RpcHttpClient, + portalRpcClient: RpcClient, trustedBlockRoot: Eth2Digest, cfg: RuntimeConfig, forkDigests: ref ForkDigests, @@ -75,7 +75,7 @@ proc gossipLCBootstrapUpdate( proc gossipLCUpdates( restClient: RestClientRef, - portalRpcClient: RpcHttpClient, + portalRpcClient: RpcClient, startPeriod: uint64, count: uint64, cfg: RuntimeConfig, @@ -135,7 +135,7 @@ proc gossipLCUpdates( proc gossipLCFinalityUpdate( restClient: RestClientRef, - portalRpcClient: RpcHttpClient, + portalRpcClient: RpcClient, cfg: RuntimeConfig, forkDigests: ref ForkDigests, ): Future[Result[Slot, string]] {.async.} = @@ -182,7 +182,7 @@ proc gossipLCFinalityUpdate( proc gossipLCOptimisticUpdate( restClient: RestClientRef, - portalRpcClient: RpcHttpClient, + portalRpcClient: RpcClient, cfg: RuntimeConfig, forkDigests: ref ForkDigests, ): Future[Result[Slot, string]] {.async.} = @@ -234,23 +234,20 @@ proc runBeacon*(config: PortalBridgeConf) {.raises: [CatchableError].} = let (cfg, forkDigests, beaconClock) = getBeaconData() getBeaconTime = beaconClock.getBeaconTimeFn() - portalRpcClient = newRpcHttpClient() + portalRpcClient = newRpcClientConnect(config.portalRpcUrl) restClient = RestClientRef.new(config.restUrl).valueOr: fatal "Cannot connect to server", error = $error quit QuitFailure proc backfill( beaconRestClient: RestClientRef, - rpcAddress: string, - rpcPort: Port, + portalRpcClient: RpcClient, backfillAmount: uint64, trustedBlockRoot: Option[TrustedDigest], ) {.async.} = # Bootstrap backfill, currently just one bootstrap selected by # trusted-block-root, could become a selected list, or some other way. if trustedBlockRoot.isSome(): - await portalRpcClient.connect(rpcAddress, rpcPort, false) - let res = await gossipLCBootstrapUpdate( beaconRestClient, portalRpcClient, trustedBlockRoot.get(), cfg, forkDigests ) @@ -274,8 +271,6 @@ proc runBeacon*(config: PortalBridgeConf) {.raises: [CatchableError].} = leftOver = backfillAmount mod updatesPerRequest for i in 0 ..< requestAmount: - await portalRpcClient.connect(rpcAddress, rpcPort, false) - let res = await gossipLCUpdates( beaconRestClient, portalRpcClient, @@ -291,8 +286,6 @@ proc runBeacon*(config: PortalBridgeConf) {.raises: [CatchableError].} = await portalRpcClient.close() if leftOver > 0: - await portalRpcClient.connect(rpcAddress, rpcPort, false) - let res = await gossipLCUpdates( beaconRestClient, portalRpcClient, @@ -340,8 +333,6 @@ proc runBeacon*(config: PortalBridgeConf) {.raises: [CatchableError].} = # Or basically `lightClientOptimisticUpdateSlotOffset` await sleepAsync((SECONDS_PER_SLOT div INTERVALS_PER_SLOT).int.seconds) - await portalRpcClient.connect(config.rpcAddress, Port(config.rpcPort), false) - let res = await gossipLCOptimisticUpdate(restClient, portalRpcClient, cfg, forkDigests) @@ -394,8 +385,7 @@ proc runBeacon*(config: PortalBridgeConf) {.raises: [CatchableError].} = timeToNextSlot = nextSlot.start_beacon_time() - getBeaconTime() waitFor backfill( - restClient, config.rpcAddress, config.rpcPort, config.backfillAmount, - config.trustedBlockRoot, + restClient, portalRpcClient, config.backfillAmount, config.trustedBlockRoot ) asyncSpawn runOnSlotLoop() diff --git a/fluffy/tools/portal_bridge/portal_bridge_common.nim b/fluffy/tools/portal_bridge/portal_bridge_common.nim new file mode 100644 index 000000000..a0c1f4c68 --- /dev/null +++ b/fluffy/tools/portal_bridge/portal_bridge_common.nim @@ -0,0 +1,30 @@ +# Fluffy +# Copyright (c) 2024 Status Research & Development GmbH +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed except according to those terms. + +{.push raises: [].} + +import chronicles, json_rpc/rpcclient, ./portal_bridge_conf + +proc newRpcClientConnect*(url: JsonRpcUrl): RpcClient = + ## Instantiate a new JSON-RPC client and try to connect. Will quit on failure. + case url.kind + of HttpUrl: + let client = newRpcHttpClient() + try: + waitFor client.connect(url.value) + except CatchableError as e: + fatal "Failed to connect to JSON-RPC server", error = $e.msg, url = url.value + quit QuitFailure + client + of WsUrl: + let client = newRpcWebSocketClient() + try: + waitFor client.connect(url.value) + except CatchableError as e: + fatal "Failed to connect to JSON-RPC server", error = $e.msg, url = url.value + quit QuitFailure + client diff --git a/fluffy/tools/portal_bridge/portal_bridge_conf.nim b/fluffy/tools/portal_bridge/portal_bridge_conf.nim index de2b47084..7a41a028f 100644 --- a/fluffy/tools/portal_bridge/portal_bridge_conf.nim +++ b/fluffy/tools/portal_bridge/portal_bridge_conf.nim @@ -29,13 +29,13 @@ proc defaultEra1DataDir*(): string = type TrustedDigest* = MDigest[32 * 8] - Web3UrlKind* = enum + JsonRpcUrlKind* = enum HttpUrl WsUrl - Web3Url* = object - kind*: Web3UrlKind - url*: string + JsonRpcUrl* = object + kind*: JsonRpcUrlKind + value*: string PortalBridgeCmd* = enum beacon = "Run a Portal bridge for the beacon network" @@ -55,18 +55,8 @@ type name: "log-format" .}: StdoutLogKind - # Portal JSON-RPC API server to connect to - rpcAddress* {. - desc: "Listening address of the Portal JSON-RPC server", - defaultValue: "127.0.0.1", - name: "rpc-address" - .}: string - - rpcPort* {. - desc: "Listening port of the Portal JSON-RPC server", - defaultValue: 8545, - name: "rpc-port" - .}: Port + portalRpcUrl* {.desc: "Portal node JSON-RPC API URL", name: "portal-rpc-url".}: + JsonRpcUrl case cmd* {.command, desc: "".}: PortalBridgeCmd of PortalBridgeCmd.beacon: @@ -91,7 +81,8 @@ type name: "trusted-block-root" .}: Option[TrustedDigest] of PortalBridgeCmd.history: - web3Url* {.desc: "Execution layer JSON-RPC API URL", name: "web3-url".}: Web3Url + web3Url* {.desc: "Execution layer JSON-RPC API URL", name: "web3-url".}: + JsonRpcUrl blockVerify* {. desc: "Verify the block header, body and receipts", @@ -128,7 +119,7 @@ type .}: InputDir of PortalBridgeCmd.state: web3UrlState* {.desc: "Execution layer JSON-RPC API URL", name: "web3-url".}: - Web3Url + JsonRpcUrl func parseCmdArg*(T: type TrustedDigest, input: string): T {.raises: [ValueError].} = TrustedDigest.fromHex(input) @@ -136,20 +127,20 @@ func parseCmdArg*(T: type TrustedDigest, input: string): T {.raises: [ValueError func completeCmdArg*(T: type TrustedDigest, input: string): seq[string] = return @[] -proc parseCmdArg*(T: type Web3Url, p: string): T {.raises: [ValueError].} = +proc parseCmdArg*(T: type JsonRpcUrl, p: string): T {.raises: [ValueError].} = let url = parseUri(p) normalizedScheme = url.scheme.toLowerAscii() if (normalizedScheme == "http" or normalizedScheme == "https"): - Web3Url(kind: HttpUrl, url: p) + JsonRpcUrl(kind: HttpUrl, value: p) elif (normalizedScheme == "ws" or normalizedScheme == "wss"): - Web3Url(kind: WsUrl, url: p) + JsonRpcUrl(kind: WsUrl, value: p) else: raise newException( ValueError, "The Web3 URL must specify one of following protocols: http/https/ws/wss", ) -proc completeCmdArg*(T: type Web3Url, val: string): seq[string] = +proc completeCmdArg*(T: type JsonRpcUrl, val: string): seq[string] = return @[] diff --git a/fluffy/tools/portal_bridge/portal_bridge_history.nim b/fluffy/tools/portal_bridge/portal_bridge_history.nim index 06c2ca1ac..004f6ef23 100644 --- a/fluffy/tools/portal_bridge/portal_bridge_history.nim +++ b/fluffy/tools/portal_bridge/portal_bridge_history.nim @@ -23,7 +23,7 @@ import ../../network_metadata, ../../eth_data/[era1, history_data_ssz_e2s, history_data_seeding], ../../database/era1_db, - ./portal_bridge_conf + ./[portal_bridge_conf, portal_bridge_common] from stew/objects import checkedEnumAssign @@ -127,11 +127,11 @@ proc getBlockByNumber( try: let res = await client.eth_getBlockByNumber(blockTag, fullTransactions) if res.isNil: - return err("failed to get latest blockHeader") + return err("EL failed to provide requested block") res except CatchableError as e: - return err("JSON-RPC eth_getBlockByNumber failed: " & e.msg) + return err("EL JSON-RPC eth_getBlockByNumber failed: " & e.msg) return ok(blck) @@ -142,9 +142,9 @@ proc getBlockReceipts( try: await client.eth_getBlockReceipts(blockId(blockNumber)) except CatchableError as e: - return err("JSON-RPC eth_getBlockReceipts failed: " & e.msg) + return err("EL JSON-RPC eth_getBlockReceipts failed: " & e.msg) if res.isNone(): - err("Failed getting receipts") + err("EL failed to provided requested receipts") else: ok(res.get()) @@ -165,7 +165,7 @@ proc gossipBlockHeader( encodedContentKeyHex, SSZ.encode(headerWithProof).toHex() ) except CatchableError as e: - return err("JSON-RPC error: " & $e.msg) + return err("JSON-RPC portal_historyGossip failed: " & $e.msg) info "Block header gossiped", peers, contentKey = encodedContentKeyHex return ok() @@ -185,7 +185,7 @@ proc gossipBlockBody( encodedContentKeyHex, SSZ.encode(body).toHex() ) except CatchableError as e: - return err("JSON-RPC error: " & $e.msg) + return err("JSON-RPC portal_historyGossip failed: " & $e.msg) info "Block body gossiped", peers, contentKey = encodedContentKeyHex return ok() @@ -204,7 +204,7 @@ proc gossipReceipts( encodedContentKeyHex, SSZ.encode(receipts).toHex() ) except CatchableError as e: - return err("JSON-RPC error: " & $e.msg) + return err("JSON-RPC portal_historyGossip failed: " & $e.msg) info "Receipts gossiped", peers, contentKey = encodedContentKeyHex return ok() @@ -266,7 +266,7 @@ proc runLatestLoop( # gossip block header (await portalClient.gossipBlockHeader(hash, headerWithProof)).isOkOr: - error "Failed to gossip block header", error + error "Failed to gossip block header", error, hash # For bodies & receipts to get verified, the header needs to be available # on the network. Wait a little to get the headers propagated through @@ -275,11 +275,11 @@ proc runLatestLoop( # gossip block body (await portalClient.gossipBlockBody(hash, body)).isOkOr: - error "Failed to gossip block body", error + error "Failed to gossip block body", error, hash # gossip receipts (await portalClient.gossipReceipts(hash, portalReceipts)).isOkOr: - error "Failed to gossip receipts", error + error "Failed to gossip receipts", error, hash # Making sure here that we poll enough times not to miss a block. # We could also do some work without awaiting it, e.g. the gossiping or @@ -319,7 +319,7 @@ proc gossipHeadersWithProof( contentKey.asSeq.toHex(), contentValue.toHex() ) except CatchableError as e: - return err("JSON-RPC error: " & $e.msg) + return err("JSON-RPC portal_historyGossip failed: " & $e.msg) info "Block header gossiped", peers, contentKey ok() @@ -339,7 +339,7 @@ proc gossipBlockContent( contentKey.asSeq.toHex(), contentValue.toHex() ) except CatchableError as e: - return err("JSON-RPC error: " & $e.msg) + return err("JSON-RPC portal_historyGossip failed: " & $e.msg) info "Block content gossiped", peers, contentKey ok() @@ -377,7 +377,7 @@ proc runBackfillLoop( try: await portalClient.portal_historyGossipHeaders(eraFile) except CatchableError as e: - error "JSON-RPC method failed", error = e.msg + error "JSON-RPC portal_historyGossipHeaders failed", error = e.msg false if headerRes: @@ -386,7 +386,7 @@ proc runBackfillLoop( try: await portalClient.portal_historyGossipBlockContent(eraFile) except CatchableError as e: - error "JSON-RPC method failed", error = e.msg + error "JSON-RPC portal_historyGossipBlockContent failed", error = e.msg false if res: error "Failed to gossip block content from era1 file", eraFile @@ -455,7 +455,7 @@ proc runBackfillLoopAuditMode( error "Block hash mismatch", blockNumber break headerBlock - info "Retrieved block header from Portal network" + info "Retrieved block header from Portal network", blockHash headerSuccess = true # body @@ -523,44 +523,28 @@ proc runBackfillLoopAuditMode( raiseAssert "Failed to build header with proof: " & error (await portalClient.gossipBlockHeader(blockHash, headerWithProof)).isOkOr: - error "Failed to gossip block header", error + error "Failed to gossip block header", error, blockHash if not bodySuccess: ( await portalClient.gossipBlockBody( blockHash, PortalBlockBodyLegacy.fromBlockBody(body) ) ).isOkOr: - error "Failed to gossip block body", error + error "Failed to gossip block body", error, blockHash if not receiptsSuccess: ( await portalClient.gossipReceipts( blockHash, PortalReceipts.fromReceipts(receipts) ) ).isOkOr: - error "Failed to gossip receipts", error + error "Failed to gossip receipts", error, blockHash await sleepAsync(2.seconds) proc runHistory*(config: PortalBridgeConf) = let - portalClient = newRpcHttpClient() - # TODO: Use Web3 object? - web3Client: RpcClient = - case config.web3Url.kind - of HttpUrl: - newRpcHttpClient() - of WsUrl: - newRpcWebSocketClient() - try: - waitFor portalClient.connect(config.rpcAddress, Port(config.rpcPort), false) - except CatchableError as e: - error "Failed to connect to portal RPC", error = $e.msg - - if config.web3Url.kind == HttpUrl: - try: - waitFor (RpcHttpClient(web3Client)).connect(config.web3Url.url) - except CatchableError as e: - error "Failed to connect to web3 RPC", error = $e.msg + portalClient = newRpcClientConnect(config.portalRpcUrl) + web3Client = newRpcClientConnect(config.web3Url) if config.latest: asyncSpawn runLatestLoop(portalClient, web3Client, config.blockVerify) diff --git a/fluffy/tools/portal_bridge/portal_bridge_state.nim b/fluffy/tools/portal_bridge/portal_bridge_state.nim index 7f66219d9..4a13089b6 100644 --- a/fluffy/tools/portal_bridge/portal_bridge_state.nim +++ b/fluffy/tools/portal_bridge/portal_bridge_state.nim @@ -7,28 +7,12 @@ {.push raises: [].} -import chronos, chronicles, ../../rpc/portal_rpc_client, ./portal_bridge_conf +import chronicles, ./[portal_bridge_conf, portal_bridge_common] proc runState*(config: PortalBridgeConf) = let - portalClient = newRpcHttpClient() - # TODO: Use Web3 object? - web3Client: RpcClient = - case config.web3UrlState.kind - of HttpUrl: - newRpcHttpClient() - of WsUrl: - newRpcWebSocketClient() - try: - waitFor portalClient.connect(config.rpcAddress, Port(config.rpcPort), false) - except CatchableError as e: - error "Failed to connect to portal RPC", error = $e.msg - - if config.web3UrlState.kind == HttpUrl: - try: - waitFor (RpcHttpClient(web3Client)).connect(config.web3UrlState.url) - except CatchableError as e: - error "Failed to connect to web3 RPC", error = $e.msg + portalClient = newRpcClientConnect(config.portalRpcUrl) + web3Client = newRpcClientConnect(config.web3Url) # TODO: # Here we'd want to implement initially a loop that backfills the state