nimbus-eth1/nimbus/common/chain_config.nim

559 lines
22 KiB
Nim

# Nimbus
# Copyright (c) 2021 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.
{.push raises: [].}
import
std/[tables, strutils, options, times, macros],
eth/[common, rlp, p2p], stint, stew/[byteutils],
json_serialization, chronicles,
json_serialization/std/options as jsoptions,
json_serialization/std/tables as jstable,
json_serialization/lexer,
"."/[genesis_alloc, hardforks]
export
hardforks
type
Genesis* = ref object
nonce* : BlockNonce
timestamp* : EthTime
extraData* : seq[byte]
gasLimit* : GasInt
difficulty* : DifficultyInt
mixHash* : Hash256
coinbase* : EthAddress
alloc* : GenesisAlloc
number* : BlockNumber
gasUser* : GasInt
parentHash* : Hash256
baseFeePerGas*: Option[UInt256]
blobGasUsed* : Option[uint64] # EIP-4844
excessBlobGas*: Option[uint64] # EIP-4844
parentBeaconBlockRoot*: Option[Hash256] # EIP-4788
GenesisAlloc* = Table[EthAddress, GenesisAccount]
GenesisAccount* = object
code* : seq[byte]
storage*: Table[UInt256, UInt256]
balance*: UInt256
nonce* : AccountNonce
NetworkParams* = object
config* : ChainConfig
genesis*: Genesis
AddressBalance = object
address {.rlpCustomSerialization.}: EthAddress
account {.rlpCustomSerialization.}: GenesisAccount
GenesisFile* = object
config : ChainConfig
nonce* : BlockNonce
timestamp* : EthTime
extraData* : seq[byte]
gasLimit* : GasInt
difficulty* : DifficultyInt
mixHash* : Hash256
coinbase* : EthAddress
alloc* : GenesisAlloc
number* : BlockNumber
gasUser* : GasInt
parentHash* : Hash256
baseFeePerGas*: Option[UInt256]
blobGasUsed* : Option[uint64] # EIP-4844
excessBlobGas*: Option[uint64] # EIP-4844
parentBeaconBlockRoot*: Option[Hash256] # EIP-4788
const
CustomNet* = 0.NetworkId
# these are public network id
MainNet* = 1.NetworkId
# No longer used: MordenNet = 2
RopstenNet* = 3.NetworkId
RinkebyNet* = 4.NetworkId
GoerliNet* = 5.NetworkId
KovanNet* = 42.NetworkId
SepoliaNet* = 11155111.NetworkId
# ------------------------------------------------------------------------------
# Private helper functions
# ------------------------------------------------------------------------------
proc read(rlp: var Rlp, x: var AddressBalance, _: type EthAddress): EthAddress
{.gcsafe, raises: [RlpError].} =
let val = rlp.read(UInt256).toBytesBE()
result[0 .. ^1] = val.toOpenArray(12, val.high)
proc read(rlp: var Rlp, x: var AddressBalance, _: type GenesisAccount): GenesisAccount
{.gcsafe, raises: [RlpError].} =
GenesisAccount(balance: rlp.read(UInt256))
func decodePrealloc*(data: seq[byte]): GenesisAlloc
{.gcsafe, raises: [RlpError].} =
for tup in rlp.decode(data, seq[AddressBalance]):
result[tup.address] = tup.account
# borrowed from `lexer.hexCharValue()` :)
proc fromHex(c: char): int =
case c
of '0'..'9': ord(c) - ord('0')
of 'a'..'f': ord(c) - ord('a') + 10
of 'A'..'F': ord(c) - ord('A') + 10
else: -1
proc readValue(reader: var JsonReader, value: var UInt256)
{.gcsafe, raises: [SerializationError, IOError].} =
## Mixin for `Json.loadFile()`. Note that this driver applies the same
## to `BlockNumber` fields as well as generic `UInt265` fields like the
## account `balance`.
var (accu, ok) = (0.u256, true)
if reader.lexer.lazyTok == tkNumeric:
try:
reader.lexer.customIntValueIt:
accu = accu * 10 + it.u256
ok = reader.lexer.lazyTok == tkExInt # non-negative wanted
except CatchableError:
ok = false
elif reader.lexer.lazyTok == tkQuoted:
try:
var (sLen, base) = (0, 10)
reader.lexer.customTextValueIt:
if ok:
var num = it.fromHex
if base <= num:
ok = false # cannot be larger than base
elif sLen < 2:
if 0 <= num:
accu = accu * base.u256 + num.u256
elif sLen == 1 and it in {'x', 'X'}:
base = 16 # handle "0x" prefix
else:
ok = false
sLen.inc
elif num < 0:
ok = false # not a hex digit
elif base == 10:
accu = accu * 10 + num.u256
else:
accu = accu * 16 + num.u256
except CatchableError:
reader.raiseUnexpectedValue("numeric string parse error")
else:
reader.raiseUnexpectedValue("expect int or hex/int string")
if not ok:
reader.raiseUnexpectedValue("Uint256 parse error")
value = accu
reader.lexer.next()
proc readValue(reader: var JsonReader, value: var ChainId)
{.gcsafe, raises: [SerializationError, IOError].} =
value = reader.readValue(int).ChainId
proc readValue(reader: var JsonReader, value: var Hash256)
{.gcsafe, raises: [SerializationError, IOError].} =
value = Hash256.fromHex(reader.readValue(string))
proc readValue(reader: var JsonReader, value: var BlockNonce)
{.gcsafe, raises: [SerializationError, IOError].} =
try:
value = fromHex[uint64](reader.readValue(string)).toBlockNonce
except ValueError as ex:
reader.raiseUnexpectedValue(ex.msg)
# genesis timestamp is in hex
proc readValue(reader: var JsonReader, value: var EthTime)
{.gcsafe, raises: [SerializationError, IOError].} =
try:
value = fromHex[int64](reader.readValue(string)).fromUnix
except ValueError as ex:
reader.raiseUnexpectedValue(ex.msg)
# but shanghaiTime and cancunTime in config is in int literal
proc readValue(reader: var JsonReader, value: var Option[EthTime])
{.gcsafe, raises: [SerializationError, IOError].} =
let tok = reader.lexer.lazyTok
if tok == tkNull:
reset value
reader.lexer.next()
else:
# both readValue(GasInt/AccountNonce) will be called if
# we use readValue(int64/uint64)
let tok {.used.} = reader.lexer.tok # resove lazy token
let val = reader.lexer.absIntVal.int64
value = some val.fromUnix
reader.lexer.next()
proc readValue(reader: var JsonReader, value: var seq[byte])
{.gcsafe, raises: [SerializationError, IOError].} =
try:
value = hexToSeqByte(reader.readValue(string))
except ValueError as ex:
reader.raiseUnexpectedValue(ex.msg)
proc readValue(reader: var JsonReader, value: var GasInt)
{.gcsafe, raises: [SerializationError, IOError].} =
try:
value = fromHex[GasInt](reader.readValue(string))
except ValueError as ex:
reader.raiseUnexpectedValue(ex.msg)
proc readValue(reader: var JsonReader, value: var EthAddress)
{.gcsafe, raises: [SerializationError, IOError].} =
try:
value = parseAddress(reader.readValue(string))
except ValueError as ex:
reader.raiseUnexpectedValue(ex.msg)
proc readValue(reader: var JsonReader, value: var AccountNonce)
{.gcsafe, raises: [SerializationError, IOError].} =
try:
value = fromHex[uint64](reader.readValue(string))
except ValueError as ex:
reader.raiseUnexpectedValue(ex.msg)
template to(a: string, b: type EthAddress): EthAddress =
# json_serialization decode table stuff
parseAddress(a)
template to(a: string, b: type UInt256): UInt256 =
# json_serialization decode table stuff
UInt256.fromHex(a)
macro fillArrayOfBlockNumberBasedForkOptionals(conf, tmp: typed): untyped =
result = newStmtList()
for i, x in forkBlockField:
let fieldIdent = newIdentNode(x)
result.add quote do:
`tmp`[`i`] = BlockNumberBasedForkOptional(
number : `conf`.`fieldIdent`,
name : `x`)
macro fillArrayOfTimeBasedForkOptionals(conf, tmp: typed): untyped =
result = newStmtList()
for i, x in forkTimeField:
let fieldIdent = newIdentNode(x)
result.add quote do:
`tmp`[`i`] = TimeBasedForkOptional(
time : `conf`.`fieldIdent`,
name : `x`)
# ------------------------------------------------------------------------------
# Public functions
# ------------------------------------------------------------------------------
proc toHardFork*(map: ForkTransitionTable, forkDeterminer: ForkDeterminationInfo): HardFork =
for fork in countdown(HardFork.high, HardFork.low):
if isGTETransitionThreshold(map, forkDeterminer, fork):
return fork
# should always have a match
doAssert(false, "unreachable code")
func forkDeterminationInfoForHeader*(header: BlockHeader): ForkDeterminationInfo =
# FIXME-Adam-mightAlsoNeedTTD?
forkDeterminationInfo(header.blockNumber, header.timestamp)
proc validateChainConfig*(conf: ChainConfig): bool =
result = true
# FIXME: factor this to remove the duplication between the
# block-based ones and the time-based ones.
var blockNumberBasedForkOptionals: array[forkBlockField.len, BlockNumberBasedForkOptional]
fillArrayOfBlockNumberBasedForkOptionals(conf, blockNumberBasedForkOptionals)
var timeBasedForkOptionals: array[forkTimeField.len, TimeBasedForkOptional]
fillArrayOfTimeBasedForkOptionals(conf, timeBasedForkOptionals)
var lastBlockNumberBasedFork = blockNumberBasedForkOptionals[0]
for i in 1..<blockNumberBasedForkOptionals.len:
let cur = blockNumberBasedForkOptionals[i]
if lastBlockNumberBasedFork.number.isSome and cur.number.isSome:
if lastBlockNumberBasedFork.number.get > cur.number.get:
error "Unsupported fork ordering",
lastFork=lastBlockNumberBasedFork.name,
lastNumber=lastBlockNumberBasedFork.number,
curFork=cur.name,
curNumber=cur.number
return false
# If it was optional and not set, then ignore it
if cur.number.isSome:
lastBlockNumberBasedFork = cur
# TODO: check to make sure the timestamps are all past the
# block numbers?
var lastTimeBasedFork = timeBasedForkOptionals[0]
for i in 1..<timeBasedForkOptionals.len:
let cur = timeBasedForkOptionals[i]
if lastTimeBasedFork.time.isSome and cur.time.isSome:
if lastTimeBasedFork.time.get > cur.time.get:
error "Unsupported fork ordering",
lastFork=lastTimeBasedFork.name,
lastTime=lastTimeBasedFork.time,
curFork=cur.name,
curTime=cur.time
return false
# If it was optional and not set, then ignore it
if cur.time.isSome:
lastTimeBasedFork = cur
if conf.clique.period.isSome or
conf.clique.epoch.isSome:
conf.consensusType = ConsensusType.POA
proc validateNetworkParams*(params: var NetworkParams): bool =
if params.genesis.isNil:
warn "Loaded custom network contains no 'genesis' data"
if params.config.isNil:
warn "Loaded custom network contains no 'config' data"
params.config = ChainConfig()
validateChainConfig(params.config)
proc loadNetworkParams*(fileName: string, params: var NetworkParams):
bool =
try:
params = Json.loadFile(fileName, NetworkParams, allowUnknownFields = true)
except IOError as e:
error "Network params I/O error", fileName, msg=e.msg
return false
except JsonReaderError as e:
error "Invalid network params file format", fileName, msg=e.formatMsg("")
return false
except CatchableError as e:
error "Error loading network params file",
fileName, exception = e.name, msg = e.msg
return false
validateNetworkParams(params)
proc decodeNetworkParams*(jsonString: string, params: var NetworkParams): bool =
try:
params = Json.decode(jsonString, NetworkParams, allowUnknownFields = true)
except JsonReaderError as e:
error "Invalid network params format", msg=e.formatMsg("")
return false
except CatchableError:
var msg = getCurrentExceptionMsg()
error "Error decoding network params", msg
return false
validateNetworkParams(params)
proc parseGenesisAlloc*(data: string, ga: var GenesisAlloc): bool
{.gcsafe, raises: [CatchableError].} =
try:
ga = Json.decode(data, GenesisAlloc, allowUnknownFields = true)
except JsonReaderError as e:
error "Invalid genesis config file format", msg=e.formatMsg("")
return false
return true
proc parseGenesis*(data: string): Genesis
{.gcsafe, raises: [CatchableError].} =
try:
result = Json.decode(data, Genesis, allowUnknownFields = true)
except JsonReaderError as e:
error "Invalid genesis config file format", msg=e.formatMsg("")
return nil
proc chainConfigForNetwork*(id: NetworkId): ChainConfig =
# For some public networks, NetworkId and ChainId value are identical
# but that is not always the case
result = case id
of MainNet:
const mainNetTTD = parse("58750000000000000000000",UInt256)
ChainConfig(
consensusType: ConsensusType.POW,
chainId: MainNet.ChainId,
# Genesis (Frontier): # 2015-07-30 15:26:13 UTC
# Frontier Thawing: 200_000.toBlockNumber, # 2015-09-07 21:33:09 UTC
homesteadBlock: some(1_150_000.toBlockNumber), # 2016-03-14 18:49:53 UTC
daoForkBlock: some(1_920_000.toBlockNumber), # 2016-07-20 13:20:40 UTC
daoForkSupport: true,
eip150Block: some(2_463_000.toBlockNumber), # 2016-10-18 13:19:31 UTC
eip150Hash: toDigest("2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0"),
eip155Block: some(2_675_000.toBlockNumber), # Same as EIP-158
eip158Block: some(2_675_000.toBlockNumber), # 2016-11-22 16:15:44 UTC
byzantiumBlock: some(4_370_000.toBlockNumber), # 2017-10-16 05:22:11 UTC
constantinopleBlock: some(7_280_000.toBlockNumber), # Skipped on Mainnet
petersburgBlock: some(7_280_000.toBlockNumber), # 2019-02-28 19:52:04 UTC
istanbulBlock: some(9_069_000.toBlockNumber), # 2019-12-08 00:25:09 UTC
muirGlacierBlock: some(9_200_000.toBlockNumber), # 2020-01-02 08:30:49 UTC
berlinBlock: some(12_244_000.toBlockNumber), # 2021-04-15 10:07:03 UTC
londonBlock: some(12_965_000.toBlockNumber), # 2021-08-05 12:33:42 UTC
arrowGlacierBlock: some(13_773_000.toBlockNumber), # 2021-12-09 19:55:23 UTC
grayGlacierBlock: some(15_050_000.toBlockNumber), # 2022-06-30 10:54:04 UTC
terminalTotalDifficulty: some(mainNetTTD),
shanghaiTime: some(1_681_338_455.fromUnix)
)
of RopstenNet:
ChainConfig(
consensusType: ConsensusType.POW,
chainId: RopstenNet.ChainId,
# Genesis: # 2016-11-20 11:48:50 UTC
homesteadBlock: some(0.toBlockNumber), # Included in genesis
daoForkSupport: false,
eip150Block: some(0.toBlockNumber), # Included in genesis
eip150Hash: toDigest("41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d"),
eip155Block: some(10.toBlockNumber), # Same as EIP-158
eip158Block: some(10.toBlockNumber), # 2016-11-20 11:50:44 UTC
byzantiumBlock: some(1_700_000.toBlockNumber), # 2017-09-19 01:08:28 UTC
constantinopleBlock: some(4_230_000.toBlockNumber), # 2018-10-13 17:19:06 UTC
petersburgBlock: some(4_939_394.toBlockNumber), # 2019-02-02 07:39:08 UTC
istanbulBlock: some(6_485_846.toBlockNumber), # 2019-09-30 03:38:06 UTC
muirGlacierBlock: some(7_117_117.toBlockNumber), # 2020-01-13 06:37:37 UTC
berlinBlock: some(9_812_189.toBlockNumber), # 2021-03-10 13:32:08 UTC
londonBlock: some(10_499_401.toBlockNumber), # 2021-06-24 02:03:37 UTC
)
of RinkebyNet:
ChainConfig(
clique: CliqueOptions(period: some(15), epoch: some(30000)),
consensusType: ConsensusType.POA,
chainId: RinkebyNet.ChainId,
# Genesis: # 2017-04-12 15:20:50 UTC
homesteadBlock: some(1.toBlockNumber), # 2017-04-12 15:20:58 UTC
daoForkSupport: false,
eip150Block: some(2.toBlockNumber), # 2017-04-12 15:21:14 UTC
eip150Hash: toDigest("9b095b36c15eaf13044373aef8ee0bd3a382a5abb92e402afa44b8249c3a90e9"),
eip155Block: some(3.toBlockNumber), # Same as EIP-158
eip158Block: some(3.toBlockNumber), # 2017-04-12 15:21:29 UTC
byzantiumBlock: some(1_035_301.toBlockNumber), # 2017-10-09 12:08:23 UTC
constantinopleBlock: some(3_660_663.toBlockNumber), # 2019-01-09 13:00:55 UTC
petersburgBlock: some(4_321_234.toBlockNumber), # 2019-05-04 05:32:45 UTC
istanbulBlock: some(5_435_345.toBlockNumber), # 2019-11-13 18:21:53 UTC
muirGlacierBlock: some(8_290_928.toBlockNumber), # Skipped on Rinkeby
berlinBlock: some(8_290_928.toBlockNumber), # 2021-03-24 14:48:36 UTC
londonBlock: some(8_897_988.toBlockNumber), # 2021-07-08 01:27:32 UTC
)
of GoerliNet:
ChainConfig(
clique: CliqueOptions(period: some(15), epoch: some(30000)),
consensusType: ConsensusType.POA,
chainId: GoerliNet.ChainId,
# Genesis: # 2015-07-30 15:26:13 UTC
homesteadBlock: some(0.toBlockNumber), # Included in genesis
daoForkSupport: false,
eip150Block: some(0.toBlockNumber), # Included in genesis
eip150Hash: toDigest("0000000000000000000000000000000000000000000000000000000000000000"),
eip155Block: some(0.toBlockNumber), # Included in genesis
eip158Block: some(0.toBlockNumber), # Included in genesis
byzantiumBlock: some(0.toBlockNumber), # Included in genesis
constantinopleBlock: some(0.toBlockNumber), # Included in genesis
petersburgBlock: some(0.toBlockNumber), # Included in genesis
istanbulBlock: some(1_561_651.toBlockNumber), # 2019-10-30 13:53:05 UTC
muirGlacierBlock: some(4_460_644.toBlockNumber), # Skipped in Goerli
berlinBlock: some(4_460_644.toBlockNumber), # 2021-03-18 05:29:51 UTC
londonBlock: some(5_062_605.toBlockNumber), # 2021-07-01 03:19:39 UTC
terminalTotalDifficulty: some(10790000.u256),
shanghaiTime: some(1_678_832_736.fromUnix)
)
of SepoliaNet:
const sepoliaTTD = parse("17000000000000000",UInt256)
ChainConfig(
consensusType: ConsensusType.POW,
chainId: SepoliaNet.ChainId,
homesteadBlock: some(0.toBlockNumber),
daoForkSupport: false,
eip150Block: some(0.toBlockNumber),
eip150Hash: toDigest("0000000000000000000000000000000000000000000000000000000000000000"),
eip155Block: some(0.toBlockNumber),
eip158Block: some(0.toBlockNumber),
byzantiumBlock: some(0.toBlockNumber),
constantinopleBlock: some(0.toBlockNumber),
petersburgBlock: some(0.toBlockNumber),
istanbulBlock: some(0.toBlockNumber),
muirGlacierBlock: some(0.toBlockNumber),
berlinBlock: some(0.toBlockNumber),
londonBlock: some(0.toBlockNumber),
terminalTotalDifficulty: some(sepoliaTTD),
shanghaiTime: some(1_677_557_088.fromUnix)
)
else:
ChainConfig()
proc genesisBlockForNetwork*(id: NetworkId): Genesis
{.gcsafe, raises: [ValueError, RlpError].} =
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: 0.toBlockNonce,
timestamp: initTime(0x58ee40ba, 0),
extraData: hexToSeqByte("0x52657370656374206d7920617574686f7269746168207e452e436172746d616e42eb768f2244c8811c63729a21a3569731535f067ffc57839b00206d1ad20c69a1981b489f772031b279182d99e65703f0076e4812653aab85fca0f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"),
gasLimit: 4700000,
difficulty: 1.u256,
alloc: decodePrealloc(rinkebyAllocData)
)
of GoerliNet:
Genesis(
nonce: 0.toBlockNonce,
timestamp: initTime(0x5c51a607, 0),
extraData: hexToSeqByte("0x22466c6578692069732061207468696e6722202d204166726900000000000000e0a2bd4258d2768837baa26a28fe71dc079f84c70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"),
gasLimit: 0xa00000,
difficulty: 1.u256,
alloc: decodePrealloc(goerliAllocData)
)
of SepoliaNet:
Genesis(
nonce: 0.toBlockNonce,
timestamp: initTime(0x6159af19, 0),
extraData: hexToSeqByte("0x5365706f6c69612c20417468656e732c204174746963612c2047726565636521"),
gasLimit: 0x1c9c380,
difficulty: 0x20000.u256,
alloc: decodePrealloc(sepoliaAllocData)
)
else:
Genesis()
proc networkParams*(id: NetworkId): NetworkParams
{.gcsafe, raises: [ValueError, RlpError].} =
result.genesis = genesisBlockForNetwork(id)
result.config = chainConfigForNetwork(id)
proc `==`*(a, b: ChainId): bool =
a.uint64 == b.uint64
proc `==`*(a, b: Genesis): bool =
if a.isNil and b.isNil: return true
if a.isNil and not b.isNil: return false
if not a.isNil and b.isNil: return false
a[] == b[]
proc `==`*(a, b: ChainConfig): bool =
if a.isNil and b.isNil: return true
if a.isNil and not b.isNil: return false
if not a.isNil and b.isNil: return false
a[] == b[]