Skip to content

Commit 08b0f90

Browse files
committed
Unified Nimbus node
This change brings Nimbus full circle to where it started all these years ago and allows running Ethereum in a single node / process, both as a wallet/web3 backend and as a validator. Among interesting properies are: * Easy to set up and run - one binary, one process, no JWT and messy setup, no cross-client communication issues, timing issues etc * Excellent performance, of course * Shared database = small database * Can run without the legacy devp2p as long as you're reasonably synced and not using it for block production - up to 5 months of history are instead sourced from the consensus network - block production requires devp2p since that's where the public mempool comes from Running ethereum and syncing mainnet is now as easy as: ```sh ./nimbus trustedNodeSync \ --trusted-node-url=http://testing.mainnet.beacon-api.nimbus.team/ \ --backfill=false ./nimbus ``` The consensus chain will start from a checkpoint while the execution chain will still be synced via P2P. You need about 500GB of space in total, but if you're buying a drive today, get 2 or 4 TB anyway. Testnets like `hoodi` can reasonably be synced from P2P all the way (takes a bit more than a day at the time of writing), without the checkpoint sync: ```nim ./nimbus --network:hoodi ``` That's it! The node can now be used both for validators and as a web3 provider. `--rpc` gives you the web3 backend which allows connecting wallets while `--rest` gives the beacon api that validator clients use. Of course, you can run your validators [in the node](https://nimbus.guide/run-a-validator.html#2-import-your-validator-keys) as well. Here's a true maxi configuration that turns on (almost) everything: ```nim ./nimbus --rpc --rest --metrics ``` The execution chain can also be imported from era files, downloading the history from https://mainnet.era.nimbus.team/ and https://mainnet.era1.nimbus.team/ and placing them in `era` and `era1` in the data directory as the [manual](https://nimbus.guide/execution-client.html#syncing-using-era-files) suggests, then running an `import` - it takes a few days: ```sh ./nimbus import ``` If you were already running nimbus, you can reuse your existing data directory - use `--data-dir:/some/path` as usual with all the commands to specify where you want your data stored - if you had both eth1 and eth2 directories, just merge their contents. To get up and running more quickly, snapshots of the mainnet execution database are maintained here: https://eth1-db.nimbus.team/ Together with checkpoint sync, you'll have a fully synced node in no time! In future versions, this will be replaced by snap sync or an equivalent state sync mechanism. To build the protoype: ```sh make update make -j8 nimbus ``` In a single process binary, the beacon and execution chain are each running in their own thread, sharing data directory and common services, similar to running the two pieces separately with the same data dir. One way to think about it is that the execution client and beacon nodes are stand-alone libraries that are being used together - this is not far from the truth and in fact, you can use either (or both!) as a library. The binary supports the union of all functionality that `nimbus_execution_client` and `nimbus_beacon_node` offers, including all the subcommands like [checkpoint sync](https://nimbus.guide/trusted-node-sync.html) and [execution history import](https://nimbus.guide/execution-client.html#import-era-files), simply using the `nimbus` command instead. Prototype notes: * cross-thread communication is done using a local instance of web3 / JSON - this is nuts of course: it should simply pass objects around and convert to directly to RLP on demand without going via JSON * the thread pool is not shared but should be - nim-taskpools needs to learn to accept tasks from threads other than the one that created it * discovery is not shared - instead, each of eth1/2 runs its own discovery protocols and consequently the node has two "identities" * there are many efficiency opportunities to exploit, in particular on the memory usage front * next up is light client and portal to be added as options, to support a wide range of feature vs performance tradeoffs
1 parent f3253f4 commit 08b0f90

19 files changed

+683
-74
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,5 @@ tests/fixtures/eest_static
5151
tests/fixtures/eest_stable
5252
tests/fixtures/eest_develop
5353
tests/fixtures/eest_devnet
54+
55+
execution_chain/nimbus

Makefile

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,8 +218,9 @@ nimbus_execution_client: | build deps rocksdb
218218
check_revision: nimbus_execution_client
219219
scripts/check_revision.sh
220220

221-
nimbus: nimbus_execution_client
222-
echo "The nimbus target is deprecated and will soon change meaning, use 'nimbus_execution_client' instead"
221+
nimbus: | build deps rocksdb
222+
echo -e $(BUILD_MSG) "build/nimbus" && \
223+
$(ENV_SCRIPT) nim c $(NIM_PARAMS) -d:chronicles_log_level=TRACE -o:build/nimbus "execution_chain/nimbus.nim"
223224

224225
# symlink
225226
nimbus.nims:

execution_chain/beacon/api_handler/api_newpayload.nim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ proc newPayload*(ben: BeaconEngineRef,
178178
# If we already have the block locally, ignore the entire execution and just
179179
# return a fake success.
180180
if chain.haveBlockAndState(blockHash):
181-
notice "Ignoring already known beacon payload",
181+
debug "Ignoring already known beacon payload",
182182
number = header.number, hash = blockHash.short
183183
return validStatus(blockHash)
184184

execution_chain/conf.nim

Lines changed: 55 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import
3737
./common/chain_config,
3838
./db/opts
3939

40-
export net, defs, jsdefs, jsnet, nimbus_binary_common, options
40+
export net, defs, jsdefs, jsnet, nat_toml, nimbus_binary_common, options
4141

4242
const NimbusCopyright* =
4343
"Copyright (c) 2018-" & compileYear & " Status Research & Development GmbH"
@@ -51,7 +51,7 @@ func getLogLevels(): string =
5151
join(logLevels, ", ")
5252

5353
const
54-
defaultPort = 30303
54+
defaultExecutionPort* = 30303
5555
defaultMetricsServerPort = 9093
5656
defaultHttpPort = 8545
5757
# https://github.com/ethereum/execution-apis/blob/v1.0.0-beta.4/src/engine/authentication.md#jwt-specifications
@@ -66,7 +66,7 @@ let
6666

6767
type
6868
NimbusCmd* {.pure.} = enum
69-
noCommand
69+
executionClient
7070
`import`
7171
`import-rlp`
7272

@@ -81,38 +81,39 @@ type
8181
V5
8282

8383
ExecutionClientConf* = object
84-
## Main Nimbus configuration object
85-
configFile {.
84+
## Main configuration for the execution client - when updating, coordinate
85+
## options shared with other executables (logging, metrics etc)
86+
configFile* {.
8687
separator: "ETHEREUM OPTIONS:"
8788
desc: "Loads the configuration from a TOML file"
8889
name: "config-file" .}: Option[InputFile]
8990

9091
dataDirFlag* {.
9192
desc: "The directory where nimbus will store all blockchain data"
9293
abbr: "d"
93-
name: "data-dir" }: Option[OutDir]
94+
name: "data-dir" .}: Option[OutDir]
9495

9596
era1DirFlag* {.
9697
desc: "Directory where era1 (pre-merge) archive can be found"
9798
defaultValueDesc: "<data-dir>/era1"
98-
name: "era1-dir" }: Option[OutDir]
99+
name: "era1-dir" .}: Option[OutDir]
99100

100101
eraDirFlag* {.
101102
desc: "Directory where era (post-merge) archive can be found"
102103
defaultValueDesc: "<data-dir>/era"
103-
name: "era-dir" }: Option[OutDir]
104+
name: "era-dir" .}: Option[OutDir]
104105

