Merge pull request #93 from status-im/genesis

Added genesis initialization
This commit is contained in:
Yuriy Glukhov 2018-08-06 18:02:39 +03:00 committed by GitHub
commit 467a9c3d7a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 320 additions and 80 deletions

View File

@ -7,8 +7,8 @@
# This file may not be copied, modified, or distributed except according to
# those terms.
import parseopt, strutils
import asyncdispatch2, eth_keys, eth_p2p
import parseopt, strutils, macros
import asyncdispatch2, eth_keys, eth_p2p, eth_common, chronicles
const
NimbusName* = "Nimbus"
@ -94,14 +94,16 @@ type
flags*: set[RpcFlags] ## RPC flags
binds*: seq[TransportAddress] ## RPC bind address
PublicNetwork* = enum
CustomNet = 0
MainNet = 1
MordenNet = 2
RopstenNet = 3
RinkebyNet = 4
KovanNet = 42
NetworkFlags* = enum
## Ethereum network flags
RopstenNet, ## Use test Ropsten network
RinkebyNet, ## Use test Rinkeby network
MordenNet, ## Use test Morden network
KovanNet, ## Use test Kovan network
CustomNet, ## Use custom network
MainNet, ## Use main network only
NoDiscover, ## Peer discovery disabled
V5Discover, ## Dicovery V5 enabled
@ -128,6 +130,22 @@ type
## Debug configuration object
flags*: set[DebugFlags] ## Debug flags
ChainConfig* = object
chainId*: uint
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
NimbusConfiguration* = ref object
## Main Nimbus configuration object
rpc*: RpcConfiguration ## JSON-RPC configuration
@ -138,6 +156,49 @@ var nimbusConfig {.threadvar.}: NimbusConfiguration
proc getConfiguration*(): NimbusConfiguration {.gcsafe.}
proc publicChainConfig*(id: PublicNetwork): ChainConfig =
result = case id
of MainNet:
ChainConfig(
chainId: MainNet.uint,
homesteadBlock: 1150000.u256,
daoForkBlock: 1920000.u256,
daoForkSupport: true,
eip150Block: 2463000.u256,
eip150Hash: toDigest("2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0"),
eip155Block: 2675000.u256,
eip158Block: 2675000.u256,
byzantiumBlock: 4370000.u256
)
of RopstenNet:
ChainConfig(
chainId: RopstenNet.uint,
homesteadBlock: 0.u256,
daoForkSupport: true,
eip150Block: 0.u256,
eip150Hash: toDigest("41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d"),
eip155Block: 10.u256,
eip158Block: 10.u256,
byzantiumBlock: 1700000.u256
)
of RinkebyNet:
ChainConfig(
chainId: RinkebyNet.uint,
homesteadBlock: 1.u256,
daoForkSupport: true,
eip150Block: 2.u256,
eip150Hash: toDigest("9b095b36c15eaf13044373aef8ee0bd3a382a5abb92e402afa44b8249c3a90e9"),
eip155Block: 3.u256,
eip158Block: 3.u256,
byzantiumBlock: 1035301.u256
)
else:
error "No chain config for public network", networkId = id
doAssert(false, "No chain config for " & $id)
ChainConfig()
result.chainId = uint(id)
proc processList(v: string, o: var seq[string]) =
## Process comma-separated list of strings.
if len(v) > 0:
@ -243,42 +304,46 @@ proc processRpcArguments(key, value: string): ConfigStatus =
else:
result = EmptyOption
template setBootnodes(onodes, nodes: untyped): untyped =
proc setBootnodes(onodes: var seq[ENode], nodeUris: openarray[string]) =
var node: ENode
for item in (nodes):
onodes = newSeqOfCap[ENode](nodeUris.len)
for item in nodeUris:
doAssert(processENode(item, node) == Success)
(onodes).add(node)
onodes.add(node)
proc setNetwork(conf: var NetConfiguration, network: NetworkFlags,
id: uint = 0) =
macro availableEnumValues(T: type enum): untyped =
let impl = getTypeImpl(T)[1].getTypeImpl()
result = newNimNode(nnkBracket)
for i in 1 ..< impl.len: result.add(newCall("uint", copyNimTree(impl[i])))
proc toPublicNetwork*(id: uint): 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.flags.excl({MainNet, MordenNet, RopstenNet, RinkebyNet, KovanNet,
CustomNet})
conf.flags.incl(network)
assert(not conf.bootNodes.isNil) # Nim bug #7833
case network
conf.networkId = uint(id)
case id
of MainNet:
conf.networkId = uint(1)
conf.bootNodes.setLen(0)
conf.bootNodes.setBootnodes(MainnetBootnodes)
of MordenNet:
conf.networkId = uint(2)
discard
of RopstenNet:
conf.networkId = uint(3)
conf.bootNodes.setLen(0)
conf.bootNodes.setBootnodes(RopstenBootnodes)
of RinkebyNet:
conf.networkId = uint(4)
conf.bootNodes.setLen(0)
conf.bootNodes.setBootnodes(RinkebyBootnodes)
of KovanNet:
conf.networkId = uint(42)
conf.bootNodes.setLen(0)
conf.bootNodes.setBootnodes(KovanBootnodes)
of CustomNet:
discard
proc setNetwork(conf: var NetConfiguration, id: uint) =
## Set network id and default network bootnodes
let pubNet = toPublicNetwork(id)
if pubNet == CustomNet:
conf.networkId = id
else:
discard
conf.setNetwork(pubNet)
proc processNetArguments(key, value: string): ConfigStatus =
## Processes only `Networking` related command line options
@ -307,24 +372,11 @@ proc processNetArguments(key, value: string): ConfigStatus =
var res = 0
result = processInteger(value, res)
if result == Success:
case res
of 1:
config.net.setNetwork(MainNet)
of 2:
config.net.setNetwork(MordenNet)
of 3:
config.net.setNetwork(RopstenNet)
of 4:
config.net.setNetwork(RinkebyNet)
of 42:
config.net.setNetwork(KovanNet)
else:
config.net.setNetwork(CustomNet, uint(res))
config.net.setNetwork(uint(result))
elif skey == "nodiscover":
config.net.flags.incl(NoDiscover)
elif skey == "v5discover":
config.net.flags.incl(V5Discover)
config.net.bootNodes.setLen(0)
config.net.bootNodes.setBootnodes(DiscoveryV5Bootnodes)
elif skey == "port":
var res = 0
@ -403,8 +455,7 @@ proc initConfiguration(): NimbusConfiguration =
result.rpc.binds = @[initTAddress("127.0.0.1:8545")]
## Network defaults
result.net.bootNodes = @[] # Nim bug #7833
result.net.setNetwork(RopstenNet)
result.net.setNetwork(MainNet)
result.net.maxPeers = 25
result.net.maxPendingPeers = 0
result.net.bindPort = 30303'u16

View File

@ -30,36 +30,55 @@ proc newBaseChainDB*(db: TrieDatabaseRef): BaseChainDB =
proc `$`*(db: BaseChainDB): string =
result = "BaseChainDB"
proc getBlockHeaderByHash*(self: BaseChainDB; blockHash: Hash256): BlockHeader =
## Returns the requested block header as specified by block hash.
##
## Raises BlockNotFound if it is not present in the db.
proc getBlockHeader*(self: BaseChainDB; blockHash: Hash256, output: var BlockHeader): bool =
try:
let blk = self.db.get(genericHashKey(blockHash).toOpenArray).toRange
return decode(blk, BlockHeader)
if blk.len != 0:
output = rlp.decode(blk, BlockHeader)
result = true
except KeyError:
discard
proc getBlockHeader*(self: BaseChainDB, blockHash: Hash256): BlockHeader =
## Returns the requested block header as specified by block hash.
##
## Raises BlockNotFound if it is not present in the db.
if not self.getBlockHeader(blockHash, result):
raise newException(BlockNotFound, "No block with hash " & blockHash.data.toHex)
proc getHash(self: BaseChainDB, key: DbKey): Hash256 {.inline.} =
rlp.decode(self.db.get(key.toOpenArray).toRange, Hash256)
proc getHash(self: BaseChainDB, key: DbKey, output: var Hash256): bool {.inline.} =
try:
output = rlp.decode(self.db.get(key.toOpenArray).toRange, Hash256)
result = true
except KeyError:
discard
proc getCanonicalHead*(self: BaseChainDB): BlockHeader =
let k = canonicalHeadHashKey()
if k.toOpenArray notin self.db:
var headHash: Hash256
if not self.getHash(canonicalHeadHashKey(), headHash) or
not self.getBlockHeader(headHash, result):
raise newException(CanonicalHeadNotFound,
"No canonical head set for this chain")
return self.getBlockHeaderByHash(self.getHash(k))
proc lookupBlockHash*(self: BaseChainDB; n: BlockNumber): Hash256 {.inline.} =
## Return the block hash for the given block number.
self.getHash(blockNumberToHashKey(n))
proc getBlockHash*(self: BaseChainDB, n: BlockNumber, output: var Hash256): bool {.inline.} =
## Return the block hash for the given block number.
self.getHash(blockNumberToHashKey(n), output)
proc getCanonicalBlockHeaderByNumber*(self: BaseChainDB; n: BlockNumber): BlockHeader =
## Returns the block header with the given number in the canonical chain.
##
## Raises BlockNotFound if there's no block header with the given number in the
## canonical chain.
self.getBlockHeaderByHash(self.lookupBlockHash(n))
proc getBlockHash*(self: BaseChainDB, n: BlockNumber): Hash256 {.inline.} =
## Return the block hash for the given block number.
if not self.getHash(blockNumberToHashKey(n), result):
raise newException(BlockNotFound, "No block hash for number " & $n)
proc getBlockHeader*(self: BaseChainDB; n: BlockNumber, output: var BlockHeader): bool =
## Returns the block header with the given number in the canonical chain.
var blockHash: Hash256
if self.getBlockHash(n, blockHash):
result = self.getBlockHeader(blockHash, output)
proc getBlockHeader*(self: BaseChainDB; n: BlockNumber): BlockHeader =
## Returns the block header with the given number in the canonical chain.
## Raises BlockNotFound error if the block is not in the DB.
self.getBlockHeader(self.getBlockHash(n))
proc getScore*(self: BaseChainDB; blockHash: Hash256): int =
rlp.decode(self.db.get(blockHashToScoreKey(blockHash).toOpenArray).toRange, int)
@ -68,20 +87,17 @@ iterator findNewAncestors(self: BaseChainDB; header: BlockHeader): BlockHeader =
## Returns the chain leading up from the given header until the first ancestor it has in
## common with our canonical chain.
var h = header
var orig: BlockHeader
while true:
try:
let orig = self.getCanonicalBlockHeaderByNumber(h.blockNumber)
if orig.hash == h.hash:
break
except BlockNotFound:
discard
if self.getBlockHeader(h.blockNumber, orig) and orig.hash == h.hash:
break
yield h
if h.parentHash == GENESIS_PARENT_HASH:
break
else:
h = self.getBlockHeaderByHash(h.parentHash)
h = self.getBlockHeader(h.parentHash)
proc addBlockNumberToHashLookup(self: BaseChainDB; header: BlockHeader) =
self.db.put(blockNumberToHashKey(header.blockNumber).toOpenArray,
@ -104,19 +120,16 @@ proc removeTransactionFromCanonicalChain(self: BaseChainDB, transactionHash: Has
proc setAsCanonicalChainHead(self: BaseChainDB; headerHash: Hash256): seq[BlockHeader] =
## Sets the header as the canonical chain HEAD.
let header = self.getBlockHeaderByHash(headerHash)
let header = self.getBlockHeader(headerHash)
var newCanonicalHeaders = sequtils.toSeq(findNewAncestors(self, header))
reverse(newCanonicalHeaders)
for h in newCanonicalHeaders:
var oldHash: Hash256
try:
oldHash = self.lookupBlockHash(h.blockNumber)
except BlockNotFound:
if not self.getBlockHash(h.blockNumber, oldHash):
break
let oldHeader = self.getBlockHeaderByHash(oldHash)
let oldHeader = self.getBlockHeader(oldHash)
for txHash in self.getBlockTransactionHashes(oldHeader):
self.removeTransactionFromCanonicalChain(txHash)
# TODO re-add txn to internal pending pool (only if local sender)
@ -171,6 +184,8 @@ proc persistHeaderToDb*(self: BaseChainDB; header: BlockHeader): seq[BlockHeader
else: self.getScore(header.parentHash).u256 + header.difficulty
self.db.put(blockHashToScoreKey(header.hash).toOpenArray, rlp.encode(score).toOpenArray)
self.addBlockNumberToHashLookup(header)
var headScore: int
try:
headScore = self.getScore(self.getCanonicalHead().hash)
@ -230,6 +245,41 @@ proc persistBlockToDb*(self: BaseChainDB; blk: Block) =
proc getStateDb*(self: BaseChainDB; stateRoot: Hash256; readOnly: bool = false): AccountStateDB =
result = newAccountStateDB(self.db, stateRoot)
method genesisHash*(db: BaseChainDB): KeccakHash =
db.getBlockHash(0.toBlockNumber)
method getBlockHeader*(db: BaseChainDB, b: HashOrNum): BlockHeaderRef =
var h: BlockHeader
var ok = case b.isHash
of true:
db.getBlockHeader(b.hash, h)
else:
db.getBlockHeader(b.number, h)
if ok:
result.new()
result[] = h
method getBestBlockHeader*(self: BaseChainDB): BlockHeaderRef =
result.new()
result[] = self.getCanonicalHead()
method getSuccessorHeader*(db: BaseChainDB, h: BlockHeader): BlockHeaderRef =
let n = h.blockNumber + 1
var r: BlockHeader
if db.getBlockHeader(n, r):
result.new()
result[] = r
method getBlockBody*(db: BaseChainDB, blockHash: KeccakHash): BlockBodyRef =
result = nil
# Deprecated:
proc getBlockHeaderByHash*(self: BaseChainDB; blockHash: Hash256): BlockHeader {.deprecated.} =
self.getBlockHeader(blockHash)
proc lookupBlockHash*(self: BaseChainDB; n: BlockNumber): Hash256 {.deprecated.} =
self.getBlockHash(n)
proc getCanonicalBlockHeaderByNumber*(self: BaseChainDB; n: BlockNumber): BlockHeader {.deprecated.} =
self.getBlockHeader(n)

114
nimbus/genesis.nim Normal file
View File

@ -0,0 +1,114 @@
import db/[db_chain, state_db], genesis_alloc, eth_common, tables, stint,
byteutils, times, config, rlp, ranges, block_types, eth_trie,
eth_trie/memdb, constants, nimcrypto, chronicles
type
Genesis* = object
config*: ChainConfig
nonce*: BlockNonce
timestamp*: EthTime
extraData*: seq[byte]
gasLimit*: GasInt
difficulty*: DifficultyInt
mixhash*: Hash256
coinbase*: EthAddress
alloc*: GenesisAlloc
GenesisAlloc = TableRef[EthAddress, GenesisAccount]
GenesisAccount = object
code*: seq[byte]
storage*: Table[UInt256, UInt256]
balance*: UInt256
nonce*: UInt256
func toAddress(n: UInt256): EthAddress =
let a = n.toByteArrayBE()
result[0 .. ^1] = a.toOpenArray(12, a.high)
func decodePrealloc(data: seq[byte]): GenesisAlloc =
result = newTable[EthAddress, GenesisAccount]()
for tup in rlp.decode(data.toRange, seq[(UInt256, UInt256)]):
result[toAddress(tup[0])] = GenesisAccount(balance: tup[1])
proc defaultGenesisBlockForNetwork*(id: PublicNetwork): Genesis =
result = case id
of MainNet:
Genesis(
nonce: 66.toBlockNonce,
extraData: hexToSeqByte("0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa"),
gasLimit: 5000,
difficulty: 17179869184.u256,
alloc: decodePrealloc(mainnetAllocData)
)
of RopstenNet:
Genesis(
nonce: 66.toBlockNonce,
extraData: hexToSeqByte("0x3535353535353535353535353535353535353535353535353535353535353535"),
gasLimit: 16777216,
difficulty: 1048576.u256,
alloc: decodePrealloc(testnetAllocData)
)
of RinkebyNet:
Genesis(
nonce: 66.toBlockNonce,
extraData: hexToSeqByte("0x3535353535353535353535353535353535353535353535353535353535353535"),
gasLimit: 16777216,
difficulty: 1048576.u256,
alloc: decodePrealloc(rinkebyAllocData)
)
else:
# TODO: Fill out the rest
error "No default genesis for network", id
doAssert(false, "No default genesis for " & $id)
Genesis()
result.config = publicChainConfig(id)
proc toBlock*(g: Genesis): BlockHeader =
let tdb = trieDB(newMemDB())
var trie = initHexaryTrie(tdb)
var sdb = newAccountStateDB(tdb, trie.rootHash)
for address, account in g.alloc:
sdb.setBalance(address, account.balance)
sdb.setCode(address, account.code.toRange)
sdb.setNonce(address, account.nonce)
for k, v in account.storage:
sdb.setStorage(address, k, v)
var root = sdb.rootHash
block tempRootHashStub: # TODO: Remove this block when we calculate the root hash correctly
if g.config.chainId == 1:
const correctMainnetRootHash = toDigest("d7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544")
if root != correctMainnetRootHash:
error "Root hash incorrect. Stubbing it out."
root = correctMainnetRootHash
else:
error "Yay! Root hash is correct. Please remove the block where this message comes from."
result = BlockHeader(
nonce: g.nonce,
timestamp: g.timestamp,
extraData: g.extraData,
gasLimit: g.gasLimit,
difficulty: g.difficulty,
mixDigest: g.mixhash,
coinbase: g.coinbase,
stateRoot: root,
parentHash: GENESIS_PARENT_HASH,
txRoot: BLANK_ROOT_HASH,
receiptRoot: BLANK_ROOT_HASH,
ommersHash: EMPTY_UNCLE_HASH
)
if g.gasLimit == 0:
result.gasLimit = GENESIS_GAS_LIMIT
if g.difficulty == 0:
result.difficulty = GENESIS_DIFFICULTY
proc commit*(g: Genesis, db: BaseChainDB) =
let b = g.toBlock()
assert(b.blockNumber == 0, "can't commit genesis block with number > 0")
discard db.persistHeaderToDb(b)

11
nimbus/genesis_alloc.nim Normal file

File diff suppressed because one or more lines are too long

View File

@ -11,7 +11,7 @@ import
os, strutils, net, eth_common, db/[storage_types, db_chain],
asyncdispatch2, json_rpc/rpcserver, eth_keys,
eth_p2p, eth_p2p/rlpx_protocols/[eth, les],
config, rpc/[common, p2p],
config, genesis, rpc/[common, p2p],
eth_trie
const UseSqlite = true
@ -43,10 +43,15 @@ type
proc newTrieDb(): TrieDatabaseRef =
# XXX: Setup db storage location according to config
result = trieDB(newChainDb(":memory:"))
result = trieDB(newChainDb("nimbus.db"))
proc initializeEmptyDb(db: BaseChainDB) =
echo "Initializing empty DB (TODO)"
echo "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)
proc start(): NimbusObject =
var nimbus = NimbusObject()
@ -75,6 +80,7 @@ proc start(): NimbusObject =
if canonicalHeadHashKey().toOpenArray notin trieDB:
initializeEmptyDb(chainDb)
assert(canonicalHeadHashKey().toOpenArray in trieDB)
nimbus.ethNode = newEthereumNode(keypair, address, conf.net.networkId,
nil, nimbusClientId)

View File

@ -11,7 +11,8 @@ when true:
./test_memory,
./test_stack,
./test_opcode,
./test_storage_backends
./test_storage_backends,
./test_genesis
when false:
import ./test_vm_json

7
tests/test_genesis.nim Normal file
View File

@ -0,0 +1,7 @@
import unittest, ../nimbus/[genesis, config], eth_common
suite "Genesis":
test "Correct mainnet hash":
let g = defaultGenesisBlockForNetwork(MainNet)
let b = g.toBlock
check(b.blockHash == "D4E56740F876AEF8C010B86A40D5F56745A118D0906A34E69AEC8C0DB1CB8FA3".toDigest)