mirror of
https://github.com/status-im/nimbus-eth1.git
synced 2025-01-12 13:24:21 +00:00
Implement combo http server for rpc, engine_api, and graphql services (#1992)
* Combo HTTP server implementation * Use json flavor for jwt_auth decoder
This commit is contained in:
parent
61abdac2d7
commit
c635e160d9
@ -42,7 +42,7 @@ proc getClient(env: TestEnv, token: string): RpcHttpClient =
|
|||||||
@[("Authorization", "Bearer " & token)]
|
@[("Authorization", "Bearer " & token)]
|
||||||
|
|
||||||
let client = newRpcHttpClient(getHeaders = authHeaders)
|
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
|
return client
|
||||||
|
|
||||||
template genAuthTest(procName: untyped, timeDriftSeconds: int64, customAuthSecretBytes: string, authOK: bool) =
|
template genAuthTest(procName: untyped, timeDriftSeconds: int64, customAuthSecretBytes: string, authOK: bool) =
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Nimbus
|
# Nimbus
|
||||||
# Copyright (c) 2023 Status Research & Development GmbH
|
# Copyright (c) 2023-2024 Status Research & Development GmbH
|
||||||
# Licensed under either of
|
# Licensed under either of
|
||||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
|
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0)
|
# http://www.apache.org/licenses/LICENSE-2.0)
|
||||||
@ -10,7 +10,6 @@
|
|||||||
|
|
||||||
import
|
import
|
||||||
std/strutils,
|
std/strutils,
|
||||||
chronicles,
|
|
||||||
./engine_spec,
|
./engine_spec,
|
||||||
../../../../nimbus/common/hardforks
|
../../../../nimbus/common/hardforks
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Nimbus
|
# Nimbus
|
||||||
# Copyright (c) 2023 Status Research & Development GmbH
|
# Copyright (c) 2023-2024 Status Research & Development GmbH
|
||||||
# Licensed under either of
|
# Licensed under either of
|
||||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
|
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0)
|
# http://www.apache.org/licenses/LICENSE-2.0)
|
||||||
@ -9,14 +9,12 @@
|
|||||||
# according to those terms.
|
# according to those terms.
|
||||||
|
|
||||||
import
|
import
|
||||||
std/strutils,
|
|
||||||
chronicles,
|
chronicles,
|
||||||
./engine_spec,
|
./engine_spec,
|
||||||
../helper,
|
../helper,
|
||||||
../cancun/customizer,
|
../cancun/customizer,
|
||||||
../../../../nimbus/common
|
../../../../nimbus/common
|
||||||
|
|
||||||
|
|
||||||
# Generate test cases for each field of NewPayload, where the payload contains a single invalid field and a valid hash.
|
# Generate test cases for each field of NewPayload, where the payload contains a single invalid field and a valid hash.
|
||||||
type
|
type
|
||||||
InvalidPayloadTestCase* = ref object of EngineSpec
|
InvalidPayloadTestCase* = ref object of EngineSpec
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Nimbus
|
# Nimbus
|
||||||
# Copyright (c) 2023 Status Research & Development GmbH
|
# Copyright (c) 2023-2024 Status Research & Development GmbH
|
||||||
# Licensed under either of
|
# Licensed under either of
|
||||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
|
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0)
|
# http://www.apache.org/licenses/LICENSE-2.0)
|
||||||
@ -9,7 +9,6 @@
|
|||||||
# according to those terms.
|
# according to those terms.
|
||||||
|
|
||||||
import
|
import
|
||||||
std/strutils,
|
|
||||||
eth/common,
|
eth/common,
|
||||||
chronicles,
|
chronicles,
|
||||||
stew/byteutils,
|
stew/byteutils,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Nimbus
|
# Nimbus
|
||||||
# Copyright (c) 2023 Status Research & Development GmbH
|
# Copyright (c) 2023-2024 Status Research & Development GmbH
|
||||||
# Licensed under either of
|
# Licensed under either of
|
||||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
|
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0)
|
# http://www.apache.org/licenses/LICENSE-2.0)
|
||||||
@ -9,7 +9,6 @@
|
|||||||
# according to those terms.
|
# according to those terms.
|
||||||
|
|
||||||
import
|
import
|
||||||
std/strutils,
|
|
||||||
eth/common,
|
eth/common,
|
||||||
chronicles,
|
chronicles,
|
||||||
./engine_spec
|
./engine_spec
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Nimbus
|
# Nimbus
|
||||||
# Copyright (c) 2023 Status Research & Development GmbH
|
# Copyright (c) 2023-2024 Status Research & Development GmbH
|
||||||
# Licensed under either of
|
# Licensed under either of
|
||||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
|
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0)
|
# http://www.apache.org/licenses/LICENSE-2.0)
|
||||||
@ -111,7 +111,10 @@ proc newEngineEnv*(conf: var NimbusConf, chainFile: string, enableAuth: bool): E
|
|||||||
let
|
let
|
||||||
hooks = if enableAuth: @[httpJwtAuth(key)]
|
hooks = if enableAuth: @[httpJwtAuth(key)]
|
||||||
else: @[]
|
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(
|
sealer = SealingEngineRef.new(
|
||||||
chain, ctx, conf.engineSigner,
|
chain, ctx, conf.engineSigner,
|
||||||
txPool, EngineStopped)
|
txPool, EngineStopped)
|
||||||
@ -135,7 +138,7 @@ proc newEngineEnv*(conf: var NimbusConf, chainFile: string, enableAuth: bool): E
|
|||||||
server.start()
|
server.start()
|
||||||
|
|
||||||
let client = newRpcHttpClient()
|
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:
|
if com.ttd().isSome:
|
||||||
sync.start()
|
sync.start()
|
||||||
@ -167,8 +170,8 @@ proc setRealTTD*(env: EngineEnv) =
|
|||||||
env.com.setTTD some(realTTD)
|
env.com.setTTD some(realTTD)
|
||||||
env.ttd = realTTD
|
env.ttd = realTTD
|
||||||
|
|
||||||
func rpcPort*(env: EngineEnv): Port =
|
func httpPort*(env: EngineEnv): Port =
|
||||||
env.conf.rpcPort
|
env.conf.httpPort
|
||||||
|
|
||||||
func client*(env: EngineEnv): RpcHttpClient =
|
func client*(env: EngineEnv): RpcHttpClient =
|
||||||
env.client
|
env.client
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Nimbus
|
# Nimbus
|
||||||
# Copyright (c) 2023 Status Research & Development GmbH
|
# Copyright (c) 2023-2024 Status Research & Development GmbH
|
||||||
# Licensed under either of
|
# Licensed under either of
|
||||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
|
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0)
|
# http://www.apache.org/licenses/LICENSE-2.0)
|
||||||
@ -36,7 +36,7 @@ type
|
|||||||
chainFile : string
|
chainFile : string
|
||||||
enableAuth: bool
|
enableAuth: bool
|
||||||
port : int
|
port : int
|
||||||
rpcPort : int
|
httpPort : int
|
||||||
clients : ClientPool
|
clients : ClientPool
|
||||||
sender : TxSender
|
sender : TxSender
|
||||||
clMock* : CLMocker
|
clMock* : CLMocker
|
||||||
@ -45,19 +45,19 @@ proc makeEnv(conf: NimbusConf): TestEnv =
|
|||||||
TestEnv(
|
TestEnv(
|
||||||
conf : conf,
|
conf : conf,
|
||||||
port : 30303,
|
port : 30303,
|
||||||
rpcPort: 8545,
|
httpPort: 8545,
|
||||||
clients: ClientPool(),
|
clients : ClientPool(),
|
||||||
sender : TxSender.new(conf.networkParams),
|
sender : TxSender.new(conf.networkParams),
|
||||||
)
|
)
|
||||||
|
|
||||||
proc addEngine(env: TestEnv, conf: var NimbusConf): EngineEnv =
|
proc addEngine(env: TestEnv, conf: var NimbusConf): EngineEnv =
|
||||||
conf.tcpPort = Port env.port
|
conf.tcpPort = Port env.port
|
||||||
conf.udpPort = 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)
|
let engine = newEngineEnv(conf, env.chainFile, env.enableAuth)
|
||||||
env.clients.add engine
|
env.clients.add engine
|
||||||
inc env.port
|
inc env.port
|
||||||
inc env.rpcPort
|
inc env.httpPort
|
||||||
engine
|
engine
|
||||||
|
|
||||||
proc setup(env: TestEnv, conf: var NimbusConf, chainFile: string, enableAuth: bool) =
|
proc setup(env: TestEnv, conf: var NimbusConf, chainFile: string, enableAuth: bool) =
|
||||||
|
@ -46,17 +46,7 @@ proc manageAccounts(ctx: EthContext, conf: NimbusConf) =
|
|||||||
proc setupRpcServer(ctx: EthContext, com: CommonRef,
|
proc setupRpcServer(ctx: EthContext, com: CommonRef,
|
||||||
ethNode: EthereumNode, txPool: TxPoolRef,
|
ethNode: EthereumNode, txPool: TxPoolRef,
|
||||||
conf: NimbusConf): RpcServer =
|
conf: NimbusConf): RpcServer =
|
||||||
let rpcServer = newRpcHttpServer([initTAddress(conf.rpcAddress, conf.rpcPort)])
|
let rpcServer = newRpcHttpServer([initTAddress(conf.httpAddress, conf.httpPort)])
|
||||||
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))
|
|
||||||
setupCommonRpc(ethNode, conf, rpcServer)
|
setupCommonRpc(ethNode, conf, rpcServer)
|
||||||
setupEthRpc(ethNode, ctx, com, txPool, rpcServer)
|
setupEthRpc(ethNode, ctx, com, txPool, rpcServer)
|
||||||
|
|
||||||
@ -68,11 +58,6 @@ proc stopRpcHttpServer(srv: RpcServer) =
|
|||||||
waitFor rpcServer.stop()
|
waitFor rpcServer.stop()
|
||||||
waitFor rpcServer.closeWait()
|
waitFor rpcServer.closeWait()
|
||||||
|
|
||||||
proc stopRpcWsServer(srv: RpcServer) =
|
|
||||||
let rpcServer = RpcWebSocketServer(srv)
|
|
||||||
rpcServer.stop()
|
|
||||||
waitFor rpcServer.closeWait()
|
|
||||||
|
|
||||||
proc setupEnv*(): TestEnv =
|
proc setupEnv*(): TestEnv =
|
||||||
let conf = makeConfig(@[
|
let conf = makeConfig(@[
|
||||||
"--prune-mode:archive",
|
"--prune-mode:archive",
|
||||||
@ -83,12 +68,8 @@ proc setupEnv*(): TestEnv =
|
|||||||
"--custom-network:" & initPath / "genesis.json",
|
"--custom-network:" & initPath / "genesis.json",
|
||||||
"--rpc",
|
"--rpc",
|
||||||
"--rpc-api:eth,debug",
|
"--rpc-api:eth,debug",
|
||||||
# "--rpc-address:0.0.0.0",
|
# "--http-address:0.0.0.0",
|
||||||
"--rpc-port:8545",
|
"--http-port:8545",
|
||||||
"--ws",
|
|
||||||
"--ws-api:eth,debug",
|
|
||||||
# "--ws-address:0.0.0.0",
|
|
||||||
"--ws-port:8546"
|
|
||||||
])
|
])
|
||||||
|
|
||||||
let
|
let
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Nimbus
|
# Nimbus
|
||||||
# Copyright (c) 2021 Status Research & Development GmbH
|
# Copyright (c) 2021-2024 Status Research & Development GmbH
|
||||||
# Licensed under either of
|
# Licensed under either of
|
||||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||||
@ -7,12 +7,13 @@
|
|||||||
# This file may not be copied, modified, or distributed except according to
|
# This file may not be copied, modified, or distributed except according to
|
||||||
# those terms.
|
# those terms.
|
||||||
|
|
||||||
{.push raises: [].}
|
{.push gcsafe, raises: [].}
|
||||||
|
|
||||||
import
|
import
|
||||||
std/[os, json, tables, strutils],
|
std/[os, json, tables, strutils],
|
||||||
stew/[byteutils, results],
|
stew/[byteutils, results],
|
||||||
eth/[keyfile, common, keys]
|
eth/[keyfile, common, keys],
|
||||||
|
json_serialization
|
||||||
|
|
||||||
from nimcrypto/utils import burnMem
|
from nimcrypto/utils import burnMem
|
||||||
|
|
||||||
@ -28,30 +29,16 @@ type
|
|||||||
proc init*(_: type AccountsManager): AccountsManager =
|
proc init*(_: type AccountsManager): AccountsManager =
|
||||||
discard
|
discard
|
||||||
|
|
||||||
proc loadKeystores*(am: var AccountsManager, path: string): Result[void, string]
|
proc loadKeystores*(am: var AccountsManager, path: string):
|
||||||
{.gcsafe, raises: [OSError].}=
|
Result[void, string] =
|
||||||
try:
|
try:
|
||||||
createDir(path)
|
createDir(path)
|
||||||
except OSError, IOError:
|
|
||||||
return err("keystore: cannot create directory")
|
|
||||||
|
|
||||||
for filename in walkDirRec(path):
|
for filename in walkDirRec(path):
|
||||||
try:
|
var data = Json.loadFile(filename, JsonNode)
|
||||||
var data = json.parseFile(filename)
|
|
||||||
let address: EthAddress = hexToByteArray[20](data["address"].getStr())
|
let address: EthAddress = hexToByteArray[20](data["address"].getStr())
|
||||||
am.accounts[address] = NimbusAccount(keystore: data, unlocked: false)
|
am.accounts[address] = NimbusAccount(keystore: data, unlocked: false)
|
||||||
except JsonParsingError:
|
except CatchableError as exc:
|
||||||
return err("keystore: json parsing error " & filename)
|
return err("loadKeystrores: " & exc.msg)
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
ok()
|
ok()
|
||||||
|
|
||||||
@ -110,3 +97,5 @@ proc importPrivateKey*(am: var AccountsManager, fileName: string): Result[void,
|
|||||||
return ok()
|
return ok()
|
||||||
except CatchableError as ex:
|
except CatchableError as ex:
|
||||||
return err(ex.msg)
|
return err(ex.msg)
|
||||||
|
|
||||||
|
{.pop.}
|
||||||
|
@ -87,11 +87,8 @@ const
|
|||||||
defaultDataDirDesc = defaultDataDir()
|
defaultDataDirDesc = defaultDataDir()
|
||||||
defaultPort = 30303
|
defaultPort = 30303
|
||||||
defaultMetricsServerPort = 9093
|
defaultMetricsServerPort = 9093
|
||||||
defaultEthRpcPort = 8545
|
defaultHttpPort = 8545
|
||||||
defaultEthWsPort = 8546
|
|
||||||
defaultEthGraphqlPort = 8547
|
|
||||||
defaultEngineApiPort = 8550
|
defaultEngineApiPort = 8550
|
||||||
defaultEngineApiWsPort = 8551
|
|
||||||
defaultListenAddress = (static parseIpAddress("0.0.0.0"))
|
defaultListenAddress = (static parseIpAddress("0.0.0.0"))
|
||||||
defaultAdminListenAddress = (static parseIpAddress("127.0.0.1"))
|
defaultAdminListenAddress = (static parseIpAddress("127.0.0.1"))
|
||||||
defaultListenAddressDesc = $defaultListenAddress & ", meaning all network interfaces"
|
defaultListenAddressDesc = $defaultListenAddress & ", meaning all network interfaces"
|
||||||
@ -380,26 +377,26 @@ type
|
|||||||
defaultValue: NimbusCmd.noCommand }: NimbusCmd
|
defaultValue: NimbusCmd.noCommand }: NimbusCmd
|
||||||
|
|
||||||
of noCommand:
|
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* {.
|
rpcEnabled* {.
|
||||||
separator: "\pLOCAL SERVICE OPTIONS:"
|
|
||||||
desc: "Enable the JSON-RPC server"
|
desc: "Enable the JSON-RPC server"
|
||||||
defaultValue: false
|
defaultValue: false
|
||||||
name: "rpc" }: bool
|
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 {.
|
rpcApi {.
|
||||||
desc: "Enable specific set of RPC API (available: eth, debug)"
|
desc: "Enable specific set of RPC API (available: eth, debug, exp)"
|
||||||
defaultValue: @[]
|
defaultValue: @[]
|
||||||
defaultValueDesc: $RpcFlag.Eth
|
defaultValueDesc: $RpcFlag.Eth
|
||||||
name: "rpc-api" }: seq[string]
|
name: "rpc-api" }: seq[string]
|
||||||
@ -409,37 +406,30 @@ type
|
|||||||
defaultValue: false
|
defaultValue: false
|
||||||
name: "ws" }: bool
|
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 {.
|
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: @[]
|
defaultValue: @[]
|
||||||
defaultValueDesc: $RpcFlag.Eth
|
defaultValueDesc: $RpcFlag.Eth
|
||||||
name: "ws-api" }: seq[string]
|
name: "ws-api" }: seq[string]
|
||||||
|
|
||||||
|
graphqlEnabled* {.
|
||||||
|
desc: "Enable the GraphQL HTTP server"
|
||||||
|
defaultValue: false
|
||||||
|
name: "graphql" }: bool
|
||||||
|
|
||||||
engineApiEnabled* {.
|
engineApiEnabled* {.
|
||||||
desc: "Enable the Engine API"
|
desc: "Enable the Engine API"
|
||||||
defaultValue: false
|
defaultValue: false
|
||||||
name: "engine-api" .}: bool
|
name: "engine-api" .}: bool
|
||||||
|
|
||||||
engineApiPort* {.
|
engineApiPort* {.
|
||||||
desc: "Listening port for the Engine API"
|
desc: "Listening port for the Engine API(http and ws)"
|
||||||
defaultValue: defaultEngineApiPort
|
defaultValue: defaultEngineApiPort
|
||||||
defaultValueDesc: $defaultEngineApiPort
|
defaultValueDesc: $defaultEngineApiPort
|
||||||
name: "engine-api-port" .}: Port
|
name: "engine-api-port" .}: Port
|
||||||
|
|
||||||
engineApiAddress* {.
|
engineApiAddress* {.
|
||||||
desc: "Listening address for the Engine API"
|
desc: "Listening address for the Engine API(http and ws)"
|
||||||
defaultValue: defaultAdminListenAddress
|
defaultValue: defaultAdminListenAddress
|
||||||
defaultValueDesc: $defaultAdminListenAddressDesc
|
defaultValueDesc: $defaultAdminListenAddressDesc
|
||||||
name: "engine-api-address" .}: IpAddress
|
name: "engine-api-address" .}: IpAddress
|
||||||
@ -449,18 +439,6 @@ type
|
|||||||
defaultValue: false
|
defaultValue: false
|
||||||
name: "engine-api-ws" .}: bool
|
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* {.
|
terminalTotalDifficulty* {.
|
||||||
desc: "The terminal total difficulty of the eth2 merge transition block." &
|
desc: "The terminal total difficulty of the eth2 merge transition block." &
|
||||||
" It takes precedence over terminalTotalDifficulty in config file."
|
" It takes precedence over terminalTotalDifficulty in config file."
|
||||||
@ -481,23 +459,6 @@ type
|
|||||||
defaultValueDesc: "\"jwt.hex\" in the data directory (see --data-dir)"
|
defaultValueDesc: "\"jwt.hex\" in the data directory (see --data-dir)"
|
||||||
name: "jwt-secret" .}: Option[InputFile]
|
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* {.
|
metricsEnabled* {.
|
||||||
desc: "Enable the built-in metrics HTTP server"
|
desc: "Enable the built-in metrics HTTP server"
|
||||||
defaultValue: false
|
defaultValue: false
|
||||||
@ -719,7 +680,7 @@ proc fromEnr*(T: type ENode, r: enr.Record): ENodeResult[ENode] =
|
|||||||
ok(ENode(
|
ok(ENode(
|
||||||
pubkey: pk,
|
pubkey: pk,
|
||||||
address: Address(
|
address: Address(
|
||||||
ip: ipv4(tr.ip.get()),
|
ip: utils.ipv4(tr.ip.get()),
|
||||||
udpPort: Port(tr.udp.get()),
|
udpPort: Port(tr.udp.get()),
|
||||||
tcpPort: Port(tr.tcp.get())
|
tcpPort: Port(tr.tcp.get())
|
||||||
)
|
)
|
||||||
@ -779,6 +740,18 @@ proc getAllowedOrigins*(conf: NimbusConf): seq[Uri] =
|
|||||||
for item in repeatingList(conf.allowedOrigins):
|
for item in repeatingList(conf.allowedOrigins):
|
||||||
result.add parseUri(item)
|
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
|
# KLUDGE: The `load()` template does currently not work within any exception
|
||||||
# annotated environment.
|
# annotated environment.
|
||||||
{.pop.}
|
{.pop.}
|
||||||
@ -834,13 +807,6 @@ proc makeConfig*(cmdLine = commandLineParams()): NimbusConf
|
|||||||
# if udpPort not set in cli, then
|
# if udpPort not set in cli, then
|
||||||
result.udpPort = result.tcpPort
|
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
|
# see issue #1346
|
||||||
if result.keyStore.string == defaultKeystoreDir() and
|
if result.keyStore.string == defaultKeystoreDir() and
|
||||||
result.dataDir.string != defaultDataDir():
|
result.dataDir.string != defaultDataDir():
|
||||||
|
@ -1459,23 +1459,10 @@ proc setupGraphqlContext*(com: CommonRef,
|
|||||||
ctx.initEthApi()
|
ctx.initEthApi()
|
||||||
ctx
|
ctx
|
||||||
|
|
||||||
proc setupGraphqlHttpServer*(conf: NimbusConf,
|
proc setupGraphqlHttpHandler*(com: CommonRef,
|
||||||
com: CommonRef,
|
|
||||||
ethNode: EthereumNode,
|
ethNode: EthereumNode,
|
||||||
txPool: TxPoolRef,
|
txPool: TxPoolRef): GraphqlHttpHandlerRef =
|
||||||
authHooks: seq[AuthHook] = @[]): GraphqlHttpServerRef =
|
|
||||||
let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr}
|
|
||||||
let ctx = setupGraphqlContext(com, ethNode, txPool)
|
let ctx = setupGraphqlContext(com, ethNode, txPool)
|
||||||
let address = initTAddress(conf.graphqlAddress, conf.graphqlPort)
|
GraphqlHttpHandlerRef.new(ctx)
|
||||||
let sres = GraphqlHttpServerRef.new(
|
|
||||||
ctx,
|
|
||||||
address,
|
|
||||||
socketFlags = socketFlags,
|
|
||||||
authHooks = authHooks
|
|
||||||
)
|
|
||||||
if sres.isErr():
|
|
||||||
echo sres.error
|
|
||||||
quit(QuitFailure)
|
|
||||||
sres.get()
|
|
||||||
|
|
||||||
{.pop.}
|
{.pop.}
|
||||||
|
@ -13,23 +13,25 @@ import
|
|||||||
import
|
import
|
||||||
std/[os, strutils, net],
|
std/[os, strutils, net],
|
||||||
chronicles,
|
chronicles,
|
||||||
chronos,
|
eth/keys,
|
||||||
eth/[keys, net/nat],
|
eth/net/nat,
|
||||||
eth/p2p as eth_p2p,
|
|
||||||
json_rpc/rpcserver,
|
|
||||||
metrics,
|
metrics,
|
||||||
metrics/[chronos_httpserver, chronicles_support],
|
metrics/chronicles_support,
|
||||||
websock/websock as ws,
|
|
||||||
kzg4844/kzg_ex as kzg,
|
kzg4844/kzg_ex as kzg,
|
||||||
|
./rpc,
|
||||||
|
./version,
|
||||||
|
./constants,
|
||||||
|
./nimbus_desc,
|
||||||
./core/eip4844,
|
./core/eip4844,
|
||||||
"."/[config, constants, version, rpc, common],
|
./core/block_import,
|
||||||
./db/[core_db/persistent, select_backend],
|
./db/select_backend,
|
||||||
./graphql/ethapi,
|
./db/core_db/persistent,
|
||||||
./core/[chain, sealer, clique/clique_desc,
|
./core/clique/clique_desc,
|
||||||
clique/clique_sealer, tx_pool, block_import],
|
./core/clique/clique_sealer,
|
||||||
./beacon/beacon_engine,
|
./sync/protocol,
|
||||||
./sync/[beacon, legacy, full, protocol, snap, stateless,
|
./sync/handlers,
|
||||||
protocol/les_protocol, handlers, peers],
|
./sync/stateless,
|
||||||
|
./sync/protocol/les_protocol,
|
||||||
./evm/async/data_sources/json_rpc_data_source
|
./evm/async/data_sources/json_rpc_data_source
|
||||||
|
|
||||||
when defined(evmc_enabled):
|
when defined(evmc_enabled):
|
||||||
@ -40,31 +42,6 @@ when defined(evmc_enabled):
|
|||||||
## * No multiple bind addresses support
|
## * No multiple bind addresses support
|
||||||
## * No database 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) =
|
proc importBlocks(conf: NimbusConf, com: CommonRef) =
|
||||||
if string(conf.blocksFile).len > 0:
|
if string(conf.blocksFile).len > 0:
|
||||||
# success or not, we quit after importing blocks
|
# 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)
|
||||||
discard setTimer(Moment.fromNow(conf.logMetricsInterval.seconds), logMetrics)
|
discard setTimer(Moment.fromNow(conf.logMetricsInterval.seconds), logMetrics)
|
||||||
|
|
||||||
# Provide JWT authentication handler for rpcHttpServer
|
nimbus.setupRpc(conf, com, protocols)
|
||||||
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()
|
|
||||||
|
|
||||||
if conf.engineSigner != ZERO_ADDRESS and not com.forkGTE(MergeFork):
|
if conf.engineSigner != ZERO_ADDRESS and not com.forkGTE(MergeFork):
|
||||||
let res = nimbus.ctx.am.getAccount(conf.engineSigner)
|
let res = nimbus.ctx.am.getAccount(conf.engineSigner)
|
||||||
@ -370,40 +257,16 @@ proc localServices(nimbus: NimbusNode, conf: NimbusConf,
|
|||||||
if conf.engineSigner != ZERO_ADDRESS:
|
if conf.engineSigner != ZERO_ADDRESS:
|
||||||
nimbus.sealingEngine.start()
|
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
|
# metrics server
|
||||||
if conf.metricsEnabled:
|
if conf.metricsEnabled:
|
||||||
info "Starting metrics HTTP server", address = conf.metricsAddress, port = conf.metricsPort
|
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) =
|
proc start(nimbus: NimbusNode, conf: NimbusConf) =
|
||||||
## logging
|
## logging
|
||||||
@ -468,42 +331,13 @@ proc start(nimbus: NimbusNode, conf: NimbusConf) =
|
|||||||
of SyncMode.Snap:
|
of SyncMode.Snap:
|
||||||
nimbus.snapSyncRef.start
|
nimbus.snapSyncRef.start
|
||||||
|
|
||||||
if nimbus.state == Starting:
|
if nimbus.state == NimbusState.Starting:
|
||||||
# it might have been set to "Stopping" with Ctrl+C
|
# it might have been set to "Stopping" with Ctrl+C
|
||||||
nimbus.state = Running
|
nimbus.state = NimbusState.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()
|
|
||||||
|
|
||||||
proc process*(nimbus: NimbusNode, conf: NimbusConf) =
|
proc process*(nimbus: NimbusNode, conf: NimbusConf) =
|
||||||
# Main event loop
|
# Main event loop
|
||||||
while nimbus.state == Running:
|
while nimbus.state == NimbusState.Running:
|
||||||
try:
|
try:
|
||||||
poll()
|
poll()
|
||||||
except CatchableError as e:
|
except CatchableError as e:
|
||||||
@ -514,14 +348,14 @@ proc process*(nimbus: NimbusNode, conf: NimbusConf) =
|
|||||||
waitFor nimbus.stop(conf)
|
waitFor nimbus.stop(conf)
|
||||||
|
|
||||||
when isMainModule:
|
when isMainModule:
|
||||||
var nimbus = NimbusNode(state: Starting, ctx: newEthContext())
|
var nimbus = NimbusNode(state: NimbusState.Starting, ctx: newEthContext())
|
||||||
|
|
||||||
## Ctrl+C handling
|
## Ctrl+C handling
|
||||||
proc controlCHandler() {.noconv.} =
|
proc controlCHandler() {.noconv.} =
|
||||||
when defined(windows):
|
when defined(windows):
|
||||||
# workaround for https://github.com/nim-lang/Nim/issues/4057
|
# workaround for https://github.com/nim-lang/Nim/issues/4057
|
||||||
setupForeignThreadGc()
|
setupForeignThreadGc()
|
||||||
nimbus.state = Stopping
|
nimbus.state = NimbusState.Stopping
|
||||||
echo "\nCtrl+C pressed. Waiting for a graceful shutdown."
|
echo "\nCtrl+C pressed. Waiting for a graceful shutdown."
|
||||||
setControlCHook(controlCHandler)
|
setControlCHook(controlCHandler)
|
||||||
|
|
||||||
|
94
nimbus/nimbus_desc.nim
Normal file
94
nimbus/nimbus_desc.nim
Normal file
@ -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.}
|
257
nimbus/rpc.nim
257
nimbus/rpc.nim
@ -8,6 +8,10 @@
|
|||||||
# those terms.
|
# those terms.
|
||||||
|
|
||||||
import
|
import
|
||||||
|
chronicles,
|
||||||
|
websock/websock,
|
||||||
|
json_rpc/rpcserver,
|
||||||
|
graphql/httpserver,
|
||||||
./rpc/common,
|
./rpc/common,
|
||||||
./rpc/debug,
|
./rpc/debug,
|
||||||
./rpc/engine_api,
|
./rpc/engine_api,
|
||||||
@ -15,7 +19,9 @@ import
|
|||||||
./rpc/jwt_auth,
|
./rpc/jwt_auth,
|
||||||
./rpc/cors,
|
./rpc/cors,
|
||||||
./rpc/rpc_server,
|
./rpc/rpc_server,
|
||||||
./rpc/experimental
|
./rpc/experimental,
|
||||||
|
./nimbus_desc,
|
||||||
|
./graphql/ethapi
|
||||||
|
|
||||||
export
|
export
|
||||||
common,
|
common,
|
||||||
@ -26,3 +32,252 @@ export
|
|||||||
cors,
|
cors,
|
||||||
rpc_server,
|
rpc_server,
|
||||||
experimental
|
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.}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Nimbus
|
# Nimbus
|
||||||
# Copyright (c) 2022 Status Research & Development GmbH
|
# Copyright (c) 2022-2024 Status Research & Development GmbH
|
||||||
# Licensed and distributed under either of
|
# Licensed and distributed under either of
|
||||||
# * MIT license (license terms in the root directory or at
|
# * MIT license (license terms in the root directory or at
|
||||||
# https://opensource.org/licenses/MIT).
|
# https://opensource.org/licenses/MIT).
|
||||||
@ -11,12 +11,12 @@
|
|||||||
import
|
import
|
||||||
std/[uri],
|
std/[uri],
|
||||||
chronos,
|
chronos,
|
||||||
chronos/apps/http/[httptable, httpserver],
|
chronos/apps/http/httptable,
|
||||||
json_rpc/rpcserver,
|
chronos/apps/http/httpserver,
|
||||||
httputils,
|
httputils,
|
||||||
websock/websock as ws
|
./rpc_server
|
||||||
|
|
||||||
{.push raises: [].}
|
{.push gcsafe, raises: [].}
|
||||||
|
|
||||||
proc sameOrigin(a, b: Uri): bool =
|
proc sameOrigin(a, b: Uri): bool =
|
||||||
a.hostname == b.hostname and
|
a.hostname == b.hostname and
|
||||||
@ -30,8 +30,9 @@ proc containsOrigin(list: seq[Uri], origin: Uri): bool =
|
|||||||
const
|
const
|
||||||
HookOK = HttpResponseRef(nil)
|
HookOK = HttpResponseRef(nil)
|
||||||
|
|
||||||
proc httpCors*(allowedOrigins: seq[Uri]): HttpAuthHook =
|
proc httpCors*(allowedOrigins: seq[Uri]): RpcAuthHook =
|
||||||
proc handler(req: HttpRequestRef): Future[HttpResponseRef] {.async.} =
|
proc handler(req: HttpRequestRef): Future[HttpResponseRef]
|
||||||
|
{.gcsafe, async: (raises: [CatchableError]).} =
|
||||||
let origins = req.headers.getList("Origin")
|
let origins = req.headers.getList("Origin")
|
||||||
let everyOriginAllowed = allowedOrigins.len == 0
|
let everyOriginAllowed = allowedOrigins.len == 0
|
||||||
|
|
||||||
@ -87,12 +88,4 @@ proc httpCors*(allowedOrigins: seq[Uri]): HttpAuthHook =
|
|||||||
# the rest of response in server
|
# the rest of response in server
|
||||||
return HookOK
|
return HookOK
|
||||||
|
|
||||||
result = HttpAuthHook(handler)
|
result = 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)
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Nimbus
|
# Nimbus
|
||||||
# Copyright (c) 2022 Status Research & Development GmbH
|
# Copyright (c) 2022-2024 Status Research & Development GmbH
|
||||||
# Licensed and distributed under either of
|
# Licensed and distributed under either of
|
||||||
# * MIT license (license terms in the root directory or at
|
# * MIT license (license terms in the root directory or at
|
||||||
# https://opensource.org/licenses/MIT).
|
# https://opensource.org/licenses/MIT).
|
||||||
@ -12,20 +12,21 @@
|
|||||||
# nimbus-eth2/beacon_chain/spec/engine_authentication.nim
|
# nimbus-eth2/beacon_chain/spec/engine_authentication.nim
|
||||||
# go-ethereum/node/jwt_handler.go
|
# go-ethereum/node/jwt_handler.go
|
||||||
|
|
||||||
{.push raises: [].}
|
{.push gcsafe, raises: [].}
|
||||||
|
|
||||||
import
|
import
|
||||||
std/[base64, json, options, os, strutils, times],
|
std/[base64, options, strutils, times],
|
||||||
bearssl/rand,
|
bearssl/rand,
|
||||||
chronicles,
|
chronicles,
|
||||||
chronos,
|
chronos,
|
||||||
chronos/apps/http/[httptable, httpserver],
|
chronos/apps/http/httptable,
|
||||||
json_rpc/rpcserver,
|
chronos/apps/http/httpserver,
|
||||||
httputils,
|
httputils,
|
||||||
websock/websock as ws,
|
|
||||||
nimcrypto/[hmac, utils],
|
nimcrypto/[hmac, utils],
|
||||||
stew/[byteutils, objects, results],
|
stew/[byteutils, objects, results],
|
||||||
../config
|
../config,
|
||||||
|
./jwt_auth_helper,
|
||||||
|
./rpc_server
|
||||||
|
|
||||||
logScope:
|
logScope:
|
||||||
topics = "Jwt/HS256 auth"
|
topics = "Jwt/HS256 auth"
|
||||||
@ -70,14 +71,7 @@ type
|
|||||||
jwtMethodUnsupported = "token protected header provides unsupported method"
|
jwtMethodUnsupported = "token protected header provides unsupported method"
|
||||||
jwtTimeValidationError = "token time validation failed"
|
jwtTimeValidationError = "token time validation failed"
|
||||||
jwtTokenValidationError = "token signature validation failed"
|
jwtTokenValidationError = "token signature validation failed"
|
||||||
|
jwtCreationError = "Cannot create jwt secret"
|
||||||
JwtHeader = object ##\
|
|
||||||
## Template used for JSON unmarshalling
|
|
||||||
typ, alg: string
|
|
||||||
|
|
||||||
JwtIatPayload = object ##\
|
|
||||||
## Template used for JSON unmarshalling
|
|
||||||
iat: uint64
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# Private functions
|
# Private functions
|
||||||
@ -113,7 +107,7 @@ proc verifyTokenHS256(token: string; key: JwtSharedKey): Result[void,JwtError] =
|
|||||||
let jsonHeader = p[0].base64urlDecode
|
let jsonHeader = p[0].base64urlDecode
|
||||||
|
|
||||||
error = jwtProtHeaderInvJson
|
error = jwtProtHeaderInvJson
|
||||||
let jwtHeader = jsonHeader.parseJson.to(JwtHeader)
|
let jwtHeader = jsonHeader.decodeJwtHeader()
|
||||||
|
|
||||||
# The following JSON decoded object is required
|
# The following JSON decoded object is required
|
||||||
if jwtHeader.typ != "JWT" and jwtHeader.alg != "HS256":
|
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
|
let jsonPayload = p[1].base64urlDecode
|
||||||
|
|
||||||
error = jwtIatPayloadInvJson
|
error = jwtIatPayloadInvJson
|
||||||
let jwtPayload = jsonPayload.parseJson.to(JwtIatPayload)
|
let jwtPayload = jsonPayload.decodeJwtIatPayload()
|
||||||
time = jwtPayload.iat.int64
|
time = jwtPayload.iat.int64
|
||||||
except CatchableError as e:
|
except CatchableError as e:
|
||||||
|
discard e
|
||||||
debug "JWT token decoding error",
|
debug "JWT token decoding error",
|
||||||
protectedHeader = p[0],
|
protectedHeader = p[0],
|
||||||
payload = p[1],
|
payload = p[1],
|
||||||
msg = e.msg,
|
msg = e.msg,
|
||||||
error
|
error
|
||||||
return err(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/
|
# github.com/ethereum/
|
||||||
# /execution-apis/blob/v1.0.0-beta.3/src/engine/authentication.md#jwt-claims
|
# /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*(
|
proc jwtSharedSecret*(
|
||||||
rndSecret: JwtGenSecret;
|
rndSecret: JwtGenSecret;
|
||||||
config: NimbusConf;
|
config: NimbusConf;
|
||||||
): Result[JwtSharedKey, JwtError]
|
): Result[JwtSharedKey, JwtError] =
|
||||||
{.gcsafe, raises: [CatchableError].}=
|
|
||||||
## Return a key for jwt authentication preferable from the argument file
|
## Return a key for jwt authentication preferable from the argument file
|
||||||
## `config.jwtSecret` (which contains at least 32 bytes hex encoded random
|
## `config.jwtSecret` (which contains at least 32 bytes hex encoded random
|
||||||
## data.) Otherwise it creates a key and stores it in the `config.dataDir`.
|
## data.) Otherwise it creates a key and stores it in the `config.dataDir`.
|
||||||
@ -226,18 +217,19 @@ proc jwtSharedSecret*(
|
|||||||
# github.com/ethereum/
|
# github.com/ethereum/
|
||||||
# /execution-apis/blob/v1.0.0-alpha.8/src/engine/
|
# /execution-apis/blob/v1.0.0-alpha.8/src/engine/
|
||||||
# /authentication.md#key-distribution
|
# /authentication.md#key-distribution
|
||||||
let
|
let jwtSecretPath = config.dataDir.string & "/" & jwtSecretFile
|
||||||
jwtSecretPath = config.dataDir.string / jwtSecretFile
|
|
||||||
newSecret = rndSecret()
|
|
||||||
try:
|
try:
|
||||||
|
let newSecret = rndSecret()
|
||||||
jwtSecretPath.writeFile(newSecret.JwtSharedKeyRaw.to0xHex)
|
jwtSecretPath.writeFile(newSecret.JwtSharedKeyRaw.to0xHex)
|
||||||
|
return ok(newSecret)
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
# Allow continuing to run, though this is effectively fatal for a merge
|
# Allow continuing to run, though this is effectively fatal for a merge
|
||||||
# client using authentication. This keeps it lower-risk initially.
|
# client using authentication. This keeps it lower-risk initially.
|
||||||
warn "Could not write JWT secret to data directory",
|
warn "Could not write JWT secret to data directory",
|
||||||
jwtSecretPath
|
jwtSecretPath
|
||||||
discard e
|
discard e
|
||||||
return ok(newSecret)
|
except CatchableError:
|
||||||
|
return err(jwtCreationError)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
let lines = config.jwtSecret.get.string.readLines(1)
|
let lines = config.jwtSecret.get.string.readLines(1)
|
||||||
@ -254,13 +246,16 @@ proc jwtSharedSecret*(
|
|||||||
return err(jwtKeyInvalidHexString)
|
return err(jwtKeyInvalidHexString)
|
||||||
|
|
||||||
proc jwtSharedSecret*(rng: ref rand.HmacDrbgContext; config: NimbusConf):
|
proc jwtSharedSecret*(rng: ref rand.HmacDrbgContext; config: NimbusConf):
|
||||||
Result[JwtSharedKey, JwtError]
|
Result[JwtSharedKey, JwtError] =
|
||||||
{.gcsafe, raises: [CatchableError].} =
|
|
||||||
## Variant of `jwtSharedSecret()` with explicit random generator argument.
|
## Variant of `jwtSharedSecret()` with explicit random generator argument.
|
||||||
|
try:
|
||||||
rng.jwtGenSecret.jwtSharedSecret(config)
|
rng.jwtGenSecret.jwtSharedSecret(config)
|
||||||
|
except CatchableError:
|
||||||
|
return err(jwtCreationError)
|
||||||
|
|
||||||
proc httpJwtAuth*(key: JwtSharedKey): HttpAuthHook =
|
proc httpJwtAuth*(key: JwtSharedKey): RpcAuthHook =
|
||||||
proc handler(req: HttpRequestRef): Future[HttpResponseRef] {.async.} =
|
proc handler(req: HttpRequestRef): Future[HttpResponseRef]
|
||||||
|
{.gcsafe, async: (raises: [CatchableError]).} =
|
||||||
let auth = req.headers.getString("Authorization", "?")
|
let auth = req.headers.getString("Authorization", "?")
|
||||||
if auth.len < 9 or auth[0..6].cmpIgnoreCase("Bearer ") != 0:
|
if auth.len < 9 or auth[0..6].cmpIgnoreCase("Bearer ") != 0:
|
||||||
return await req.respond(Http403, "Missing authorization token")
|
return await req.respond(Http403, "Missing authorization token")
|
||||||
@ -278,31 +273,7 @@ proc httpJwtAuth*(key: JwtSharedKey): HttpAuthHook =
|
|||||||
else:
|
else:
|
||||||
return await req.respond(Http403, "Malformed token")
|
return await req.respond(Http403, "Malformed token")
|
||||||
|
|
||||||
result = HttpAuthHook(handler)
|
result = 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)
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# End
|
# End
|
||||||
|
45
nimbus/rpc/jwt_auth_helper.nim
Normal file
45
nimbus/rpc/jwt_auth_helper.nim
Normal file
@ -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.}
|
@ -1,5 +1,5 @@
|
|||||||
# Nimbus
|
# Nimbus
|
||||||
# Copyright (c) 2023 Status Research & Development GmbH
|
# Copyright (c) 2023-2024 Status Research & Development GmbH
|
||||||
# Licensed under either of
|
# Licensed under either of
|
||||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||||
@ -7,22 +7,57 @@
|
|||||||
# This file may not be copied, modified, or distributed except according to
|
# This file may not be copied, modified, or distributed except according to
|
||||||
# those terms.
|
# those terms.
|
||||||
|
|
||||||
|
when false:
|
||||||
|
import
|
||||||
|
std/importutils
|
||||||
|
|
||||||
import
|
import
|
||||||
chronos,
|
json_rpc/servers/httpserver as jrpc_server,
|
||||||
json_rpc/rpcserver
|
chronos/apps/http/httpserver {.all.}
|
||||||
|
|
||||||
type
|
type
|
||||||
RpcHttpServerParams = object
|
RpcHttpServerParams = object
|
||||||
|
serverFlags: set[HttpServerFlags]
|
||||||
socketFlags: set[ServerFlags]
|
socketFlags: set[ServerFlags]
|
||||||
serverUri: Uri
|
serverUri: Uri
|
||||||
serverIdent: string
|
serverIdent: string
|
||||||
maxConnections: int
|
maxConnections: int
|
||||||
bufferSize: int
|
bufferSize: int
|
||||||
backlogSize: int
|
backlogSize: int
|
||||||
httpHeadersTimeout: chronos.Duration
|
httpHeadersTimeout: Duration
|
||||||
maxHeadersSize: int
|
maxHeadersSize: int
|
||||||
maxRequestBodySize: 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 =
|
func defaultRpcHttpServerParams(): RpcHttpServerParams =
|
||||||
RpcHttpServerParams(
|
RpcHttpServerParams(
|
||||||
@ -37,38 +72,243 @@ func defaultRpcHttpServerParams(): RpcHttpServerParams =
|
|||||||
maxRequestBodySize: 2 * 1024 * 1024,
|
maxRequestBodySize: 2 * 1024 * 1024,
|
||||||
)
|
)
|
||||||
|
|
||||||
template processResolvedAddresses =
|
proc resolvedAddress(address: string): Result[TransportAddress, string] =
|
||||||
if tas4.len + tas6.len == 0:
|
var tas: seq[TransportAddress]
|
||||||
# Addresses could not be resolved, critical error.
|
|
||||||
raise newException(RpcAddressUnresolvableError, "Unable to get address!")
|
|
||||||
|
|
||||||
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:
|
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:
|
except CatchableError:
|
||||||
|
# It might be an IPv6
|
||||||
discard
|
discard
|
||||||
|
|
||||||
# Attempt to resolve `address` for IPv6 address space.
|
|
||||||
try:
|
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:
|
except CatchableError:
|
||||||
|
return err("Failed to decode HTTP server address")
|
||||||
|
|
||||||
|
proc createServer(address: TransportAddress,
|
||||||
|
params: RpcHttpServerParams): HttpResult[HttpServerRef] =
|
||||||
|
|
||||||
|
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,
|
||||||
|
params.maxConnections,
|
||||||
|
params.bufferSize,
|
||||||
|
params.backlogSize,
|
||||||
|
params.httpHeadersTimeout,
|
||||||
|
params.maxHeadersSize,
|
||||||
|
params.maxRequestBodySize)
|
||||||
|
|
||||||
|
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 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
|
discard
|
||||||
|
|
||||||
processResolvedAddresses()
|
try:
|
||||||
|
let response =
|
||||||
|
try:
|
||||||
|
await invokeProcessCallback(nserver, requestFence)
|
||||||
|
except CancelledError:
|
||||||
|
# Cancelled, exiting
|
||||||
|
return RpcProcessExitType.Immediate
|
||||||
|
|
||||||
proc addServer*(server: RpcHttpServer, address: TransportAddress, params: RpcHttpServerParams) =
|
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(
|
server.addHttpServer(
|
||||||
address,
|
address,
|
||||||
params.socketFlags,
|
params.socketFlags,
|
||||||
@ -80,22 +320,33 @@ proc addServer*(server: RpcHttpServer, address: TransportAddress, params: RpcHtt
|
|||||||
params.httpHeadersTimeout,
|
params.httpHeadersTimeout,
|
||||||
params.maxHeadersSize,
|
params.maxHeadersSize,
|
||||||
params.maxRequestBodySize)
|
params.maxRequestBodySize)
|
||||||
|
return ok()
|
||||||
|
except CatchableError as exc:
|
||||||
|
return err(exc.msg)
|
||||||
|
|
||||||
proc addServer*(server: RpcHttpServer, address: string, params: RpcHttpServerParams) =
|
proc addServer*(server: RpcHttpServer,
|
||||||
## Create new server and assign it to addresses ``addresses``.
|
address: string,
|
||||||
for a in resolvedAddresses(address):
|
params: RpcHttpServerParams): Result[void, string] =
|
||||||
# TODO handle partial failures, ie when 1/N addresses fail
|
let serverAddress = resolvedAddress(address).valueOr:
|
||||||
server.addServer(a, params)
|
return err(error)
|
||||||
|
|
||||||
proc newRpcHttpServerWithParams*(address: TransportAddress, authHooks: seq[HttpAuthHook] = @[]): RpcHttpServer =
|
server.addServer(serverAddress, params)
|
||||||
|
|
||||||
|
proc newRpcHttpServerWithParams*(address: TransportAddress,
|
||||||
|
authHooks: seq[HttpAuthHook] = @[]): Result[RpcHttpServer, string] =
|
||||||
## Create new server and assign it to addresses ``addresses``.
|
## Create new server and assign it to addresses ``addresses``.
|
||||||
let server = RpcHttpServer.new(authHooks)
|
let server = RpcHttpServer.new(authHooks)
|
||||||
let params = defaultRpcHttpServerParams()
|
let params = defaultRpcHttpServerParams()
|
||||||
server.addServer(address, params)
|
server.addServer(address, params).isOkOr:
|
||||||
server
|
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 server = RpcHttpServer.new(authHooks)
|
||||||
let params = defaultRpcHttpServerParams()
|
let params = defaultRpcHttpServerParams()
|
||||||
server.addServer(address, params)
|
server.addServer(address, params).isOkOr:
|
||||||
server
|
return err(error)
|
||||||
|
ok(server)
|
||||||
|
|
||||||
|
{.pop.}
|
||||||
|
@ -17,9 +17,6 @@ import
|
|||||||
../nimbus/common/[chain_config, context],
|
../nimbus/common/[chain_config, context],
|
||||||
./test_helpers
|
./test_helpers
|
||||||
|
|
||||||
proc `==`(a, b: ChainId): bool =
|
|
||||||
a.int == b.int
|
|
||||||
|
|
||||||
proc configurationMain*() =
|
proc configurationMain*() =
|
||||||
suite "configuration test suite":
|
suite "configuration test suite":
|
||||||
const
|
const
|
||||||
@ -226,32 +223,64 @@ proc configurationMain*() =
|
|||||||
check conf.getBootnodes().len == 0
|
check conf.getBootnodes().len == 0
|
||||||
|
|
||||||
test "json-rpc enabled when json-engine api enabled and share same port":
|
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"])
|
let conf = makeConfig(@["--engine-api", "--engine-api-port:8545", "--http-port:8545"])
|
||||||
check conf.engineApiEnabled
|
check:
|
||||||
check conf.rpcEnabled
|
conf.engineApiEnabled == true
|
||||||
check conf.wsEnabled == false
|
conf.rpcEnabled == false
|
||||||
check conf.engineApiWsEnabled == 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":
|
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"])
|
let conf = makeConfig(@["--ws", "--engine-api-ws", "--engine-api-port:8546", "--http-port:8546"])
|
||||||
check conf.engineApiWsEnabled
|
check:
|
||||||
check conf.wsEnabled
|
conf.engineApiWsEnabled
|
||||||
check conf.engineApiEnabled == false
|
conf.wsEnabled
|
||||||
check conf.rpcEnabled == false
|
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":
|
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"])
|
let conf = makeConfig(@["--rpc", "--engine-api", "--engine-api-port:8550", "--http-port:8545"])
|
||||||
check conf.engineApiEnabled
|
check:
|
||||||
check conf.rpcEnabled
|
conf.engineApiEnabled
|
||||||
check conf.engineApiWsEnabled == false
|
conf.rpcEnabled
|
||||||
check conf.wsEnabled == false
|
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":
|
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"])
|
let conf = makeConfig(@["--ws", "--engine-api-ws", "--engine-api-port:8551", "--http-port:8546"])
|
||||||
check conf.engineApiWsEnabled
|
check:
|
||||||
check conf.wsEnabled
|
conf.engineApiWsEnabled
|
||||||
check conf.engineApiEnabled == false
|
conf.wsEnabled
|
||||||
check conf.rpcEnabled == false
|
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()
|
let ctx = newEthContext()
|
||||||
test "net-key random":
|
test "net-key random":
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Nimbus
|
# Nimbus
|
||||||
# Copyright (c) 2022-2023 Status Research & Development GmbH
|
# Copyright (c) 2022-2024 Status Research & Development GmbH
|
||||||
# Licensed under either of
|
# Licensed under either of
|
||||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
|
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0)
|
# http://www.apache.org/licenses/LICENSE-2.0)
|
||||||
@ -15,18 +15,20 @@ import
|
|||||||
std/[base64, json, options, os, strutils, times],
|
std/[base64, json, options, os, strutils, times],
|
||||||
../nimbus/config,
|
../nimbus/config,
|
||||||
../nimbus/rpc/jwt_auth,
|
../nimbus/rpc/jwt_auth,
|
||||||
|
../nimbus/rpc {.all.},
|
||||||
./replay/pp,
|
./replay/pp,
|
||||||
chronicles,
|
chronicles,
|
||||||
chronos/apps/http/httpclient as chronoshttpclient,
|
chronos/apps/http/httpclient as chronoshttpclient,
|
||||||
chronos/apps/http/httptable,
|
chronos/apps/http/httptable,
|
||||||
eth/[common, keys, p2p],
|
eth/[common, keys, p2p],
|
||||||
json_rpc/rpcserver,
|
|
||||||
nimcrypto/[hmac, utils],
|
nimcrypto/[hmac, utils],
|
||||||
stew/results,
|
stew/results,
|
||||||
stint,
|
stint,
|
||||||
unittest2,
|
unittest2,
|
||||||
graphql,
|
graphql,
|
||||||
graphql/[httpserver, httpclient]
|
websock/websock,
|
||||||
|
graphql/[httpserver, httpclient],
|
||||||
|
json_rpc/[rpcserver, rpcclient]
|
||||||
|
|
||||||
type
|
type
|
||||||
UnGuardedKey =
|
UnGuardedKey =
|
||||||
@ -115,7 +117,7 @@ proc getHttpAuthReqHeader2(secret: JwtSharedKey; time: uint64): HttpTable =
|
|||||||
let bearer = secret.UnGuardedKey.getSignedToken2($getIatToken(time))
|
let bearer = secret.UnGuardedKey.getSignedToken2($getIatToken(time))
|
||||||
result.add("aUtHoRiZaTiOn", "Bearer " & bearer)
|
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}
|
let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr}
|
||||||
var ctx = GraphqlRef.new()
|
var ctx = GraphqlRef.new()
|
||||||
|
|
||||||
@ -141,6 +143,48 @@ proc createServer(serverAddress: TransportAddress, authHooks: seq[HttpAuthHook]
|
|||||||
proc setupClient(address: TransportAddress): GraphqlHttpClientRef =
|
proc setupClient(address: TransportAddress): GraphqlHttpClientRef =
|
||||||
GraphqlHttpClientRef.new(address, secure = false).get()
|
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
|
# Test Runners
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
@ -255,7 +299,7 @@ proc runJwtAuth(noisy = true; keyFile = jwtKeyFile) =
|
|||||||
authHook = secret.value.httpJwtAuth
|
authHook = secret.value.httpJwtAuth
|
||||||
|
|
||||||
const
|
const
|
||||||
serverAddress = initTAddress("127.0.0.1:8547")
|
serverAddress = initTAddress("127.0.0.1:0")
|
||||||
query = """{ __type(name: "ID") { kind }}"""
|
query = """{ __type(name: "ID") { kind }}"""
|
||||||
|
|
||||||
suite "EngineAuth: Http/rpc authentication mechanics":
|
suite "EngineAuth: Http/rpc authentication mechanics":
|
||||||
@ -283,7 +327,7 @@ proc runJwtAuth(noisy = true; keyFile = jwtKeyFile) =
|
|||||||
setTraceLevel()
|
setTraceLevel()
|
||||||
|
|
||||||
# Run http authorisation request
|
# Run http authorisation request
|
||||||
let client = setupClient(serverAddress)
|
let client = setupClient(server.localAddress)
|
||||||
let res = waitFor client.sendRequest(query, req.toList)
|
let res = waitFor client.sendRequest(query, req.toList)
|
||||||
check res.isOk
|
check res.isOk
|
||||||
if res.isErr:
|
if res.isErr:
|
||||||
@ -309,7 +353,7 @@ proc runJwtAuth(noisy = true; keyFile = jwtKeyFile) =
|
|||||||
setTraceLevel()
|
setTraceLevel()
|
||||||
|
|
||||||
# Run http authorisation request
|
# Run http authorisation request
|
||||||
let client = setupClient(serverAddress)
|
let client = setupClient(server.localAddress)
|
||||||
let res = waitFor client.sendRequest(query, req.toList)
|
let res = waitFor client.sendRequest(query, req.toList)
|
||||||
check res.isOk
|
check res.isOk
|
||||||
if res.isErr:
|
if res.isErr:
|
||||||
@ -325,6 +369,73 @@ proc runJwtAuth(noisy = true; keyFile = jwtKeyFile) =
|
|||||||
|
|
||||||
waitFor server.closeWait()
|
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)
|
# Main function(s)
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
@ -75,7 +75,7 @@ proc runTest(steps: Steps) =
|
|||||||
com.initializeEmptyDb()
|
com.initializeEmptyDb()
|
||||||
|
|
||||||
var
|
var
|
||||||
rpcServer = newRpcSocketServer(["127.0.0.1:" & $conf.rpcPort])
|
rpcServer = newRpcSocketServer(["127.0.0.1:" & $conf.httpPort])
|
||||||
client = newRpcSocketClient()
|
client = newRpcSocketClient()
|
||||||
txPool = TxPoolRef.new(com, conf.engineSigner)
|
txPool = TxPoolRef.new(com, conf.engineSigner)
|
||||||
sealingEngine = SealingEngineRef.new(
|
sealingEngine = SealingEngineRef.new(
|
||||||
@ -89,7 +89,7 @@ proc runTest(steps: Steps) =
|
|||||||
|
|
||||||
sealingEngine.start()
|
sealingEngine.start()
|
||||||
rpcServer.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":
|
suite "Engine API tests":
|
||||||
for i, step in steps:
|
for i, step in steps:
|
||||||
|
@ -155,7 +155,9 @@ proc rpcExperimentalJsonMain*() =
|
|||||||
RPC_PORT = 0 # let the OS choose a port
|
RPC_PORT = 0 # let the OS choose a port
|
||||||
|
|
||||||
var
|
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()
|
client = newRpcHttpClient()
|
||||||
|
|
||||||
rpcServer.start()
|
rpcServer.start()
|
||||||
|
2
vendor/nim-confutils
vendored
2
vendor/nim-confutils
vendored
@ -1 +1 @@
|
|||||||
Subproject commit fa6e9b09e2dfe09be5b734e7a7a394a36d953831
|
Subproject commit 7568f1b7c3142d8e87c1f3dd42924238926affbe
|
2
vendor/nim-graphql
vendored
2
vendor/nim-graphql
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 4cc36cea8184233761a12c60aa805ed78295d787
|
Subproject commit c47f0c5d2f965e30e14ca65540797aac0e38f453
|
2
vendor/nim-json-rpc
vendored
2
vendor/nim-json-rpc
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 9a34452e235806f97387dd0c5711b82a24d47ba0
|
Subproject commit 85d6a67fbc4d490da90e58f3fe97253967401ca1
|
Loading…
x
Reference in New Issue
Block a user