105106
keyStoreDirFlag* {.
106107
desc: "Load one or more keystore files from this directory"
107108
defaultValueDesc: "inside datadir"
108109
abbr: "k"
109-
name: "key-store" }: Option[OutDir]
110+
name: "key-store" .}: Option[OutDir]
110111

111112
importKey* {.
112113
desc: "Import unencrypted 32 bytes hex private key from a file"
113114
defaultValue: ""
114115
abbr: "e"
115-
name: "import-key" }: InputFile
116+
name: "import-key" .}: InputFile
116117

117118
trustedSetupFile* {.
118119
desc: "Load EIP-4844 trusted setup file"
@@ -146,32 +147,31 @@ type
146147
defaultValue: @[] # the default value is set in makeConfig
147148
defaultValueDesc: "mainnet(1)"
148149
abbr: "i"
149-
name: "network" }: seq[string]
150+
name: "network" .}: seq[string]
150151

151152
customNetwork {.
152153
ignore
153154
desc: "Use custom genesis block for private Ethereum Network (as /path/to/genesis.json)"
154155
defaultValueDesc: ""
155156
abbr: "c"
156-
name: "custom-network" }: Option[NetworkParams]
157+
name: "custom-network" .}: Option[NetworkParams]
157158

158159
networkId* {.
159160
ignore # this field is not processed by confutils
160161
defaultValue: MainNet # the defaultValue value is set by `makeConfig`
161162
defaultValueDesc: "MainNet"
162-
name: "network-id"}: NetworkId
163+
name: "network-id" .}: NetworkId
163164

164165
networkParams* {.
165166
ignore # this field is not processed by confutils
166167
defaultValue: NetworkParams() # the defaultValue value is set by `makeConfig`
167-
name: "network-params"}: NetworkParams
168+
name: "network-params" .}: NetworkParams
168169

169170
logLevel* {.
170171
separator: "\pLOGGING AND DEBUGGING OPTIONS:"
171172
desc: "Sets the log level for process and topics (" & logLevelDesc & ")"
172173
defaultValue: "INFO"
173-
defaultValueDesc: "Info topic level logging"
174-
name: "log-level" }: string
174+
name: "log-level" .}: string
175175

176176
logStdout* {.
177177
hidden
@@ -183,79 +183,78 @@ type
183183
metricsEnabled* {.
184184
desc: "Enable the built-in metrics HTTP server"
185185
defaultValue: false
186-
name: "metrics" }: bool
186+
name: "metrics" .}: bool
187187

188188
metricsPort* {.
189189
desc: "Listening port of the built-in metrics HTTP server"
190190
defaultValue: defaultMetricsServerPort
191191
defaultValueDesc: $defaultMetricsServerPort
192-
name: "metrics-port" }: Port
192+
name: "metrics-port" .}: Port
193193

194194
metricsAddress* {.
195195
desc: "Listening IP address of the built-in metrics HTTP server"
196196
defaultValue: defaultAdminListenAddress
197197
defaultValueDesc: $defaultAdminListenAddressDesc
198-
name: "metrics-address" }: IpAddress
198+
name: "metrics-address" .}: IpAddress
199199

200200
bootstrapNodes {.
201201
separator: "\pNETWORKING OPTIONS:"
202202
desc: "Specifies one or more bootstrap nodes(ENR or enode URL) to use when connecting to the network"
203203
defaultValue: @[]
204204
defaultValueDesc: ""
205205
abbr: "b"
206-
name: "bootstrap-node" }: seq[string]
206+
name: "bootstrap-node" .}: seq[string]
207207

208208
bootstrapFile {.
209209
desc: "Specifies a file of bootstrap Ethereum network addresses(ENR or enode URL). " &
210210
"Both line delimited or YAML format are supported"
211211
defaultValue: ""
212-
name: "bootstrap-file" }: InputFile
212+
name: "bootstrap-file" .}: InputFile
213213

214214
staticPeers {.
215215
desc: "Connect to one or more trusted peers(ENR or enode URL)"
216216
defaultValue: @[]
217217
defaultValueDesc: ""
218-
name: "static-peers" }: seq[string]
218+
name: "static-peers" .}: seq[string]
219219

220220
staticPeersFile {.
221221
desc: "Specifies a file of trusted peers addresses(ENR or enode URL). " &
222222
"Both line delimited or YAML format are supported"
223223
defaultValue: ""
224-
name: "static-peers-file" }: InputFile
224+
name: "static-peers-file" .}: InputFile
225225

226226
reconnectMaxRetry* {.
227227
desc: "Specifies max number of retries if static peers disconnected/not connected. " &
228228
"0 = infinite."
229229
defaultValue: 0
230-
name: "reconnect-max-retry" }: int
230+
name: "reconnect-max-retry" .}: int
231231

232232
reconnectInterval* {.
233233
desc: "Interval in seconds before next attempt to reconnect to static peers. Min 5 seconds."
234234
defaultValue: 15
235-
name: "reconnect-interval" }: int
235+
name: "reconnect-interval" .}: int
236236

237237
listenAddress* {.
238238
desc: "Listening IP address for Ethereum P2P and Discovery traffic"
239239
defaultValue: defaultListenAddress
240240
defaultValueDesc: $defaultListenAddressDesc
241-
name: "listen-address" }: IpAddress
241+
name: "listen-address" .}: IpAddress
242242

243243
tcpPort* {.
244244
desc: "Ethereum P2P network listening TCP port"
245-
defaultValue: defaultPort
246-
defaultValueDesc: $defaultPort
247-
name: "tcp-port" }: Port
245+
defaultValue: defaultExecutionPort
246+
defaultValueDesc: $defaultExecutionPort
247+
name: "tcp-port" .}: Port
248248

249-
udpPort* {.
249+
udpPortFlag* {.
250250
desc: "Ethereum P2P network listening UDP port"
251-
defaultValue: 0 # set udpPort defaultValue in `makeConfig`
252251
defaultValueDesc: "default to --tcp-port"
253-
name: "udp-port" }: Port
252+
name: "udp-port" .}: Option[Port]
254253

255254
maxPeers* {.
256255
desc: "Maximum number of peers to connect to"
257256
defaultValue: 25
258-
name: "max-peers" }: int
257+
name: "max-peers" .}: int
259258

260259
nat* {.
261260
desc: "Specify method to use for determining public address. " &
@@ -375,68 +374,68 @@ type
375374
" by stateless clients such as generation and storage of block witnesses" &
376375
" and serving these witnesses to peers over the p2p network."
377376
defaultValue: false
378-
name: "stateless-provider" }: bool
377+
name: "stateless-provider" .}: bool
379378

380379
statelessWitnessValidation* {.
381380
hidden
382381
desc: "Enable full validation of execution witnesses."
383382
defaultValue: false
384-
name: "stateless-witness-validation" }: bool
383+
name: "stateless-witness-validation" .}: bool
385384

