nimbus-eth1/nimbus/config.nim
Jordan Hrycaj 215e9856d3
Jordan/custom network (#962)
* Rearrange/rename test_kintsugu => test_custom_network

why:
  Debug, fix and test more general problems related to running
  nimbus on a custom network.

* Update UInt265/Json parser for --custom-network command line option

why:
  As found out with the Kintsugi configuration, block number and balance
  have the same Nim type which led to misunderstandings. This patch makes
  sure that UInt265 encoded string values "0x11" decodes to 17, and "b"
  and "11" to 11.

* Refactored genesis.toBlock() => genesis.toBlockHeader()

why:
  The function toBlock(g,db) may return different results depending on
  whether the db descriptor argument is nil, or initialised. This is due
  to the db.config data sub-descriptor which may give various outcomes
  for the baseFee field of the genesis header.

  Also, the version where db is non-nil initialised is used internally
  only. So the public rewrite toBlockHeader() that replaces the toBlock()
  function expects a full set of NetworkParams.

* update comments

* Rename toBlockHeader() => toGenesisHeader()

why:
  Polymorphic prototype used for BaseChainDB or NetworkParams argument.
  With a BaseChainDB descriptor argument, the name shall imply that the
  header is generated from the config fields rather than fetched from
  the database.

* Added command line option --static-peers-file

why:
  Handy feature to keep peer nodes in a file, similar to the
  --bootstrap-file option.
2022-02-11 16:28:39 +00:00

685 lines
21 KiB
Nim

# Nimbus
# Copyright (c) 2018-2021 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
std/[options, strutils, times, os],
pkg/[
chronicles,
confutils,
confutils/defs,
stew/byteutils,
confutils/std/net
],
stew/shims/net as stewNet,
eth/[p2p, common, net/nat, p2p/bootnodes],
"."/[db/select_backend, chain_config,
constants, vm_compile_info
]
export stewNet
const
NimbusName* = "nimbus-eth1"
## project name string
NimbusMajor*: int = 0
## is the major number of Nimbus' version.
NimbusMinor*: int = 1
## is the minor number of Nimbus' version.
NimbusPatch*: int = 0
## is the patch number of Nimbus' version.
NimbusVersion* = $NimbusMajor & "." & $NimbusMinor & "." & $NimbusPatch
## is the version of Nimbus as a string.
GitRevision = staticExec("git rev-parse --short HEAD").replace("\n") # remove CR
NimVersion = staticExec("nim --version").strip()
# TODO: fix this agent-string format to match other
# eth clients format
NimbusIdent* = "$# v$# [$#: $#, $#, $#, $#]" % [
NimbusName,
NimbusVersion,
hostOS,
hostCPU,
nimbus_db_backend,
VmName,
GitRevision
]
## project ident name for networking services
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,
NimVersion
]
proc defaultDataDir*(): string =
when defined(windows):
getHomeDir() / "AppData" / "Roaming" / "Nimbus"
elif defined(macosx):
getHomeDir() / "Library" / "Application Support" / "Nimbus"
else:
getHomeDir() / ".cache" / "nimbus"
proc defaultKeystoreDir*(): string =
defaultDataDir() / "keystore"
proc 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
defaultEthRpcPort = 8545
defaultEthWsPort = 8546
defaultEthGraphqlPort = 8547
defaultEngineApiPort = 8550
defaultEngineApiWsPort = 8551
defaultListenAddress = (static ValidIpAddress.init("0.0.0.0"))
defaultAdminListenAddress = (static ValidIpAddress.init("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
PruneMode* {.pure.} = enum
Full
Archive
NimbusCmd* {.pure.} = enum
noCommand
`import`
ProtocolFlag* {.pure.} = enum
## Protocol flags
Eth ## enable eth subprotocol
Les ## enable les subprotocol
RpcFlag* {.pure.} = enum
## RPC flags
Eth ## enable eth_ set of RPC API
Debug ## enable debug_ set of RPC API
DiscoveryType* {.pure.} = enum
None
V4
V5
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
keyStore* {.
desc: "Load one or more keystore files from this directory"
defaultValue: defaultKeystoreDir()
defaultValueDesc: "inside datadir"
abbr: "k"
name: "key-store" }: OutDir
pruneMode* {.
desc: "Blockchain prune mode (Full or Archive)"
defaultValue: PruneMode.Full
defaultValueDesc: $PruneMode.Full
abbr : "p"
name: "prune-mode" }: PruneMode
importKey* {.
desc: "Import unencrypted 32 bytes hex private key from a file"
defaultValue: ""
abbr: "e"
name: "import-key" }: InputFile
engineSigner* {.
desc: "Set the signer address(as 20 bytes hex) and enable sealing engine to run and " &
"producing blocks at specified interval (only PoA/Clique supported)"
defaultValue: ZERO_ADDRESS
defaultValueDesc: ""
abbr: "s"
name: "engine-signer" }: EthAddress
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
network {.
separator: "\pETHEREUM NETWORK OPTIONS:"
desc: "Name or id number of Ethereum network(mainnet(1), ropsten(3), rinkeby(4), goerli(5), kovan(42), other=custom)"
longDesc:
"- mainnet: Ethereum main network\n" &
"- ropsten: Test network (proof-of-work, the one most like Ethereum mainnet)\n" &
"- rinkeby: Test network (proof-of-authority, for those running Geth clients)\n" &
"- görli : Test network (proof-of-authority, works across all clients)\n" &
"- kovan : Test network (proof-of-authority, for those running OpenEthereum clients)"
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
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
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 --staticPeers list. If the first line equals to the word `override`, "&
"the file contents will replace the --staticPeers list"
defaultValue: ""
name: "static-peers-file" }: InputFile
listenAddress* {.
desc: "Listening IP address for Ethereum P2P and Discovery traffic"
defaultValue: defaultListenAddress
defaultValueDesc: $defaultListenAddressDesc
name: "listen-address" }: ValidIpAddress
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
terminalTotalDifficulty* {.
desc: "The terminal total difficulty of the eth2 merge transition block"
name: "terminal-total-difficulty" .}: Option[UInt256]
engineApiEnabled* {.
desc: "Enable the Engine API"
defaultValue: false
name: "engine-api" .}: bool
engineApiPort* {.
desc: "Listening port for the Engine API"
defaultValue: defaultEngineApiPort
defaultValueDesc: $defaultEngineApiPort
name: "engine-api-port" .}: Port
engineApiAddress* {.
desc: "Listening address for the Engine API"
defaultValue: defaultAdminListenAddress
defaultValueDesc: defaultAdminListenAddressDesc
name: "engine-api-address" .}: ValidIpAddress
engineApiWsEnabled* {.
desc: "Enable the WebSocket Engine API"
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" .}: ValidIpAddress
nodeKeyHex* {.
desc: "P2P node private key (as 32 bytes hex string)"
defaultValue: ""
defaultValueDesc: "random"
name: "node-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 protocols (available: Eth, Les)"
defaultValue: @[]
defaultValueDesc: $ProtocolFlag.Eth
name: "protocols" .}: seq[string]
case cmd* {.
command
defaultValue: NimbusCmd.noCommand }: NimbusCmd
of noCommand:
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" }: ValidIpAddress
rpcApi {.
desc: "Enable specific set of RPC API (available: eth, debug)"
defaultValue: @[]
defaultValueDesc: $RpcFlag.Eth
name: "rpc-api" }: seq[string]
wsEnabled* {.
desc: "Enable the Websocket JSON-RPC server"
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" }: ValidIpAddress
wsApi {.
desc: "Enable specific set of Websocket RPC API (available: eth, debug)"
defaultValue: @[]
defaultValueDesc: $RpcFlag.Eth
name: "ws-api" }: seq[string]
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" }: ValidIpAddress
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" }: ValidIpAddress
of `import`:
blocksFile* {.
argument
desc: "Import RLP encoded block(s) from a file, validate, write to database and quit"
defaultValue: ""
name: "blocks-file" }: InputFile
proc parseCmdArg(T: type NetworkId, p: TaintedString): T =
parseInt(p.string).T
proc completeCmdArg(T: type NetworkId, val: TaintedString): seq[string] =
return @[]
proc parseCmdArg(T: type UInt256, p: TaintedString): T =
parse(string p, T)
proc completeCmdArg(T: type UInt256, val: TaintedString): seq[string] =
return @[]
proc parseCmdArg(T: type EthAddress, p: TaintedString): T =
try:
result = hexToByteArray(p.string, 20)
except CatchableError:
raise newException(ValueError, "failed to parse EthAddress")
proc completeCmdArg(T: type EthAddress, val: TaintedString): seq[string] =
return @[]
proc 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: TaintedString): T =
try:
if not loadNetworkParams(p.string, result):
raise newException(ValueError, "failed to load customNetwork")
except Exception as exc:
# on linux/mac, nim compiler refuse to compile
# with unlisted exception error
raise newException(ValueError, "failed to load customNetwork")
proc completeCmdArg(T: type NetworkParams, val: TaintedString): seq[string] =
return @[]
proc setBootnodes(output: var seq[ENode], nodeUris: openarray[string]) =
output = newSeqOfCap[ENode](nodeUris.len)
for item in nodeUris:
output.add(ENode.fromString(item).tryGet())
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) =
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 "ropsten": return some RopstenNet
of "rinkeby": return some RinkebyNet
of "goerli" : return some GoerliNet
of "kovan" : return some KovanNet
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}
for item in repeatingList(conf.protocols):
case item.toLowerAscii()
of "eth": result.incl ProtocolFlag.Eth
of "les": result.incl ProtocolFlag.Les
else:
error "Unknown protocol", name=item
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
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)
proc getBootNodes*(conf: NimbusConf): seq[Enode] =
# Ignore standard bootnodes if customNetwork is loaded
if conf.customNetwork.isNone:
case conf.networkId
of MainNet:
result.setBootnodes(MainnetBootnodes)
of RopstenNet:
result.setBootnodes(RopstenBootnodes)
of RinkebyNet:
result.setBootnodes(RinkebyBootnodes)
of GoerliNet:
result.setBootnodes(GoerliBootnodes)
of KovanNet:
result.setBootnodes(KovanBootnodes)
else:
# custom network id
discard
# always allow custom boostrap nodes
# if it is set by user
if conf.bootstrapNodes.len > 0:
result.append(conf.bootstrapNodes)
# bootstrap nodes loaded from file might append or
# override built-in bootnodes
loadBootstrapFile(string conf.bootstrapFile, result)
proc getStaticPeers*(conf: NimbusConf): seq[Enode] =
result.append(conf.staticPeers)
loadStaticPeersFile(string conf.staticPeersFile, result)
proc makeConfig*(cmdLine = commandLineParams()): NimbusConf =
{.push warning[ProveInit]: off.}
result = NimbusConf.load(
cmdLine,
version = NimbusBuild,
copyrightBanner = NimbusHeader
)
{.pop.}
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
when isMainModule:
# for testing purpose
discard makeConfig()