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