nimbus-eth1/nimbus/config.nim

1042 lines
37 KiB
Nim
Raw Normal View History

2018-04-27 08:53:53 +00:00
# Nimbus
# Copyright (c) 2018-2019 Status Research & Development GmbH
2018-04-27 08:53:53 +00:00
# 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],
2019-04-17 01:56:28 +00:00
chronos, eth/[keys, common, p2p, net/nat], chronicles, nimcrypto/hash,
2019-08-15 11:08:07 +00:00
eth/p2p/bootnodes, eth/p2p/rlpx_protocols/whisper_protocol,
2020-07-20 17:16:59 +00:00
./db/select_backend, eth/keys,
./vm_types2
2018-04-27 08:53:53 +00:00
2019-01-15 15:25:14 +00:00
const
2018-04-27 08:53:53 +00:00
NimbusName* = "Nimbus"
## project name string
2018-04-27 08:53:53 +00:00
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.
2019-01-15 15:25:14 +00:00
NimbusIdent* = "$1/$2 ($3/$4)" % [NimbusName, NimbusVersion, hostCPU, hostOS]
## project ident name for networking services
2020-07-22 11:40:12 +00:00
GitRevision = staticExec("git rev-parse --short HEAD").replace("\n") # remove CR
NimVersion = staticExec("nim --version")
2019-01-15 15:25:14 +00:00
2019-11-10 17:44:40 +00:00
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]
2018-06-20 17:27:32 +00:00
2018-04-27 08:53:53 +00:00
type
ConfigStatus* = enum
## Configuration status flags
2018-06-20 17:27:32 +00:00
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
2018-04-27 08:53:53 +00:00
RpcFlags* {.pure.} = enum
## RPC flags
2018-06-20 17:27:32 +00:00
Enabled ## RPC enabled
Eth ## enable eth_ set of RPC API
Shh ## enable shh_ set of RPC API
Debug ## enable debug_ set of RPC API
2018-04-27 08:53:53 +00:00
ProtocolFlags* {.pure.} = enum
## Protocol flags
Eth ## enable eth subprotocol
Shh ## enable whisper subprotocol
Les ## enable les subprotocol
2018-04-27 08:53:53 +00:00
RpcConfiguration* = object
## JSON-RPC configuration object
2018-06-20 17:27:32 +00:00
flags*: set[RpcFlags] ## RPC flags
binds*: seq[TransportAddress] ## RPC bind address
2018-04-27 08:53:53 +00:00
GraphqlConfiguration* = object
enabled*: bool
address*: TransportAddress
2018-08-01 12:50:44 +00:00
PublicNetwork* = enum
CustomNet = 0
MainNet = 1
# No longer used: MordenNet = 2
2018-08-01 12:50:44 +00:00
RopstenNet = 3
RinkebyNet = 4
GoerliNet = 5
2018-08-01 12:50:44 +00:00
KovanNet = 42
2018-04-27 08:53:53 +00:00
NetworkFlags* = enum
## Ethereum network flags
2018-06-20 17:27:32 +00:00
NoDiscover, ## Peer discovery disabled
V5Discover, ## Dicovery V5 enabled
2018-04-27 08:53:53 +00:00
DebugFlags* {.pure.} = enum
## Debug selection flags
2018-06-20 17:27:32 +00:00
Enabled, ## Debugging enabled
Test1, ## Test1 enabled
Test2, ## Test2 enabled
Test3 ## Test3 enabled
2018-04-27 08:53:53 +00:00
NetConfiguration* = object
## Network configuration object
2018-06-20 17:27:32 +00:00
flags*: set[NetworkFlags] ## Network flags
bootNodes*: seq[ENode] ## List of bootnodes
staticNodes*: seq[ENode] ## List of static nodes to connect to
2018-06-20 17:27:32 +00:00
bindPort*: uint16 ## Main TCP bind port
discPort*: uint16 ## Discovery UDP bind port
metricsServer*: bool ## Enable metrics server
metricsServerPort*: uint16 ## metrics HTTP server port
2018-06-20 17:27:32 +00:00
maxPeers*: int ## Maximum allowed number of peers
maxPendingPeers*: int ## Maximum allowed pending peers
networkId*: NetworkId ## Network ID as integer
2018-06-20 17:27:32 +00:00
ident*: string ## Server ident name string
nodeKey*: PrivateKey ## Server private key
2019-04-17 01:56:28 +00:00
nat*: NatStrategy ## NAT strategy
externalIP*: string ## user-provided external IP
protocols*: set[ProtocolFlags]## Enabled subprotocols
2018-04-27 08:53:53 +00:00
DebugConfiguration* = object
## Debug configuration object
2018-06-20 17:27:32 +00:00
flags*: set[DebugFlags] ## Debug flags
logLevel*: LogLevel ## Log level
logFile*: string ## Log file
logMetrics*: bool ## Enable metrics logging
logMetricsInterval*: int ## Metrics logging interval
2018-04-27 08:53:53 +00:00
2018-11-30 03:03:30 +00:00
PruneMode* {.pure.} = enum
Full
Archive
NimbusAccount* = object
privateKey*: PrivateKey
keystore*: JsonNode
unlocked*: bool
# beware that although in some cases
# chainId have identical value to networkId
# they are separate entity
ChainId* = distinct uint
2018-08-01 12:50:44 +00:00
ChainConfig* = object
chainId*: ChainId
2018-08-01 12:50:44 +00:00
homesteadBlock*: BlockNumber
daoForkBlock*: BlockNumber
daoForkSupport*: bool
# EIP150 implements the Gas price changes (https://github.com/ethereum/EIPs/issues/150)
eip150Block*: BlockNumber
eip150Hash*: Hash256
eip155Block*: BlockNumber
eip158Block*: BlockNumber
byzantiumBlock*: BlockNumber
constantinopleBlock*: BlockNumber
petersburgBlock*: BlockNumber
istanbulBlock*: BlockNumber
2020-04-11 09:59:46 +00:00
muirGlacierBlock*: BlockNumber
2020-11-19 04:59:53 +00:00
berlinBlock*: BlockNumber
2020-04-11 09:59:46 +00:00
2018-04-27 08:53:53 +00:00
NimbusConfiguration* = ref object
## Main Nimbus configuration object
dataDir*: string
keyStore*: string
2018-11-30 03:03:30 +00:00
prune*: PruneMode
graphql*: GraphqlConfiguration
2018-06-20 17:27:32 +00:00
rpc*: RpcConfiguration ## JSON-RPC configuration
net*: NetConfiguration ## Network configuration
debug*: DebugConfiguration ## Debug configuration
shh*: WhisperConfig ## Whisper configuration
2020-01-13 18:35:40 +00:00
customGenesis*: CustomGenesisConfig ## Custom Genesis Configuration
2020-07-20 17:16:59 +00:00
# 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
2020-01-13 18:35:40 +00:00
CustomGenesisConfig* = object
chainId*: ChainId
2020-01-13 18:35:40 +00:00
homesteadBlock*: BlockNumber
daoForkBlock*: BlockNumber
daoForkSupport*: bool
eip150Block*: BlockNumber
eip150Hash*: Hash256
eip155Block*: BlockNumber
eip158Block*: BlockNumber
byzantiumBlock*: BlockNumber
constantinopleBlock*: BlockNumber
petersburgBlock*: BlockNumber
istanbulBlock*: BlockNumber
2020-04-12 10:07:09 +00:00
muirGlacierBlock*: BlockNumber
2020-11-19 04:59:53 +00:00
berlinBlock*: BlockNumber
2020-01-13 18:35:40 +00:00
nonce*: BlockNonce
extraData*: seq[byte]
gasLimit*: int64
difficulty*: UInt256
prealloc*: JsonNode
mixHash*: Hash256
coinBase*: EthAddress
timestamp*: EthTime
2018-04-27 08:53:53 +00:00
const
defaultRpcApi = {RpcFlags.Eth, RpcFlags.Shh}
defaultProtocols = {ProtocolFlags.Eth, ProtocolFlags.Shh}
defaultLogLevel = LogLevel.WARN
defaultNetwork = MainNet
2018-04-27 08:53:53 +00:00
var nimbusConfig {.threadvar.}: NimbusConfiguration
2018-06-20 17:27:32 +00:00
proc getConfiguration*(): NimbusConfiguration {.gcsafe.}
2018-04-27 08:53:53 +00:00
proc `$`*(c: ChainId): string =
$(c.int)
2020-04-12 10:07:09 +00:00
proc toFork*(c: ChainConfig, number: BlockNumber): Fork =
2020-11-19 04:59:53 +00:00
if number >= c.berlinBlock: FkBerlin
elif number >= c.istanbulBlock: FkIstanbul
2020-04-12 10:07:09 +00:00
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
2020-04-11 09:59:46 +00:00
2020-01-13 18:35:40 +00:00
proc privateChainConfig*(): ChainConfig =
let config = getConfiguration()
result = ChainConfig(
chainId: config.customGenesis.chainId,
homesteadBlock: config.customGenesis.homesteadBlock,
daoForkSupport: config.customGenesis.daoForkSupport,
daoForkBlock: config.customGenesis.daoForkBlock,
eip150Block: config.customGenesis.eip150Block,
eip150Hash: config.customGenesis.eip150Hash,
eip155Block: config.customGenesis.eip155Block,
eip158Block: config.customGenesis.eip158Block,
byzantiumBlock: config.customGenesis.byzantiumBlock,
constantinopleBlock: config.customGenesis.constantinopleBlock,
petersburgBlock: config.customGenesis.petersburgBlock,
2020-04-11 09:59:46 +00:00
istanbulBlock: config.customGenesis.istanbulBlock,
2020-11-19 04:59:53 +00:00
muirGlacierBlock: config.customGenesis.muirGlacierBlock,
berlinBlock: config.customGenesis.berlinBlock
2020-01-13 18:35:40 +00:00
)
trace "Custom genesis block configuration loaded", configuration=result
2018-08-01 12:50:44 +00:00
proc publicChainConfig*(id: PublicNetwork): ChainConfig =
# For some public networks, NetworkId and ChainId value are identical
# but that is not always the case
2018-08-01 12:50:44 +00:00
result = case id
of MainNet:
ChainConfig(
chainId: MainNet.ChainId,
2020-04-11 09:59:46 +00:00
homesteadBlock: 1_150_000.toBlockNumber, # 14/03/2016 20:49:53
daoForkBlock: 1_920_000.toBlockNumber,
2018-08-01 12:50:44 +00:00
daoForkSupport: true,
2020-04-11 09:59:46 +00:00
eip150Block: 2_463_000.toBlockNumber, # 18/10/2016 17:19:31
2018-08-01 12:50:44 +00:00
eip150Hash: toDigest("2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0"),
2020-04-11 09:59:46 +00:00
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
2020-11-19 04:59:53 +00:00
muirGlacierBlock: 9_200_000.toBlockNumber, # 02/01/2020 08:30:49
berlinBlock: high(BlockNumber).toBlockNumber
2018-08-01 12:50:44 +00:00
)
of RopstenNet:
ChainConfig(
chainId: RopstenNet.ChainId,
homesteadBlock: 0.toBlockNumber,
2020-04-08 09:53:26 +00:00
daoForkSupport: false,
eip150Block: 0.toBlockNumber,
2018-08-01 12:50:44 +00:00
eip150Hash: toDigest("41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d"),
eip155Block: 10.toBlockNumber,
eip158Block: 10.toBlockNumber,
2020-04-11 09:59:46 +00:00
byzantiumBlock: 1_700_000.toBlockNumber,
constantinopleBlock: 4_230_000.toBlockNumber,
petersburgBlock:4_939_394.toBlockNumber,
istanbulBlock: 6_485_846.toBlockNumber,
2020-11-19 04:59:53 +00:00
muirGlacierBlock: 7_117_117.toBlockNumber,
berlinBlock: high(BlockNumber).toBlockNumber
2018-08-01 12:50:44 +00:00
)
of RinkebyNet:
ChainConfig(
chainId: RinkebyNet.ChainId,
homesteadBlock: 1.toBlockNumber,
2020-04-08 09:53:26 +00:00
daoForkSupport: false,
eip150Block: 2.toBlockNumber,
2018-08-01 12:50:44 +00:00
eip150Hash: toDigest("9b095b36c15eaf13044373aef8ee0bd3a382a5abb92e402afa44b8249c3a90e9"),
eip155Block: 3.toBlockNumber,
eip158Block: 3.toBlockNumber,
2020-04-11 09:59:46 +00:00
byzantiumBlock: 1_035_301.toBlockNumber,
constantinopleBlock: 3_660_663.toBlockNumber,
petersburgBlock:4_321_234.toBlockNumber,
istanbulBlock: 5_435_345.toBlockNumber,
2020-11-19 04:59:53 +00:00
muirGlacierBlock: high(BlockNumber).toBlockNumber,
berlinBlock: high(BlockNumber).toBlockNumber
2018-08-01 12:50:44 +00:00
)
of GoerliNet:
ChainConfig(
chainId: GoerliNet.ChainId,
homesteadBlock: 0.toBlockNumber,
daoForkSupport: false,
eip150Block: 0.toBlockNumber,
eip150Hash: toDigest("0000000000000000000000000000000000000000000000000000000000000000"),
eip155Block: 0.toBlockNumber,
eip158Block: 0.toBlockNumber,
byzantiumBlock: 0.toBlockNumber,
2020-04-11 09:59:46 +00:00
constantinopleBlock: 0.toBlockNumber,
petersburgBlock: 0.toBlockNumber,
istanbulBlock: 1_561_651.toBlockNumber,
2020-11-19 04:59:53 +00:00
muirGlacierBlock: high(BlockNumber).toBlockNumber,
berlinBlock: high(BlockNumber).toBlockNumber
)
2020-01-13 18:35:40 +00:00
of CustomNet:
privateChainConfig()
2018-08-01 12:50:44 +00:00
else:
2018-08-03 11:10:07 +00:00
error "No chain config for public network", networkId = id
doAssert(false, "No chain config for " & $id)
ChainConfig()
2018-08-01 12:50:44 +00:00
2020-01-13 18:35:40 +00:00
proc processCustomGenesisConfig(customGenesis: JsonNode): ConfigStatus =
## Parses Custom Genesis Block config options when customnetwork option provided
template checkForFork(chain, currentFork, previousFork: untyped) =
# Template to load fork blocks and validate order
let currentForkName = currentFork.astToStr()
if chain.hasKey(currentForkName):
if chain[currentForkName].kind == JInt:
currentFork = chain[currentForkName].getInt().toBlockNumber
if currentFork < previousFork:
error "Forks can't be assigned out of order"
quit(1)
else:
error "Invalid block value provided for", currentForkName, invalidValue=currentFork
quit(1)
proc parseConfig(T: type, c: JsonNode, field: string): T =
when T is string:
c[field].getStr()
2020-01-13 18:35:40 +00:00
elif T is Uint256:
hexToUint[256](c[field].getStr())
2020-01-13 18:35:40 +00:00
elif T is bool:
c[field].getBool()
elif T is Hash256:
MDigest[256].fromHex(c[field].getStr())
elif T is uint:
c[field].getInt().uint
elif T is Blocknonce:
(parseHexInt(c[field].getStr()).uint64).toBlockNonce
elif T is EthTime:
fromHex[int64](c[field].getStr()).fromUnix
elif T is EthAddress:
parseAddress(c[field].getStr())
elif T is seq[byte]:
hexToSeqByte(c[field].getStr())
elif T is int64:
fromHex[int64](c[field].getStr())
elif T is ChainId:
c[field].getInt().T
2020-01-13 18:35:40 +00:00
template validateConfigValue(chainDetails, field, jtype, T: untyped, checkError: static[bool] = true) =
let fieldName = field.astToStr()
if chainDetails.hasKey(fieldName):
if chainDetails[fieldName].kind == jtype:
field = parseConfig(T, chainDetails, fieldName)
else:
error "Invalid value provided for ", fieldName
quit(1)
else:
when checkError:
error "No value found in genesis block for", fieldName
quit(1)
let config = getConfiguration()
2020-01-13 18:35:40 +00:00
result = Success
var
2021-03-31 07:18:21 +00:00
chainId = 1.ChainId
homesteadBlock, daoForkblock, eip150Block, eip155Block, eip158Block, byzantiumBlock, constantinopleBlock = high(BlockNumber).toBlockNumber
petersburgBlock, istanbulBlock, muirGlacierBlock, berlinBlock = high(BlockNumber).toBlockNumber
2020-01-13 18:35:40 +00:00
eip150Hash, mixHash : MDigest[256]
daoForkSupport = false
nonce = 66.toBlockNonce
extraData = hexToSeqByte("0x")
gasLimit = 16777216.int64
difficulty = 1048576.u256
alloc = parseJson("{}")
timestamp : EthTime
coinbase : EthAddress
if customGenesis.hasKey("config"):
# Validate all fork blocks for custom genesis
2020-01-13 18:35:40 +00:00
let forkDetails = customGenesis["config"]
2021-03-31 07:18:21 +00:00
validateConfigValue(forkDetails, chainId, JInt, ChainId, checkError=false)
2020-01-13 18:35:40 +00:00
checkForFork(forkDetails, homesteadBlock, 0.toBlockNumber)
validateConfigValue(forkDetails, daoForkSupport, JBool, bool, checkError=false)
if daoForkSupport == true:
checkForFork(forkDetails, daoForkBlock, 0.toBlockNumber)
2020-01-13 18:35:40 +00:00
checkForFork(forkDetails, eip150Block, homesteadBlock)
validateConfigValue(forkDetails, eip150Hash, JString, Hash256, checkError = false)
2020-01-13 18:35:40 +00:00
checkForFork(forkDetails, eip155Block, eip150Block)
checkForFork(forkDetails, eip158Block, eip155Block)
checkForFork(forkDetails, byzantiumBlock, eip158Block)
checkForFork(forkDetails, constantinopleBlock, byzantiumBlock)
checkForFork(forkDetails, petersburgBlock, constantinopleBlock)
checkForFork(forkDetails, istanbulBlock, petersburgBlock)
2020-04-12 10:07:09 +00:00
checkForFork(forkDetails, muirGlacierBlock, istanbulBlock)
2020-11-19 04:59:53 +00:00
checkForFork(forkDetails, istanbulBlock, berlinBlock)
validateConfigValue(customGenesis, nonce, JString, BlockNonce, checkError = false)
2020-01-13 18:35:40 +00:00
validateConfigValue(customGenesis, extraData, JSTring, seq[byte], checkError = false)
validateConfigValue(customGenesis, gasLimit, JString, int64, checkError = false)
validateConfigValue(customGenesis, difficulty, JString, UInt256)
if customGenesis.hasKey("alloc"):
alloc = customGenesis["alloc"]
validateConfigValue(customGenesis, mixHash, JString, Hash256, checkError = false)
2020-01-13 18:35:40 +00:00
validateConfigValue(customGenesis, coinbase, JString, EthAddress, checkError = false)
validateConfigValue(customGenesis, timestamp, JString, EthTime, checkError = false)
2020-01-13 18:35:40 +00:00
config.customGenesis = CustomGenesisConfig(
chainId: chainId,
homesteadBlock: homesteadBlock,
eip150Block: eip150Block,
eip150Hash: eip150Hash,
eip155Block: eip155Block,
eip158Block: eip158Block,
daoForkSupport: daoForkSupport,
byzantiumBlock: byzantiumBlock,
constantinopleBlock: constantinopleBlock,
petersburgBlock: petersburgBlock,
istanbulBlock: istanbulBlock,
2020-04-12 10:07:09 +00:00
muirGlacierBlock: muirGlacierBlock,
2020-11-19 04:59:53 +00:00
berlinBlock: berlinBlock,
2020-01-13 18:35:40 +00:00
nonce: nonce,
extraData: extraData,
gasLimit: gasLimit,
difficulty: difficulty,
prealloc: alloc,
mixHash: mixHash,
coinbase: coinbase,
timestamp: timestamp
)
proc customGenesis(key, value: string): ConfigStatus =
if value == "":
error "No genesis block config provided for custom network", network=key
result = ErrorParseOption
else:
try:
result = processCustomGenesisConfig(parseFile(value))
except IOError:
error "Genesis block config file not found", invalidFileName=value
result = ErrorParseOption
except JsonParsingError:
error "Invalid genesis block config file format", invalidFileName=value
result = ErrorIncorrectOption
except:
var msg = getCurrentExceptionMsg()
error "Error loading genesis block config file", invalidFileName=msg
result = Error
2018-04-27 08:53:53 +00:00
proc processList(v: string, o: var seq[string]) =
## Process comma-separated list of strings.
2018-04-27 08:53:53 +00:00
if len(v) > 0:
for n in v.split({' ', ','}):
if len(n) > 0:
o.add(n)
2019-01-08 11:29:56 +00:00
proc processInteger*(v: string, o: var int): ConfigStatus =
## Convert string to integer.
2018-04-27 08:53:53 +00:00
try:
o = parseInt(v)
2018-04-30 07:24:51 +00:00
result = Success
2019-12-04 12:43:18 +00:00
except ValueError:
2018-04-30 07:24:51 +00:00
result = ErrorParseOption
proc processFloat*(v: string, o: var float): ConfigStatus =
## Convert string to float.
try:
o = parseFloat(v)
result = Success
2019-12-04 12:43:18 +00:00
except ValueError:
result = ErrorParseOption
proc processAddressPort(addrStr: string, ta: var TransportAddress): ConfigStatus =
try:
ta = initTAddress(addrStr)
return Success
except CatchableError:
return ErrorParseOption
2018-06-20 17:27:32 +00:00
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
2018-06-20 17:27:32 +00:00
else:
return ErrorParseOption
2018-06-20 17:27:32 +00:00
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 "shh": flags.incl RpcFlags.Shh
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 "shh": flags.incl ProtocolFlags.Shh
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
2018-04-30 07:24:51 +00:00
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
2018-11-30 03:03:30 +00:00
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 processEthArguments(key, value: string): ConfigStatus =
result = Success
let config = getConfiguration()
case key.toLowerAscii()
of "keystore":
config.keyStore = value
2018-12-28 08:24:57 +00:00
of "datadir":
config.dataDir = value
2018-11-30 03:03:30 +00:00
of "prune":
result = processPruneList(value, config.prune)
of "import":
config.importFile = value
else:
result = EmptyOption
2018-04-27 08:53:53 +00:00
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)
2018-04-27 08:53:53 +00:00
elif skey == "rpcbind":
2018-06-20 17:27:32 +00:00
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)
2018-04-27 08:53:53 +00:00
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
2018-08-01 12:50:44 +00:00
proc setBootnodes(onodes: var seq[ENode], nodeUris: openarray[string]) =
2018-06-20 17:27:32 +00:00
var node: ENode
2018-08-01 12:50:44 +00:00
onodes = newSeqOfCap[ENode](nodeUris.len)
for item in nodeUris:
2018-06-20 17:27:32 +00:00
doAssert(processENode(item, node) == Success)
2018-08-01 12:50:44 +00:00
onodes.add(node)
2018-06-20 17:27:32 +00:00
2018-08-02 08:18:12 +00:00
macro availableEnumValues(T: type enum): untyped =
let impl = getTypeImpl(T)[1].getTypeImpl()
result = newNimNode(nnkBracket)
for i in 1 ..< impl.len: result.add(newCall("NetworkId", copyNimTree(impl[i])))
2018-08-02 08:18:12 +00:00
proc toPublicNetwork*(id: NetworkId): PublicNetwork {.inline.} =
2018-08-02 08:18:12 +00:00
if id in availableEnumValues(PublicNetwork):
result = PublicNetwork(id)
2018-08-01 12:50:44 +00:00
proc setNetwork(conf: var NetConfiguration, id: PublicNetwork) =
2018-06-20 17:27:32 +00:00
## Set network id and default network bootnodes
conf.networkId = NetworkId(id)
2018-08-01 12:50:44 +00:00
case id
2018-06-20 17:27:32 +00:00
of MainNet:
conf.bootNodes.setBootnodes(MainnetBootnodes)
of RopstenNet:
conf.bootNodes.setBootnodes(RopstenBootnodes)
of RinkebyNet:
conf.bootNodes.setBootnodes(RinkebyBootnodes)
of GoerliNet:
conf.bootNodes.setBootnodes(GoerliBootnodes)
2018-06-20 17:27:32 +00:00
of KovanNet:
conf.bootNodes.setBootnodes(KovanBootnodes)
of CustomNet:
2018-08-01 12:50:44 +00:00
discard
proc setNetwork(conf: var NetConfiguration, id: NetworkId) =
2018-08-01 12:50:44 +00:00
## Set network id and default network bootnodes
let pubNet = toPublicNetwork(id)
if pubNet == CustomNet:
2018-06-20 17:27:32 +00:00
conf.networkId = id
else:
2018-08-01 12:50:44 +00:00
conf.setNetwork(pubNet)
2018-06-20 17:27:32 +00:00
2018-04-27 08:53:53 +00:00
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":
2018-09-25 13:30:44 +00:00
result = processENodesList(value, config.net.bootNodes)
2018-04-27 08:53:53 +00:00
elif skey == "bootnodesv4":
2018-06-20 17:27:32 +00:00
result = processENodesList(value, config.net.bootNodes)
2018-04-27 08:53:53 +00:00
elif skey == "bootnodesv5":
2018-06-20 17:27:32 +00:00
result = processENodesList(value, config.net.bootNodes)
elif skey == "staticnodes":
result = processENodesList(value, config.net.staticNodes)
2018-04-27 08:53:53 +00:00
elif skey == "testnet":
2018-06-20 17:27:32 +00:00
config.net.setNetwork(RopstenNet)
2018-04-27 08:53:53 +00:00
elif skey == "mainnet":
2018-06-20 17:27:32 +00:00
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)
2018-06-20 17:27:32 +00:00
elif skey == "kovan":
config.net.setNetwork(KovanNet)
2020-01-13 18:35:40 +00:00
elif skey == "customnetwork":
result = customGenesis(key, value)
config.net.networkId = NetworkId(CustomNet)
2018-06-20 17:27:32 +00:00
elif skey == "networkid":
var res = 0
result = processInteger(value, res)
if result == Success:
config.net.setNetwork(NetworkId(result))
2018-04-27 08:53:53 +00:00
elif skey == "nodiscover":
config.net.flags.incl(NoDiscover)
elif skey == "v5discover":
config.net.flags.incl(V5Discover)
2018-06-20 17:27:32 +00:00
config.net.bootNodes.setBootnodes(DiscoveryV5Bootnodes)
2018-04-27 08:53:53 +00:00
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)
2018-04-27 08:53:53 +00:00
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
2018-06-20 17:27:32 +00:00
elif skey == "ident":
config.net.ident = value
2019-04-17 01:56:28 +00:00
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 processShhArguments(key, value: string): ConfigStatus =
## Processes only `Shh` related command line options
result = Success
let config = getConfiguration()
let skey = key.toLowerAscii()
if skey == "shh-maxsize":
var res = 0
result = processInteger(value, res)
if result == Success:
config.shh.maxMsgSize = res.uint32
elif skey == "shh-pow":
var res = 0.0
result = processFloat(value, res)
if result == Success:
config.shh.powRequirement = res
elif skey == "shh-light":
config.shh.isLightNode = true
2018-04-27 08:53:53 +00:00
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
2018-04-27 08:53:53 +00:00
proc dumpConfiguration*(): string =
## Dumps current configuration as string
let config = getConfiguration()
result = repr config
template processArgument(processor, key, value, msg: untyped) =
2018-06-20 17:27:32 +00:00
## Checks if arguments got processed successfully
var res = processor(string(key), string(value))
2018-04-27 08:53:53 +00:00
if res == Success:
result = res
2018-04-27 08:53:53 +00:00
continue
elif res == ErrorParseOption:
msg = "Error processing option '" & key & "' with value '" & value & "'."
2018-04-27 08:53:53 +00:00
result = res
break
2018-06-20 17:27:32 +00:00
elif res == ErrorIncorrectOption:
msg = "Incorrect value for option '" & key & "': '" & value & "'."
2018-06-20 17:27:32 +00:00
result = res
break
2019-01-08 11:29:56 +00:00
proc getDefaultDataDir*(): string =
when defined(windows):
"AppData" / "Roaming" / "Nimbus"
2019-01-08 11:29:56 +00:00
elif defined(macosx):
"Library" / "Application Support" / "Nimbus"
2019-01-08 11:29:56 +00:00
else:
".cache" / "nimbus"
proc getDefaultKeystoreDir*(): string =
getDefaultDataDir() / "keystore"
2019-01-08 11:29:56 +00:00
2018-06-20 17:27:32 +00:00
proc initConfiguration(): NimbusConfiguration =
## Allocates and initializes `NimbusConfiguration` with default values
result = new NimbusConfiguration
2020-07-20 17:16:59 +00:00
result.rng = newRng()
result.accounts = initTable[EthAddress, NimbusAccount]()
2020-07-20 17:16:59 +00:00
## Graphql defaults
result.graphql.enabled = false
result.graphql.address = initTAddress("127.0.0.1:8547")
2018-06-20 17:27:32 +00:00
## RPC defaults
result.rpc.flags = {}
result.rpc.binds = @[initTAddress("127.0.0.1:8545")]
## Network defaults
result.net.setNetwork(defaultNetwork.NetworkId)
2018-06-20 17:27:32 +00:00
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
2019-01-15 15:25:14 +00:00
result.net.ident = NimbusIdent
2019-04-17 01:56:28 +00:00
result.net.nat = NatAny
result.net.protocols = defaultProtocols
2020-07-20 17:16:59 +00:00
result.net.nodekey = random(PrivateKey, result.rng[])
2018-04-27 08:53:53 +00:00
const
dataDir = getDefaultDataDir()
keystore = getDefaultKeystoreDir()
result.dataDir = getHomeDir() / dataDir
result.keystore = getHomeDir() / keystore
2018-11-30 03:03:30 +00:00
result.prune = PruneMode.Full
## Whisper defaults
result.shh.maxMsgSize = defaultMaxMsgSize
result.shh.powRequirement = defaultMinPow
result.shh.isLightNode = false
result.shh.bloom = fullBloom()
2018-06-20 17:27:32 +00:00
## Debug defaults
result.debug.flags = {}
result.debug.logLevel = defaultLogLevel
result.debug.logMetrics = false
result.debug.logMetricsInterval = 10
2018-06-20 17:27:32 +00:00
proc getConfiguration*(): NimbusConfiguration =
## Retreive current configuration object `NimbusConfiguration`.
if isNil(nimbusConfig):
nimbusConfig = initConfiguration()
result = nimbusConfig
2018-04-27 08:53:53 +00:00
proc getHelpString*(): string =
var logLevels: seq[string]
for level in LogLevel:
if level < enabledLogLevel:
continue
logLevels.add($level)
2018-06-20 17:27:32 +00:00
result = """
2018-04-27 08:53:53 +00:00
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
2018-04-27 08:53:53 +00:00
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)
2018-04-27 08:53:53 +00:00
--maxpeers:<value> Maximum number of network peers (default: 25)
--maxpendpeers:<value> Maximum number of pending connection attempts (default: 0)
2019-04-17 01:56:28 +00:00
--nat:<value> NAT port mapping mechanism (any|none|upnp|pmp|<external IP>) (default: "any")
2018-04-27 08:53:53 +00:00
--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)
2018-06-20 17:27:32 +00:00
--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)
WHISPER OPTIONS:
--shh-maxsize:<value> Max message size accepted (default: $5)
--shh-pow:<value> Minimum POW accepted (default: $6)
--shh-light Run as Whisper light client (no outgoing messages)
LOCAL SERVICE OPTIONS:
--metrics Enable the metrics HTTP server
--metricsport:<value> Set port (always on localhost) metrics HTTP server will bind to (default: 9093)
2018-04-27 08:53:53 +00:00
--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, shh, debug)
--graphql Enable the HTTP-GraphQL server
--graphqlbind:<value> Set address:port pair GraphQL server will bind (default: localhost:8547)
2018-04-27 08:53:53 +00:00
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)
2018-04-27 08:53:53 +00:00
--debug Enable debug mode
--test:<value> Perform specified test
2018-06-20 17:27:32 +00:00
""" % [
NimbusIdent,
join(logLevels, ", "),
$defaultLogLevel,
strip($defaultProtocols, chars = {'{','}'}),
$defaultMaxMsgSize,
$defaultMinPow,
$ord(defaultNetwork)
2018-06-20 17:27:32 +00:00
]
2018-04-27 08:53:53 +00:00
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.bootnodes` is likely populated with network default
# bootnodes. We want to override those if at least one custom bootnode is
# specified on the command line. We temporarily set `config.net.bootNodes`
# to empty seq, and in the end restore it if no bootnodes were spricified on
# the command line.
# TODO: This is pretty hacky and it's better to refactor it to make a clear
# distinction between default and custom bootnodes.
var tempBootNodes: seq[ENode]
2018-09-25 13:30:44 +00:00
swap(tempBootNodes, config.net.bootNodes)
# The same trick is done to discPort
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 processShhArguments, key, value, msg
processArgument processDebugArguments, key, value, msg
processArgument processGraphqlArguments, 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.bootNodes.len == 0:
# No custom bootnodes were specified on the command line, restore to
# previous values
swap(tempBootNodes, config.net.bootNodes)
if config.net.discPort == 0:
config.net.discPort = config.net.bindPort
2018-04-27 08:53:53 +00:00
proc processArguments*(msg: var string): ConfigStatus =
var opt = initOptParser()
processArguments(msg, opt)
proc processConfiguration*(pathname: string): ConfigStatus =
2018-04-27 08:53:53 +00:00
## Process configuration file `pathname` and update `NimbusConfiguration`.
result = Success