895 lines
27 KiB
Nim
895 lines
27 KiB
Nim
# Copyright (c) 2018-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.
|
|
|
|
{.push raises: [].}
|
|
|
|
import
|
|
std/[options, strutils, times, os, uri, net],
|
|
pkg/[chronicles, confutils, confutils/defs, confutils/std/net],
|
|
eth/[common, net/utils, net/nat, p2p/bootnodes, p2p/enode, p2p/discoveryv5/enr],
|
|
"."/[constants, compile_info, version],
|
|
common/chain_config,
|
|
db/opts
|
|
|
|
export net, defs
|
|
|
|
const
|
|
# TODO: fix this agent-string format to match other
|
|
# eth clients format
|
|
NimbusIdent* =
|
|
"$# v$# [$#: $#, $#, $#]" %
|
|
[NimbusName, NimbusVersion, hostOS, hostCPU, VmName, GitRevision]
|
|
|
|
let
|
|
# e.g.: Copyright (c) 2018-2021 Status Research & Development GmbH
|
|
NimbusCopyright* =
|
|
"Copyright (c) 2018-" & $(now().utc.year) & " Status Research & Development GmbH"
|
|
|
|
# e.g.:
|
|
# Nimbus v0.1.0 [windows: amd64, rocksdb, evmc, dda8914f]
|
|
# Copyright (c) 2018-2021 Status Research & Development GmbH
|
|
NimbusBuild* = "$#\p$#" % [NimbusIdent, NimbusCopyright]
|
|
|
|
NimbusHeader* = "$#\p\p$#" % [NimbusBuild, version.NimVersion]
|
|
|
|
func defaultDataDir*(): string =
|
|
when defined(windows):
|
|
getHomeDir() / "AppData" / "Roaming" / "Nimbus"
|
|
elif defined(macosx):
|
|
getHomeDir() / "Library" / "Application Support" / "Nimbus"
|
|
else:
|
|
getHomeDir() / ".cache" / "nimbus"
|
|
|
|
func defaultKeystoreDir*(): string =
|
|
defaultDataDir() / "keystore"
|
|
|
|
func getLogLevels(): string =
|
|
var logLevels: seq[string]
|
|
for level in LogLevel:
|
|
if level < enabledLogLevel:
|
|
continue
|
|
logLevels.add($level)
|
|
join(logLevels, ", ")
|
|
|
|
const
|
|
defaultDataDirDesc = defaultDataDir()
|
|
defaultPort = 30303
|
|
defaultMetricsServerPort = 9093
|
|
defaultHttpPort = 8545
|
|
defaultEngineApiPort = 8550
|
|
defaultListenAddress = (static parseIpAddress("0.0.0.0"))
|
|
defaultAdminListenAddress = (static parseIpAddress("127.0.0.1"))
|
|
defaultListenAddressDesc = $defaultListenAddress & ", meaning all network interfaces"
|
|
defaultAdminListenAddressDesc =
|
|
$defaultAdminListenAddress & ", meaning local host only"
|
|
logLevelDesc = getLogLevels()
|
|
|
|
# `when` around an option doesn't work with confutils; it fails to compile.
|
|
# Workaround that by setting the `ignore` pragma on EVMC-specific options.
|
|
when defined(evmc_enabled):
|
|
{.pragma: includeIfEvmc.}
|
|
else:
|
|
{.pragma: includeIfEvmc, ignore.}
|
|
|
|
const sharedLibText =
|
|
if defined(linux):
|
|
" (*.so, *.so.N)"
|
|
elif defined(windows):
|
|
" (*.dll)"
|
|
elif defined(macosx):
|
|
" (*.dylib)"
|
|
else:
|
|
""
|
|
|
|
type
|
|
ChainDbMode* {.pure.} = enum
|
|
Aristo
|
|
AriPrune
|
|
|
|
NimbusCmd* {.pure.} = enum
|
|
noCommand
|
|
`import`
|
|
|
|
ProtocolFlag* {.pure.} = enum
|
|
## Protocol flags
|
|
Eth ## enable eth subprotocol
|
|
#Snap ## enable snap sub-protocol
|
|
|
|
RpcFlag* {.pure.} = enum
|
|
## RPC flags
|
|
Eth ## enable eth_ set of RPC API
|
|
Debug ## enable debug_ set of RPC API
|
|
Exp ## enable exp_ set of RPC API
|
|
|
|
DiscoveryType* {.pure.} = enum
|
|
None
|
|
V4
|
|
V5
|
|
|
|
SyncMode* {.pure.} = enum
|
|
Default #Snap ## Beware, experimental
|
|
|
|
NimbusConf* = object of RootObj ## Main Nimbus configuration object
|
|
dataDir* {.
|
|
separator: "ETHEREUM OPTIONS:",
|
|
desc: "The directory where nimbus will store all blockchain data",
|
|
defaultValue: defaultDataDir(),
|
|
defaultValueDesc: $defaultDataDirDesc,
|
|
abbr: "d",
|
|
name: "data-dir"
|
|
.}: OutDir
|
|
|
|
era1DirOpt* {.
|
|
desc: "Directory where era1 (pre-merge) archive can be found",
|
|
defaultValueDesc: "<data-dir>/era1",
|
|
name: "era1-dir"
|
|
.}: Option[OutDir]
|
|
|
|
eraDirOpt* {.
|
|
desc: "Directory where era (post-merge) archive can be found",
|
|
defaultValueDesc: "<data-dir>/era",
|
|
name: "era-dir"
|
|
.}: Option[OutDir]
|
|
|
|
keyStore* {.
|
|
desc: "Load one or more keystore files from this directory",
|
|
defaultValue: defaultKeystoreDir(),
|
|
defaultValueDesc: "inside datadir",
|
|
abbr: "k",
|
|
name: "key-store"
|
|
.}: OutDir
|
|
|
|
chainDbMode* {.
|
|
desc: "Blockchain database",
|
|
longDesc:
|
|
"- Aristo -- Single state DB, full node\n" &
|
|
"- AriPrune -- Aristo with curbed block history (for testing)\n" & "",
|
|
defaultValue: ChainDbMode.Aristo,
|
|
defaultValueDesc: $ChainDbMode.Aristo,
|
|
abbr: "p",
|
|
name: "chaindb"
|
|
.}: ChainDbMode
|
|
|
|
syncMode* {.
|
|
desc: "Specify particular blockchain sync mode.",
|
|
longDesc:
|
|
"- default -- beacon sync mode\n" &
|
|
# "- snap -- experimental snap mode (development only)\n" &
|
|
"",
|
|
defaultValue: SyncMode.Default,
|
|
defaultValueDesc: $SyncMode.Default,
|
|
abbr: "y",
|
|
name: "sync-mode"
|
|
.}: SyncMode
|
|
|
|
syncCtrlFile* {.
|
|
desc:
|
|
"Specify a file that is regularly checked for updates. If it " &
|
|
"exists it is checked for whether it contains extra information " &
|
|
"specific to the type of sync process. This option is primarily " &
|
|
"intended only for sync testing and debugging.",
|
|
abbr: "z",
|
|
name: "sync-ctrl-file"
|
|
.}: Option[string]
|
|
|
|
importKey* {.
|
|
desc: "Import unencrypted 32 bytes hex private key from a file",
|
|
defaultValue: "",
|
|
abbr: "e",
|
|
name: "import-key"
|
|
.}: InputFile
|
|
|
|
verifyFrom* {.
|
|
desc:
|
|
"Enable extra verification when current block number greater than verify-from",
|
|
defaultValueDesc: "",
|
|
name: "verify-from"
|
|
.}: Option[uint64]
|
|
|
|
evm* {.
|
|
desc: "Load alternative EVM from EVMC-compatible shared library" & sharedLibText,
|
|
defaultValue: "",
|
|
name: "evm",
|
|
includeIfEvmc
|
|
.}: string
|
|
|
|
trustedSetupFile* {.
|
|
desc: "Load EIP-4844 trusted setup file",
|
|
defaultValue: none(string),
|
|
defaultValueDesc: "Baked in trusted setup",
|
|
name: "trusted-setup-file"
|
|
.}: Option[string]
|
|
|
|
network {.
|
|
separator: "\pETHEREUM NETWORK OPTIONS:",
|
|
desc:
|
|
"Name or id number of Ethereum network(mainnet(1), sepolia(11155111), holesky(17000), other=custom)",
|
|
longDesc:
|
|
"- mainnet: Ethereum main network\n" &
|
|
"- sepolia: Test network (proof-of-work)\n" &
|
|
"- holesky: The holesovice post-merge testnet",
|
|
defaultValue: "", # the default value is set in makeConfig
|
|
defaultValueDesc: "mainnet(1)",
|
|
abbr: "i",
|
|
name: "network"
|
|
.}: string
|
|
|
|
customNetwork {.
|
|
desc:
|
|
"Use custom genesis block for private Ethereum Network (as /path/to/genesis.json)",
|
|
defaultValueDesc: "",
|
|
abbr: "c",
|
|
name: "custom-network"
|
|
.}: Option[NetworkParams]
|
|
|
|
networkId* {.
|
|
ignore, # this field is not processed by confutils
|
|
defaultValue: MainNet, # the defaultValue value is set by `makeConfig`
|
|
name: "network-id"
|
|
.}: NetworkId
|
|
|
|
networkParams* {.
|
|
ignore, # this field is not processed by confutils
|
|
defaultValue: NetworkParams(), # the defaultValue value is set by `makeConfig`
|
|
name: "network-params"
|
|
.}: NetworkParams
|
|
|
|
logLevel* {.
|
|
separator: "\pLOGGING AND DEBUGGING OPTIONS:",
|
|
desc: "Sets the log level for process and topics (" & logLevelDesc & ")",
|
|
defaultValue: LogLevel.INFO,
|
|
defaultValueDesc: $LogLevel.INFO,
|
|
name: "log-level"
|
|
.}: LogLevel
|
|
|
|
logFile* {.
|
|
desc: "Specifies a path for the written Json log file", name: "log-file"
|
|
.}: Option[OutFile]
|
|
|
|
logMetricsEnabled* {.
|
|
desc: "Enable metrics logging", defaultValue: false, name: "log-metrics"
|
|
.}: bool
|
|
|
|
logMetricsInterval* {.
|
|
desc: "Interval at which to log metrics, in seconds",
|
|
defaultValue: 10,
|
|
name: "log-metrics-interval"
|
|
.}: int
|
|
|
|
metricsEnabled* {.
|
|
desc: "Enable the built-in metrics HTTP server",
|
|
defaultValue: false,
|
|
name: "metrics"
|
|
.}: bool
|
|
|
|
metricsPort* {.
|
|
desc: "Listening port of the built-in metrics HTTP server",
|
|
defaultValue: defaultMetricsServerPort,
|
|
defaultValueDesc: $defaultMetricsServerPort,
|
|
name: "metrics-port"
|
|
.}: Port
|
|
|
|
metricsAddress* {.
|
|
desc: "Listening IP address of the built-in metrics HTTP server",
|
|
defaultValue: defaultAdminListenAddress,
|
|
defaultValueDesc: $defaultAdminListenAddressDesc,
|
|
name: "metrics-address"
|
|
.}: IpAddress
|
|
|
|
bootstrapNodes {.
|
|
separator: "\pNETWORKING OPTIONS:",
|
|
desc:
|
|
"Specifies one or more bootstrap nodes(as enode URL) to use when connecting to the network",
|
|
defaultValue: @[],
|
|
defaultValueDesc: "",
|
|
abbr: "b",
|
|
name: "bootstrap-node"
|
|
.}: seq[string]
|
|
|
|
bootstrapFile {.
|
|
desc:
|
|
"Specifies a line-delimited file of bootstrap Ethereum network addresses(enode URL). " &
|
|
"By default, addresses will be added to bootstrap node list. " &
|
|
"But if the first line equals to `override` word, it will override built-in list",
|
|
defaultValue: "",
|
|
name: "bootstrap-file"
|
|
.}: InputFile
|
|
|
|
bootstrapEnrs {.
|
|
desc: "ENR URI of node to bootstrap discovery from. Argument may be repeated",
|
|
defaultValue: @[],
|
|
defaultValueDesc: "",
|
|
name: "bootstrap-enr"
|
|
.}: seq[enr.Record]
|
|
|
|
staticPeers {.
|
|
desc: "Connect to one or more trusted peers(as enode URL)",
|
|
defaultValue: @[],
|
|
defaultValueDesc: "",
|
|
name: "static-peers"
|
|
.}: seq[string]
|
|
|
|
staticPeersFile {.
|
|
desc:
|
|
"Specifies a line-delimited file of trusted peers addresses(enode URL)" &
|
|
"to be added to the --static-peers list. If the first line equals to the word `override`, " &
|
|
"the file contents will replace the --static-peers list",
|
|
defaultValue: "",
|
|
name: "static-peers-file"
|
|
.}: InputFile
|
|
|
|
staticPeersEnrs {.
|
|
desc: "ENR URI of node to connect to as trusted peer. Argument may be repeated",
|
|
defaultValue: @[],
|
|
defaultValueDesc: "",
|
|
name: "static-peer-enr"
|
|
.}: seq[enr.Record]
|
|
|
|
reconnectMaxRetry* {.
|
|
desc:
|
|
"Specifies max number of retries if static peers disconnected/not connected. " &
|
|
"0 = infinite.",
|
|
defaultValue: 0,
|
|
name: "reconnect-max-retry"
|
|
.}: int
|
|
|
|
reconnectInterval* {.
|
|
desc:
|
|
"Interval in seconds before next attempt to reconnect to static peers. Min 5 seconds.",
|
|
defaultValue: 15,
|
|
name: "reconnect-interval"
|
|
.}: int
|
|
|
|
listenAddress* {.
|
|
desc: "Listening IP address for Ethereum P2P and Discovery traffic",
|
|
defaultValue: defaultListenAddress,
|
|
defaultValueDesc: $defaultListenAddressDesc,
|
|
name: "listen-address"
|
|
.}: IpAddress
|
|
|
|
tcpPort* {.
|
|
desc: "Ethereum P2P network listening TCP port",
|
|
defaultValue: defaultPort,
|
|
defaultValueDesc: $defaultPort,
|
|
name: "tcp-port"
|
|
.}: Port
|
|
|
|
udpPort* {.
|
|
desc: "Ethereum P2P network listening UDP port",
|
|
defaultValue: 0, # set udpPort defaultValue in `makeConfig`
|
|
defaultValueDesc: "default to --tcp-port",
|
|
name: "udp-port"
|
|
.}: Port
|
|
|
|
maxPeers* {.
|
|
desc: "Maximum number of peers to connect to", defaultValue: 25, name: "max-peers"
|
|
.}: int
|
|
|
|
nat* {.
|
|
desc:
|
|
"Specify method to use for determining public address. " &
|
|
"Must be one of: any, none, upnp, pmp, extip:<IP>",
|
|
defaultValue: NatConfig(hasExtIp: false, nat: NatAny),
|
|
defaultValueDesc: "any",
|
|
name: "nat"
|
|
.}: NatConfig
|
|
|
|
discovery* {.
|
|
desc: "Specify method to find suitable peer in an Ethereum network (None, V4, V5)",
|
|
longDesc:
|
|
"- None: Disables the peer discovery mechanism (manual peer addition)\n" &
|
|
"- V4 : Node Discovery Protocol v4(default)\n" &
|
|
"- V5 : Node Discovery Protocol v5",
|
|
defaultValue: DiscoveryType.V4,
|
|
defaultValueDesc: $DiscoveryType.V4,
|
|
name: "discovery"
|
|
.}: DiscoveryType
|
|
|
|
netKey* {.
|
|
desc: "P2P ethereum node (secp256k1) private key (random, path, hex)",
|
|
longDesc:
|
|
"- random: generate random network key for this node instance\n" &
|
|
"- path : path to where the private key will be loaded or auto generated\n" &
|
|
"- hex : 32 bytes hex of network private key",
|
|
defaultValue: "random",
|
|
name: "net-key"
|
|
.}: string
|
|
|
|
agentString* {.
|
|
desc: "Node agent string which is used as identifier in network",
|
|
defaultValue: NimbusIdent,
|
|
defaultValueDesc: $NimbusIdent,
|
|
name: "agent-string"
|
|
.}: string
|
|
|
|
protocols {.
|
|
desc:
|
|
"Enable specific set of server protocols (available: Eth, " &
|
|
" None.) This will not affect the sync mode",
|
|
# " Snap, None.) This will not affect the sync mode"
|
|
defaultValue: @[],
|
|
defaultValueDesc: $ProtocolFlag.Eth,
|
|
name: "protocols"
|
|
.}: seq[string]
|
|
|
|
rocksdbMaxOpenFiles {.
|
|
hidden,
|
|
defaultValue: defaultMaxOpenFiles,
|
|
defaultValueDesc: $defaultMaxOpenFiles,
|
|
name: "debug-rocksdb-max-open-files"
|
|
.}: int
|
|
|
|
rocksdbWriteBufferSize {.
|
|
hidden,
|
|
defaultValue: defaultWriteBufferSize,
|
|
defaultValueDesc: $defaultWriteBufferSize,
|
|
name: "debug-rocksdb-write-buffer-size"
|
|
.}: int
|
|
|
|
rocksdbRowCacheSize {.
|
|
hidden,
|
|
defaultValue: defaultRowCacheSize,
|
|
defaultValueDesc: $defaultRowCacheSize,
|
|
name: "debug-rocksdb-row-cache-size"
|
|
.}: int
|
|
|
|
rocksdbBlockCacheSize {.
|
|
hidden,
|
|
defaultValue: defaultBlockCacheSize,
|
|
defaultValueDesc: $defaultBlockCacheSize,
|
|
name: "debug-rocksdb-block-cache-size"
|
|
.}: int
|
|
|
|
case cmd* {.command, 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* {.
|
|
desc: "Enable the JSON-RPC server", defaultValue: false, name: "rpc"
|
|
.}: bool
|
|
|
|
rpcApi {.
|
|
desc: "Enable specific set of RPC API (available: eth, debug, exp)",
|
|
defaultValue: @[],
|
|
defaultValueDesc: $RpcFlag.Eth,
|
|
name: "rpc-api"
|
|
.}: seq[string]
|
|
|
|
wsEnabled* {.
|
|
desc: "Enable the Websocket JSON-RPC server", defaultValue: false, name: "ws"
|
|
.}: bool
|
|
|
|
wsApi {.
|
|
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(http and ws)",
|
|
defaultValue: defaultEngineApiPort,
|
|
defaultValueDesc: $defaultEngineApiPort,
|
|
name: "engine-api-port"
|
|
.}: Port
|
|
|
|
engineApiAddress* {.
|
|
desc: "Listening address for the Engine API(http and ws)",
|
|
defaultValue: defaultAdminListenAddress,
|
|
defaultValueDesc: $defaultAdminListenAddressDesc,
|
|
name: "engine-api-address"
|
|
.}: IpAddress
|
|
|
|
engineApiWsEnabled* {.
|
|
desc: "Enable the WebSocket Engine API",
|
|
defaultValue: false,
|
|
name: "engine-api-ws"
|
|
.}: bool
|
|
|
|
allowedOrigins* {.
|
|
desc:
|
|
"Comma separated list of domains from which to accept cross origin requests",
|
|
defaultValue: @[],
|
|
defaultValueDesc: "*",
|
|
name: "allowed-origins"
|
|
.}: seq[string]
|
|
|
|
# github.com/ethereum/execution-apis/
|
|
# /blob/v1.0.0-alpha.8/src/engine/authentication.md#key-distribution
|
|
jwtSecret* {.
|
|
desc:
|
|
"Path to a file containing a 32 byte hex-encoded shared secret" &
|
|
" needed for websocket authentication. By default, the secret key" &
|
|
" is auto-generated.",
|
|
defaultValueDesc: "\"jwt.hex\" in the data directory (see --data-dir)",
|
|
name: "jwt-secret"
|
|
.}: Option[InputFile]
|
|
of `import`:
|
|
blocksFile* {.
|
|
argument, desc: "One or more RLP encoded block(s) files", name: "blocks-file"
|
|
.}: seq[InputFile]
|
|
|
|
maxBlocks* {.
|
|
desc: "Maximum number of blocks to import",
|
|
defaultValue: uint64.high(),
|
|
name: "max-blocks"
|
|
.}: uint64
|
|
|
|
chunkSize* {.
|
|
desc: "Number of blocks per database transaction",
|
|
defaultValue: 8192,
|
|
name: "chunk-size"
|
|
.}: uint64
|
|
|
|
csvStats* {.
|
|
hidden, desc: "Save performance statistics to CSV", name: "debug-csv-stats"
|
|
.}: Option[string]
|
|
|
|
# TODO validation and storage options should be made non-hidden when the
|
|
# UX has stabilised and era1 storage is in the app
|
|
fullValidation* {.
|
|
hidden,
|
|
desc: "Enable full per-block validation (slow)",
|
|
defaultValue: false,
|
|
name: "debug-full-validation"
|
|
.}: bool
|
|
|
|
noValidation* {.
|
|
hidden,
|
|
desc: "Disble per-chunk validation",
|
|
defaultValue: true,
|
|
name: "debug-no-validation"
|
|
.}: bool
|
|
|
|
storeBodies* {.
|
|
hidden,
|
|
desc: "Store block blodies in database",
|
|
defaultValue: false,
|
|
name: "debug-store-bodies"
|
|
.}: bool
|
|
|
|
# TODO this option should probably only cover the redundant parts, ie
|
|
# those that are in era1 files - era files presently do not store
|
|
# receipts
|
|
storeReceipts* {.
|
|
hidden,
|
|
desc: "Store receipts in database",
|
|
defaultValue: false,
|
|
name: "debug-store-receipts"
|
|
.}: bool
|
|
|
|
func parseCmdArg(T: type NetworkId, p: string): T {.gcsafe, raises: [ValueError].} =
|
|
parseInt(p).T
|
|
|
|
func completeCmdArg(T: type NetworkId, val: string): seq[string] =
|
|
return @[]
|
|
|
|
func parseCmdArg*(T: type enr.Record, p: string): T {.raises: [ValueError].} =
|
|
result = fromURI(enr.Record, p).valueOr:
|
|
raise newException(ValueError, "Invalid ENR")
|
|
|
|
func completeCmdArg*(T: type enr.Record, val: string): seq[string] =
|
|
return @[]
|
|
|
|
func processList(v: string, o: var seq[string]) =
|
|
## Process comma-separated list of strings.
|
|
if len(v) > 0:
|
|
for n in v.split({' ', ','}):
|
|
if len(n) > 0:
|
|
o.add(n)
|
|
|
|
proc parseCmdArg(T: type NetworkParams, p: string): T {.gcsafe, raises: [ValueError].} =
|
|
try:
|
|
if not loadNetworkParams(p, result):
|
|
raise newException(ValueError, "failed to load customNetwork")
|
|
except CatchableError:
|
|
raise newException(ValueError, "failed to load customNetwork")
|
|
|
|
func completeCmdArg(T: type NetworkParams, val: string): seq[string] =
|
|
return @[]
|
|
|
|
func setBootnodes(output: var seq[ENode], nodeUris: openArray[string]) =
|
|
output = newSeqOfCap[ENode](nodeUris.len)
|
|
for item in nodeUris:
|
|
output.add(ENode.fromString(item).expect("valid hardcoded ENode"))
|
|
|
|
iterator repeatingList(listOfList: openArray[string]): string =
|
|
for strList in listOfList:
|
|
var list = newSeq[string]()
|
|
processList(strList, list)
|
|
for item in list:
|
|
yield item
|
|
|
|
proc append(output: var seq[ENode], nodeUris: openArray[string]) =
|
|
for item in repeatingList(nodeUris):
|
|
let res = ENode.fromString(item)
|
|
if res.isErr:
|
|
warn "Ignoring invalid bootstrap address", address = item
|
|
continue
|
|
output.add res.get()
|
|
|
|
iterator strippedLines(filename: string): (int, string) {.gcsafe, raises: [IOError].} =
|
|
var i = 0
|
|
for line in lines(filename):
|
|
let stripped = strip(line)
|
|
if stripped.startsWith('#'): # Comments
|
|
continue
|
|
|
|
if stripped.len > 0:
|
|
yield (i, stripped)
|
|
inc i
|
|
|
|
proc loadEnodeFile(fileName: string, output: var seq[ENode], info: string) =
|
|
if fileName.len == 0:
|
|
return
|
|
|
|
try:
|
|
for i, ln in strippedLines(fileName):
|
|
if cmpIgnoreCase(ln, "override") == 0 and i == 0:
|
|
# override built-in list if the first line is 'override'
|
|
output = newSeq[ENode]()
|
|
continue
|
|
|
|
let res = ENode.fromString(ln)
|
|
if res.isErr:
|
|
warn "Ignoring invalid address",
|
|
address = ln, line = i, file = fileName, purpose = info
|
|
continue
|
|
|
|
output.add res.get()
|
|
except IOError as e:
|
|
error "Could not read file", msg = e.msg, purpose = info
|
|
quit 1
|
|
|
|
proc loadBootstrapFile(fileName: string, output: var seq[ENode]) =
|
|
fileName.loadEnodeFile(output, "bootstrap")
|
|
|
|
proc loadStaticPeersFile(fileName: string, output: var seq[ENode]) =
|
|
fileName.loadEnodeFile(output, "static peers")
|
|
|
|
proc getNetworkId(conf: NimbusConf): Option[NetworkId] =
|
|
if conf.network.len == 0:
|
|
return none NetworkId
|
|
|
|
let network = toLowerAscii(conf.network)
|
|
case network
|
|
of "mainnet":
|
|
return some MainNet
|
|
of "sepolia":
|
|
return some SepoliaNet
|
|
of "holesky":
|
|
return some HoleskyNet
|
|
else:
|
|
try:
|
|
some parseInt(network).NetworkId
|
|
except CatchableError:
|
|
error "Failed to parse network name or id", network
|
|
quit QuitFailure
|
|
|
|
proc getProtocolFlags*(conf: NimbusConf): set[ProtocolFlag] =
|
|
if conf.protocols.len == 0:
|
|
return {ProtocolFlag.Eth}
|
|
|
|
var noneOk = false
|
|
for item in repeatingList(conf.protocols):
|
|
case item.toLowerAscii()
|
|
of "eth":
|
|
result.incl ProtocolFlag.Eth
|
|
# of "snap": result.incl ProtocolFlag.Snap
|
|
of "none":
|
|
noneOk = true
|
|
else:
|
|
error "Unknown protocol", name = item
|
|
quit QuitFailure
|
|
if noneOk and 0 < result.len:
|
|
error "Setting none contradicts wire protocols", names = $result
|
|
quit QuitFailure
|
|
|
|
proc getRpcFlags(api: openArray[string]): set[RpcFlag] =
|
|
if api.len == 0:
|
|
return {RpcFlag.Eth}
|
|
|
|
for item in repeatingList(api):
|
|
case item.toLowerAscii()
|
|
of "eth":
|
|
result.incl RpcFlag.Eth
|
|
of "debug":
|
|
result.incl RpcFlag.Debug
|
|
of "exp":
|
|
result.incl RpcFlag.Exp
|
|
else:
|
|
error "Unknown RPC API: ", name = item
|
|
quit QuitFailure
|
|
|
|
proc getRpcFlags*(conf: NimbusConf): set[RpcFlag] =
|
|
getRpcFlags(conf.rpcApi)
|
|
|
|
proc getWsFlags*(conf: NimbusConf): set[RpcFlag] =
|
|
getRpcFlags(conf.wsApi)
|
|
|
|
func fromEnr*(T: type ENode, r: enr.Record): ENodeResult[ENode] =
|
|
let
|
|
# TODO: there must always be a public key, else no signature verification
|
|
# could have been done and no Record would exist here.
|
|
# TypedRecord should be reworked not to have public key as an option.
|
|
pk = r.get(PublicKey).get()
|
|
tr = TypedRecord.fromRecord(r) #.expect("id in valid record")
|
|
|
|
if tr.ip.isNone():
|
|
return err(IncorrectIP)
|
|
if tr.udp.isNone():
|
|
return err(IncorrectDiscPort)
|
|
if tr.tcp.isNone():
|
|
return err(IncorrectPort)
|
|
|
|
ok(
|
|
ENode(
|
|
pubkey: pk,
|
|
address: Address(
|
|
ip: utils.ipv4(tr.ip.get()),
|
|
udpPort: Port(tr.udp.get()),
|
|
tcpPort: Port(tr.tcp.get()),
|
|
),
|
|
)
|
|
)
|
|
|
|
proc getBootNodes*(conf: NimbusConf): seq[ENode] =
|
|
var bootstrapNodes: seq[ENode]
|
|
# Ignore standard bootnodes if customNetwork is loaded
|
|
if conf.customNetwork.isNone:
|
|
case conf.networkId
|
|
of MainNet:
|
|
bootstrapNodes.setBootnodes(MainnetBootnodes)
|
|
of SepoliaNet:
|
|
bootstrapNodes.setBootnodes(SepoliaBootnodes)
|
|
of HoleskyNet:
|
|
bootstrapNodes.setBootnodes(HoleskyBootnodes)
|
|
else:
|
|
# custom network id
|
|
discard
|
|
|
|
# always allow bootstrap nodes provided by the user
|
|
if conf.bootstrapNodes.len > 0:
|
|
bootstrapNodes.append(conf.bootstrapNodes)
|
|
|
|
# bootstrap nodes loaded from file might append or
|
|
# override built-in bootnodes
|
|
loadBootstrapFile(string conf.bootstrapFile, bootstrapNodes)
|
|
|
|
# Bootstrap nodes provided as ENRs
|
|
for enr in conf.bootstrapEnrs:
|
|
let enode = Enode.fromEnr(enr).valueOr:
|
|
fatal "Invalid bootstrap ENR provided", error
|
|
quit 1
|
|
|
|
bootstrapNodes.add(enode)
|
|
|
|
bootstrapNodes
|
|
|
|
proc getStaticPeers*(conf: NimbusConf): seq[ENode] =
|
|
var staticPeers: seq[ENode]
|
|
staticPeers.append(conf.staticPeers)
|
|
loadStaticPeersFile(string conf.staticPeersFile, staticPeers)
|
|
|
|
# Static peers provided as ENRs
|
|
for enr in conf.staticPeersEnrs:
|
|
let enode = Enode.fromEnr(enr).valueOr:
|
|
fatal "Invalid static peer ENR provided", error
|
|
quit 1
|
|
|
|
staticPeers.add(enode)
|
|
|
|
staticPeers
|
|
|
|
func 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
|
|
|
|
func era1Dir*(conf: NimbusConf): OutDir =
|
|
conf.era1DirOpt.get(OutDir(conf.dataDir.string & "/era1"))
|
|
|
|
func eraDir*(conf: NimbusConf): OutDir =
|
|
conf.eraDirOpt.get(OutDir(conf.dataDir.string & "/era"))
|
|
|
|
func dbOptions*(conf: NimbusConf): DbOptions =
|
|
DbOptions.init(
|
|
maxOpenFiles = conf.rocksdbMaxOpenFiles,
|
|
writeBufferSize = conf.rocksdbWriteBufferSize,
|
|
rowCacheSize = conf.rocksdbRowCacheSize,
|
|
blockCacheSize = conf.rocksdbBlockCacheSize,
|
|
)
|
|
|
|
# KLUDGE: The `load()` template does currently not work within any exception
|
|
# annotated environment.
|
|
{.pop.}
|
|
|
|
proc makeConfig*(
|
|
cmdLine = commandLineParams()
|
|
): NimbusConf {.raises: [CatchableError].} =
|
|
## Note: this function is not gc-safe
|
|
|
|
# The try/catch clause can go away when `load()` is clean
|
|
try:
|
|
{.push warning[ProveInit]: off.}
|
|
result =
|
|
NimbusConf.load(cmdLine, version = NimbusBuild, copyrightBanner = NimbusHeader)
|
|
{.pop.}
|
|
except CatchableError as e:
|
|
raise e
|
|
|
|
var networkId = result.getNetworkId()
|
|
|
|
if result.customNetwork.isSome:
|
|
result.networkParams = result.customNetwork.get()
|
|
if networkId.isNone:
|
|
# WARNING: networkId and chainId are two distinct things
|
|
# they usage should not be mixed in other places.
|
|
# We only set networkId to chainId if networkId not set in cli and
|
|
# --custom-network is set.
|
|
# If chainId is not defined in config file, it's ok because
|
|
# zero means CustomNet
|
|
networkId = some(NetworkId(result.networkParams.config.chainId))
|
|
|
|
if networkId.isNone:
|
|
# bootnodes is set via getBootNodes
|
|
networkId = some MainNet
|
|
|
|
result.networkId = networkId.get()
|
|
|
|
if result.customNetwork.isNone:
|
|
result.networkParams = networkParams(result.networkId)
|
|
|
|
if result.cmd == noCommand:
|
|
if result.udpPort == Port(0):
|
|
# if udpPort not set in cli, then
|
|
result.udpPort = result.tcpPort
|
|
|
|
# see issue #1346
|
|
if result.keyStore.string == defaultKeystoreDir() and
|
|
result.dataDir.string != defaultDataDir():
|
|
result.keyStore = OutDir(result.dataDir.string / "keystore")
|
|
|
|
# For consistency
|
|
if result.syncCtrlFile.isSome and result.syncCtrlFile.unsafeGet == "":
|
|
error "Argument missing", option = "sync-ctrl-file"
|
|
quit QuitFailure
|
|
|
|
when isMainModule:
|
|
# for testing purpose
|
|
discard makeConfig()
|