# 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, eth/p2p/rlpx_protocols/whisper_protocol, ./db/select_backend, eth/keys, ./vm_types2 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 Shh ## enable shh_ set of RPC API Debug ## enable debug_ set of RPC API ProtocolFlags* {.pure.} = enum ## Protocol flags Eth ## enable eth subprotocol Shh ## enable whisper 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 PublicNetwork* = enum CustomNet = 0 MainNet = 1 # No longer used: MordenNet = 2 RopstenNet = 3 RinkebyNet = 4 GoerliNet = 5 KovanNet = 42 NetworkFlags* = enum ## Ethereum network flags NoDiscover, ## Peer discovery disabled V5Discover, ## Dicovery V5 enabled 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 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 # beware that although in some cases # chainId have identical value to networkId # they are separate entity ChainId* = distinct uint ChainConfig* = object chainId*: ChainId 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 muirGlacierBlock*: BlockNumber berlinBlock*: BlockNumber NimbusConfiguration* = ref object ## Main Nimbus configuration object dataDir*: string keyStore*: string prune*: PruneMode graphql*: GraphqlConfiguration rpc*: RpcConfiguration ## JSON-RPC configuration net*: NetConfiguration ## Network configuration debug*: DebugConfiguration ## Debug configuration shh*: WhisperConfig ## Whisper configuration customGenesis*: CustomGenesisConfig ## 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 CustomGenesisConfig* = object chainId*: ChainId homesteadBlock*: BlockNumber daoForkBlock*: BlockNumber daoForkSupport*: bool eip150Block*: BlockNumber eip150Hash*: Hash256 eip155Block*: BlockNumber eip158Block*: BlockNumber byzantiumBlock*: BlockNumber constantinopleBlock*: BlockNumber petersburgBlock*: BlockNumber istanbulBlock*: BlockNumber muirGlacierBlock*: BlockNumber berlinBlock*: BlockNumber nonce*: BlockNonce extraData*: seq[byte] gasLimit*: int64 difficulty*: UInt256 prealloc*: JsonNode mixHash*: Hash256 coinBase*: EthAddress timestamp*: EthTime const defaultRpcApi = {RpcFlags.Eth, RpcFlags.Shh} defaultProtocols = {ProtocolFlags.Eth, ProtocolFlags.Shh} 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.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 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, istanbulBlock: config.customGenesis.istanbulBlock, muirGlacierBlock: config.customGenesis.muirGlacierBlock, berlinBlock: config.customGenesis.berlinBlock ) trace "Custom genesis block configuration loaded", configuration=result proc publicChainConfig*(id: PublicNetwork): ChainConfig = # For some public networks, NetworkId and ChainId value are identical # but that is not always the case result = case id of MainNet: ChainConfig( 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 ) of RopstenNet: ChainConfig( 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 ) of RinkebyNet: ChainConfig( 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 ) 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, 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 ) of CustomNet: privateChainConfig() else: error "No chain config for public network", networkId = id doAssert(false, "No chain config for " & $id) ChainConfig() proc processCustomGenesisConfig(customGenesis: JsonNode): ConfigStatus = ## Parses Custom Genesis Block config options when customnetwork option provided template checkForFork(chain, currentFork, previousFork: untyped, optional = false) = # Template to load fork blocks and validate order let currentForkName = currentFork.astToStr() if chain.hasKey(currentForkName) and not optional: 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() elif T is Uint256: hexToUint[256](c[field].getStr()) 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 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() result = Success var chainId = 1.ChainId homesteadBlock, daoForkblock, eip150Block, eip155Block, eip158Block, byzantiumBlock, constantinopleBlock = high(BlockNumber).toBlockNumber petersburgBlock, istanbulBlock, muirGlacierBlock, berlinBlock = high(BlockNumber).toBlockNumber 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 let forkDetails = customGenesis["config"] validateConfigValue(forkDetails, chainId, JInt, ChainId, checkError=false) checkForFork(forkDetails, homesteadBlock, 0.toBlockNumber) validateConfigValue(forkDetails, daoForkSupport, JBool, bool, checkError=false) if daoForkSupport == true: checkForFork(forkDetails, daoForkBlock, 0.toBlockNumber) checkForFork(forkDetails, eip150Block, homesteadBlock) validateConfigValue(forkDetails, eip150Hash, JString, Hash256, checkError = false) checkForFork(forkDetails, eip155Block, eip150Block) checkForFork(forkDetails, eip158Block, eip155Block) checkForFork(forkDetails, byzantiumBlock, eip158Block) checkForFork(forkDetails, constantinopleBlock, byzantiumBlock) checkForFork(forkDetails, petersburgBlock, constantinopleBlock) checkForFork(forkDetails, istanbulBlock, petersburgBlock) checkForFork(forkDetails, muirGlacierBlock, istanbulBlock, optional = true) checkForFork(forkDetails, istanbulBlock, berlinBlock) validateConfigValue(customGenesis, nonce, JString, BlockNonce, checkError = false) 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) validateConfigValue(customGenesis, coinbase, JString, EthAddress, checkError = false) validateConfigValue(customGenesis, timestamp, JString, EthTime, checkError = false) 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, muirGlacierBlock: muirGlacierBlock, berlinBlock: berlinBlock, 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 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 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 ;...; 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 "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 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 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 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 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) 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]))) proc toPublicNetwork*(id: NetworkId): PublicNetwork {.inline.} = if id in availableEnumValues(PublicNetwork): result = PublicNetwork(id) proc setNetwork(conf: var NetConfiguration, id: PublicNetwork) = ## Set network id and default network bootnodes conf.networkId = 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) of CustomNet: discard proc setNetwork(conf: var NetConfiguration, id: NetworkId) = ## Set network id and default network bootnodes let pubNet = toPublicNetwork(id) if pubNet == CustomNet: conf.networkId = id else: conf.setNetwork(pubNet) 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.bootNodes) elif skey == "bootnodesv4": result = processENodesList(value, config.net.bootNodes) elif skey == "bootnodesv5": result = processENodesList(value, config.net.bootNodes) 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": result = customGenesis(key, value) config.net.networkId = NetworkId(CustomNet) elif skey == "networkid": var res = 0 result = processInteger(value, res) if result == Success: config.net.setNetwork(NetworkId(result)) elif skey == "nodiscover": config.net.flags.incl(NoDiscover) elif skey == "v5discover": config.net.flags.incl(V5Discover) config.net.bootNodes.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 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 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")] ## Network defaults result.net.setNetwork(defaultNetwork.NetworkId) 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 ## Whisper defaults result.shh.maxMsgSize = defaultMaxMsgSize result.shh.powRequirement = defaultMinPow result.shh.isLightNode = false result.shh.bloom = fullBloom() ## 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: Base directory for all blockchain-related data --keystore: Directory for the keystore (default: inside datadir) --prune: Blockchain prune mode (full or archive, default: full) --import: Import RLP encoded block(s), validate, write to database and quit NETWORKING OPTIONS: --bootnodes: Comma separated enode URLs for P2P discovery bootstrap (set v4+v5 instead for light servers) --bootnodesv4: Comma separated enode URLs for P2P v4 discovery bootstrap (light server, full nodes) --bootnodesv5: Comma separated enode URLs for P2P v5 discovery bootstrap (light server, light nodes) --staticnodes: Comma separated enode URLs to connect with --port: Network listening TCP port (default: 30303) --discport: Network listening UDP port (defaults to --port argument) --maxpeers: Maximum number of network peers (default: 25) --maxpendpeers: Maximum number of pending connection attempts (default: 0) --nat: NAT port mapping mechanism (any|none|upnp|pmp|) (default: "any") --nodiscover Disables the peer discovery mechanism (manual peer addition) --v5discover Enables the experimental RLPx V5 (topic discovery) mechanism --nodekey: P2P node private key (as hexadecimal string) --ident: Client identifier (default is '$1') --protocols: 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: Network id (0=custom, 1=mainnet, 3=ropsten, 4=rinkeby, 5=goerli, 42=kovan, other...) --customnetwork: Use custom genesis block for private Ethereum Network (as /path/to/genesis.json) WHISPER OPTIONS: --shh-maxsize: Max message size accepted (default: $5) --shh-pow: 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: Set port (always on localhost) metrics HTTP server will bind to (default: 9093) --rpc Enable the HTTP-RPC server --rpcbind: Set address:port pair(s) (comma-separated) HTTP-RPC server will bind to (default: localhost:8545) --rpcapi: Enable specific set of RPC API from list (comma-separated) (available: eth, shh, debug) --graphql Enable the HTTP-GraphQL server --graphqlbind: Set address:port pair GraphQL server will bind (default: localhost:8547) LOGGING AND DEBUGGING OPTIONS: --log-level: One of: $2 (default: $3) --log-file: Optional log file, replacing stdout --logmetrics Enable metrics logging --logmetricsinterval: Interval at which to log metrics, in seconds (default: 10) --debug Enable debug mode --test: Perform specified test """ % [ NimbusIdent, join(logLevels, ", "), $defaultLogLevel, strip($defaultProtocols, chars = {'{','}'}), $defaultMaxMsgSize, $defaultMinPow, $ord(defaultNetwork) ] 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] 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 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