-
Notifications
You must be signed in to change notification settings - Fork 159
Expand file tree
/
Copy pathnimbus_execution_client.nim
More file actions
406 lines (341 loc) · 12.9 KB
/
Copy pathnimbus_execution_client.nim
File metadata and controls
406 lines (341 loc) · 12.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
# Nimbus
# Copyright (c) 2018-2025 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.
{.push raises: [].}
import
../execution_chain/compile_info
import
chronicles,
eth/net/nat,
metrics,
stew/byteutils,
kzg4844/kzg,
./[conf, constants, nimbus_desc, nimbus_import, rpc, version_info],
./core/block_import,
./core/chain/forked_chain/chain_serialize,
./db/core_db/persistent,
./db/storage_types,
./sync/wire_protocol,
./common/chain_config_hash,
./portal/portal,
./networking/[bootnodes, netkeys],
beacon_chain/[nimbus_binary_common, process_state],
beacon_chain/validators/keystore_management
const
DontQuit = low(int)
## To be used with `onException()` or `onCancelledException()`
# ------------------------------------------------------------------------------
# Private helpers
# ------------------------------------------------------------------------------
template onException(
quitCode: static[int];
info: static[string];
code: untyped) =
try:
code
except CatchableError as e:
when quitCode == DontQuit:
error info, error=($e.name), msg=e.msg
else:
fatal info, error=($e.name), msg=e.msg
quit(quitCode)
# ------------------------------------------------------------------------------
# Private functions
# ------------------------------------------------------------------------------
proc basicServices(nimbus: NimbusNode, config: ExecutionClientConf, com: CommonRef) =
# Setup the chain
let fc = ForkedChainRef.init(com,
eagerStateRoot = config.eagerStateRootCheck,
persistBatchSize = config.persistBatchSize,
dynamicBatchSize = config.dynamicBatchSize,
enableQueue = true)
if config.deserializeFcState:
fc.deserialize().isOkOr:
warn "Loading block DAG from database", msg=error
else:
warn "Skipped loading of block DAG from database", deserializeFcState = config.deserializeFcState
nimbus.fc = fc
# Setup history expiry and portal
QuitFailure.onException("Cannot initialise RPC client history"):
nimbus.fc.portal = HistoryExpiryRef.init(config, com)
# txPool must be informed of active head
# so it can know the latest account state
# e.g. sender nonce, etc
nimbus.txPool = TxPoolRef.new(nimbus.fc)
nimbus.beaconEngine = BeaconEngineRef.new(nimbus.txPool)
proc manageAccounts(nimbus: NimbusNode, config: ExecutionClientConf) =
if config.keyStoreDir.len > 0:
nimbus.accountsManager[].loadKeystores(config.keyStoreDir).isOkOr:
fatal "Load keystore error", msg = error
quit(QuitFailure)
if string(config.importKey).len > 0:
nimbus.accountsManager[].importPrivateKey(string config.importKey).isOkOr:
fatal "Import private key error", msg = error
quit(QuitFailure)
proc setupP2P(nimbus: NimbusNode, config: ExecutionClientConf, com: CommonRef) =
## Creating P2P Server
let
keypair = nimbus.rng[].getNetKeys(config.netKey).valueOr:
fatal "Get network keys error", msg = error
quit(QuitFailure)
natId = NimbusName & " " & NimbusVersion
(extIp, extPorts) = setupAddress(
config.nat,
config.listenAddress,
@[
(port: config.tcpPort, protocol: PortProtocol.TCP),
(port: config.udpPort, protocol: PortProtocol.UDP),
],
natId,
)
extTcpPort = extPorts[0].toPort()
extUdpPort = extPorts[1].toPort()
bootstrapNodes = config.getBootstrapNodes()
fc = nimbus.fc
func forkIdProc(): ForkId =
let header = fc.latestHeader()
com.forkId(header.number, header.timestamp)
func compatibleForkIdProc(id: ForkId): bool =
let header = fc.latestHeader()
com.compatibleForkId(id, header.number, header.timestamp)
let forkIdProcs = ForkIdProcs(
forkId: forkIdProc,
compatibleForkId: compatibleForkIdProc,
)
nimbus.ethNode = newEthereumNode(
keypair, extIp, extTcpPort, extUdpPort, config.networkId, config.agentString,
minPeers = config.maxPeers,
bootstrapNodes = bootstrapNodes,
bindUdpPort = config.udpPort, bindTcpPort = config.tcpPort,
bindIp = config.listenAddress,
rng = nimbus.rng,
forkIdProcs = forkIdProcs)
# Add peer service protocol capabilities. If `snap` sync is used, then there
# can be only one active `eth` protocol version assuming that the number of
# messages differ with the `eth` versions. Due to tight packaging of message
# IDs, different `eth` protocol lengths lead to varying `snap` message IDs
# depending on the `eth` version. To handle this is currently unsupported.
#
let doSnapSync = config.snapSyncEnabled or config.snapServerEnabled
nimbus.ethWire =
nimbus.ethNode.addEthHandlerCapability(nimbus.txPool, latestOnly=doSnapSync)
if doSnapSync:
nimbus.snapWire = nimbus.ethNode.addSnapHandlerCapability()
# Connect directly to the static nodes
let staticPeers = config.getStaticPeers()
if staticPeers.len > 0:
nimbus.peerManager = PeerManagerRef.new(
nimbus.ethNode.peerPool,
config.reconnectInterval,
config.reconnectMaxRetry,
staticPeers
)
nimbus.peerManager.start()
# Start Eth node
if config.maxPeers > 0:
let discovery = config.getDiscoveryFlags()
nimbus.ethNode.connectToNetwork(
enableDiscV4 = DiscoveryType.V4 in discovery,
enableDiscV5 = DiscoveryType.V5 in discovery,
)
# Initalise beacon sync descriptor.
var syncerShouldRun = (config.maxPeers > 0 or staticPeers.len > 0) and
config.engineApiServerEnabled()
# The beacon sync descriptor might have been pre-allocated with additional
# features. So do not override.
if nimbus.beaconSyncRef.isNil:
nimbus.beaconSyncRef = BeaconSyncRef.init()
else:
syncerShouldRun = true
# Configure beacon syncer.
nimbus.beaconSyncRef.config(
nimbus.ethNode, nimbus.fc, config.maxPeers, latestOnly=doSnapSync)
# Optional for pre-setting the sync target (e.g. for debugging)
if config.beaconSyncTarget.isSome():
syncerShouldRun = true
let
hex = config.beaconSyncTarget.unsafeGet
isFinal = config.beaconSyncTargetIsFinal
if not nimbus.beaconSyncRef.configTarget(hex, isFinal):
fatal "Error parsing hash32 argument for --debug-beacon-sync-target",
hash32=hex
quit QuitFailure
# Configure snap sync if enabled. When done it will resume beacon sync.
if config.snapSyncEnabled:
if nimbus.snapSyncRef.isNil:
nimbus.snapSyncRef = SnapSyncRef.init()
else:
syncerShouldRun = true
# Configure snap syncer.
nimbus.snapSyncRef.config(nimbus.ethNode, config.maxPeers)
# Deactivating syncer if there is definitely no need to run it. This
# avoids polling (i.e. waiting for instructions) and some logging.
if not syncerShouldRun:
nimbus.beaconSyncRef = BeaconSyncRef(nil)
nimbus.snapSyncRef = SnapSyncRef(nil)
proc init*(nimbus: NimbusNode, config: ExecutionClientConf, com: CommonRef, channel: Opt[RpcChannelPtrs]) =
nimbus.accountsManager = new AccountsManager
nimbus.rng = newRng()
basicServices(nimbus, config, com)
manageAccounts(nimbus, config)
setupP2P(nimbus, config, com)
setupRpc(nimbus, config, com, channel)
# Not starting any syncer if there is definitely no way to run it. This
# avoids polling (i.e. waiting for instructions) and some logging.
block startSyncer:
if not nimbus.beaconSyncRef.isNil:
if nimbus.snapSyncRef.isNil:
# Run full sync by starting beacon sync now.
if nimbus.beaconSyncRef.start():
break startSyncer
else:
# Start snap sync. When done it will resume beacon sync.
if nimbus.snapSyncRef.start(nimbus.beaconSyncRef):
break startSyncer
nimbus.beaconSyncRef = BeaconSyncRef(nil)
nimbus.snapSyncRef = SnapSyncRef(nil)
proc init*(T: type NimbusNode, config: ExecutionClientConf, com: CommonRef, channel: Opt[RpcChannelPtrs]): T =
let nimbus = T()
nimbus.init(config, com, channel)
nimbus
proc preventLoadingDataDirForTheWrongNetwork(db: CoreDbRef; config: ExecutionClientConf) =
proc writeDataDirId(kvt: CoreDbTxRef, calculatedId: Hash32) =
info "Writing data dir ID", ID=calculatedId
kvt.put(dataDirIdKey().toOpenArray, calculatedId.data).isOkOr:
fatal "Cannot write data dir ID", ID=calculatedId
quit(QuitFailure)
db.persist(kvt)
let
kvt = db.baseTxFrame()
calculatedId = calcHash(config.networkId, config.networkParams)
dataDirIdBytes = kvt.get(dataDirIdKey().toOpenArray).valueOr:
# an empty database
writeDataDirId(kvt, calculatedId)
return
if config.rewriteDatadirId:
writeDataDirId(kvt, calculatedId)
return
if calculatedId.data != dataDirIdBytes:
fatal "Data dir already initialized with other network configuration",
get=dataDirIdBytes.toHex,
expected=calculatedId
quit(QuitFailure)
proc setupCommonRef*(config: ExecutionClientConf): CommonRef =
let coreDB = AristoDbRocks.newCoreDbRef(
config.dataDir,
config.dbOptions(noKeyCache = config.cmd == NimbusCmd.`import`))
preventLoadingDataDirForTheWrongNetwork(coreDB, config)
let com = CommonRef.new(
db = coreDB,
networkId = config.networkId,
params = config.networkParams,
statelessProviderEnabled = config.statelessProviderEnabled,
statelessWitnessValidation = config.statelessWitnessValidation)
if config.extraData.len > 32:
warn "ExtraData exceeds 32 bytes limit, truncate",
extraData=config.extraData,
len=config.extraData.len
if config.gasLimit > GAS_LIMIT_MAXIMUM or
config.gasLimit < GAS_LIMIT_MINIMUM:
warn "GasLimit not in expected range, truncate",
min=GAS_LIMIT_MINIMUM,
max=GAS_LIMIT_MAXIMUM,
get=config.gasLimit
com.extraData = config.extraData
com.gasLimit = config.gasLimit
com
# ------------------------------------------------------------------------------
# Public functions, `main()` API
# ------------------------------------------------------------------------------
type StopFuture = Future[void].Raising([CancelledError])
proc runExeClient*(
config: ExecutionClientConf,
com: CommonRef,
stopper: StopFuture,
nimbus = NimbusNode(nil),
channel = Opt.none(RpcChannelPtrs),
) =
## Launches and runs the execution client for pre-configured `nimbus` and
## `conf` argument descriptors.
##
var nimbus = nimbus
if nimbus.isNil:
nimbus = NimbusNode.init(config, com, channel)
else:
nimbus.init(config, com, channel)
defer:
let
fc = nimbus.fc
txFrame = fc.baseTxFrame
fc.serialize(txFrame).isOkOr:
error "FC.serialize error: ", msg = error
txFrame.checkpoint(fc.base.blk.header.number, skipSnapshot = true)
com.db.persist(txFrame)
# Be graceful about ctrl-c during init
if ProcessState.stopping.isNone:
ProcessState.notifyRunning()
while true:
if (let reason = ProcessState.stopping(); reason.isSome()):
notice "Shutting down", reason = reason[]
break
if stopper != nil and stopper.finished():
break
chronos.poll()
# Stop loop
QuitFailure.onException("Exception while shutting down"):
waitFor nimbus.closeWait()
# noinline to keep it in stack traces
proc main*(config = makeConfig(), nimbus = NimbusNode(nil)) {.noinline.} =
# Set up logging before everything else
setupLogging(config.logLevel, config.logStdout)
setupFileLimits()
info "Launching execution client", version = FullVersionStr, config
ProcessState.setupStopHandlers()
# TODO provide option for fixing / ignoring permission errors
if not (checkAndCreateDataDir(config.dataDir)):
# We are unable to access/create data folder or data folder's
# permissions are insecure.
quit QuitFailure
# Trusted setup is needed for Cancun+ blocks and is shared between threads,
# so it needs to be initalized from the main thread before anything else tries
# to use it
if config.trustedSetupFile.isSome:
kzg.loadTrustedSetup(config.trustedSetupFile.get(), 0).isOkOr:
fatal "Cannot load KZG trusted setup from file", msg = error
quit(QuitFailure)
# Metrics are useful not just when running node but also during import
let metricsServer =
try:
waitFor(initMetricsServer(config)).valueOr:
quit(QuitFailure)
except CancelledError:
raiseAssert "Never cancelled"
defer:
if metricsServer.isSome():
waitFor metricsServer.stopMetricsServer()
when compileOption("threads"):
let
taskpool = setupTaskpool(config.numThreads)
com = setupCommonRef(config)
com.taskpool = taskpool
else:
let com = setupCommonRef(config)
defer:
com.db.finish()
case config.cmd
of NimbusCmd.`import`:
importBlocks(config, com)
of NimbusCmd.`import - rlp`:
try:
waitFor importRlpBlocks(config, com)
except CancelledError:
raiseAssert "Nothing cancels the future"
else:
runExeClient(config, com, nil, nimbus=nimbus)
when isMainModule:
main()