864 lines
30 KiB
Nim
864 lines
30 KiB
Nim
# Nimbus
|
|
# Copyright (c) 2018-2019 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
|
|
parseopt, strutils, macros, os, times, json, tables, stew/[byteutils],
|
|
chronos, eth/[keys, common, p2p, net/nat], chronicles, nimcrypto/hash,
|
|
eth/p2p/bootnodes, ./db/select_backend, eth/keys, ./chain_config, ./forks
|
|
|
|
const
|
|
NimbusName* = "Nimbus"
|
|
## project name string
|
|
|
|
NimbusMajor*: int = 0
|
|
## is the major number of Nimbus' version.
|
|
|
|
NimbusMinor*: int = 0
|
|
## is the minor number of Nimbus' version.
|
|
|
|
NimbusPatch*: int = 1
|
|
## is the patch number of Nimbus' version.
|
|
|
|
NimbusVersion* = $NimbusMajor & "." & $NimbusMinor & "." & $NimbusPatch
|
|
## is the version of Nimbus as a string.
|
|
|
|
NimbusIdent* = "$1/$2 ($3/$4)" % [NimbusName, NimbusVersion, hostCPU, hostOS]
|
|
## project ident name for networking services
|
|
|
|
GitRevision = staticExec("git rev-parse --short HEAD").replace("\n") # remove CR
|
|
|
|
NimVersion = staticExec("nim --version")
|
|
|
|
let
|
|
NimbusCopyright* = "Copyright (c) 2018-" & $(now().utc.year) & " Status Research & Development GmbH"
|
|
NimbusHeader* = "$# Version $# [$#: $#, $#, $#]\p$#\p\p$#\p" %
|
|
[NimbusName, NimbusVersion, hostOS, hostCPU, nimbus_db_backend, GitRevision, NimbusCopyright, NimVersion]
|
|
|
|
type
|
|
ConfigStatus* = enum
|
|
## Configuration status flags
|
|
Success, ## Success
|
|
EmptyOption, ## No options in category
|
|
ErrorUnknownOption, ## Unknown option in command line found
|
|
ErrorParseOption, ## Error in parsing command line option
|
|
ErrorIncorrectOption, ## Option has incorrect value
|
|
Error ## Unspecified error
|
|
|
|
RpcFlags* {.pure.} = enum
|
|
## RPC flags
|
|
Enabled ## RPC enabled
|
|
Eth ## enable eth_ set of RPC API
|
|
Debug ## enable debug_ set of RPC API
|
|
|
|
ProtocolFlags* {.pure.} = enum
|
|
## Protocol flags
|
|
Eth ## enable eth subprotocol
|
|
Les ## enable les subprotocol
|
|
|
|
RpcConfiguration* = object
|
|
## JSON-RPC configuration object
|
|
flags*: set[RpcFlags] ## RPC flags
|
|
binds*: seq[TransportAddress] ## RPC bind address
|
|
|
|
GraphqlConfiguration* = object
|
|
enabled*: bool
|
|
address*: TransportAddress
|
|
|
|
NetworkFlags* = enum
|
|
## Ethereum network flags
|
|
NoDiscover ## Peer discovery disabled
|
|
V5Discover ## Dicovery V5 enabled
|
|
NetworkIdSet ## prevent CustomNetwork replacement
|
|
|
|
DebugFlags* {.pure.} = enum
|
|
## Debug selection flags
|
|
Enabled, ## Debugging enabled
|
|
Test1, ## Test1 enabled
|
|
Test2, ## Test2 enabled
|
|
Test3 ## Test3 enabled
|
|
|
|
NetConfiguration* = object
|
|
## Network configuration object
|
|
flags*: set[NetworkFlags] ## Network flags
|
|
bootNodes*: seq[ENode] ## List of bootnodes
|
|
staticNodes*: seq[ENode] ## List of static nodes to connect to
|
|
customBootNodes*: seq[ENode]
|
|
bindPort*: uint16 ## Main TCP bind port
|
|
discPort*: uint16 ## Discovery UDP bind port
|
|
metricsServer*: bool ## Enable metrics server
|
|
metricsServerPort*: uint16 ## metrics HTTP server port
|
|
maxPeers*: int ## Maximum allowed number of peers
|
|
maxPendingPeers*: int ## Maximum allowed pending peers
|
|
networkId*: NetworkId ## Network ID as integer
|
|
ident*: string ## Server ident name string
|
|
nodeKey*: PrivateKey ## Server private key
|
|
nat*: NatStrategy ## NAT strategy
|
|
externalIP*: string ## user-provided external IP
|
|
protocols*: set[ProtocolFlags]## Enabled subprotocols
|
|
|
|
DebugConfiguration* = object
|
|
## Debug configuration object
|
|
flags*: set[DebugFlags] ## Debug flags
|
|
logLevel*: LogLevel ## Log level
|
|
logFile*: string ## Log file
|
|
logMetrics*: bool ## Enable metrics logging
|
|
logMetricsInterval*: int ## Metrics logging interval
|
|
|
|
PruneMode* {.pure.} = enum
|
|
Full
|
|
Archive
|
|
|
|
NimbusAccount* = object
|
|
privateKey*: PrivateKey
|
|
keystore*: JsonNode
|
|
unlocked*: bool
|
|
|
|
NimbusConfiguration* = ref object
|
|
## Main Nimbus configuration object
|
|
dataDir*: string
|
|
keyStore*: string
|
|
prune*: PruneMode
|
|
graphql*: GraphqlConfiguration
|
|
rpc*: RpcConfiguration ## JSON-RPC configuration
|
|
ws*: RpcConfiguration ## Websocket JSON-RPC configuration
|
|
net*: NetConfiguration ## Network configuration
|
|
debug*: DebugConfiguration ## Debug configuration
|
|
customGenesis*: CustomGenesis ## Custom Genesis Configuration
|
|
# You should only create one instance of the RNG per application / library
|
|
# Ref is used so that it can be shared between components
|
|
rng*: ref BrHmacDrbgContext
|
|
accounts*: Table[EthAddress, NimbusAccount]
|
|
importFile*: string
|
|
verifyFromOk*: bool ## activate `verifyFrom` setting
|
|
verifyFrom*: uint64 ## verification start block, 0 for disable
|
|
engineSigner*: EthAddress ## Miner account
|
|
|
|
const
|
|
# these are public network id
|
|
CustomNet* = 0.NetworkId
|
|
MainNet* = 1.NetworkId
|
|
# No longer used: MordenNet = 2
|
|
RopstenNet* = 3.NetworkId
|
|
RinkebyNet* = 4.NetworkId
|
|
GoerliNet* = 5.NetworkId
|
|
KovanNet* = 42.NetworkId
|
|
|
|
const
|
|
defaultRpcApi = {RpcFlags.Eth}
|
|
defaultProtocols = {ProtocolFlags.Eth}
|
|
defaultLogLevel = LogLevel.WARN
|
|
defaultNetwork = MainNet
|
|
|
|
var nimbusConfig {.threadvar.}: NimbusConfiguration
|
|
|
|
proc getConfiguration*(): NimbusConfiguration {.gcsafe.}
|
|
|
|
proc `$`*(c: ChainId): string =
|
|
$(c.int)
|
|
|
|
proc toFork*(c: ChainConfig, number: BlockNumber): Fork =
|
|
if number >= c.londonBlock: FkLondon
|
|
elif number >= c.berlinBlock: FkBerlin
|
|
elif number >= c.istanbulBlock: FkIstanbul
|
|
elif number >= c.petersburgBlock: FkPetersburg
|
|
elif number >= c.constantinopleBlock: FkConstantinople
|
|
elif number >= c.byzantiumBlock: FkByzantium
|
|
elif number >= c.eip158Block: FkSpurious
|
|
elif number >= c.eip150Block: FkTangerine
|
|
elif number >= c.homesteadBlock: FkHomestead
|
|
else: FkFrontier
|
|
|
|
proc chainConfig*(id: NetworkId): ChainConfig =
|
|
# For some public networks, NetworkId and ChainId value are identical
|
|
# but that is not always the case
|
|
|
|
result = case id
|
|
of MainNet:
|
|
ChainConfig(
|
|
poaEngine: false, # TODO: use real engine conf: PoW
|
|
chainId: MainNet.ChainId,
|
|
homesteadBlock: 1_150_000.toBlockNumber, # 14/03/2016 20:49:53
|
|
daoForkBlock: 1_920_000.toBlockNumber,
|
|
daoForkSupport: true,
|
|
eip150Block: 2_463_000.toBlockNumber, # 18/10/2016 17:19:31
|
|
eip150Hash: toDigest("2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0"),
|
|
eip155Block: 2_675_000.toBlockNumber, # 22/11/2016 18:15:44
|
|
eip158Block: 2_675_000.toBlockNumber,
|
|
byzantiumBlock: 4_370_000.toBlockNumber, # 16/10/2017 09:22:11
|
|
constantinopleBlock: 7_280_000.toBlockNumber, # Never Occured in MainNet
|
|
petersburgBlock:7_280_000.toBlockNumber, # 28/02/2019 07:52:04
|
|
istanbulBlock: 9_069_000.toBlockNumber, # 08/12/2019 12:25:09
|
|
muirGlacierBlock: 9_200_000.toBlockNumber, # 02/01/2020 08:30:49
|
|
berlinBlock: 12_244_000.toBlockNumber, # 15/04/2021 10:07:03
|
|
londonBlock: high(BlockNumber)
|
|
)
|
|
of RopstenNet:
|
|
ChainConfig(
|
|
poaEngine: false, # TODO: use real engine conf: PoW
|
|
chainId: RopstenNet.ChainId,
|
|
homesteadBlock: 0.toBlockNumber,
|
|
daoForkSupport: false,
|
|
eip150Block: 0.toBlockNumber,
|
|
eip150Hash: toDigest("41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d"),
|
|
eip155Block: 10.toBlockNumber,
|
|
eip158Block: 10.toBlockNumber,
|
|
byzantiumBlock: 1_700_000.toBlockNumber,
|
|
constantinopleBlock: 4_230_000.toBlockNumber,
|
|
petersburgBlock:4_939_394.toBlockNumber,
|
|
istanbulBlock: 6_485_846.toBlockNumber,
|
|
muirGlacierBlock: 7_117_117.toBlockNumber,
|
|
berlinBlock: 9_812_189.toBlockNumber,
|
|
londonBlock: high(BlockNumber)
|
|
)
|
|
of RinkebyNet:
|
|
ChainConfig(
|
|
poaEngine: true, # TODO: use real engine conf: PoA
|
|
chainId: RinkebyNet.ChainId,
|
|
homesteadBlock: 1.toBlockNumber,
|
|
daoForkSupport: false,
|
|
eip150Block: 2.toBlockNumber,
|
|
eip150Hash: toDigest("9b095b36c15eaf13044373aef8ee0bd3a382a5abb92e402afa44b8249c3a90e9"),
|
|
eip155Block: 3.toBlockNumber,
|
|
eip158Block: 3.toBlockNumber,
|
|
byzantiumBlock: 1_035_301.toBlockNumber,
|
|
constantinopleBlock: 3_660_663.toBlockNumber,
|
|
petersburgBlock:4_321_234.toBlockNumber,
|
|
istanbulBlock: 5_435_345.toBlockNumber,
|
|
muirGlacierBlock: 8_290_928.toBlockNumber, # never occured in rinkeby network
|
|
berlinBlock: 8_290_928.toBlockNumber,
|
|
londonBlock: high(BlockNumber)
|
|
)
|
|
of GoerliNet:
|
|
ChainConfig(
|
|
poaEngine: true, # TODO: use real engine conf: PoA
|
|
chainId: GoerliNet.ChainId,
|
|
homesteadBlock: 0.toBlockNumber,
|
|
daoForkSupport: false,
|
|
eip150Block: 0.toBlockNumber,
|
|
eip150Hash: toDigest("0000000000000000000000000000000000000000000000000000000000000000"),
|
|
eip155Block: 0.toBlockNumber,
|
|
eip158Block: 0.toBlockNumber,
|
|
byzantiumBlock: 0.toBlockNumber,
|
|
constantinopleBlock: 0.toBlockNumber,
|
|
petersburgBlock: 0.toBlockNumber,
|
|
istanbulBlock: 1_561_651.toBlockNumber,
|
|
muirGlacierBlock: 4_460_644.toBlockNumber, # never occured in goerli network
|
|
berlinBlock: 4_460_644.toBlockNumber,
|
|
londonBlock: high(BlockNumber)
|
|
)
|
|
else:
|
|
# everything else will use CustomNet config
|
|
let conf = getConfiguration()
|
|
trace "Custom genesis block configuration loaded", conf=conf.customGenesis.config
|
|
conf.customGenesis.config
|
|
|
|
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 processInteger*(v: string, o: var int): ConfigStatus =
|
|
## Convert string to integer.
|
|
try:
|
|
o = parseInt(v)
|
|
result = Success
|
|
except ValueError:
|
|
result = ErrorParseOption
|
|
|
|
proc processUInt64*(v: string, o: var uint64): ConfigStatus =
|
|
## Convert string to integer.
|
|
try:
|
|
o = parseBiggestUInt(v).uint64
|
|
result = Success
|
|
except ValueError:
|
|
result = ErrorParseOption
|
|
|
|
proc processFloat*(v: string, o: var float): ConfigStatus =
|
|
## Convert string to float.
|
|
try:
|
|
o = parseFloat(v)
|
|
result = Success
|
|
except ValueError:
|
|
result = ErrorParseOption
|
|
|
|
proc processAddressPort(addrStr: string, ta: var TransportAddress): ConfigStatus =
|
|
try:
|
|
ta = initTAddress(addrStr)
|
|
return Success
|
|
except CatchableError:
|
|
return ErrorParseOption
|
|
|
|
proc processAddressPortsList(v: string,
|
|
o: var seq[TransportAddress]): ConfigStatus =
|
|
## Convert <hostname:port>;...;<hostname:port> to list of `TransportAddress`.
|
|
var list = newSeq[string]()
|
|
processList(v, list)
|
|
for item in list:
|
|
var ta: TransportAddress
|
|
if processAddressPort(item, ta) == Success:
|
|
o.add ta
|
|
else:
|
|
return ErrorParseOption
|
|
result = Success
|
|
|
|
proc processRpcApiList(v: string, flags: var set[RpcFlags]): ConfigStatus =
|
|
var list = newSeq[string]()
|
|
processList(v, list)
|
|
result = Success
|
|
for item in list:
|
|
case item.toLowerAscii()
|
|
of "eth": flags.incl RpcFlags.Eth
|
|
of "debug": flags.incl RpcFlags.Debug
|
|
else:
|
|
warn "unknown rpc api", name = item
|
|
result = ErrorIncorrectOption
|
|
|
|
proc processProtocolList(v: string, flags: var set[ProtocolFlags]): ConfigStatus =
|
|
var list = newSeq[string]()
|
|
processList(v, list)
|
|
result = Success
|
|
for item in list:
|
|
case item.toLowerAscii()
|
|
of "eth": flags.incl ProtocolFlags.Eth
|
|
of "les": flags.incl ProtocolFlags.Les
|
|
else:
|
|
warn "unknown protocol", name = item
|
|
result = ErrorIncorrectOption
|
|
|
|
proc processENode(v: string, o: var ENode): ConfigStatus =
|
|
## Convert string to ENode.
|
|
let res = ENode.fromString(v)
|
|
if res.isOk:
|
|
o = res[]
|
|
result = Success
|
|
else:
|
|
result = ErrorParseOption
|
|
|
|
proc processENodesList(v: string, o: var seq[ENode]): ConfigStatus =
|
|
## Convert comma-separated list of strings to list of ENode.
|
|
var
|
|
node: ENode
|
|
list = newSeq[string]()
|
|
processList(v, list)
|
|
for item in list:
|
|
result = processENode(item, node)
|
|
if result == Success:
|
|
o.add(node)
|
|
else:
|
|
break
|
|
|
|
proc processPrivateKey(v: string, o: var PrivateKey): ConfigStatus =
|
|
## Convert hexadecimal string to private key object.
|
|
let seckey = PrivateKey.fromHex(v)
|
|
if seckey.isOk():
|
|
o = seckey[]
|
|
return Success
|
|
|
|
result = ErrorParseOption
|
|
|
|
proc processPruneList(v: string, flags: var PruneMode): ConfigStatus =
|
|
var list = newSeq[string]()
|
|
processList(v, list)
|
|
result = Success
|
|
for item in list:
|
|
case item.toLowerAscii()
|
|
of "full": flags = PruneMode.Full
|
|
of "archive": flags = PruneMode.Archive
|
|
else:
|
|
warn "unknown prune flags", name = item
|
|
result = ErrorIncorrectOption
|
|
|
|
proc processEthAddress(value: string, address: var EthAddress): ConfigStatus =
|
|
try:
|
|
address = hexToByteArray[20](value)
|
|
return Success
|
|
except CatchableError:
|
|
return ErrorParseOption
|
|
|
|
proc importPrivateKey(conf: NimbusConfiguration, fileName: string): ConfigStatus =
|
|
|
|
try:
|
|
let pkhex = readFile(fileName)
|
|
let res = PrivateKey.fromHex(pkhex)
|
|
if res.isErr:
|
|
error "not a valid private key, expect 32 bytes hex"
|
|
return ErrorParseOption
|
|
|
|
let seckey = res.get()
|
|
let acc = seckey.toPublicKey().toCanonicalAddress()
|
|
|
|
conf.accounts[acc] = NimbusAccount(
|
|
privateKey: seckey,
|
|
unlocked: true
|
|
)
|
|
|
|
except CatchableError:
|
|
return ErrorParseOption
|
|
|
|
proc processEthArguments(key, value: string): ConfigStatus =
|
|
result = Success
|
|
let config = getConfiguration()
|
|
case key.toLowerAscii()
|
|
of "keystore":
|
|
config.keyStore = value
|
|
of "datadir":
|
|
config.dataDir = value
|
|
of "prune":
|
|
result = processPruneList(value, config.prune)
|
|
of "import":
|
|
config.importFile = value
|
|
of "verifyfrom":
|
|
var res = 0u64
|
|
result = processUInt64(value, res)
|
|
config.verifyFrom = uint64(result)
|
|
config.verifyFromOk = true
|
|
of "engine-signer":
|
|
result = processEthAddress(value, config.engineSigner)
|
|
of "import-key":
|
|
result = config.importPrivateKey(value)
|
|
else:
|
|
result = EmptyOption
|
|
|
|
proc processRpcArguments(key, value: string): ConfigStatus =
|
|
## Processes only `RPC` related command line options
|
|
result = Success
|
|
let config = getConfiguration()
|
|
let skey = key.toLowerAscii()
|
|
if skey == "rpc":
|
|
if RpcFlags.Enabled notin config.rpc.flags:
|
|
config.rpc.flags.incl(RpcFlags.Enabled)
|
|
config.rpc.flags.incl(defaultRpcApi)
|
|
elif skey == "rpcbind":
|
|
config.rpc.binds.setLen(0)
|
|
result = processAddressPortsList(value, config.rpc.binds)
|
|
elif skey == "rpcapi":
|
|
if RpcFlags.Enabled in config.rpc.flags:
|
|
config.rpc.flags.excl(defaultRpcApi)
|
|
else:
|
|
config.rpc.flags.incl(RpcFlags.Enabled)
|
|
result = processRpcApiList(value, config.rpc.flags)
|
|
else:
|
|
result = EmptyOption
|
|
|
|
proc processGraphqlArguments(key, value: string): ConfigStatus =
|
|
## Processes only `Graphql` related command line options
|
|
result = Success
|
|
let conf = getConfiguration()
|
|
case key.toLowerAscii()
|
|
of "graphql":
|
|
conf.graphql.enabled = true
|
|
of "graphqlbind":
|
|
result = processAddressPort(value, conf.graphql.address)
|
|
else:
|
|
result = EmptyOption
|
|
|
|
proc processWsArguments(key, value: string): ConfigStatus =
|
|
## Processes only `Websocket RPC` related command line options
|
|
result = Success
|
|
let config = getConfiguration()
|
|
let skey = key.toLowerAscii()
|
|
if skey == "ws":
|
|
if RpcFlags.Enabled notin config.ws.flags:
|
|
config.ws.flags.incl(RpcFlags.Enabled)
|
|
config.ws.flags.incl(defaultRpcApi)
|
|
elif skey == "wsbind":
|
|
config.ws.binds.setLen(0)
|
|
result = processAddressPortsList(value, config.ws.binds)
|
|
elif skey == "wsapi":
|
|
if RpcFlags.Enabled in config.ws.flags:
|
|
config.ws.flags.excl(defaultRpcApi)
|
|
else:
|
|
config.ws.flags.incl(RpcFlags.Enabled)
|
|
result = processRpcApiList(value, config.ws.flags)
|
|
else:
|
|
result = EmptyOption
|
|
|
|
proc setBootnodes(onodes: var seq[ENode], nodeUris: openarray[string]) =
|
|
var node: ENode
|
|
onodes = newSeqOfCap[ENode](nodeUris.len)
|
|
for item in nodeUris:
|
|
doAssert(processENode(item, node) == Success)
|
|
onodes.add(node)
|
|
|
|
|
|
proc setNetwork(conf: var NetConfiguration, id: NetworkId) =
|
|
## Set network id and default network bootnodes
|
|
conf.networkId = id
|
|
case id
|
|
of MainNet:
|
|
conf.bootNodes.setBootnodes(MainnetBootnodes)
|
|
of RopstenNet:
|
|
conf.bootNodes.setBootnodes(RopstenBootnodes)
|
|
of RinkebyNet:
|
|
conf.bootNodes.setBootnodes(RinkebyBootnodes)
|
|
of GoerliNet:
|
|
conf.bootNodes.setBootnodes(GoerliBootnodes)
|
|
of KovanNet:
|
|
conf.bootNodes.setBootnodes(KovanBootnodes)
|
|
else:
|
|
# everything else will use bootnodes
|
|
# from --bootnodes switch
|
|
discard
|
|
|
|
proc processNetArguments(key, value: string): ConfigStatus =
|
|
## Processes only `Networking` related command line options
|
|
result = Success
|
|
let config = getConfiguration()
|
|
let skey = key.toLowerAscii()
|
|
if skey == "bootnodes":
|
|
result = processENodesList(value, config.net.customBootNodes)
|
|
elif skey == "bootnodesv4":
|
|
result = processENodesList(value, config.net.customBootNodes)
|
|
elif skey == "bootnodesv5":
|
|
result = processENodesList(value, config.net.customBootNodes)
|
|
elif skey == "staticnodes":
|
|
result = processENodesList(value, config.net.staticNodes)
|
|
elif skey == "testnet":
|
|
config.net.setNetwork(RopstenNet)
|
|
elif skey == "mainnet":
|
|
config.net.setNetwork(MainNet)
|
|
elif skey == "ropsten":
|
|
config.net.setNetwork(RopstenNet)
|
|
elif skey == "rinkeby":
|
|
config.net.setNetwork(RinkebyNet)
|
|
elif skey == "goerli":
|
|
config.net.setNetwork(GoerliNet)
|
|
elif skey == "kovan":
|
|
config.net.setNetwork(KovanNet)
|
|
elif skey == "customnetwork":
|
|
if not loadCustomGenesis(value, config.customGenesis):
|
|
result = Error
|
|
if NetworkIdSet notin config.net.flags:
|
|
# prevent clash with --networkid if it already set
|
|
# because any --networkid value that is not
|
|
# in the public network will also translated as
|
|
# CustomNetwork
|
|
config.net.networkId = CustomNet
|
|
elif skey == "networkid":
|
|
var res = 0
|
|
result = processInteger(value, res)
|
|
if result == Success:
|
|
config.net.setNetwork(NetworkId(res))
|
|
config.net.flags.incl NetworkIdSet
|
|
elif skey == "nodiscover":
|
|
config.net.flags.incl(NoDiscover)
|
|
elif skey == "v5discover":
|
|
config.net.flags.incl(V5Discover)
|
|
config.net.customBootNodes.setBootnodes(DiscoveryV5Bootnodes)
|
|
elif skey == "port":
|
|
var res = 0
|
|
result = processInteger(value, res)
|
|
if result == Success:
|
|
config.net.bindPort = uint16(res and 0xFFFF)
|
|
elif skey == "discport":
|
|
var res = 0
|
|
result = processInteger(value, res)
|
|
if result == Success:
|
|
config.net.discPort = uint16(res and 0xFFFF)
|
|
elif skey == "metrics":
|
|
config.net.metricsServer = true
|
|
elif skey == "metricsport":
|
|
var res = 0
|
|
result = processInteger(value, res)
|
|
if result == Success:
|
|
config.net.metricsServerPort = uint16(res and 0xFFFF)
|
|
elif skey == "maxpeers":
|
|
var res = 0
|
|
result = processInteger(value, res)
|
|
if result == Success:
|
|
config.net.maxPeers = res
|
|
elif skey == "maxpendpeers":
|
|
var res = 0
|
|
result = processInteger(value, res)
|
|
if result == Success:
|
|
config.net.maxPendingPeers = res
|
|
elif skey == "nodekey":
|
|
var res: PrivateKey
|
|
result = processPrivateKey(value, res)
|
|
if result == Success:
|
|
config.net.nodeKey = res
|
|
elif skey == "ident":
|
|
config.net.ident = value
|
|
elif skey == "nat":
|
|
case value.toLowerAscii:
|
|
of "any":
|
|
config.net.nat = NatAny
|
|
of "upnp":
|
|
config.net.nat = NatUpnp
|
|
of "pmp":
|
|
config.net.nat = NatPmp
|
|
of "none":
|
|
config.net.nat = NatNone
|
|
else:
|
|
if isIpAddress(value):
|
|
config.net.externalIP = value
|
|
config.net.nat = NatNone
|
|
else:
|
|
error "not a valid NAT mechanism, nor a valid IP address", value
|
|
result = ErrorParseOption
|
|
elif skey == "protocols":
|
|
config.net.protocols = {}
|
|
result = processProtocolList(value, config.net.protocols)
|
|
else:
|
|
result = EmptyOption
|
|
|
|
proc processDebugArguments(key, value: string): ConfigStatus =
|
|
## Processes only `Debug` related command line options
|
|
let config = getConfiguration()
|
|
result = Success
|
|
let skey = key.toLowerAscii()
|
|
if skey == "debug":
|
|
config.debug.flags.incl(DebugFlags.Enabled)
|
|
elif skey == "test":
|
|
var res = newSeq[string]()
|
|
processList(value, res)
|
|
for item in res:
|
|
if item == "test1":
|
|
config.debug.flags.incl(DebugFlags.Test1)
|
|
elif item == "test2":
|
|
config.debug.flags.incl(DebugFlags.Test2)
|
|
elif item == "test3":
|
|
config.debug.flags.incl(DebugFlags.Test3)
|
|
elif skey == "log-level":
|
|
try:
|
|
let logLevel = parseEnum[LogLevel](value)
|
|
if logLevel >= enabledLogLevel:
|
|
config.debug.logLevel = logLevel
|
|
else:
|
|
result = ErrorIncorrectOption
|
|
except ValueError:
|
|
result = ErrorIncorrectOption
|
|
elif skey == "log-file":
|
|
if len(value) == 0:
|
|
result = ErrorIncorrectOption
|
|
else:
|
|
config.debug.logFile = value
|
|
elif skey == "logmetrics":
|
|
config.debug.logMetrics = true
|
|
elif skey == "logmetricsinterval":
|
|
var res = 0
|
|
result = processInteger(value, res)
|
|
if result == Success:
|
|
config.debug.logMetricsInterval = res
|
|
else:
|
|
result = EmptyOption
|
|
|
|
proc dumpConfiguration*(): string =
|
|
## Dumps current configuration as string
|
|
let config = getConfiguration()
|
|
result = repr config
|
|
|
|
template processArgument(processor, key, value, msg: untyped) =
|
|
## Checks if arguments got processed successfully
|
|
var res = processor(string(key), string(value))
|
|
if res == Success:
|
|
result = res
|
|
continue
|
|
elif res == ErrorParseOption:
|
|
msg = "Error processing option '" & key & "' with value '" & value & "'."
|
|
result = res
|
|
break
|
|
elif res == ErrorIncorrectOption:
|
|
msg = "Incorrect value for option '" & key & "': '" & value & "'."
|
|
result = res
|
|
break
|
|
|
|
proc getDefaultDataDir*(): string =
|
|
when defined(windows):
|
|
"AppData" / "Roaming" / "Nimbus"
|
|
elif defined(macosx):
|
|
"Library" / "Application Support" / "Nimbus"
|
|
else:
|
|
".cache" / "nimbus"
|
|
|
|
proc getDefaultKeystoreDir*(): string =
|
|
getDefaultDataDir() / "keystore"
|
|
|
|
proc initConfiguration(): NimbusConfiguration =
|
|
## Allocates and initializes `NimbusConfiguration` with default values
|
|
result = new NimbusConfiguration
|
|
result.rng = newRng()
|
|
result.accounts = initTable[EthAddress, NimbusAccount]()
|
|
|
|
## Graphql defaults
|
|
result.graphql.enabled = false
|
|
result.graphql.address = initTAddress("127.0.0.1:8547")
|
|
|
|
## RPC defaults
|
|
result.rpc.flags = {}
|
|
result.rpc.binds = @[initTAddress("127.0.0.1:8545")]
|
|
|
|
## Websocket RPC defaults
|
|
result.ws.flags = {}
|
|
result.ws.binds = @[initTAddress("127.0.0.1:8546")]
|
|
|
|
## Network defaults
|
|
result.net.setNetwork(defaultNetwork)
|
|
result.net.maxPeers = 25
|
|
result.net.maxPendingPeers = 0
|
|
result.net.bindPort = 30303'u16
|
|
result.net.discPort = 30303'u16
|
|
result.net.metricsServer = false
|
|
result.net.metricsServerPort = 9093'u16
|
|
result.net.ident = NimbusIdent
|
|
result.net.nat = NatAny
|
|
result.net.protocols = defaultProtocols
|
|
result.net.nodekey = random(PrivateKey, result.rng[])
|
|
|
|
const
|
|
dataDir = getDefaultDataDir()
|
|
keystore = getDefaultKeystoreDir()
|
|
|
|
result.dataDir = getHomeDir() / dataDir
|
|
result.keystore = getHomeDir() / keystore
|
|
result.prune = PruneMode.Full
|
|
|
|
## Debug defaults
|
|
result.debug.flags = {}
|
|
result.debug.logLevel = defaultLogLevel
|
|
result.debug.logMetrics = false
|
|
result.debug.logMetricsInterval = 10
|
|
|
|
proc getConfiguration*(): NimbusConfiguration =
|
|
## Retreive current configuration object `NimbusConfiguration`.
|
|
if isNil(nimbusConfig):
|
|
nimbusConfig = initConfiguration()
|
|
result = nimbusConfig
|
|
|
|
proc getHelpString*(): string =
|
|
var logLevels: seq[string]
|
|
for level in LogLevel:
|
|
if level < enabledLogLevel:
|
|
continue
|
|
logLevels.add($level)
|
|
|
|
result = """
|
|
|
|
USAGE:
|
|
nimbus [options]
|
|
|
|
ETHEREUM OPTIONS:
|
|
--datadir:<value> Base directory for all blockchain-related data
|
|
--keystore:<value> Directory for the keystore (default: inside datadir)
|
|
--prune:<value> Blockchain prune mode (full or archive, default: full)
|
|
--import:<path> Import RLP encoded block(s), validate, write to database and quit
|
|
--engine-signer:<value> Enables mining. value is EthAddress in hex
|
|
--import-key:<path> Import unencrypted 32 bytes hex private key file
|
|
|
|
NETWORKING OPTIONS:
|
|
--bootnodes:<value> Comma separated enode URLs for P2P discovery bootstrap (set v4+v5 instead for light servers)
|
|
--bootnodesv4:<value> Comma separated enode URLs for P2P v4 discovery bootstrap (light server, full nodes)
|
|
--bootnodesv5:<value> Comma separated enode URLs for P2P v5 discovery bootstrap (light server, light nodes)
|
|
--staticnodes:<value> Comma separated enode URLs to connect with
|
|
--port:<value> Network listening TCP port (default: 30303)
|
|
--discport:<value> Network listening UDP port (defaults to --port argument)
|
|
--maxpeers:<value> Maximum number of network peers (default: 25)
|
|
--maxpendpeers:<value> Maximum number of pending connection attempts (default: 0)
|
|
--nat:<value> NAT port mapping mechanism (any|none|upnp|pmp|<external IP>) (default: "any")
|
|
--nodiscover Disables the peer discovery mechanism (manual peer addition)
|
|
--v5discover Enables the experimental RLPx V5 (topic discovery) mechanism
|
|
--nodekey:<value> P2P node private key (as hexadecimal string)
|
|
--ident:<value> Client identifier (default is '$1')
|
|
--protocols:<value> Enable specific set of protocols (default: $4)
|
|
|
|
ETHEREUM NETWORK OPTIONS:
|
|
--mainnet Use Ethereum main network (default)
|
|
--testnet Use Ropsten test network
|
|
--ropsten Use Ropsten test network (proof-of-work, the one most like Ethereum mainnet)
|
|
--goerli Use Görli test network (proof-of-authority, works across all clients)
|
|
--rinkeby Use Rinkeby test network (proof-of-authority, for those running Geth clients)
|
|
--kovan Use Kovan test network (proof-of-authority, for those running OpenEthereum clients)
|
|
--networkid:<value> Network id (0=custom, 1=mainnet, 3=ropsten, 4=rinkeby, 5=goerli, 42=kovan, other...)
|
|
--customnetwork:<path> Use custom genesis block for private Ethereum Network (as /path/to/genesis.json)
|
|
|
|
LOCAL SERVICE OPTIONS:
|
|
--metrics Enable the metrics HTTP server
|
|
--metricsport:<value> Set port (always on localhost) metrics HTTP server will bind to (default: 9093)
|
|
--rpc Enable the HTTP-RPC server
|
|
--rpcbind:<value> Set address:port pair(s) (comma-separated) HTTP-RPC server will bind to (default: localhost:8545)
|
|
--rpcapi:<value> Enable specific set of RPC API from list (comma-separated) (available: eth, debug)
|
|
--graphql Enable the HTTP-GraphQL server
|
|
--graphqlbind:<value> Set address:port pair GraphQL server will bind (default: localhost:8547)
|
|
--ws Enable the Websocket JSON-RPC server
|
|
--wsbind:<value> Set address:port pair Websocket JSON-RPC server will bind to (default: localhost:8546)
|
|
--wsapi:<value> Enable specific set of Websocket RPC API from list (comma-separated) (available: eth, debug)
|
|
|
|
LOGGING AND DEBUGGING OPTIONS:
|
|
--log-level:<value> One of: $2 (default: $3)
|
|
--log-file:<value> Optional log file, replacing stdout
|
|
--logmetrics Enable metrics logging
|
|
--logmetricsinterval:<value> Interval at which to log metrics, in seconds (default: 10)
|
|
--debug Enable debug mode
|
|
--test:<value> Perform specified test
|
|
""" % [
|
|
NimbusIdent,
|
|
join(logLevels, ", "),
|
|
$defaultLogLevel,
|
|
strip($defaultProtocols, chars = {'{','}'}),
|
|
]
|
|
|
|
when declared(os.paramCount): # not available with `--app:lib`
|
|
proc processArguments*(msg: var string, opt: var OptParser): ConfigStatus =
|
|
## Process command line argument and update `NimbusConfiguration`.
|
|
let config = getConfiguration()
|
|
|
|
# At this point `config.net.discPort` is likely populated with network default
|
|
# discPort. We want to override those if it is specified on the command line.
|
|
config.net.discPort = 0
|
|
|
|
var length = 0
|
|
for kind, key, value in opt.getopt():
|
|
result = Error
|
|
case kind
|
|
of cmdArgument:
|
|
discard
|
|
of cmdLongOption, cmdShortOption:
|
|
inc(length)
|
|
case key.toLowerAscii()
|
|
of "help", "h":
|
|
msg = getHelpString()
|
|
result = Success
|
|
break
|
|
of "version", "ver", "v":
|
|
msg = NimbusVersion
|
|
result = Success
|
|
break
|
|
else:
|
|
processArgument processEthArguments, key, value, msg
|
|
processArgument processRpcArguments, key, value, msg
|
|
processArgument processNetArguments, key, value, msg
|
|
processArgument processDebugArguments, key, value, msg
|
|
processArgument processGraphqlArguments, key, value, msg
|
|
processArgument processWsArguments, key, value, msg
|
|
if result != Success:
|
|
msg = "Unknown option: '" & key & "'."
|
|
break
|
|
of cmdEnd:
|
|
doAssert(false) # we're never getting this kind here
|
|
|
|
if config.net.discPort == 0:
|
|
config.net.discPort = config.net.bindPort
|
|
|
|
if NetworkIdSet notin config.net.flags and config.net.networkId == CustomNet:
|
|
# 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
|
|
# we are using custom genesis/custom chain config via json file.
|
|
config.net.networkId = NetworkId(config.customGenesis.config.chainId)
|
|
|
|
proc processArguments*(msg: var string): ConfigStatus =
|
|
var opt = initOptParser()
|
|
processArguments(msg, opt)
|
|
|
|
proc processConfiguration*(pathname: string): ConfigStatus =
|
|
## Process configuration file `pathname` and update `NimbusConfiguration`.
|
|
result = Success
|