Add support for custom genesis blocks

This commit is contained in:
acolytec3 2020-01-13 13:35:40 -05:00
parent 5f24757cd7
commit 45cda8bab0
3 changed files with 207 additions and 10 deletions

View File

@ -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:<value> Client identifier (default is '$1')
--protocols:<value> 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:<value> 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

View File

@ -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)

View File

@ -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: