diff --git a/nimbus/config.nim b/nimbus/config.nim index 0818895c9..1d31fd682 100644 --- a/nimbus/config.nim +++ b/nimbus/config.nim @@ -8,7 +8,7 @@ # those terms. import - parseopt, strutils, macros, os, times, + parseopt, strutils, macros, os, times, json, stew/[byteutils, ranges], chronos, eth/[keys, common, p2p, net/nat], chronicles, nimcrypto/hash, eth/p2p/bootnodes, eth/p2p/rlpx_protocols/whisper_protocol, ./db/select_backend, @@ -143,6 +143,27 @@ type net*: NetConfiguration ## Network configuration debug*: DebugConfiguration ## Debug configuration shh*: WhisperConfig ## Whisper configuration + customGenesis*: CustomGenesisConfig ## Custom Genesis Configuration + + CustomGenesisConfig = object + chainId*: uint + homesteadBlock*: BlockNumber + daoForkBlock*: BlockNumber + daoForkSupport*: bool + eip150Block*: BlockNumber + eip150Hash*: Hash256 + eip155Block*: BlockNumber + eip158Block*: BlockNumber + byzantiumBlock*: BlockNumber + constantinopleBlock*: BlockNumber + nonce*: BlockNonce + extraData*: seq[byte] + gasLimit*: int64 + difficulty*: UInt256 + prealloc*: JsonNode + mixHash*: Hash256 + coinBase*: EthAddress + timestamp*: EthTime const defaultRpcApi = {RpcFlags.Eth, RpcFlags.Shh} @@ -154,6 +175,22 @@ var nimbusConfig {.threadvar.}: NimbusConfiguration proc getConfiguration*(): NimbusConfiguration {.gcsafe.} +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, + ) + trace "Custom genesis block configuration loaded", configuration=result + proc publicChainConfig*(id: PublicNetwork): ChainConfig = result = case id of MainNet: @@ -190,6 +227,8 @@ proc publicChainConfig*(id: PublicNetwork): ChainConfig = eip158Block: 3.toBlockNumber, byzantiumBlock: 1035301.toBlockNumber ) + of CustomNet: + privateChainConfig() else: error "No chain config for public network", networkId = id doAssert(false, "No chain config for " & $id) @@ -197,6 +236,124 @@ proc publicChainConfig*(id: PublicNetwork): ChainConfig = result.chainId = uint(id) +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() + elif T is Uint256: + parseHexInt(c[field].getStr()).u256 + 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()) + + + 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 = 0.uint + homesteadBlock, daoForkblock, eip150Block, eip155Block, eip158Block, byzantiumBlock, constantinopleBlock = 0.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, uint) + config.net.networkId = chainId + 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) + checkForFork(forkDetails, eip155Block, eip150Block) + checkForFork(forkDetails, eip158Block, eip155Block) + checkForFork(forkDetails, byzantiumBlock, eip158Block) + checkForFork(forkDetails, constantinopleBlock, byzantiumBlock) + else: + error "No chain configuration found." + quit(1) + validateConfigValue(customGenesis, nonce, JString, BlockNonce) + 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) + 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, + nonce: nonce, + extraData: extraData, + gasLimit: gasLimit, + difficulty: difficulty, + prealloc: alloc, + mixHash: mixHash, + coinbase: coinbase, + timestamp: timestamp + ) + proc processList(v: string, o: var seq[string]) = ## Process comma-separated list of strings. if len(v) > 0: @@ -436,6 +593,24 @@ proc processNetArguments(key, value: string): ConfigStatus = config.net.setNetwork(RinkebyNet) elif skey == "kovan": config.net.setNetwork(KovanNet) + elif skey == "customnetwork": + 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 exceptionType = getCurrentException() + var msg = getCurrentExceptionMsg() + error "Error loading genesis block config file", invalidFileName=msg + result = Error elif skey == "networkid": var res = 0 result = processInteger(value, res) @@ -678,6 +853,7 @@ NETWORKING OPTIONS: --rinkeby Use Ethereum Rinkeby Test Network --ident: Client identifier (default is '$1') --protocols: Enable specific set of protocols (default: $4) + --customnetwork Use custom genesis block for private Ethereum Network (as /path/to/genesis.json) WHISPER OPTIONS: --shh-maxsize: Max message size accepted (default: $5) @@ -766,4 +942,3 @@ when declared(os.paramCount): # not available with `--app:lib` proc processConfiguration*(pathname: string): ConfigStatus = ## Process configuration file `pathname` and update `NimbusConfiguration`. result = Success - diff --git a/nimbus/genesis.nim b/nimbus/genesis.nim index ec52fbcba..634d2e34a 100644 --- a/nimbus/genesis.nim +++ b/nimbus/genesis.nim @@ -1,5 +1,5 @@ import - tables, + tables, json, strutils, eth/[common, rlp, trie], stint, stew/[byteutils, ranges], chronicles, eth/trie/db, db/[db_chain, state_db], genesis_alloc, config, constants @@ -32,6 +32,12 @@ func decodePrealloc(data: seq[byte]): GenesisAlloc = for tup in rlp.decode(data.toRange, seq[(UInt256, UInt256)]): result[toAddress(tup[0])] = GenesisAccount(balance: tup[1]) +proc customNetPrealloc(genesisBlock: JsonNode): GenesisAlloc = + result = newTable[EthAddress, GenesisAccount]() + for address, balance in genesisBlock.pairs(): + let balance = fromHex(UInt256,balance["balance"].getStr()) + result[parseAddress(address)] = GenesisAccount(balance: balance) + proc defaultGenesisBlockForNetwork*(id: PublicNetwork): Genesis = result = case id of MainNet: @@ -58,6 +64,22 @@ proc defaultGenesisBlockForNetwork*(id: PublicNetwork): Genesis = difficulty: 1048576.u256, alloc: decodePrealloc(rinkebyAllocData) ) + of CustomNet: + let genesis = getConfiguration().customGenesis + var alloc = new GenesisAlloc + if genesis.prealloc != parseJson("{}"): + alloc = customNetPrealloc(genesis.prealloc) + Genesis( + nonce: genesis.nonce, + extraData: genesis.extraData, + gasLimit: genesis.gasLimit, + difficulty: genesis.difficulty, + alloc: alloc, + timestamp: genesis.timestamp, + mixhash: genesis.mixHash, + coinbase: genesis.coinbase + ) + else: # TODO: Fill out the rest error "No default genesis for network", id @@ -108,7 +130,7 @@ proc commit*(g: Genesis, db: BaseChainDB) = proc initializeEmptyDb*(db: BaseChainDB) = trace "Writing genesis to DB" let networkId = getConfiguration().net.networkId.toPublicNetwork() - if networkId == CustomNet: - raise newException(Exception, "Custom genesis not implemented") - else: - defaultGenesisBlockForNetwork(networkId).commit(db) +# if networkId == CustomNet: +# raise newException(Exception, "Custom genesis not implemented") +# else: + defaultGenesisBlockForNetwork(networkId).commit(db) diff --git a/nimbus/nimbus.nim b/nimbus/nimbus.nim index 237d17084..80f1a33cc 100644 --- a/nimbus/nimbus.nim +++ b/nimbus/nimbus.nim @@ -106,14 +106,14 @@ proc start() = createDir(conf.dataDir) let trieDB = trieDB newChainDb(conf.dataDir) - let chainDB = newBaseChainDB(trieDB, + var chainDB = newBaseChainDB(trieDB, conf.prune == PruneMode.Full, conf.net.networkId.toPublicNetwork()) if canonicalHeadHashKey().toOpenArray notin trieDB: initializeEmptyDb(chainDb) doAssert(canonicalHeadHashKey().toOpenArray in trieDB) - + nimbus.ethNode = newEthereumNode(keypair, address, conf.net.networkId, nil, nimbusClientId, addAllCapabilities = false, @@ -145,7 +145,7 @@ proc start() = nimbus.state = Stopping result = "EXITING" nimbus.rpcServer.start() - + # metrics server when defined(insecure): if conf.net.metricsServer: