nimbus-eth1/nimbus/common/hardforks.nim

435 lines
15 KiB
Nim

# Nimbus
# Copyright (c) 2022-2023 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
std/[options, strutils],
eth/common,
stew/endians2,
json_serialization,
../utils/utils,
./evmforks
{.push raises: [].}
type
ConsensusType* {.pure.} = enum
# Proof of Work
# algorithm: Ethash
POW
# Proof of Authority
# algorithm: Clique
POA
# Proof of Stake
# algorithm: Casper
POS
HardFork* = enum
Frontier
Homestead
DAOFork
Tangerine # a.k.a. EIP150
Spurious # a.k.a. EIP158
Byzantium
Constantinople
Petersburg # a.k.a. ConstantinopleFix
Istanbul
MuirGlacier
Berlin
London
ArrowGlacier
GrayGlacier
MergeFork # a.k.a. Paris
Shanghai
Cancun
const lastPurelyBlockNumberBasedFork* = GrayGlacier
# MergeFork is special because of TTD.
const firstTimeBasedFork* = Shanghai
type
CliqueOptions* = object
epoch* : Option[int]
period*: Option[int]
MergeForkTransitionThreshold* = object
blockNumber*: Option[BlockNumber]
ttd*: Option[DifficultyInt]
ttdPassed*: Option[bool]
ForkTransitionTable* = object
blockNumberThresholds*: array[Frontier..GrayGlacier, Option[BlockNumber]]
mergeForkTransitionThreshold*: MergeForkTransitionThreshold
timeThresholds*: array[Shanghai..Cancun, Option[EthTime]]
# Starting with Shanghai, forking is based on timestamp
# rather than block number.
#
# I'm not sure what to call this type, but we used to pass
# just the block number into various places that need to
# determine which fork we're on, and now we need to pass
# around both block number and also time. And the config
# info for each individual fork will be either a block
# number or a time.
#
# Note that time and TD are optional. TD being optional
# is because it's perfectly fine, if mergeForkBlock is
# set, to not bother with TTD anymore. But I'm not sure
# it makes sense to allow time to be optional. See the
# comment below on forkDeterminationInfo.
ForkDeterminationInfo* = object
blockNumber*: BlockNumber
time*: Option[EthTime]
td*: Option[DifficultyInt]
func forkDeterminationInfo*(n: BlockNumber): ForkDeterminationInfo =
# FIXME: All callers of this function are suspect; I'm guess we should
# always be using both block number and time. But we have a few places,
# like various tests, where we only have block number and the tests are
# meant for pre-Merge forks, so maybe those are okay.
ForkDeterminationInfo(blockNumber: n, time: none[EthTime](), td: none[DifficultyInt]())
func forkDeterminationInfo*(n: BlockNumber, t: EthTime): ForkDeterminationInfo =
ForkDeterminationInfo(blockNumber: n, time: some(t), td: none[DifficultyInt]())
# FIXME: Is this called anywhere?
func forkDeterminationInfoIncludingTd*(n: BlockNumber, t: EthTime, td: DifficultyInt): ForkDeterminationInfo =
ForkDeterminationInfo(blockNumber: n, time: some(t), td: some(td))
func forkDeterminationInfo*(header: BlockHeader): ForkDeterminationInfo =
# FIXME-Adam-mightAlsoNeedTTD?
forkDeterminationInfo(header.blockNumber, header.timestamp)
proc adjustForNextBlock*(n: BlockNumber): BlockNumber =
n + 1
func adjustForNextBlock*(t: EthTime): EthTime =
# FIXME-Adam: what's the right thing to do here?
# How do we calculate "the timestamp for the block
# after this one"?
#
# If this makes no sense, what should the callers
# do instead?
t + 12
func adjustForNextBlock*(f: ForkDeterminationInfo): ForkDeterminationInfo =
ForkDeterminationInfo(
blockNumber: adjustForNextBlock(f.blockNumber),
time: f.time.map(adjustForNextBlock),
td: f.td
)
# This function is awkward because there are various different ways now of
# doing a hard-fork transition (block number, ttd, time, block number *or*
# time). We used to have a simple array called forkToBlock that mapped each
# HardFork to a BlockNumber; now we have this ForkTransitionTable, which
# contains a couple of arrays and also special cases for MergeBlock and
# Shanghai.
func isGTETransitionThreshold*(map: ForkTransitionTable, forkDeterminer: ForkDeterminationInfo, fork: HardFork): bool =
if fork <= lastPurelyBlockNumberBasedFork:
map.blockNumberThresholds[fork].isSome and forkDeterminer.blockNumber >= map.blockNumberThresholds[fork].get
elif fork == MergeFork:
# MergeFork is a special case that can use either block number or ttd;
# ttdPassed > block number > ttd takes precedence.
let t = map.mergeForkTransitionThreshold
if t.ttdPassed.isSome:
t.ttdPassed.get
elif t.blockNumber.isSome:
forkDeterminer.blockNumber >= t.blockNumber.get
elif t.ttd.isSome and forkDeterminer.td.isSome:
forkDeterminer.td.get >= t.ttd.get
else:
false
elif fork <= HardFork.high:
map.timeThresholds[fork].isSome and forkDeterminer.time.isSome and forkDeterminer.time.get >= map.timeThresholds[fork].get
else:
raise newException(Defect, "Why is this hard fork not in one of the above categories?")
type
# if you add more fork block
# please update forkBlockField constant too
ChainConfig* = ref object
chainId* : ChainId
homesteadBlock* : Option[BlockNumber]
daoForkBlock* : Option[BlockNumber]
daoForkSupport* : bool
eip150Block* : Option[BlockNumber]
eip150Hash* : Hash256
eip155Block* : Option[BlockNumber]
eip158Block* : Option[BlockNumber]
byzantiumBlock* : Option[BlockNumber]
constantinopleBlock*: Option[BlockNumber]
petersburgBlock* : Option[BlockNumber]
istanbulBlock* : Option[BlockNumber]
muirGlacierBlock* : Option[BlockNumber]
berlinBlock* : Option[BlockNumber]
londonBlock* : Option[BlockNumber]
arrowGlacierBlock* : Option[BlockNumber]
grayGlacierBlock* : Option[BlockNumber]
# mergeNetsplitBlock is an alias to mergeForkBlock
# and is used for geth compatibility layer
mergeNetsplitBlock* : Option[BlockNumber]
mergeForkBlock* : Option[BlockNumber]
shanghaiTime* : Option[EthTime]
cancunTime* : Option[EthTime]
clique* : CliqueOptions
terminalTotalDifficulty*: Option[UInt256]
terminalTotalDifficultyPassed*: Option[bool]
consensusType*
{.dontSerialize.} : ConsensusType
# These are used for checking that the values of the fields
# are in a valid order.
BlockNumberBasedForkOptional* = object
name*: string
number*: Option[BlockNumber]
TimeBasedForkOptional* = object
name*: string
time*: Option[EthTime]
func countTimeFields(): int {.compileTime.} =
var z = Chainconfig()
for name, _ in fieldPairs(z[]):
if name.endsWith("Time"):
inc result
func countBlockFields(): int {.compileTime.} =
var z = Chainconfig()
for name, _ in fieldPairs(z[]):
if name == "mergeNetsplitBlock":
# skip mergeForkBlock alias
# continue is not supported
discard
elif name.endsWith("Block"):
inc result
const
timeFieldsCount = countTimeFields()
blockFieldsCount = countBlockFields()
func collectTimeFields(): array[timeFieldsCount, string] =
var z = Chainconfig()
var i = 0
for name, _ in fieldPairs(z[]):
if name.endsWith("Time"):
result[i] = name
inc i
func collectBlockFields(): array[blockFieldsCount, string] =
var z = Chainconfig()
var i = 0
for name, _ in fieldPairs(z[]):
if name == "mergeNetsplitBlock":
# skip mergeForkBlock alias
# continue is not supported
discard
elif name.endsWith("Block"):
result[i] = name
inc i
const
# this table is used for generate
# code at compile time to check
# the order of blok number in ChainConfig
forkBlockField* = collectBlockFields()
forkTimeField* = collectTimeFields()
func mergeForkTransitionThreshold*(conf: ChainConfig): MergeForkTransitionThreshold =
MergeForkTransitionThreshold(
blockNumber: conf.mergeForkBlock,
ttd: conf.terminalTotalDifficulty,
ttdPassed: conf.terminalTotalDifficultyPassed
)
proc toForkTransitionTable*(conf: ChainConfig): ForkTransitionTable =
# We used to auto-generate this code from a list of
# field names, but it doesn't seem worthwhile anymore
# (now that there's irregularity due to block-based vs
# timestamp-based forking).
result.blockNumberThresholds[Frontier ] = some(0.toBlockNumber)
result.blockNumberThresholds[Homestead ] = conf.homesteadBlock
result.blockNumberThresholds[DAOFork ] = conf.daoForkBlock
result.blockNumberThresholds[Tangerine ] = conf.eip150Block
result.blockNumberThresholds[Spurious ] = conf.eip158Block
result.blockNumberThresholds[Byzantium ] = conf.byzantiumBlock
result.blockNumberThresholds[Constantinople] = conf.constantinopleBlock
result.blockNumberThresholds[Petersburg ] = conf.petersburgBlock
result.blockNumberThresholds[Istanbul ] = conf.istanbulBlock
result.blockNumberThresholds[MuirGlacier ] = conf.muirGlacierBlock
result.blockNumberThresholds[Berlin ] = conf.berlinBlock
result.blockNumberThresholds[London ] = conf.londonBlock
result.blockNumberThresholds[ArrowGlacier ] = conf.arrowGlacierBlock
result.blockNumberThresholds[GrayGlacier ] = conf.grayGlacierBlock
result.mergeForkTransitionThreshold = conf.mergeForkTransitionThreshold
result.timeThresholds[Shanghai] = conf.shanghaiTime
result.timeThresholds[Cancun] = conf.cancunTime
proc populateFromForkTransitionTable*(conf: ChainConfig, t: ForkTransitionTable) =
conf.homesteadBlock = t.blockNumberThresholds[HardFork.Homestead]
conf.daoForkBlock = t.blockNumberThresholds[HardFork.DAOFork]
conf.eip150Block = t.blockNumberThresholds[HardFork.Tangerine]
conf.eip155Block = t.blockNumberThresholds[HardFork.Spurious]
conf.eip158Block = t.blockNumberThresholds[HardFork.Spurious]
conf.byzantiumBlock = t.blockNumberThresholds[HardFork.Byzantium]
conf.constantinopleBlock = t.blockNumberThresholds[HardFork.Constantinople]
conf.petersburgBlock = t.blockNumberThresholds[HardFork.Petersburg]
conf.istanbulBlock = t.blockNumberThresholds[HardFork.Istanbul]
conf.muirGlacierBlock = t.blockNumberThresholds[HardFork.MuirGlacier]
conf.berlinBlock = t.blockNumberThresholds[HardFork.Berlin]
conf.londonBlock = t.blockNumberThresholds[HardFork.London]
conf.arrowGlacierBlock = t.blockNumberThresholds[HardFork.ArrowGlacier]
conf.grayGlacierBlock = t.blockNumberThresholds[HardFork.GrayGlacier]
conf.mergeForkBlock = t.mergeForkTransitionThreshold.blockNumber
conf.terminalTotalDifficulty = t.mergeForkTransitionThreshold.ttd
conf.terminalTotalDifficultyPassed = t.mergeForkTransitionThreshold.ttdPassed
conf.shanghaiTime = t.timeThresholds[HardFork.Shanghai]
conf.cancunTime = t.timeThresholds[HardFork.Cancun]
# ------------------------------------------------------------------------------
# Map HardFork to EVM/EVMC Fork
# ------------------------------------------------------------------------------
const
ToEVMFork*: array[HardFork, EVMFork] = [
FkFrontier, # Frontier
FkHomestead, # Homestead
FkHomestead, # DAOFork
FkTangerine, # Tangerine
FkSpurious, # Spurious
FkByzantium, # Byzantium
FkConstantinople, # Constantinople
FkPetersburg, # Petersburg
FkIstanbul, # Istanbul
FkIstanbul, # MuirGlacier
FkBerlin, # Berlin
FkLondon, # London
FkLondon, # ArrowGlacier
FkLondon, # GrayGlacier
FkParis, # MergeFork
FkShanghai, # Shanghai
FkCancun, # Cancun
]
# ------------------------------------------------------------------------------
# Block reward helpers
# ------------------------------------------------------------------------------
func eth(n: int): UInt256 {.compileTime.} =
n.u256 * pow(10.u256, 18)
const
eth5 = 5.eth
eth3 = 3.eth
eth2 = 2.eth
eth0 = 0.u256
BlockRewards*: array[HardFork, UInt256] = [
eth5, # Frontier
eth5, # Homestead
eth5, # DAOFork
eth5, # Tangerine
eth5, # Spurious
eth3, # Byzantium
eth2, # Constantinople
eth2, # Petersburg
eth2, # Istanbul
eth2, # MuirGlacier
eth2, # Berlin
eth2, # London
eth2, # ArrowGlacier
eth2, # GrayGlacier
eth0, # MergeFork
eth0, # Shanghai
eth0, # Cancun
]
# ------------------------------------------------------------------------------
# Fork ID helpers
# ------------------------------------------------------------------------------
type
ForkIdCalculator* = object
byBlock: seq[uint64]
byTime: seq[uint64]
genesisCRC: uint32
func newID*(calc: ForkIdCalculator, head, time: uint64): ForkID =
var hash = calc.genesisCRC
for fork in calc.byBlock:
if fork <= head:
# Fork already passed, checksum the previous hash and the fork number
hash = crc32(hash, fork.toBytesBE)
continue
return (hash, fork)
for fork in calc.byTime:
if fork <= time:
# Fork already passed, checksum the previous hash and fork timestamp
hash = crc32(hash, fork.toBytesBE)
continue
return (hash, fork)
(hash, 0'u64)
func initForkIdCalculator*(map: ForkTransitionTable,
genesisCRC: uint32,
genesisTime: uint64): ForkIdCalculator =
# Extract the fork rule block number aggregate it
var forksByBlock: seq[uint64]
for fork, val in map.blockNumberThresholds:
if val.isNone: continue
let val64 = val.get.truncate(uint64)
if forksByBlock.len == 0:
forksByBlock.add val64
elif forksByBlock[^1] != val64:
# Deduplicate fork identifiers applying multiple forks
forksByBlock.add val64
if map.mergeForkTransitionThreshold.blockNumber.isSome:
let val64 = map.mergeForkTransitionThreshold.blockNumber.get.truncate(uint64)
if forksByBlock.len == 0:
forksByBlock.add val64
elif forksByBlock[^1] != val64:
# Deduplicate fork identifiers applying multiple forks
forksByBlock.add val64
# Skip any forks in block 0, that's the genesis ruleset
if forksByBlock.len > 0 and forksByBlock[0] == 0:
forksByBlock.delete(0)
# Extract the fork rule timestamp number aggregate it
var forksByTime: seq[uint64]
for fork, val in map.timeThresholds:
if val.isNone: continue
let val64 = val.get.uint64
if forksByTime.len == 0:
forksByTime.add val64
elif forksByTime[^1] != val64:
forksByTime.add val64
# Skip any forks before genesis.
while forksByTime.len > 0 and forksByTime[0] <= genesisTime:
forksByTime.delete(0)
result.genesisCRC = genesisCRC
result.byBlock = system.move(forksByBlock)
result.byTime = system.move(forksByTime)
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------