diff --git a/.gitmodules b/.gitmodules index 3ba0fff381..2f0c9f6dae 100644 --- a/.gitmodules +++ b/.gitmodules @@ -242,4 +242,7 @@ path = vendor/nim-intops url = https://github.com/vacp2p/nim-intops.git ignore = untracked +[submodule "vendor/nim-async-channels"] + path = vendor/nim-async-channels + url = https://github.com/status-im/nim-async-channels branch = master diff --git a/execution_chain/conf.nim b/execution_chain/conf.nim index a3303663e6..56d351be33 100644 --- a/execution_chain/conf.nim +++ b/execution_chain/conf.nim @@ -437,6 +437,12 @@ type defaultValue: false name: "engine-api" .}: bool + engineApiChannelEnabled* {. + hidden + desc: "Enable the Engine API Channel" + defaultValue: false + name: "debug-engine-api-channel" .}: bool + engineApiPort* {. desc: "Listening port for the Engine API(http and ws)" defaultValue: defaultEngineApiPort @@ -773,7 +779,7 @@ func getAllowedOrigins*(config: ExecutionClientConf): seq[Uri] = result.add parseUri(item) func engineApiServerEnabled*(config: ExecutionClientConf): bool = - config.engineApiEnabled or config.engineApiWsEnabled + config.engineApiEnabled or config.engineApiWsEnabled or config.engineApiChannelEnabled func shareServerWithEngineApi*(config: ExecutionClientConf): bool = config.engineApiServerEnabled and diff --git a/execution_chain/el_sync.nim b/execution_chain/el_sync.nim index a9775a2a6a..e714baa5ca 100644 --- a/execution_chain/el_sync.nim +++ b/execution_chain/el_sync.nim @@ -1,5 +1,5 @@ # Nimbus -# Copyright (c) 2024-2025 Status Research & Development GmbH +# Copyright (c) 2024-2026 Status Research & Development GmbH # Licensed under either of # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) # * MIT license ([LICENSE-MIT](LICENSE-MIT)) @@ -18,7 +18,8 @@ import web3/[engine_api, primitives, conversions], beacon_chain/consensus_object_pools/blockchain_dag, beacon_chain/el/[el_manager, engine_api_conversions], - beacon_chain/spec/[forks, presets, state_transition_block] + beacon_chain/spec/[forks, presets, state_transition_block], + json_rpc/client logScope: topics = "elsync" @@ -87,24 +88,15 @@ proc findSlot( Opt.some importedSlot -proc syncToEngineApi*(dag: ChainDAGRef, url: EngineApiUrl) {.async.} = +proc syncToEngineApi*(dag: ChainDAGRef, rpcClient: RpcClient) {.async.} = # Takes blocks from the CL and sends them to the EL - the attempt is made # optimistically until something unexpected happens (reorg etc) at which point # the process ends let # Create the client for the engine api - # And exchange the capabilities for a test communication - web3 = await url.newWeb3() - rpcClient = web3.provider (lastEra1Block, firstSlotAfterMerge) = dag.cfg.loadNetworkConfig() - defer: - try: - await web3.close() - except: - discard - # Load the EL state detials and create the beaconAPI client var elBlockNumber = uint64(await rpcClient.eth_blockNumber()) diff --git a/execution_chain/nimbus.nim b/execution_chain/nimbus.nim index 2a452f0057..5ca14d38b0 100644 --- a/execution_chain/nimbus.nim +++ b/execution_chain/nimbus.nim @@ -16,15 +16,15 @@ proc workaround*(): int {.exportc.} = return int(Future[Quantity]().internalValue) import - std/[os, net, options, strformat, terminal, typetraits], + std/[os, net, options, terminal, typetraits], stew/io2, chronos/threadsync, chronicles, metrics, metrics/chronos_httpserver, - nimcrypto/sysrand, eth/enr/enr, eth/net/nat, + json_rpc/rpcchannels, eth/p2p/discoveryv5/random2, beacon_chain/spec/[engine_authentication], beacon_chain/validators/keystore_management, @@ -36,7 +36,6 @@ import nimbus_binary_common, process_state, ], - ./rpc/jwt_auth, ./[ constants, conf as ecconf, @@ -178,13 +177,13 @@ type tcpPort: Port udpPort: Port elSync: bool + channel: RpcChannelPtrs ExecutionThreadConfig = object tsp: ThreadSignalPtr tcpPort: Port udpPort: Option[Port] - -var jwtKey: JwtSharedKey + channel: RpcChannelPtrs proc dataDir*(config: NimbusConf): string = string config.dataDirFlag.get( @@ -198,14 +197,19 @@ proc justWait(tsp: ThreadSignalPtr) {.async: (raises: [CancelledError]).} = notice "Waiting failed", err = exc.msg proc elSyncLoop( - dag: ChainDAGRef, url: EngineApiUrl + dag: ChainDAGRef, elManager: ELManager ) {.async: (raises: [CancelledError]).} = while true: await sleepAsync(12.seconds) # TODO trigger only when the EL needs syncing try: - await syncToEngineApi(dag, url) + let channel = elManager.channel() + if channel == nil: + debug "Channel not ready" + continue + + await syncToEngineApi(dag, channel) except CatchableError as exc: # This can happen when the EL is busy doing some work, specially on # startup @@ -216,14 +220,8 @@ proc runBeaconNode(p: BeaconThreadConfig) {.thread.} = stderr.writeLine error # Logging not yet set up quit QuitFailure - let engineUrl = - EngineApiUrl.init(&"http://127.0.0.1:{defaultEngineApiPort}/", Opt.some(jwtKey)) - config.metricsEnabled = false - config.elUrls.add EngineApiUrlConfigValue( - url: engineUrl.url, jwtSecret: some toHex(distinctBase(jwtKey)) - ) - + config.elUrls = @[EngineApiUrlConfigValue(channel: Opt.some(p.channel))] config.statusBarEnabled = false # Multi-threading issues due to logging config.tcpPort = p.tcpPort config.udpPort = p.udpPort @@ -251,7 +249,7 @@ proc runBeaconNode(p: BeaconThreadConfig) {.thread.} = return if p.elSync: - discard elSyncLoop(node.dag, engineUrl) + discard elSyncLoop(node.dag, node.elManager) dynamicLogScope(comp = "bn"): if node.nickname != "": @@ -266,11 +264,8 @@ proc runBeaconNode(p: BeaconThreadConfig) {.thread.} = proc runExecutionClient(p: ExecutionThreadConfig) {.thread.} = var config = makeConfig(ignoreUnknown = true) config.metricsEnabled = false - config.engineApiEnabled = true - config.engineApiPort = Port(defaultEngineApiPort) - config.engineApiAddress = defaultAdminListenAddress - config.jwtSecret.reset() - config.jwtSecretValue = some toHex(distinctBase(jwtKey)) + config.engineApiEnabled = false + config.engineApiChannelEnabled = true config.agentString = "nimbus" config.tcpPort = p.tcpPort config.udpPortFlag = p.udpPort @@ -288,7 +283,9 @@ proc runExecutionClient(p: ExecutionThreadConfig) {.thread.} = let com = setupCommonRef(config) dynamicLogScope(comp = "ec"): - nimbus_execution_client.runExeClient(config, com, p.tsp.justWait()) + nimbus_execution_client.runExeClient( + config, com, p.tsp.justWait(), channel = Opt.some p.channel + ) # Stop the other thread as well, in case `runExeClient` stopped early waitFor p.tsp.fire() @@ -298,7 +295,7 @@ proc runCombinedClient() = # go away discard randomBytes(distinctBase(jwtKey)) - const banner = ClientId & "\p\pSubcommand options can also be used with the main node, see `beaconNode --help` and `executionClient --help`" + const banner = projectId & "\p\pSubcommand options can also be used with the main node, see `beaconNode --help` and `executionClient --help`" var config = NimbusConf.loadWithBanners( banner, copyright, [specBanner], ignoreUnknown = true, setupLogger = true @@ -337,6 +334,9 @@ proc runCombinedClient() = "Baked-in KZG setup is correct" ) + var channel: RpcChannel + let pairs = channel.open().expect("working channel") + var bnThread: Thread[BeaconThreadConfig] let bnStop = ThreadSignalPtr.new().expect("working ThreadSignalPtr") createThread( @@ -347,6 +347,7 @@ proc runCombinedClient() = tcpPort: config.beaconTcpPort.get(config.tcpPort.get(Port defaultEth2TcpPort)), udpPort: config.beaconUdpPort.get(config.udpPort.get(Port defaultEth2TcpPort)), elSync: config.elSync, + channel: pairs, ), ) @@ -369,6 +370,7 @@ proc runCombinedClient() = some(Port(uint16(config.udpPort.get()) + 1)) else: none(Port), + channel: pairs, ), ) diff --git a/execution_chain/nimbus_desc.nim b/execution_chain/nimbus_desc.nim index eb2c294e4e..40e227e523 100644 --- a/execution_chain/nimbus_desc.nim +++ b/execution_chain/nimbus_desc.nim @@ -1,5 +1,5 @@ # Nimbus -# Copyright (c) 2024-2025 Status Research & Development GmbH +# Copyright (c) 2024-2026 Status Research & Development GmbH # Licensed under either of # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) # * MIT license ([LICENSE-MIT](LICENSE-MIT)) @@ -22,7 +22,8 @@ import ./sync/snap as snap_sync, ./sync/wire_protocol, ./beacon/beacon_engine, - ./common + ./common, + json_rpc/rpcchannels when enabledLogLevel == TRACE: import std/sequtils @@ -44,6 +45,7 @@ type NimbusNode* = ref object httpServer*: NimbusHttpServerRef engineApiServer*: NimbusHttpServerRef + engineApiChannel*: RpcChannelServer ethNode*: EthereumNode fc*: ForkedChainRef txPool*: TxPoolRef diff --git a/execution_chain/nimbus_execution_client.nim b/execution_chain/nimbus_execution_client.nim index 15600673b7..2dda7b496c 100644 --- a/execution_chain/nimbus_execution_client.nim +++ b/execution_chain/nimbus_execution_client.nim @@ -205,14 +205,14 @@ proc setupP2P(nimbus: NimbusNode, config: ExecutionClientConf, com: CommonRef) = nimbus.beaconSyncRef = BeaconSyncRef(nil) nimbus.snapSyncRef = SnapSyncRef(nil) -proc init*(nimbus: NimbusNode, config: ExecutionClientConf, com: CommonRef) = +proc init*(nimbus: NimbusNode, config: ExecutionClientConf, com: CommonRef, channel: Opt[RpcChannelPtrs]) = nimbus.accountsManager = new AccountsManager nimbus.rng = newRng() basicServices(nimbus, config, com) manageAccounts(nimbus, config) setupP2P(nimbus, config, com) - setupRpc(nimbus, config, com) + setupRpc(nimbus, config, com, channel) # Not starting any syncer if there is definitely no way to run it. This # avoids polling (i.e. waiting for instructions) and some logging. @@ -229,9 +229,9 @@ proc init*(nimbus: NimbusNode, config: ExecutionClientConf, com: CommonRef) = nimbus.beaconSyncRef = BeaconSyncRef(nil) nimbus.snapSyncRef = SnapSyncRef(nil) -proc init*(T: type NimbusNode, config: ExecutionClientConf, com: CommonRef): T = +proc init*(T: type NimbusNode, config: ExecutionClientConf, com: CommonRef, channel: Opt[RpcChannelPtrs]): T = let nimbus = T() - nimbus.init(config, com) + nimbus.init(config, com, channel) nimbus proc preventLoadingDataDirForTheWrongNetwork(db: CoreDbRef; config: ExecutionClientConf) = @@ -303,6 +303,7 @@ proc runExeClient*( com: CommonRef, stopper: StopFuture, nimbus = NimbusNode(nil), + channel = Opt.none(RpcChannelPtrs), ) = ## Launches and runs the execution client for pre-configured `nimbus` and ## `conf` argument descriptors. @@ -310,9 +311,9 @@ proc runExeClient*( var nimbus = nimbus if nimbus.isNil: - nimbus = NimbusNode.init(config, com) + nimbus = NimbusNode.init(config, com, channel) else: - nimbus.init(config, com) + nimbus.init(config, com, channel) defer: let diff --git a/execution_chain/rpc.nim b/execution_chain/rpc.nim index 9ac5951f16..1de99f7054 100644 --- a/execution_chain/rpc.nim +++ b/execution_chain/rpc.nim @@ -12,7 +12,7 @@ import chronicles, websock/websock, - json_rpc/rpcserver, + json_rpc/[rpcserver, rpcchannels], ./rpc/[common, cors, debug, engine_api, jwt_auth, rpc_server, server_api], ./[conf, nimbus_desc] @@ -23,7 +23,8 @@ export jwt_auth, cors, rpc_server, - server_api + server_api, + rpcchannels const DefaultChunkSize = 1024*1024 @@ -53,7 +54,6 @@ func installRPC(server: RpcServer, if RpcFlag.Debug in flags: setupDebugRpc(com, nimbus.txPool, server) - proc newRpcWebsocketHandler(): RpcWebSocketHandler = let rng = HmacDrbgContext.new() RpcWebSocketHandler( @@ -198,8 +198,8 @@ proc addServices(handlers: var seq[RpcHandlerProc], handlers.addHandler(server) proc setupRpc*(nimbus: NimbusNode, config: ExecutionClientConf, - com: CommonRef) = - if not config.engineApiEnabled: + com: CommonRef, channel: Opt[RpcChannelPtrs]) = + if not config.engineApiEnabled and channel.isNone(): warn "Engine API disabled, the node will not respond to consensus client updates (enable with `--engine-api`)" if not config.serverEnabled: @@ -251,3 +251,10 @@ proc setupRpc*(nimbus: NimbusNode, config: ExecutionClientConf, quit(QuitFailure) nimbus.engineApiServer = res.get nimbus.engineApiServer.start() + + if channel.isSome(): + nimbus.engineApiChannel = RpcChannelServer.new(channel[]) + + setupEngineAPI(nimbus.beaconEngine, nimbus.engineApiChannel) + installRPC(nimbus.engineApiChannel, nimbus, config, com, serverApi, {RpcFlag.Eth}) + nimbus.engineApiChannel.start() diff --git a/vendor/nim-async-channels b/vendor/nim-async-channels new file mode 160000 index 0000000000..f22daeae4c --- /dev/null +++ b/vendor/nim-async-channels @@ -0,0 +1 @@ +Subproject commit f22daeae4ca2c0971bbbbd99d289ff0ea71a56fc diff --git a/vendor/nim-json-rpc b/vendor/nim-json-rpc index 841d1c9fcc..f52b92e499 160000 --- a/vendor/nim-json-rpc +++ b/vendor/nim-json-rpc @@ -1 +1 @@ -Subproject commit 841d1c9fcc7ce7ebbd1f9a774cfdcd7470e5e976 +Subproject commit f52b92e499653781c244b5d9363884034b4fe4db diff --git a/vendor/nimbus-eth2 b/vendor/nimbus-eth2 index 1e3ce63e44..c106703021 160000 --- a/vendor/nimbus-eth2 +++ b/vendor/nimbus-eth2 @@ -1 +1 @@ -Subproject commit 1e3ce63e44c6b895722bcfaf0230c3b82839569f +Subproject commit c106703021d01a858d1f175804eb08a92676433e