386385
case cmd* {.
387386
command
388-
defaultValue: NimbusCmd.noCommand }: NimbusCmd
387+
defaultValue: NimbusCmd.executionClient .}: NimbusCmd
389388

390-
of noCommand:
389+
of NimbusCmd.executionClient:
391390
httpPort* {.
392391
separator: "\pLOCAL SERVICES OPTIONS:"
393392
desc: "Listening port of the HTTP server(rpc, ws)"
394393
defaultValue: defaultHttpPort
395394
defaultValueDesc: $defaultHttpPort
396-
name: "http-port" }: Port
395+
name: "http-port" .}: Port
397396

398397
httpAddress* {.
399398
desc: "Listening IP address of the HTTP server(rpc, ws)"
400399
defaultValue: defaultAdminListenAddress
401400
defaultValueDesc: $defaultAdminListenAddressDesc
402-
name: "http-address" }: IpAddress
401+
name: "http-address" .}: IpAddress
403402

404403
rpcEnabled* {.
405404
desc: "Enable the JSON-RPC server"
406405
defaultValue: false
407-
name: "rpc" }: bool
406+
name: "rpc" .}: bool
408407

409408
rpcApi {.
410409
desc: "Enable specific set of RPC API (available: eth, debug, admin)"
411410
defaultValue: @[]
412411
defaultValueDesc: $RpcFlag.Eth
413-
name: "rpc-api" }: seq[string]
412+
name: "rpc-api" .}: seq[string]
414413

415414
wsEnabled* {.
416415
desc: "Enable the Websocket JSON-RPC server"
417416
defaultValue: false
418-
name: "ws" }: bool
417+
name: "ws" .}: bool
419418

420419
wsApi {.
421420
desc: "Enable specific set of Websocket RPC API (available: eth, debug, admin)"
422421
defaultValue: @[]
423422
defaultValueDesc: $RpcFlag.Eth
424-
name: "ws-api" }: seq[string]
423+
name: "ws-api" .}: seq[string]
425424

426425
historyExpiry* {.
427426
desc: "Enable the data from Portal Network"
428427
defaultValue: false
429-
name: "history-expiry" }: bool
428+
name: "history-expiry" .}: bool
430429

431430
historyExpiryLimit* {.
432431
hidden
433432
desc: "Limit the number of blocks to be kept in history"
434-
name: "debug-history-expiry-limit" }: Option[BlockNumber]
433+
name: "debug-history-expiry-limit" .}: Option[BlockNumber]
435434

436435
portalUrl* {.
437436
desc: "URL of the Portal JSON-RPC API"
438437
defaultValue: ""
439-
name: "portal-url" }: string
438+
name: "portal-url" .}: string
440439

441440
engineApiEnabled* {.
442441
desc: "Enable the Engine API"
@@ -474,6 +473,12 @@ type
474473
defaultValueDesc: "\"jwt.hex\" in the data directory (see --data-dir)"
475474
name: "jwt-secret" .}: Option[InputFile]
476475

476+
jwtSecretValue* {.
477+
hidden
478+
desc: "Hex string with jwt secret"
479+
defaultValueDesc: "\"jwt.hex\" in the data directory (see --data-dir)"
480+
name: "debug-jwt-secret-value" .}: Option[string]
481+
477482
beaconSyncTarget* {.
478483
hidden
479484
desc: "Manually set the initial sync target specified by its 32 byte" &
@@ -545,7 +550,7 @@ type
545550
blocksFile* {.
546551
argument
547552
desc: "One or more RLP encoded block(s) files"
548-
name: "blocks-file" }: seq[InputFile]
553+
name: "blocks-file" .}: seq[InputFile]
549554

550555
func parseHexOrDec256(p: string): UInt256 {.raises: [ValueError].} =
551556
if startsWith(p, "0x"):
@@ -763,6 +768,9 @@ proc era1Dir*(config: ExecutionClientConf): string =
763768
proc eraDir*(config: ExecutionClientConf): string =
764769
string config.eraDirFlag.get(OutDir config.dataDir / "era")
765770

771+
func udpPort*(config: ExecutionClientConf): Port =
772+
config.udpPortFlag.get(config.tcpPort)
773+
766774
func dbOptions*(config: ExecutionClientConf, noKeyCache = false): DbOptions =
767775
DbOptions.init(
768776
maxOpenFiles = config.rocksdbMaxOpenFiles,
@@ -796,11 +804,6 @@ proc makeConfig*(cmdLine = commandLineParams(), ignoreUnknown = false): Executio
796804

797805
processNetworkParamsAndNetworkId(result)
798806

799-
if result.cmd == noCommand:
800-
if result.udpPort == Port(0):
801-
# if udpPort not set in cli, then
802-
result.udpPort = result.tcpPort
803-
804807
when isMainModule:
805808
# for testing purpose
806809
discard makeConfig()

0 commit comments

Comments
 (0)