diff --git a/hive_integration/nodocker/engine/auths_tests.nim b/hive_integration/nodocker/engine/auths_tests.nim index 055c4202e..dd80312d7 100644 --- a/hive_integration/nodocker/engine/auths_tests.nim +++ b/hive_integration/nodocker/engine/auths_tests.nim @@ -42,7 +42,7 @@ proc getClient(env: TestEnv, token: string): RpcHttpClient = @[("Authorization", "Bearer " & token)] let client = newRpcHttpClient(getHeaders = authHeaders) - waitFor client.connect("127.0.0.1", env.engine.rpcPort, false) + waitFor client.connect("127.0.0.1", env.engine.httpPort, false) return client template genAuthTest(procName: untyped, timeDriftSeconds: int64, customAuthSecretBytes: string, authOK: bool) = diff --git a/hive_integration/nodocker/engine/engine/fork_id.nim b/hive_integration/nodocker/engine/engine/fork_id.nim index a6c700b65..5887d80fe 100644 --- a/hive_integration/nodocker/engine/engine/fork_id.nim +++ b/hive_integration/nodocker/engine/engine/fork_id.nim @@ -1,5 +1,5 @@ # Nimbus -# Copyright (c) 2023 Status Research & Development GmbH +# Copyright (c) 2023-2024 Status Research & Development GmbH # Licensed under either of # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or # http://www.apache.org/licenses/LICENSE-2.0) @@ -10,7 +10,6 @@ import std/strutils, - chronicles, ./engine_spec, ../../../../nimbus/common/hardforks diff --git a/hive_integration/nodocker/engine/engine/invalid_payload.nim b/hive_integration/nodocker/engine/engine/invalid_payload.nim index 3508436ae..b4c7e602f 100644 --- a/hive_integration/nodocker/engine/engine/invalid_payload.nim +++ b/hive_integration/nodocker/engine/engine/invalid_payload.nim @@ -1,5 +1,5 @@ # Nimbus -# Copyright (c) 2023 Status Research & Development GmbH +# Copyright (c) 2023-2024 Status Research & Development GmbH # Licensed under either of # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or # http://www.apache.org/licenses/LICENSE-2.0) @@ -9,14 +9,12 @@ # according to those terms. import - std/strutils, chronicles, ./engine_spec, ../helper, ../cancun/customizer, ../../../../nimbus/common - # Generate test cases for each field of NewPayload, where the payload contains a single invalid field and a valid hash. type InvalidPayloadTestCase* = ref object of EngineSpec diff --git a/hive_integration/nodocker/engine/engine/reorg.nim b/hive_integration/nodocker/engine/engine/reorg.nim index f5f879402..462317cc4 100644 --- a/hive_integration/nodocker/engine/engine/reorg.nim +++ b/hive_integration/nodocker/engine/engine/reorg.nim @@ -1,5 +1,5 @@ # Nimbus -# Copyright (c) 2023 Status Research & Development GmbH +# Copyright (c) 2023-2024 Status Research & Development GmbH # Licensed under either of # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or # http://www.apache.org/licenses/LICENSE-2.0) @@ -9,7 +9,6 @@ # according to those terms. import - std/strutils, eth/common, chronicles, stew/byteutils, diff --git a/hive_integration/nodocker/engine/engine/rpc.nim b/hive_integration/nodocker/engine/engine/rpc.nim index 1537943d4..742ed4e3e 100644 --- a/hive_integration/nodocker/engine/engine/rpc.nim +++ b/hive_integration/nodocker/engine/engine/rpc.nim @@ -1,5 +1,5 @@ # Nimbus -# Copyright (c) 2023 Status Research & Development GmbH +# Copyright (c) 2023-2024 Status Research & Development GmbH # Licensed under either of # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or # http://www.apache.org/licenses/LICENSE-2.0) @@ -9,7 +9,6 @@ # according to those terms. import - std/strutils, eth/common, chronicles, ./engine_spec diff --git a/hive_integration/nodocker/engine/engine_env.nim b/hive_integration/nodocker/engine/engine_env.nim index 8963b52b6..d8cce788b 100644 --- a/hive_integration/nodocker/engine/engine_env.nim +++ b/hive_integration/nodocker/engine/engine_env.nim @@ -1,5 +1,5 @@ # Nimbus -# Copyright (c) 2023 Status Research & Development GmbH +# Copyright (c) 2023-2024 Status Research & Development GmbH # Licensed under either of # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or # http://www.apache.org/licenses/LICENSE-2.0) @@ -111,7 +111,10 @@ proc newEngineEnv*(conf: var NimbusConf, chainFile: string, enableAuth: bool): E let hooks = if enableAuth: @[httpJwtAuth(key)] else: @[] - server = newRpcHttpServerWithParams("127.0.0.1:" & $conf.rpcPort, hooks) + server = newRpcHttpServerWithParams("127.0.0.1:" & $conf.httpPort, hooks).valueOr: + echo "Failed to create rpc server: ", error + quit(QuitFailure) + sealer = SealingEngineRef.new( chain, ctx, conf.engineSigner, txPool, EngineStopped) @@ -135,7 +138,7 @@ proc newEngineEnv*(conf: var NimbusConf, chainFile: string, enableAuth: bool): E server.start() let client = newRpcHttpClient() - waitFor client.connect("127.0.0.1", conf.rpcPort, false) + waitFor client.connect("127.0.0.1", conf.httpPort, false) if com.ttd().isSome: sync.start() @@ -167,8 +170,8 @@ proc setRealTTD*(env: EngineEnv) = env.com.setTTD some(realTTD) env.ttd = realTTD -func rpcPort*(env: EngineEnv): Port = - env.conf.rpcPort +func httpPort*(env: EngineEnv): Port = + env.conf.httpPort func client*(env: EngineEnv): RpcHttpClient = env.client diff --git a/hive_integration/nodocker/engine/test_env.nim b/hive_integration/nodocker/engine/test_env.nim index da9c4ce44..dd8fc74ce 100644 --- a/hive_integration/nodocker/engine/test_env.nim +++ b/hive_integration/nodocker/engine/test_env.nim @@ -1,5 +1,5 @@ # Nimbus -# Copyright (c) 2023 Status Research & Development GmbH +# Copyright (c) 2023-2024 Status Research & Development GmbH # Licensed under either of # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or # http://www.apache.org/licenses/LICENSE-2.0) @@ -36,28 +36,28 @@ type chainFile : string enableAuth: bool port : int - rpcPort : int + httpPort : int clients : ClientPool sender : TxSender clMock* : CLMocker proc makeEnv(conf: NimbusConf): TestEnv = TestEnv( - conf : conf, - port : 30303, - rpcPort: 8545, - clients: ClientPool(), - sender : TxSender.new(conf.networkParams), + conf : conf, + port : 30303, + httpPort: 8545, + clients : ClientPool(), + sender : TxSender.new(conf.networkParams), ) proc addEngine(env: TestEnv, conf: var NimbusConf): EngineEnv = conf.tcpPort = Port env.port conf.udpPort = Port env.port - conf.rpcPort = Port env.rpcPort + conf.httpPort = Port env.httpPort let engine = newEngineEnv(conf, env.chainFile, env.enableAuth) env.clients.add engine inc env.port - inc env.rpcPort + inc env.httpPort engine proc setup(env: TestEnv, conf: var NimbusConf, chainFile: string, enableAuth: bool) = diff --git a/hive_integration/nodocker/rpc/test_env.nim b/hive_integration/nodocker/rpc/test_env.nim index 9efc9560c..6c7a84ce4 100644 --- a/hive_integration/nodocker/rpc/test_env.nim +++ b/hive_integration/nodocker/rpc/test_env.nim @@ -46,17 +46,7 @@ proc manageAccounts(ctx: EthContext, conf: NimbusConf) = proc setupRpcServer(ctx: EthContext, com: CommonRef, ethNode: EthereumNode, txPool: TxPoolRef, conf: NimbusConf): RpcServer = - let rpcServer = newRpcHttpServer([initTAddress(conf.rpcAddress, conf.rpcPort)]) - setupCommonRpc(ethNode, conf, rpcServer) - setupEthRpc(ethNode, ctx, com, txPool, rpcServer) - - rpcServer.start() - rpcServer - -proc setupWsRpcServer(ctx: EthContext, com: CommonRef, - ethNode: EthereumNode, txPool: TxPoolRef, - conf: NimbusConf): RpcServer = - let rpcServer = newRpcWebSocketServer(initTAddress(conf.wsAddress, conf.wsPort)) + let rpcServer = newRpcHttpServer([initTAddress(conf.httpAddress, conf.httpPort)]) setupCommonRpc(ethNode, conf, rpcServer) setupEthRpc(ethNode, ctx, com, txPool, rpcServer) @@ -68,11 +58,6 @@ proc stopRpcHttpServer(srv: RpcServer) = waitFor rpcServer.stop() waitFor rpcServer.closeWait() -proc stopRpcWsServer(srv: RpcServer) = - let rpcServer = RpcWebSocketServer(srv) - rpcServer.stop() - waitFor rpcServer.closeWait() - proc setupEnv*(): TestEnv = let conf = makeConfig(@[ "--prune-mode:archive", @@ -83,12 +68,8 @@ proc setupEnv*(): TestEnv = "--custom-network:" & initPath / "genesis.json", "--rpc", "--rpc-api:eth,debug", - # "--rpc-address:0.0.0.0", - "--rpc-port:8545", - "--ws", - "--ws-api:eth,debug", - # "--ws-address:0.0.0.0", - "--ws-port:8546" + # "--http-address:0.0.0.0", + "--http-port:8545", ]) let diff --git a/nimbus/common/manager.nim b/nimbus/common/manager.nim index 32b82ff3d..59416f4ae 100644 --- a/nimbus/common/manager.nim +++ b/nimbus/common/manager.nim @@ -1,5 +1,5 @@ # Nimbus -# Copyright (c) 2021 Status Research & Development GmbH +# Copyright (c) 2021-2024 Status Research & Development GmbH # Licensed under either of # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) # * MIT license ([LICENSE-MIT](LICENSE-MIT)) @@ -7,12 +7,13 @@ # This file may not be copied, modified, or distributed except according to # those terms. -{.push raises: [].} +{.push gcsafe, raises: [].} import std/[os, json, tables, strutils], stew/[byteutils, results], - eth/[keyfile, common, keys] + eth/[keyfile, common, keys], + json_serialization from nimcrypto/utils import burnMem @@ -28,31 +29,17 @@ type proc init*(_: type AccountsManager): AccountsManager = discard -proc loadKeystores*(am: var AccountsManager, path: string): Result[void, string] - {.gcsafe, raises: [OSError].}= +proc loadKeystores*(am: var AccountsManager, path: string): + Result[void, string] = try: createDir(path) - except OSError, IOError: - return err("keystore: cannot create directory") - - for filename in walkDirRec(path): - try: - var data = json.parseFile(filename) + for filename in walkDirRec(path): + var data = Json.loadFile(filename, JsonNode) let address: EthAddress = hexToByteArray[20](data["address"].getStr()) am.accounts[address] = NimbusAccount(keystore: data, unlocked: false) - except JsonParsingError: - return err("keystore: json parsing error " & filename) - except ValueError: - return err("keystore: data parsing error") - except IOError: - return err("keystore: data read error") - except CatchableError as e: # json raises Exception - return err("keystore: " & e.msg) - except Exception as e: - {.warning: "Kludge(BareExcept): `parseFile()` in json vendor package needs to be updated".} - raiseAssert "Ooops loadKeystores(): name=" & $e.name & " msg=" & e.msg - - + except CatchableError as exc: + return err("loadKeystrores: " & exc.msg) + ok() proc getAccount*(am: var AccountsManager, address: EthAddress): Result[NimbusAccount, string] = @@ -110,3 +97,5 @@ proc importPrivateKey*(am: var AccountsManager, fileName: string): Result[void, return ok() except CatchableError as ex: return err(ex.msg) + +{.pop.} diff --git a/nimbus/config.nim b/nimbus/config.nim index 56164190b..1f1d3a6ee 100644 --- a/nimbus/config.nim +++ b/nimbus/config.nim @@ -87,11 +87,8 @@ const defaultDataDirDesc = defaultDataDir() defaultPort = 30303 defaultMetricsServerPort = 9093 - defaultEthRpcPort = 8545 - defaultEthWsPort = 8546 - defaultEthGraphqlPort = 8547 + defaultHttpPort = 8545 defaultEngineApiPort = 8550 - defaultEngineApiWsPort = 8551 defaultListenAddress = (static parseIpAddress("0.0.0.0")) defaultAdminListenAddress = (static parseIpAddress("127.0.0.1")) defaultListenAddressDesc = $defaultListenAddress & ", meaning all network interfaces" @@ -380,26 +377,26 @@ type defaultValue: NimbusCmd.noCommand }: NimbusCmd of noCommand: + httpPort* {. + separator: "\pLOCAL SERVICES OPTIONS:" + desc: "Listening port of the HTTP server(rpc, ws, graphql)" + defaultValue: defaultHttpPort + defaultValueDesc: $defaultHttpPort + name: "http-port" }: Port + + httpAddress* {. + desc: "Listening IP address of the HTTP server(rpc, ws, graphql)" + defaultValue: defaultAdminListenAddress + defaultValueDesc: $defaultAdminListenAddressDesc + name: "http-address" }: IpAddress + rpcEnabled* {. - separator: "\pLOCAL SERVICE OPTIONS:" desc: "Enable the JSON-RPC server" defaultValue: false name: "rpc" }: bool - rpcPort* {. - desc: "Listening port of the JSON-RPC server" - defaultValue: defaultEthRpcPort - defaultValueDesc: $defaultEthRpcPort - name: "rpc-port" }: Port - - rpcAddress* {. - desc: "Listening IP address of the JSON-RPC server" - defaultValue: defaultAdminListenAddress - defaultValueDesc: $defaultAdminListenAddressDesc - name: "rpc-address" }: IpAddress - rpcApi {. - desc: "Enable specific set of RPC API (available: eth, debug)" + desc: "Enable specific set of RPC API (available: eth, debug, exp)" defaultValue: @[] defaultValueDesc: $RpcFlag.Eth name: "rpc-api" }: seq[string] @@ -409,37 +406,30 @@ type defaultValue: false name: "ws" }: bool - wsPort* {. - desc: "Listening port of the Websocket JSON-RPC server" - defaultValue: defaultEthWsPort - defaultValueDesc: $defaultEthWsPort - name: "ws-port" }: Port - - wsAddress* {. - desc: "Listening IP address of the Websocket JSON-RPC server" - defaultValue: defaultAdminListenAddress - defaultValueDesc: $defaultAdminListenAddressDesc - name: "ws-address" }: IpAddress - wsApi {. - desc: "Enable specific set of Websocket RPC API (available: eth, debug)" + desc: "Enable specific set of Websocket RPC API (available: eth, debug, exp)" defaultValue: @[] defaultValueDesc: $RpcFlag.Eth name: "ws-api" }: seq[string] + graphqlEnabled* {. + desc: "Enable the GraphQL HTTP server" + defaultValue: false + name: "graphql" }: bool + engineApiEnabled* {. desc: "Enable the Engine API" defaultValue: false name: "engine-api" .}: bool engineApiPort* {. - desc: "Listening port for the Engine API" + desc: "Listening port for the Engine API(http and ws)" defaultValue: defaultEngineApiPort defaultValueDesc: $defaultEngineApiPort name: "engine-api-port" .}: Port engineApiAddress* {. - desc: "Listening address for the Engine API" + desc: "Listening address for the Engine API(http and ws)" defaultValue: defaultAdminListenAddress defaultValueDesc: $defaultAdminListenAddressDesc name: "engine-api-address" .}: IpAddress @@ -449,18 +439,6 @@ type defaultValue: false name: "engine-api-ws" .}: bool - engineApiWsPort* {. - desc: "Listening port for the WebSocket Engine API" - defaultValue: defaultEngineApiWsPort - defaultValueDesc: $defaultEngineApiWsPort - name: "engine-api-ws-port" .}: Port - - engineApiWsAddress* {. - desc: "Listening address for the WebSocket Engine API" - defaultValue: defaultAdminListenAddress - defaultValueDesc: $defaultAdminListenAddressDesc - name: "engine-api-ws-address" .}: IpAddress - terminalTotalDifficulty* {. desc: "The terminal total difficulty of the eth2 merge transition block." & " It takes precedence over terminalTotalDifficulty in config file." @@ -481,23 +459,6 @@ type defaultValueDesc: "\"jwt.hex\" in the data directory (see --data-dir)" name: "jwt-secret" .}: Option[InputFile] - graphqlEnabled* {. - desc: "Enable the GraphQL HTTP server" - defaultValue: false - name: "graphql" }: bool - - graphqlPort* {. - desc: "Listening port of the GraphQL HTTP server" - defaultValue: defaultEthGraphqlPort - defaultValueDesc: $defaultEthGraphqlPort - name: "graphql-port" }: Port - - graphqlAddress* {. - desc: "Listening IP address of the GraphQL HTTP server" - defaultValue: defaultAdminListenAddress - defaultValueDesc: $defaultAdminListenAddressDesc - name: "graphql-address" }: IpAddress - metricsEnabled* {. desc: "Enable the built-in metrics HTTP server" defaultValue: false @@ -685,7 +646,7 @@ proc getProtocolFlags*(conf: NimbusConf): set[ProtocolFlag] = proc getRpcFlags(api: openArray[string]): set[RpcFlag] = if api.len == 0: return {RpcFlag.Eth} - + for item in repeatingList(api): case item.toLowerAscii() of "eth": result.incl RpcFlag.Eth @@ -719,7 +680,7 @@ proc fromEnr*(T: type ENode, r: enr.Record): ENodeResult[ENode] = ok(ENode( pubkey: pk, address: Address( - ip: ipv4(tr.ip.get()), + ip: utils.ipv4(tr.ip.get()), udpPort: Port(tr.udp.get()), tcpPort: Port(tr.tcp.get()) ) @@ -779,6 +740,18 @@ proc getAllowedOrigins*(conf: NimbusConf): seq[Uri] = for item in repeatingList(conf.allowedOrigins): result.add parseUri(item) +func engineApiServerEnabled*(conf: NimbusConf): bool = + conf.engineApiEnabled or conf.engineApiWsEnabled + +func shareServerWithEngineApi*(conf: NimbusConf): bool = + conf.engineApiServerEnabled and + conf.engineApiPort == conf.httpPort + +func httpServerEnabled*(conf: NimbusConf): bool = + conf.graphqlEnabled or + conf.wsEnabled or + conf.rpcEnabled + # KLUDGE: The `load()` template does currently not work within any exception # annotated environment. {.pop.} @@ -834,13 +807,6 @@ proc makeConfig*(cmdLine = commandLineParams()): NimbusConf # if udpPort not set in cli, then result.udpPort = result.tcpPort - # enable rpc server or ws server if they share common port with engine api - let rpcMustEnabled = result.engineApiEnabled and (result.engineApiPort == result.rpcPort) - let wsMustEnabled = result.engineApiWsEnabled and (result.engineApiWsPort == result.wsPort) - - result.rpcEnabled = result.rpcEnabled or rpcMustEnabled - result.wsEnabled = result.wsEnabled or wsMustEnabled - # see issue #1346 if result.keyStore.string == defaultKeystoreDir() and result.dataDir.string != defaultDataDir(): diff --git a/nimbus/graphql/ethapi.nim b/nimbus/graphql/ethapi.nim index dc6170a54..0dbc45d2e 100644 --- a/nimbus/graphql/ethapi.nim +++ b/nimbus/graphql/ethapi.nim @@ -1459,23 +1459,10 @@ proc setupGraphqlContext*(com: CommonRef, ctx.initEthApi() ctx -proc setupGraphqlHttpServer*(conf: NimbusConf, - com: CommonRef, - ethNode: EthereumNode, - txPool: TxPoolRef, - authHooks: seq[AuthHook] = @[]): GraphqlHttpServerRef = - let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr} +proc setupGraphqlHttpHandler*(com: CommonRef, + ethNode: EthereumNode, + txPool: TxPoolRef): GraphqlHttpHandlerRef = let ctx = setupGraphqlContext(com, ethNode, txPool) - let address = initTAddress(conf.graphqlAddress, conf.graphqlPort) - let sres = GraphqlHttpServerRef.new( - ctx, - address, - socketFlags = socketFlags, - authHooks = authHooks - ) - if sres.isErr(): - echo sres.error - quit(QuitFailure) - sres.get() + GraphqlHttpHandlerRef.new(ctx) {.pop.} diff --git a/nimbus/nimbus.nim b/nimbus/nimbus.nim index 5a456b32f..4ad29ee81 100644 --- a/nimbus/nimbus.nim +++ b/nimbus/nimbus.nim @@ -13,23 +13,25 @@ import import std/[os, strutils, net], chronicles, - chronos, - eth/[keys, net/nat], - eth/p2p as eth_p2p, - json_rpc/rpcserver, + eth/keys, + eth/net/nat, metrics, - metrics/[chronos_httpserver, chronicles_support], - websock/websock as ws, + metrics/chronicles_support, kzg4844/kzg_ex as kzg, + ./rpc, + ./version, + ./constants, + ./nimbus_desc, ./core/eip4844, - "."/[config, constants, version, rpc, common], - ./db/[core_db/persistent, select_backend], - ./graphql/ethapi, - ./core/[chain, sealer, clique/clique_desc, - clique/clique_sealer, tx_pool, block_import], - ./beacon/beacon_engine, - ./sync/[beacon, legacy, full, protocol, snap, stateless, - protocol/les_protocol, handlers, peers], + ./core/block_import, + ./db/select_backend, + ./db/core_db/persistent, + ./core/clique/clique_desc, + ./core/clique/clique_sealer, + ./sync/protocol, + ./sync/handlers, + ./sync/stateless, + ./sync/protocol/les_protocol, ./evm/async/data_sources/json_rpc_data_source when defined(evmc_enabled): @@ -40,31 +42,6 @@ when defined(evmc_enabled): ## * No multiple bind addresses support ## * No database support -type - NimbusState = enum - Starting, Running, Stopping - - NimbusNode = ref object - rpcServer: RpcHttpServer - engineApiServer: RpcHttpServer - engineApiWsServer: RpcWebSocketServer - ethNode: EthereumNode - state: NimbusState - graphqlServer: GraphqlHttpServerRef - wsRpcServer: RpcWebSocketServer - sealingEngine: SealingEngineRef - ctx: EthContext - chainRef: ChainRef - txPool: TxPoolRef - networkLoop: Future[void] - peerManager: PeerManagerRef - legaSyncRef: LegacySyncRef - snapSyncRef: SnapSyncRef - fullSyncRef: FullSyncRef - beaconSyncRef: BeaconSyncRef - statelessSyncRef: StatelessSyncRef - beaconEngine: BeaconEngineRef - proc importBlocks(conf: NimbusConf, com: CommonRef) = if string(conf.blocksFile).len > 0: # success or not, we quit after importing blocks @@ -244,97 +221,7 @@ proc localServices(nimbus: NimbusNode, conf: NimbusConf, discard setTimer(Moment.fromNow(conf.logMetricsInterval.seconds), logMetrics) discard setTimer(Moment.fromNow(conf.logMetricsInterval.seconds), logMetrics) - # Provide JWT authentication handler for rpcHttpServer - let jwtKey = block: - # Create or load shared secret - let rc = nimbus.ctx.rng.jwtSharedSecret(conf) - if rc.isErr: - fatal "Failed create or load shared secret", - msg = $(rc.unsafeError) # avoid side effects - quit(QuitFailure) - rc.value - let allowedOrigins = conf.getAllowedOrigins() - - # Provide JWT authentication handler for rpcHttpServer - let httpJwtAuthHook = httpJwtAuth(jwtKey) - let httpCorsHook = httpCors(allowedOrigins) - - # Creating RPC Server - if conf.rpcEnabled: - let enableAuthHook = conf.engineApiEnabled and - conf.engineApiPort == conf.rpcPort - - let hooks = if enableAuthHook: - @[httpJwtAuthHook, httpCorsHook] - else: - @[httpCorsHook] - - nimbus.rpcServer = newRpcHttpServerWithParams( - initTAddress(conf.rpcAddress, conf.rpcPort), - authHooks = hooks - ) - setupCommonRpc(nimbus.ethNode, conf, nimbus.rpcServer) - - # Enable RPC APIs based on RPC flags and protocol flags - let rpcFlags = conf.getRpcFlags() - if (RpcFlag.Eth in rpcFlags and ProtocolFlag.Eth in protocols) or - (conf.engineApiPort == conf.rpcPort): - setupEthRpc(nimbus.ethNode, nimbus.ctx, com, nimbus.txPool, nimbus.rpcServer) - if RpcFlag.Debug in rpcFlags: - setupDebugRpc(com, nimbus.rpcServer) - if RpcFlag.Exp in rpcFlags: - setupExpRpc(com, nimbus.rpcServer) - - nimbus.rpcServer.rpc("admin_quit") do() -> string: - {.gcsafe.}: - nimbus.state = Stopping - result = "EXITING" - - nimbus.rpcServer.start() - - # Provide JWT authentication handler for rpcWebsocketServer - let wsJwtAuthHook = wsJwtAuth(jwtKey) - let wsCorsHook = wsCors(allowedOrigins) - - # Creating Websocket RPC Server - if conf.wsEnabled: - let enableAuthHook = conf.engineApiWsEnabled and - conf.engineApiWsPort == conf.wsPort - - let hooks = if enableAuthHook: - @[wsJwtAuthHook, wsCorsHook] - else: - @[wsCorsHook] - - # Construct server object - nimbus.wsRpcServer = newRpcWebSocketServer( - initTAddress(conf.wsAddress, conf.wsPort), - authHooks = hooks, - rng = nimbus.ctx.rng - ) - setupCommonRpc(nimbus.ethNode, conf, nimbus.wsRpcServer) - - # Enable Websocket RPC APIs based on RPC flags and protocol flags - let wsFlags = conf.getWsFlags() - if (RpcFlag.Eth in wsFlags and ProtocolFlag.Eth in protocols) or - (conf.engineApiWsPort == conf.wsPort): - setupEthRpc(nimbus.ethNode, nimbus.ctx, com, nimbus.txPool, nimbus.wsRpcServer) - if RpcFlag.Debug in wsFlags: - setupDebugRpc(com, nimbus.wsRpcServer) - if RpcFlag.Exp in wsFlags: - setupExpRpc(com, nimbus.wsRpcServer) - - nimbus.wsRpcServer.start() - - if conf.graphqlEnabled: - nimbus.graphqlServer = setupGraphqlHttpServer( - conf, - com, - nimbus.ethNode, - nimbus.txPool, - @[httpCorsHook] - ) - nimbus.graphqlServer.start() + nimbus.setupRpc(conf, com, protocols) if conf.engineSigner != ZERO_ADDRESS and not com.forkGTE(MergeFork): let res = nimbus.ctx.am.getAccount(conf.engineSigner) @@ -370,40 +257,16 @@ proc localServices(nimbus: NimbusNode, conf: NimbusConf, if conf.engineSigner != ZERO_ADDRESS: nimbus.sealingEngine.start() - if conf.engineApiEnabled: - #let maybeAsyncDataSource = maybeStatelessAsyncDataSource(nimbus, conf) - if conf.engineApiPort != conf.rpcPort: - nimbus.engineApiServer = newRpcHttpServerWithParams( - initTAddress(conf.engineApiAddress, conf.engineApiPort), - authHooks = @[httpJwtAuthHook, httpCorsHook] - ) - setupEngineAPI(nimbus.beaconEngine, nimbus.engineApiServer) - setupEthRpc(nimbus.ethNode, nimbus.ctx, com, nimbus.txPool, nimbus.engineApiServer) - nimbus.engineApiServer.start() - else: - setupEngineAPI(nimbus.beaconEngine, nimbus.rpcServer) - - info "Starting engine API server", port = conf.engineApiPort - - if conf.engineApiWsEnabled: - #let maybeAsyncDataSource = maybeStatelessAsyncDataSource(nimbus, conf) - if conf.engineApiWsPort != conf.wsPort: - nimbus.engineApiWsServer = newRpcWebSocketServer( - initTAddress(conf.engineApiWsAddress, conf.engineApiWsPort), - authHooks = @[wsJwtAuthHook, wsCorsHook] - ) - setupEngineAPI(nimbus.beaconEngine, nimbus.engineApiWsServer) - setupEthRpc(nimbus.ethNode, nimbus.ctx, com, nimbus.txPool, nimbus.engineApiWsServer) - nimbus.engineApiWsServer.start() - else: - setupEngineAPI(nimbus.beaconEngine, nimbus.wsRpcServer) - - info "Starting WebSocket engine API server", port = conf.engineApiWsPort - # metrics server if conf.metricsEnabled: info "Starting metrics HTTP server", address = conf.metricsAddress, port = conf.metricsPort - startMetricsHttpServer($conf.metricsAddress, conf.metricsPort) + let res = MetricsHttpServerRef.new($conf.metricsAddress, conf.metricsPort) + if res.isErr: + fatal "Failed to create metrics server", msg=res.error + quit(QuitFailure) + + nimbus.metricsServer = res.get + waitFor nimbus.metricsServer.start() proc start(nimbus: NimbusNode, conf: NimbusConf) = ## logging @@ -468,42 +331,13 @@ proc start(nimbus: NimbusNode, conf: NimbusConf) = of SyncMode.Snap: nimbus.snapSyncRef.start - if nimbus.state == Starting: + if nimbus.state == NimbusState.Starting: # it might have been set to "Stopping" with Ctrl+C - nimbus.state = Running - -proc stop*(nimbus: NimbusNode, conf: NimbusConf) {.async, gcsafe.} = - trace "Graceful shutdown" - if conf.rpcEnabled: - await nimbus.rpcServer.stop() - # nimbus.engineApiServer can be nil if conf.engineApiPort == conf.rpcPort - if conf.engineApiEnabled and nimbus.engineApiServer.isNil.not: - await nimbus.engineApiServer.stop() - if conf.wsEnabled: - nimbus.wsRpcServer.stop() - # nimbus.engineApiWsServer can be nil if conf.engineApiWsPort == conf.wsPort - if conf.engineApiWsEnabled and nimbus.engineApiWsServer.isNil.not: - nimbus.engineApiWsServer.stop() - if conf.graphqlEnabled: - await nimbus.graphqlServer.stop() - if conf.engineSigner != ZERO_ADDRESS and nimbus.sealingEngine.isNil.not: - await nimbus.sealingEngine.stop() - if conf.maxPeers > 0: - await nimbus.networkLoop.cancelAndWait() - if nimbus.peerManager.isNil.not: - await nimbus.peerManager.stop() - if nimbus.statelessSyncRef.isNil.not: - nimbus.statelessSyncRef.stop() - if nimbus.snapSyncRef.isNil.not: - nimbus.snapSyncRef.stop() - if nimbus.fullSyncRef.isNil.not: - nimbus.fullSyncRef.stop() - if nimbus.beaconSyncRef.isNil.not: - nimbus.beaconSyncRef.stop() + nimbus.state = NimbusState.Running proc process*(nimbus: NimbusNode, conf: NimbusConf) = # Main event loop - while nimbus.state == Running: + while nimbus.state == NimbusState.Running: try: poll() except CatchableError as e: @@ -514,14 +348,14 @@ proc process*(nimbus: NimbusNode, conf: NimbusConf) = waitFor nimbus.stop(conf) when isMainModule: - var nimbus = NimbusNode(state: Starting, ctx: newEthContext()) + var nimbus = NimbusNode(state: NimbusState.Starting, ctx: newEthContext()) ## Ctrl+C handling proc controlCHandler() {.noconv.} = when defined(windows): # workaround for https://github.com/nim-lang/Nim/issues/4057 setupForeignThreadGc() - nimbus.state = Stopping + nimbus.state = NimbusState.Stopping echo "\nCtrl+C pressed. Waiting for a graceful shutdown." setControlCHook(controlCHandler) diff --git a/nimbus/nimbus_desc.nim b/nimbus/nimbus_desc.nim new file mode 100644 index 000000000..55d0dfe23 --- /dev/null +++ b/nimbus/nimbus_desc.nim @@ -0,0 +1,94 @@ +# Nimbus +# Copyright (c) 2024 Status Research & Development GmbH +# Licensed under either of +# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) +# * MIT license ([LICENSE-MIT](LICENSE-MIT)) +# at your option. +# This file may not be copied, modified, or distributed except according to +# those terms. + +import + chronos, + eth/p2p, + metrics/chronos_httpserver, + ./rpc/rpc_server, + ./core/sealer, + ./core/chain, + ./core/tx_pool, + ./sync/peers, + ./sync/beacon, + ./sync/legacy, + ./sync/snap, + ./sync/stateless, + ./sync/full, + ./beacon/beacon_engine, + ./common, + ./config + +export + chronos, + p2p, + chronos_httpserver, + rpc_server, + sealer, + chain, + tx_pool, + peers, + beacon, + legacy, + snap, + stateless, + full, + beacon_engine, + common, + config + +type + NimbusState* = enum + Starting, Running, Stopping + + NimbusNode* = ref object + httpServer*: NimbusHttpServerRef + engineApiServer*: NimbusHttpServerRef + ethNode*: EthereumNode + state*: NimbusState + sealingEngine*: SealingEngineRef + ctx*: EthContext + chainRef*: ChainRef + txPool*: TxPoolRef + networkLoop*: Future[void] + peerManager*: PeerManagerRef + legaSyncRef*: LegacySyncRef + snapSyncRef*: SnapSyncRef + fullSyncRef*: FullSyncRef + beaconSyncRef*: BeaconSyncRef + statelessSyncRef*: StatelessSyncRef + beaconEngine*: BeaconEngineRef + metricsServer*: MetricsHttpServerRef + +{.push gcsafe, raises: [].} + +proc stop*(nimbus: NimbusNode, conf: NimbusConf) {.async, gcsafe.} = + trace "Graceful shutdown" + if nimbus.httpServer.isNil.not: + await nimbus.httpServer.stop() + if nimbus.engineApiServer.isNil.not: + await nimbus.engineApiServer.stop() + if conf.engineSigner != ZERO_ADDRESS and nimbus.sealingEngine.isNil.not: + await nimbus.sealingEngine.stop() + if conf.maxPeers > 0: + await nimbus.networkLoop.cancelAndWait() + if nimbus.peerManager.isNil.not: + await nimbus.peerManager.stop() + if nimbus.statelessSyncRef.isNil.not: + nimbus.statelessSyncRef.stop() + if nimbus.snapSyncRef.isNil.not: + nimbus.snapSyncRef.stop() + if nimbus.fullSyncRef.isNil.not: + nimbus.fullSyncRef.stop() + if nimbus.beaconSyncRef.isNil.not: + nimbus.beaconSyncRef.stop() + if nimbus.metricsServer.isNil.not: + await nimbus.metricsServer.stop() + +{.pop.} diff --git a/nimbus/rpc.nim b/nimbus/rpc.nim index 9ff898cca..f0e1d3ddd 100644 --- a/nimbus/rpc.nim +++ b/nimbus/rpc.nim @@ -8,6 +8,10 @@ # those terms. import + chronicles, + websock/websock, + json_rpc/rpcserver, + graphql/httpserver, ./rpc/common, ./rpc/debug, ./rpc/engine_api, @@ -15,7 +19,9 @@ import ./rpc/jwt_auth, ./rpc/cors, ./rpc/rpc_server, - ./rpc/experimental + ./rpc/experimental, + ./nimbus_desc, + ./graphql/ethapi export common, @@ -26,3 +32,252 @@ export cors, rpc_server, experimental + +{.push gcsafe, raises: [].} + +const DefaultChunkSize = 8192 + +func serverEnabled(conf: NimbusConf): bool = + conf.httpServerEnabled or + conf.engineApiServerEnabled + +func combinedServer(conf: NimbusConf): bool = + conf.httpServerEnabled and + conf.shareServerWithEngineApi + +proc installRPC(server: RpcServer, + nimbus: NimbusNode, + conf: NimbusConf, + com: CommonRef, + flags: set[RpcFlag]) = + + setupCommonRpc(nimbus.ethNode, conf, server) + + if RpcFlag.Eth in flags: + setupEthRpc(nimbus.ethNode, nimbus.ctx, com, nimbus.txPool, server) + + if RpcFlag.Debug in flags: + setupDebugRpc(com, server) + + if RpcFlag.Exp in flags: + setupExpRpc(com, server) + + server.rpc("admin_quit") do() -> string: + {.gcsafe.}: + nimbus.state = NimbusState.Stopping + result = "EXITING" + +proc newRpcWebsocketHandler(): RpcWebSocketHandler = + let rng = HmacDrbgContext.new() + RpcWebSocketHandler( + wsserver: WSServer.new(rng = rng), + ) + +proc newRpcHttpHandler(): RpcHttpHandler = + RpcHttpHandler( + maxChunkSize: DefaultChunkSize, + ) + +proc addHandler(handlers: var seq[RpcHandlerProc], + server: RpcHttpHandler) = + + proc handlerProc(request: HttpRequestRef): + Future[RpcHandlerResult] {.async: (raises: []).} = + try: + let res = await server.serveHTTP(request) + if res.isNil: + return RpcHandlerResult(status: RpcHandlerStatus.Skip) + else: + return RpcHandlerResult(status: RpcHandlerStatus.Response, response: res) + except CancelledError: + return RpcHandlerResult(status: RpcHandlerStatus.Error) + + handlers.add handlerProc + +proc addHandler(handlers: var seq[RpcHandlerProc], + server: RpcWebSocketHandler) = + + proc handlerProc(request: HttpRequestRef): + Future[RpcHandlerResult] {.async: (raises: []).} = + + if not request.headers.contains("Sec-WebSocket-Version"): + return RpcHandlerResult(status: RpcHandlerStatus.Skip) + + let stream = websock.AsyncStream( + reader: request.connection.mainReader, + writer: request.connection.mainWriter, + ) + + let req = websock.HttpRequest( + meth: request.meth, + uri: request.uri, + version: request.version, + headers: request.headers, + stream: stream, + ) + + try: + await server.serveHTTP(req) + return RpcHandlerResult(status: RpcHandlerStatus.KeepConnection) + except CancelledError: + return RpcHandlerResult(status: RpcHandlerStatus.Error) + + handlers.add handlerProc + +proc addHandler(handlers: var seq[RpcHandlerProc], + server: GraphqlHttpHandlerRef) = + + proc handlerProc(request: HttpRequestRef): + Future[RpcHandlerResult] {.async: (raises: []).} = + try: + let res = await server.serveHTTP(request) + if res.isNil: + return RpcHandlerResult(status: RpcHandlerStatus.Skip) + else: + return RpcHandlerResult(status: RpcHandlerStatus.Response, response: res) + except CatchableError: + return RpcHandlerResult(status: RpcHandlerStatus.Error) + + handlers.add handlerProc + +proc addHttpServices(handlers: var seq[RpcHandlerProc], + nimbus: NimbusNode, conf: NimbusConf, + com: CommonRef, protocols: set[ProtocolFlag]) = + + # The order is important: graphql, ws, rpc + # graphql depends on /graphl path + # ws depends on Sec-WebSocket-Version header + # json-rpc have no reliable identification + + if conf.graphqlEnabled: + let ctx = setupGraphqlContext(com, nimbus.ethNode, nimbus.txPool) + let server = GraphqlHttpHandlerRef.new(ctx) + handlers.addHandler(server) + + if conf.wsEnabled: + let server = newRpcWebsocketHandler() + var rpcFlags = conf.getWsFlags() + if ProtocolFlag.Eth in protocols: rpcFlags.incl RpcFlag.Eth + installRPC(server, nimbus, conf, com, rpcFlags) + handlers.addHandler(server) + + if conf.rpcEnabled: + let server = newRpcHttpHandler() + var rpcFlags = conf.getRpcFlags() + if ProtocolFlag.Eth in protocols: rpcFlags.incl RpcFlag.Eth + installRPC(server, nimbus, conf, com, rpcFlags) + handlers.addHandler(server) + +proc addEngineApiServices(handlers: var seq[RpcHandlerProc], + nimbus: NimbusNode, conf: NimbusConf, + com: CommonRef) = + + # The order is important: ws, rpc + + if conf.engineApiWsEnabled: + let server = newRpcWebsocketHandler() + setupEngineAPI(nimbus.beaconEngine, server) + installRPC(server, nimbus, conf, com, {RpcFlag.Eth}) + handlers.addHandler(server) + + if conf.engineApiEnabled: + let server = newRpcHttpHandler() + setupEngineAPI(nimbus.beaconEngine, server) + installRPC(server, nimbus, conf, com, {RpcFlag.Eth}) + handlers.addHandler(server) + +proc addServices(handlers: var seq[RpcHandlerProc], + nimbus: NimbusNode, conf: NimbusConf, + com: CommonRef, protocols: set[ProtocolFlag]) = + + # The order is important: graphql, ws, rpc + + if conf.graphqlEnabled: + let ctx = setupGraphqlContext(com, nimbus.ethNode, nimbus.txPool) + let server = GraphqlHttpHandlerRef.new(ctx) + handlers.addHandler(server) + + if conf.wsEnabled or conf.engineApiWsEnabled: + let server = newRpcWebsocketHandler() + if conf.engineApiWsEnabled: + setupEngineAPI(nimbus.beaconEngine, server) + if not conf.wsEnabled: + installRPC(server, nimbus, conf, com, {RpcFlag.Eth}) + + if conf.wsEnabled: + var rpcFlags = conf.getWsFlags() + if ProtocolFlag.Eth in protocols: rpcFlags.incl RpcFlag.Eth + installRPC(server, nimbus, conf, com, rpcFlags) + handlers.addHandler(server) + + if conf.rpcEnabled or conf.engineApiEnabled: + let server = newRpcHttpHandler() + if conf.engineApiEnabled: + setupEngineAPI(nimbus.beaconEngine, server) + if not conf.rpcEnabled: + installRPC(server, nimbus, conf, com, {RpcFlag.Eth}) + + if conf.rpcEnabled: + var rpcFlags = conf.getRpcFlags() + if ProtocolFlag.Eth in protocols: rpcFlags.incl RpcFlag.Eth + installRPC(server, nimbus, conf, com, rpcFlags) + handlers.addHandler(server) + +proc setupRpc*(nimbus: NimbusNode, conf: NimbusConf, + com: CommonRef, protocols: set[ProtocolFlag]) = + if not conf.serverEnabled: + return + + # Provide JWT authentication handler for rpcHttpServer + let jwtKey = block: + # Create or load shared secret + let rc = nimbus.ctx.rng.jwtSharedSecret(conf) + if rc.isErr: + fatal "Failed create or load shared secret", + msg = $(rc.unsafeError) # avoid side effects + quit(QuitFailure) + rc.value + + let + allowedOrigins = conf.getAllowedOrigins() + jwtAuthHook = httpJwtAuth(jwtKey) + corsHook = httpCors(allowedOrigins) + + if conf.combinedServer: + let hooks = @[jwtAuthHook, corsHook] + var handlers: seq[RpcHandlerProc] + handlers.addServices(nimbus, conf, com, protocols) + let address = initTAddress(conf.httpAddress, conf.httpPort) + let res = newHttpServerWithParams(address, hooks, handlers) + if res.isErr: + fatal "Cannot create RPC server", msg=res.error + quit(QuitFailure) + nimbus.httpServer = res.get + nimbus.httpServer.start() + return + + if conf.httpServerEnabled: + let hooks = @[corsHook] + var handlers: seq[RpcHandlerProc] + handlers.addHttpServices(nimbus, conf, com, protocols) + let address = initTAddress(conf.httpAddress, conf.httpPort) + let res = newHttpServerWithParams(address, hooks, handlers) + if res.isErr: + fatal "Cannot create RPC server", msg=res.error + quit(QuitFailure) + nimbus.httpServer = res.get + nimbus.httpServer.start() + + if conf.engineApiServerEnabled: + let hooks = @[jwtAuthHook, corsHook] + var handlers: seq[RpcHandlerProc] + handlers.addEngineApiServices(nimbus, conf, com) + let address = initTAddress(conf.engineApiAddress, conf.engineApiPort) + let res = newHttpServerWithParams(address, hooks, handlers) + if res.isErr: + fatal "Cannot create RPC server", msg=res.error + quit(QuitFailure) + nimbus.engineApiServer = res.get + nimbus.engineApiServer.start() + +{.pop.} diff --git a/nimbus/rpc/cors.nim b/nimbus/rpc/cors.nim index 8e007d166..baa670c52 100644 --- a/nimbus/rpc/cors.nim +++ b/nimbus/rpc/cors.nim @@ -1,5 +1,5 @@ # Nimbus -# Copyright (c) 2022 Status Research & Development GmbH +# Copyright (c) 2022-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). @@ -11,12 +11,12 @@ import std/[uri], chronos, - chronos/apps/http/[httptable, httpserver], - json_rpc/rpcserver, + chronos/apps/http/httptable, + chronos/apps/http/httpserver, httputils, - websock/websock as ws + ./rpc_server -{.push raises: [].} +{.push gcsafe, raises: [].} proc sameOrigin(a, b: Uri): bool = a.hostname == b.hostname and @@ -30,8 +30,9 @@ proc containsOrigin(list: seq[Uri], origin: Uri): bool = const HookOK = HttpResponseRef(nil) -proc httpCors*(allowedOrigins: seq[Uri]): HttpAuthHook = - proc handler(req: HttpRequestRef): Future[HttpResponseRef] {.async.} = +proc httpCors*(allowedOrigins: seq[Uri]): RpcAuthHook = + proc handler(req: HttpRequestRef): Future[HttpResponseRef] + {.gcsafe, async: (raises: [CatchableError]).} = let origins = req.headers.getList("Origin") let everyOriginAllowed = allowedOrigins.len == 0 @@ -87,12 +88,4 @@ proc httpCors*(allowedOrigins: seq[Uri]): HttpAuthHook = # the rest of response in server return HookOK - result = HttpAuthHook(handler) - -proc wsCors*(allowedOrigins: seq[Uri]): WsAuthHook = - proc handler(req: ws.HttpRequest): Future[bool] {.async.} = - # TODO: implement websock equivalent of - # request.getResponse - return true - - result = WsAuthHook(handler) + result = handler diff --git a/nimbus/rpc/jwt_auth.nim b/nimbus/rpc/jwt_auth.nim index a2131f7ba..4cb4ccfe8 100644 --- a/nimbus/rpc/jwt_auth.nim +++ b/nimbus/rpc/jwt_auth.nim @@ -1,5 +1,5 @@ # Nimbus -# Copyright (c) 2022 Status Research & Development GmbH +# Copyright (c) 2022-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). @@ -12,20 +12,21 @@ # nimbus-eth2/beacon_chain/spec/engine_authentication.nim # go-ethereum/node/jwt_handler.go -{.push raises: [].} +{.push gcsafe, raises: [].} import - std/[base64, json, options, os, strutils, times], + std/[base64, options, strutils, times], bearssl/rand, chronicles, chronos, - chronos/apps/http/[httptable, httpserver], - json_rpc/rpcserver, + chronos/apps/http/httptable, + chronos/apps/http/httpserver, httputils, - websock/websock as ws, nimcrypto/[hmac, utils], stew/[byteutils, objects, results], - ../config + ../config, + ./jwt_auth_helper, + ./rpc_server logScope: topics = "Jwt/HS256 auth" @@ -70,14 +71,7 @@ type jwtMethodUnsupported = "token protected header provides unsupported method" jwtTimeValidationError = "token time validation failed" jwtTokenValidationError = "token signature validation failed" - - JwtHeader = object ##\ - ## Template used for JSON unmarshalling - typ, alg: string - - JwtIatPayload = object ##\ - ## Template used for JSON unmarshalling - iat: uint64 + jwtCreationError = "Cannot create jwt secret" # ------------------------------------------------------------------------------ # Private functions @@ -113,7 +107,7 @@ proc verifyTokenHS256(token: string; key: JwtSharedKey): Result[void,JwtError] = let jsonHeader = p[0].base64urlDecode error = jwtProtHeaderInvJson - let jwtHeader = jsonHeader.parseJson.to(JwtHeader) + let jwtHeader = jsonHeader.decodeJwtHeader() # The following JSON decoded object is required if jwtHeader.typ != "JWT" and jwtHeader.alg != "HS256": @@ -124,18 +118,16 @@ proc verifyTokenHS256(token: string; key: JwtSharedKey): Result[void,JwtError] = let jsonPayload = p[1].base64urlDecode error = jwtIatPayloadInvJson - let jwtPayload = jsonPayload.parseJson.to(JwtIatPayload) + let jwtPayload = jsonPayload.decodeJwtIatPayload() time = jwtPayload.iat.int64 except CatchableError as e: + discard e debug "JWT token decoding error", protectedHeader = p[0], payload = p[1], msg = e.msg, error return err(error) - except Exception as e: - {.warning: "Kludge(BareExcept): `parseJson()` in vendor package needs to be updated".} - raiseAssert "Ooops verifyTokenHS256(): name=" & $e.name & " msg=" & e.msg # github.com/ethereum/ # /execution-apis/blob/v1.0.0-beta.3/src/engine/authentication.md#jwt-claims @@ -195,8 +187,7 @@ proc jwtGenSecret*(rng: ref rand.HmacDrbgContext): JwtGenSecret = proc jwtSharedSecret*( rndSecret: JwtGenSecret; config: NimbusConf; - ): Result[JwtSharedKey, JwtError] - {.gcsafe, raises: [CatchableError].}= + ): Result[JwtSharedKey, JwtError] = ## Return a key for jwt authentication preferable from the argument file ## `config.jwtSecret` (which contains at least 32 bytes hex encoded random ## data.) Otherwise it creates a key and stores it in the `config.dataDir`. @@ -226,18 +217,19 @@ proc jwtSharedSecret*( # github.com/ethereum/ # /execution-apis/blob/v1.0.0-alpha.8/src/engine/ # /authentication.md#key-distribution - let - jwtSecretPath = config.dataDir.string / jwtSecretFile - newSecret = rndSecret() + let jwtSecretPath = config.dataDir.string & "/" & jwtSecretFile try: + let newSecret = rndSecret() jwtSecretPath.writeFile(newSecret.JwtSharedKeyRaw.to0xHex) + return ok(newSecret) except IOError as e: # Allow continuing to run, though this is effectively fatal for a merge # client using authentication. This keeps it lower-risk initially. warn "Could not write JWT secret to data directory", jwtSecretPath discard e - return ok(newSecret) + except CatchableError: + return err(jwtCreationError) try: let lines = config.jwtSecret.get.string.readLines(1) @@ -254,13 +246,16 @@ proc jwtSharedSecret*( return err(jwtKeyInvalidHexString) proc jwtSharedSecret*(rng: ref rand.HmacDrbgContext; config: NimbusConf): - Result[JwtSharedKey, JwtError] - {.gcsafe, raises: [CatchableError].} = + Result[JwtSharedKey, JwtError] = ## Variant of `jwtSharedSecret()` with explicit random generator argument. - rng.jwtGenSecret.jwtSharedSecret(config) + try: + rng.jwtGenSecret.jwtSharedSecret(config) + except CatchableError: + return err(jwtCreationError) -proc httpJwtAuth*(key: JwtSharedKey): HttpAuthHook = - proc handler(req: HttpRequestRef): Future[HttpResponseRef] {.async.} = +proc httpJwtAuth*(key: JwtSharedKey): RpcAuthHook = + proc handler(req: HttpRequestRef): Future[HttpResponseRef] + {.gcsafe, async: (raises: [CatchableError]).} = let auth = req.headers.getString("Authorization", "?") if auth.len < 9 or auth[0..6].cmpIgnoreCase("Bearer ") != 0: return await req.respond(Http403, "Missing authorization token") @@ -278,31 +273,7 @@ proc httpJwtAuth*(key: JwtSharedKey): HttpAuthHook = else: return await req.respond(Http403, "Malformed token") - result = HttpAuthHook(handler) - -proc wsJwtAuth*(key: JwtSharedKey): WsAuthHook = - proc handler(req: ws.HttpRequest): Future[bool] {.async.} = - let auth = req.headers.getString("Authorization", "?") - if auth.len < 9 or auth[0..6].cmpIgnoreCase("Bearer ") != 0: - await req.sendResponse(code = Http403, data = "Missing authorization token") - return false - - let rc = auth[7..^1].strip.verifyTokenHS256(key) - if rc.isOk: - return true - - debug "Could not authenticate", - error = rc.error - - case rc.error: - of jwtTokenValidationError, jwtMethodUnsupported: - await req.sendResponse(code = Http403, data = "Unauthorized access") - else: - await req.sendResponse(code = Http403, data = "Malformed token") - - return false - - result = WsAuthHook(handler) + result = handler # ------------------------------------------------------------------------------ # End diff --git a/nimbus/rpc/jwt_auth_helper.nim b/nimbus/rpc/jwt_auth_helper.nim new file mode 100644 index 000000000..211bc0b72 --- /dev/null +++ b/nimbus/rpc/jwt_auth_helper.nim @@ -0,0 +1,45 @@ +# Nimbus +# 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. + +import + json_serialization + +type + JwtHeader* = object ##\ + ## Template used for JSON unmarshalling + typ*, alg*: string + + JwtIatPayload* = object ##\ + ## Template used for JSON unmarshalling + iat*: uint64 + +createJsonFlavor JAuth, + automaticObjectSerialization = false, + requireAllFields = false, + allowUnknownFields = true + +JwtHeader.useDefaultSerializationIn JAuth +JwtIatPayload.useDefaultSerializationIn JAuth + +# This file separated from jwt_auth.nim +# is to prevent generic resolution clash between +# json_serialization and base64 + +{.push gcsafe, raises: [].} + +proc decodeJwtHeader*(jsonBytes: string): JwtHeader + {.gcsafe, raises: [SerializationError].} = + JAuth.decode(jsonBytes, JwtHeader) + +proc decodeJwtIatPayload*(jsonBytes: string): JwtIatPayload + {.gcsafe, raises: [SerializationError].} = + JAuth.decode(jsonBytes, JwtIatPayload) + +{.pop.} diff --git a/nimbus/rpc/rpc_server.nim b/nimbus/rpc/rpc_server.nim index 5ae516fc9..039293e00 100644 --- a/nimbus/rpc/rpc_server.nim +++ b/nimbus/rpc/rpc_server.nim @@ -1,5 +1,5 @@ # Nimbus -# Copyright (c) 2023 Status Research & Development GmbH +# Copyright (c) 2023-2024 Status Research & Development GmbH # Licensed under either of # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) # * MIT license ([LICENSE-MIT](LICENSE-MIT)) @@ -7,22 +7,57 @@ # This file may not be copied, modified, or distributed except according to # those terms. +when false: + import + std/importutils + import - chronos, - json_rpc/rpcserver + json_rpc/servers/httpserver as jrpc_server, + chronos/apps/http/httpserver {.all.} type RpcHttpServerParams = object + serverFlags: set[HttpServerFlags] socketFlags: set[ServerFlags] serverUri: Uri serverIdent: string maxConnections: int bufferSize: int backlogSize: int - httpHeadersTimeout: chronos.Duration + httpHeadersTimeout: Duration maxHeadersSize: int maxRequestBodySize: int + RpcHandlerStatus* {.pure.} = enum + Skip + Response + KeepConnection + Error + + RpcHandlerResult* = object + status*: RpcHandlerStatus + response*: HttpResponseRef + + RpcProcessExitType* {.pure.} = enum + KeepAlive + Graceful + Immediate + KeepConnection + + RpcAuthHook* = proc(request: HttpRequestRef): Future[HttpResponseRef] + {.gcsafe, async: (raises: [CatchableError]).} + + RpcHandlerProc* = proc(request: HttpRequestRef): Future[RpcHandlerResult] + {.async: (raises: []).} + + NimbusHttpServer* = object of RootObj + server: HttpServerRef + authHooks: seq[RpcAuthHook] + handlers: seq[RpcHandlerProc] + + NimbusHttpServerRef* = ref NimbusHttpServer + +{.push gcsafe, raises: [].} func defaultRpcHttpServerParams(): RpcHttpServerParams = RpcHttpServerParams( @@ -37,40 +72,44 @@ func defaultRpcHttpServerParams(): RpcHttpServerParams = maxRequestBodySize: 2 * 1024 * 1024, ) -template processResolvedAddresses = - if tas4.len + tas6.len == 0: - # Addresses could not be resolved, critical error. - raise newException(RpcAddressUnresolvableError, "Unable to get address!") +proc resolvedAddress(address: string): Result[TransportAddress, string] = + var tas: seq[TransportAddress] - for r in tas4: - yield r - - if tas4.len == 0: # avoid ipv4 + ipv6 running together - for r in tas6: - yield r - -iterator resolvedAddresses(address: string): TransportAddress = - var - tas4: seq[TransportAddress] - tas6: seq[TransportAddress] - - # Attempt to resolve `address` for IPv4 address space. try: - tas4 = resolveTAddress(address, AddressFamily.IPv4) + tas = resolveTAddress(address, AddressFamily.IPv4) + if tas.len == 1: + return ok(tas[0]) + if tas.len > 1: + return err("Too much address for HTTP server: " & $tas.len) except CatchableError: + # It might be an IPv6 discard - # Attempt to resolve `address` for IPv6 address space. try: - tas6 = resolveTAddress(address, AddressFamily.IPv6) + tas = resolveTAddress(address, AddressFamily.IPv6) + if tas.len == 1: + return ok(tas[0]) + if tas.len > 1: + return err("Too much address for HTTP server: " & $tas.len) + if tas.len == 0: + return err("No address found for HTTP server") except CatchableError: - discard + return err("Failed to decode HTTP server address") - processResolvedAddresses() +proc createServer(address: TransportAddress, + params: RpcHttpServerParams): HttpResult[HttpServerRef] = -proc addServer*(server: RpcHttpServer, address: TransportAddress, params: RpcHttpServerParams) = - server.addHttpServer( + proc processCallback(req: RequestFence): + Future[HttpResponseRef] {. + async: (raises: [CancelledError]).} = + # This is a dummy callback because we are going to + # create our own callback + return nil + + HttpServerRef.new( address, + processCallback, + params.serverFlags, params.socketFlags, params.serverUri, params.serverIdent, @@ -81,21 +120,233 @@ proc addServer*(server: RpcHttpServer, address: TransportAddress, params: RpcHtt params.maxHeadersSize, params.maxRequestBodySize) -proc addServer*(server: RpcHttpServer, address: string, params: RpcHttpServerParams) = - ## Create new server and assign it to addresses ``addresses``. - for a in resolvedAddresses(address): - # TODO handle partial failures, ie when 1/N addresses fail - server.addServer(a, params) +proc createServer(address: string, + params: RpcHttpServerParams): HttpResult[HttpServerRef] = + ## Create new server and assign it to ``address``. + let serverAddress = resolvedAddress(address).valueOr: + return err(error) + createServer(serverAddress, params) -proc newRpcHttpServerWithParams*(address: TransportAddress, authHooks: seq[HttpAuthHook] = @[]): RpcHttpServer = +proc newHttpServerWithParams*(address: TransportAddress or string, + authHooks: sink seq[RpcAuthHook] = @[], + handlers: sink seq[RpcHandlerProc]): + HttpResult[NimbusHttpServerRef] = + ## Create new server and assign it to ``address``. + let params = defaultRpcHttpServerParams() + let inner = createServer(address, params) + if inner.isErr: + return err(inner.error) + + let server = NimbusHttpServerRef( + server: inner.get, + authHooks: system.move(authHooks), + handlers: system.move(handlers), + ) + + return ok(server) + +proc invokeProcessCallback(nserver: NimbusHttpServerRef, + req: RequestFence): Future[RpcHandlerResult] {. + async: (raises: []).} = + when false: + let server = nserver.server + privateAccess(type server) + if len(server.middlewares) > 0: + server.middlewares[0](req) + else: + server.processCallback(req) + + if req.isErr: + return RpcHandlerResult( + status: RpcHandlerStatus.Response, + response: defaultResponse(), + ) + + let request = req.get() + # If hook result is not nil, + # it means we should return immediately + try: + for hook in nserver.authHooks: + let res = await hook(request) + if not res.isNil: + return RpcHandlerResult( + status: RpcHandlerStatus.Response, + response: res, + ) + except CatchableError as exc: + return RpcHandlerResult( + status: RpcHandlerStatus.Response, + response: defaultResponse(exc), + ) + + # If handler result.status != Skip, + # return immediately + for handler in nserver.handlers: + let res = await handler(request) + if res.status != RpcHandlerStatus.Skip: + return res + + # not handled + return RpcHandlerResult( + status: RpcHandlerStatus.Response, + response: defaultResponse(), + ) + +proc processRequest(nserver: NimbusHttpServerRef, + connection: HttpConnectionRef, + connId: string): Future[RpcProcessExitType] {. + async: (raises: []).} = + let server = nserver.server + let requestFence = await getRequestFence(server, connection) + if requestFence.isErr(): + case requestFence.error.kind + of HttpServerError.InterruptError: + # Cancelled, exiting + return RpcProcessExitType.Immediate + of HttpServerError.DisconnectError: + # Remote peer disconnected + if HttpServerFlags.NotifyDisconnect notin server.flags: + return RpcProcessExitType.Immediate + else: + # Request is incorrect or unsupported, sending notification + discard + + try: + let response = + try: + await invokeProcessCallback(nserver, requestFence) + except CancelledError: + # Cancelled, exiting + return RpcProcessExitType.Immediate + + case response.status + of RpcHandlerStatus.Skip: discard + of RpcHandlerStatus.Response: + let res = await connection.sendDefaultResponse(requestFence, response.response) + return RpcProcessExitType(res.ord) + of RpcHandlerStatus.KeepConnection: + return RpcProcessExitType.KeepConnection + of RpcHandlerStatus.Error: + return RpcProcessExitType.Immediate + finally: + if requestFence.isOk(): + let request = requestFence.get() + if result == RpcProcessExitType.KeepConnection: + request.response = Opt.none(HttpResponseRef) + await request.closeWait() + +proc processLoop(nserver: NimbusHttpServerRef, holder: HttpConnectionHolderRef) {.async: (raises: []).} = + let + server = holder.server + transp = holder.transp + connectionId = holder.connectionId + connection = + block: + let res = await getConnectionFence(server, transp) + if res.isErr(): + if res.error.kind != HttpServerError.InterruptError: + discard await noCancel( + invokeProcessCallback(nserver, RequestFence.err(res.error))) + server.connections.del(connectionId) + return + res.get() + + holder.connection = connection + + var runLoop = RpcProcessExitType.KeepAlive + while runLoop == RpcProcessExitType.KeepAlive: + runLoop = await nserver.processRequest(connection, connectionId) + + case runLoop + of RpcProcessExitType.KeepAlive: + await connection.closeWait() + of RpcProcessExitType.Immediate: + await connection.closeWait() + of RpcProcessExitType.Graceful: + await connection.gracefulCloseWait() + of RpcProcessExitType.KeepConnection: + discard + server.connections.del(connectionId) + +proc acceptClientLoop(nserver: NimbusHttpServerRef) {.async: (raises: []).} = + let server = nserver.server + var runLoop = true + while runLoop: + try: + let transp = await server.instance.accept() + let resId = transp.getId() + if resId.isErr(): + # We are unable to identify remote peer, it means that remote peer + # disconnected before identification. + await transp.closeWait() + runLoop = false + else: + let connId = resId.get() + let holder = HttpConnectionHolderRef.new(server, transp, resId.get()) + server.connections[connId] = holder + holder.future = processLoop(nserver, holder) + except TransportTooManyError, TransportAbortedError: + # Non-critical error + discard + except CancelledError, TransportOsError, CatchableError: + # Critical, cancellation or unexpected error + runLoop = false + +proc start*(server: NimbusHttpServerRef) = + if server.server.state == ServerStopped: + server.server.acceptLoop = acceptClientLoop(server) + +proc stop*(server: NimbusHttpServerRef) {.async: (raises: []).} = + await server.server.stop() + +proc closeWait*(server: NimbusHttpServerRef) {.async: (raises: []).} = + await server.server.closeWait() + +func localAddress*(server: NimbusHttpServerRef): TransportAddress = + server.server.instance.localAddress() + +proc addServer*(server: RpcHttpServer, + address: TransportAddress, + params: RpcHttpServerParams): Result[void, string] = + try: + server.addHttpServer( + address, + params.socketFlags, + params.serverUri, + params.serverIdent, + params.maxConnections, + params.bufferSize, + params.backlogSize, + params.httpHeadersTimeout, + params.maxHeadersSize, + params.maxRequestBodySize) + return ok() + except CatchableError as exc: + return err(exc.msg) + +proc addServer*(server: RpcHttpServer, + address: string, + params: RpcHttpServerParams): Result[void, string] = + let serverAddress = resolvedAddress(address).valueOr: + return err(error) + + server.addServer(serverAddress, params) + +proc newRpcHttpServerWithParams*(address: TransportAddress, + authHooks: seq[HttpAuthHook] = @[]): Result[RpcHttpServer, string] = ## Create new server and assign it to addresses ``addresses``. let server = RpcHttpServer.new(authHooks) let params = defaultRpcHttpServerParams() - server.addServer(address, params) - server + server.addServer(address, params).isOkOr: + return err(error) + ok(server) -proc newRpcHttpServerWithParams*(address: string, authHooks: seq[HttpAuthHook] = @[]): RpcHttpServer = +proc newRpcHttpServerWithParams*(address: string, + authHooks: seq[HttpAuthHook] = @[]): Result[RpcHttpServer, string] = let server = RpcHttpServer.new(authHooks) let params = defaultRpcHttpServerParams() - server.addServer(address, params) - server + server.addServer(address, params).isOkOr: + return err(error) + ok(server) + +{.pop.} diff --git a/tests/test_configuration.nim b/tests/test_configuration.nim index 9dab2f917..0b23445ab 100644 --- a/tests/test_configuration.nim +++ b/tests/test_configuration.nim @@ -17,9 +17,6 @@ import ../nimbus/common/[chain_config, context], ./test_helpers -proc `==`(a, b: ChainId): bool = - a.int == b.int - proc configurationMain*() = suite "configuration test suite": const @@ -226,32 +223,64 @@ proc configurationMain*() = check conf.getBootnodes().len == 0 test "json-rpc enabled when json-engine api enabled and share same port": - let conf = makeConfig(@["--engine-api", "--engine-api-port:8545", "--rpc-port:8545"]) - check conf.engineApiEnabled - check conf.rpcEnabled - check conf.wsEnabled == false - check conf.engineApiWsEnabled == false + let conf = makeConfig(@["--engine-api", "--engine-api-port:8545", "--http-port:8545"]) + check: + conf.engineApiEnabled == true + conf.rpcEnabled == false + conf.wsEnabled == false + conf.engineApiWsEnabled == false + conf.graphqlEnabled == false + conf.engineApiServerEnabled + conf.httpServerEnabled == false + conf.shareServerWithEngineApi test "ws-rpc enabled when ws-engine api enabled and share same port": - let conf = makeConfig(@["--engine-api-ws", "--engine-api-ws-port:8546", "--ws-port:8546"]) - check conf.engineApiWsEnabled - check conf.wsEnabled - check conf.engineApiEnabled == false - check conf.rpcEnabled == false + let conf = makeConfig(@["--ws", "--engine-api-ws", "--engine-api-port:8546", "--http-port:8546"]) + check: + conf.engineApiWsEnabled + conf.wsEnabled + conf.engineApiEnabled == false + conf.rpcEnabled == false + conf.graphqlEnabled == false + conf.engineApiServerEnabled + conf.httpServerEnabled + conf.shareServerWithEngineApi test "json-rpc stay enabled when json-engine api enabled and using different port": - let conf = makeConfig(@["--rpc", "--engine-api", "--engine-api-port:8550", "--rpc-port:8545"]) - check conf.engineApiEnabled - check conf.rpcEnabled - check conf.engineApiWsEnabled == false - check conf.wsEnabled == false + let conf = makeConfig(@["--rpc", "--engine-api", "--engine-api-port:8550", "--http-port:8545"]) + check: + conf.engineApiEnabled + conf.rpcEnabled + conf.engineApiWsEnabled == false + conf.wsEnabled == false + conf.graphqlEnabled == false + conf.httpServerEnabled + conf.engineApiServerEnabled + conf.shareServerWithEngineApi == false test "ws-rpc stay enabled when ws-engine api enabled and using different port": - let conf = makeConfig(@["--ws", "--engine-api-ws", "--engine-api-ws-port:8551", "--ws-port:8546"]) - check conf.engineApiWsEnabled - check conf.wsEnabled - check conf.engineApiEnabled == false - check conf.rpcEnabled == false + let conf = makeConfig(@["--ws", "--engine-api-ws", "--engine-api-port:8551", "--http-port:8546"]) + check: + conf.engineApiWsEnabled + conf.wsEnabled + conf.engineApiEnabled == false + conf.rpcEnabled == false + conf.graphqlEnabled == false + conf.httpServerEnabled + conf.engineApiServerEnabled + conf.shareServerWithEngineApi == false + + test "graphql enabled. ws, rpc, and engine api not enabled": + let conf = makeConfig(@["--graphql"]) + check: + conf.engineApiWsEnabled == false + conf.wsEnabled == false + conf.engineApiEnabled == false + conf.rpcEnabled == false + conf.graphqlEnabled == true + conf.httpServerEnabled == true + conf.engineApiServerEnabled == false + conf.shareServerWithEngineApi == false let ctx = newEthContext() test "net-key random": diff --git a/tests/test_jwt_auth.nim b/tests/test_jwt_auth.nim index 5be7ef851..cd5ac5214 100644 --- a/tests/test_jwt_auth.nim +++ b/tests/test_jwt_auth.nim @@ -1,5 +1,5 @@ # Nimbus -# Copyright (c) 2022-2023 Status Research & Development GmbH +# Copyright (c) 2022-2024 Status Research & Development GmbH # Licensed under either of # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or # http://www.apache.org/licenses/LICENSE-2.0) @@ -15,18 +15,20 @@ import std/[base64, json, options, os, strutils, times], ../nimbus/config, ../nimbus/rpc/jwt_auth, + ../nimbus/rpc {.all.}, ./replay/pp, chronicles, chronos/apps/http/httpclient as chronoshttpclient, chronos/apps/http/httptable, eth/[common, keys, p2p], - json_rpc/rpcserver, nimcrypto/[hmac, utils], stew/results, stint, unittest2, graphql, - graphql/[httpserver, httpclient] + websock/websock, + graphql/[httpserver, httpclient], + json_rpc/[rpcserver, rpcclient] type UnGuardedKey = @@ -115,7 +117,7 @@ proc getHttpAuthReqHeader2(secret: JwtSharedKey; time: uint64): HttpTable = let bearer = secret.UnGuardedKey.getSignedToken2($getIatToken(time)) result.add("aUtHoRiZaTiOn", "Bearer " & bearer) -proc createServer(serverAddress: TransportAddress, authHooks: seq[HttpAuthHook] = @[]): GraphqlHttpServerRef = +proc createServer(serverAddress: TransportAddress, authHooks: seq[AuthHook] = @[]): GraphqlHttpServerRef = let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr} var ctx = GraphqlRef.new() @@ -141,6 +143,48 @@ proc createServer(serverAddress: TransportAddress, authHooks: seq[HttpAuthHook] proc setupClient(address: TransportAddress): GraphqlHttpClientRef = GraphqlHttpClientRef.new(address, secure = false).get() +func localAddress(server: GraphqlHttpServerRef): TransportAddress = + server.server.instance.localAddress() + +# ------------------------------------------------------------------------------ +# Http combo helpers +# ------------------------------------------------------------------------------ + +proc newGraphqlHandler(): GraphqlHttpHandlerRef = + const schema = """type Query {name: String}""" + + let ctx = GraphqlRef.new() + let r = ctx.parseSchema(schema) + if r.isErr: + debugEcho r.error + # continue with empty schema + + GraphqlHttpHandlerRef.new(ctx) + +proc installRPC(server: RpcServer) = + server.rpc("rpc_echo") do(input: int) -> string: + result = "hello: " & $input + +proc setupComboServer(hooks: sink seq[RpcAuthHook]): HttpResult[NimbusHttpServerRef] = + var handlers: seq[RpcHandlerProc] + + let qlServer = newGraphqlHandler() + handlers.addHandler(qlServer) + + let wsServer = newRpcWebsocketHandler() + wsServer.installRPC() + handlers.addHandler(wsServer) + + let rpcServer = newRpcHttpHandler() + rpcServer.installRPC() + handlers.addHandler(rpcServer) + + let address = initTAddress("127.0.0.1:0") + newHttpServerWithParams(address, hooks, handlers) + +createRpcSigsFromNim(RpcClient): + proc rpc_echo(input: int): string + # ------------------------------------------------------------------------------ # Test Runners # ------------------------------------------------------------------------------ @@ -255,7 +299,7 @@ proc runJwtAuth(noisy = true; keyFile = jwtKeyFile) = authHook = secret.value.httpJwtAuth const - serverAddress = initTAddress("127.0.0.1:8547") + serverAddress = initTAddress("127.0.0.1:0") query = """{ __type(name: "ID") { kind }}""" suite "EngineAuth: Http/rpc authentication mechanics": @@ -283,7 +327,7 @@ proc runJwtAuth(noisy = true; keyFile = jwtKeyFile) = setTraceLevel() # Run http authorisation request - let client = setupClient(serverAddress) + let client = setupClient(server.localAddress) let res = waitFor client.sendRequest(query, req.toList) check res.isOk if res.isErr: @@ -309,7 +353,7 @@ proc runJwtAuth(noisy = true; keyFile = jwtKeyFile) = setTraceLevel() # Run http authorisation request - let client = setupClient(serverAddress) + let client = setupClient(server.localAddress) let res = waitFor client.sendRequest(query, req.toList) check res.isOk if res.isErr: @@ -325,6 +369,73 @@ proc runJwtAuth(noisy = true; keyFile = jwtKeyFile) = waitFor server.closeWait() + suite "Test combo http server": + let res = setupComboServer(@[authHook]) + if res.isErr: + debugEcho res.error + quit(QuitFailure) + + let + server = res.get + time = getTime().toUnix.uint64 + req = secret.value.getHttpAuthReqHeader(time) + + server.start() + + test "Graphql query no auth": + let client = setupClient(server.localAddress) + let res = waitFor client.sendRequest(query) + check res.isOk + let resp = res.get() + check resp.status == 403 + check resp.reason == "Forbidden" + check resp.response == "Missing authorization token" + + test "Graphql query with auth": + let client = setupClient(server.localAddress) + let res = waitFor client.sendRequest(query, req.toList) + check res.isOk + let resp = res.get() + check resp.status == 200 + check resp.reason == "OK" + check resp.response == """{"data":{"__type":{"kind":"SCALAR"}}}""" + + test "rpc query no auth": + let client = newRpcHttpClient() + waitFor client.connect("http://" & $server.localAddress) + try: + let res = waitFor client.rpc_echo(100) + discard res + check false + except ErrorResponse as exc: + check exc.msg == "Forbidden" + + test "rpc query with uth": + proc authHeaders(): seq[(string, string)] = + req.toList + let client = newRpcHttpClient(getHeaders = authHeaders) + waitFor client.connect("http://" & $server.localAddress) + let res = waitFor client.rpc_echo(100) + check res == "hello: 100" + + test "ws query no auth": + let client = newRpcWebSocketClient() + expect WSFailedUpgradeError: + waitFor client.connect("ws://" & $server.localAddress) + + test "ws query with auth": + proc authHeaders(): seq[(string, string)] = + req.toList + let client = newRpcWebSocketClient(authHeaders) + waitFor client.connect("ws://" & $server.localAddress) + let res = waitFor client.rpc_echo(123) + check res == "hello: 123" + + let res2 = waitFor client.rpc_echo(145) + check res2 == "hello: 145" + + waitFor server.closeWait() + # ------------------------------------------------------------------------------ # Main function(s) # ------------------------------------------------------------------------------ diff --git a/tests/test_merge.nim b/tests/test_merge.nim index f3c0cf4b5..5bbeabe22 100644 --- a/tests/test_merge.nim +++ b/tests/test_merge.nim @@ -75,7 +75,7 @@ proc runTest(steps: Steps) = com.initializeEmptyDb() var - rpcServer = newRpcSocketServer(["127.0.0.1:" & $conf.rpcPort]) + rpcServer = newRpcSocketServer(["127.0.0.1:" & $conf.httpPort]) client = newRpcSocketClient() txPool = TxPoolRef.new(com, conf.engineSigner) sealingEngine = SealingEngineRef.new( @@ -89,7 +89,7 @@ proc runTest(steps: Steps) = sealingEngine.start() rpcServer.start() - waitFor client.connect("127.0.0.1", conf.rpcPort) + waitFor client.connect("127.0.0.1", conf.httpPort) suite "Engine API tests": for i, step in steps: diff --git a/tests/test_rpc_experimental_json.nim b/tests/test_rpc_experimental_json.nim index ecc97ceb3..be9531109 100644 --- a/tests/test_rpc_experimental_json.nim +++ b/tests/test_rpc_experimental_json.nim @@ -155,7 +155,9 @@ proc rpcExperimentalJsonMain*() = RPC_PORT = 0 # let the OS choose a port var - rpcServer = newRpcHttpServerWithParams(initTAddress(RPC_HOST, RPC_PORT)) + rpcServer = newRpcHttpServerWithParams(initTAddress(RPC_HOST, RPC_PORT)).valueOr: + echo "Failed to create RPC server: ", error + quit(QuitFailure) client = newRpcHttpClient() rpcServer.start() diff --git a/vendor/nim-confutils b/vendor/nim-confutils index fa6e9b09e..7568f1b7c 160000 --- a/vendor/nim-confutils +++ b/vendor/nim-confutils @@ -1 +1 @@ -Subproject commit fa6e9b09e2dfe09be5b734e7a7a394a36d953831 +Subproject commit 7568f1b7c3142d8e87c1f3dd42924238926affbe diff --git a/vendor/nim-graphql b/vendor/nim-graphql index 4cc36cea8..c47f0c5d2 160000 --- a/vendor/nim-graphql +++ b/vendor/nim-graphql @@ -1 +1 @@ -Subproject commit 4cc36cea8184233761a12c60aa805ed78295d787 +Subproject commit c47f0c5d2f965e30e14ca65540797aac0e38f453 diff --git a/vendor/nim-json-rpc b/vendor/nim-json-rpc index 9a34452e2..85d6a67fb 160000 --- a/vendor/nim-json-rpc +++ b/vendor/nim-json-rpc @@ -1 +1 @@ -Subproject commit 9a34452e235806f97387dd0c5711b82a24d47ba0 +Subproject commit 85d6a67fbc4d490da90e58f3fe97253967401ca1