Time based forking (#1465)

* Refactoring in preparation for time-based forking.

* Timestamp-based hard-fork-transition.

* Workaround SideEffect issue / compiler bug for both failing locations in Portal history code

---------

Co-authored-by: kdeme <kim.demey@gmail.com>
This commit is contained in:
Adam Spitz 2023-02-16 11:40:07 +00:00 committed by GitHub
parent 54958d4731
commit fad3ed64cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 396 additions and 287 deletions

View File

@ -12,7 +12,7 @@ import
stew/results, chronos, chronicles,
eth/common/eth_types, eth/rlp,
../network/wire/portal_protocol,
../network/history/[history_content, accumulator],
../network/history/[history_content, history_network, accumulator],
"."/[history_data_json_store, history_data_ssz_e2s]
export results

View File

@ -21,6 +21,29 @@ logScope:
export accumulator
# This looks like it makes no sense, because it makes no sense. It's a
# workaround for what seems to be a compiler bug; see here:
#
# https://github.com/status-im/nimbus-eth1/pull/1465
#
# Without this, the call `error` on a `Result` might give a compiler error for
# the `Result[BlockHeader, string]` or `Result[seq[BlockHeader], string]` types.
# The error is due to the `$` for `BlockHeader causing side effects, which
# appears to be due to the timestamp field, which is of `times.Time` type. Its
# `$` from the times module has side effects (Yes, silly times). In (my) theory
# this `$` should not leak here, but it seems to do. To workaround this we
# introduce this additional `$` call, which appears to work.
#
# Note that this also fixes the same error in another module, even when not
# specifically exporting (no asterisk) the call.
#
# If you think this is unnecessary, feel free to try deleting it; if all the
# tests still pass after deleting it, feel free to leave it out. In the
# meantime, please just ignore it and go on with your life.
#
proc `$`(x: BlockHeader): string =
$x
const
historyProtocolId* = [byte 0x50, 0x0B]

View File

@ -24,8 +24,8 @@ proc genesisToTrie(filePath: string): HexaryTrie =
quit(1)
let sdb = newStateDB(newMemoryDB(), false)
let map = toForkToBlockNumber(cn.config)
let fork = map.toHardFork(0.toBlockNumber)
let map = toForkTransitionTable(cn.config)
let fork = map.toHardFork(forkDeterminationInfo(0.toBlockNumber, cn.genesis.timestamp))
discard toGenesisHeader(cn.genesis, sdb, fork)
sdb.getTrie

View File

@ -187,73 +187,89 @@ template to(a: string, b: type UInt256): UInt256 =
# json_serialization decode table stuff
UInt256.fromHex(a)
macro fillTmpArray(conf, tmp: typed): untyped =
macro fillArrayOfBlockNumberBasedForkOptionals(conf, tmp: typed): untyped =
result = newStmtList()
for i, x in forkBlockField:
let fieldIdent = newIdentNode(x)
result.add quote do:
`tmp`[`i`] = ForkOptional(
`tmp`[`i`] = BlockNumberBasedForkOptional(
number : `conf`.`fieldIdent`,
name : `x`)
macro fillToBlockNumberArray(conf, arr: typed): untyped =
macro fillArrayOfTimeBasedForkOptionals(conf, tmp: typed): untyped =
result = newStmtList()
for fork, forkField in forkBlockNumber:
let
fieldIdent = newIdentNode(forkField)
forkIdent = newIdentNode($HardFork(fork.ord))
for i, x in forkTimeField:
let fieldIdent = newIdentNode(x)
result.add quote do:
`arr`[`forkIdent`] = `conf`.`fieldIdent`
`tmp`[`i`] = TimeBasedForkOptional(
time : `conf`.`fieldIdent`,
name : `x`)
# ------------------------------------------------------------------------------
# Public functions
# ------------------------------------------------------------------------------
proc toForkToBlockNumber*(conf: ChainConfig): ForkToBlockNumber =
fillToBlockNumberArray(conf, result)
result[Frontier] = some(0.toBlockNumber)
proc toHardFork*(forkToBlock: ForkToBlockNumber, number: BlockNumber): HardFork =
proc toHardFork*(map: ForkTransitionTable, forkDeterminer: ForkDeterminationInfo): HardFork =
for fork in countdown(HardFork.high, HardFork.low):
let forkBlock = forkToBlock[fork]
if forkBlock.isSome and number >= forkBlock.get:
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
var tmp: array[forkBlockField.len, ForkOptional]
fillTmpArray(conf, tmp)
# FIXME: factor this to remove the duplication between the
# block-based ones and the time-based ones.
var lastFork = tmp[0]
for i in 1..<tmp.len:
let cur = tmp[i]
var blockNumberBasedForkOptionals: array[forkBlockField.len, BlockNumberBasedForkOptional]
fillArrayOfBlockNumberBasedForkOptionals(conf, blockNumberBasedForkOptionals)
# Next one must be higher number
#if lastFork.number.isNone and cur.number.isSome:
# error "Unsupported fork ordering",
# lastFork=lastFork.name,
# lastNumber=lastFork.number,
# curFork=cur.name,
# curNumber=cur.number
# return false
var timeBasedForkOptionals: array[forkTimeField.len, TimeBasedForkOptional]
fillArrayOfTimeBasedForkOptionals(conf, timeBasedForkOptionals)
if lastFork.number.isSome and cur.number.isSome:
if lastFork.number.get > cur.number.get:
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=lastFork.name,
lastNumber=lastFork.number,
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:
lastFork = cur
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

View File

@ -53,10 +53,9 @@ type
genesisHash: KeccakHash
genesisHeader: BlockHeader
# map block number and ttd to
# map block number and ttd and time to
# HardFork
forkToBlock: ForkToBlockNumber
blockToFork: BlockToForks
forkTransitionTable: ForkTransitionTable
# Eth wire protocol need this
forkIds: array[HardFork, ForkID]
@ -86,8 +85,7 @@ type
# Forward declarations
# ------------------------------------------------------------------------------
proc hardForkTransition*(com: CommonRef,
number: BlockNumber, td: Option[DifficultyInt]) {.gcsafe, raises: [CatchableError].}
proc hardForkTransition*(com: CommonRef, forkDeterminer: ForkDeterminationInfo) {.gcsafe, raises: [CatchableError].}
func cliquePeriod*(com: CommonRef): int
@ -129,8 +127,7 @@ proc init(com : CommonRef,
com.db = ChainDBRef.new(db)
com.pruneTrie = pruneTrie
com.config = config
com.forkToBlock = config.toForkToBlockNumber()
com.blockToFork = config.blockToForks(com.forkToBlock)
com.forkTransitionTable = config.toForkTransitionTable()
com.networkId = networkId
com.syncProgress= SyncProgress()
@ -139,7 +136,8 @@ proc init(com : CommonRef,
# set it before creating genesis block
# TD need to be some(0.u256) because it can be the genesis
# already at the MergeFork
com.hardForkTransition(0.toBlockNumber, some(0.u256))
let optionalGenesisTime = if genesis.isNil: none[EthTime]() else: some(genesis.timestamp)
com.hardForkTransition(ForkDeterminationInfo(blockNumber: 0.toBlockNumber, td: some(0.u256), time: optionalGenesisTime))
# com.forkIds and com.blockZeroHash is set
# by setForkId
@ -156,6 +154,24 @@ proc init(com : CommonRef,
com.pow = PowRef.new
com.pos = CasperRef.new
proc getTd(com: CommonRef, blockHash: Hash256): Option[DifficultyInt] =
var td: DifficultyInt
if not com.db.getTd(blockHash, td):
# TODO: Is this really ok?
none[DifficultyInt]()
else:
some(td)
proc needTdForHardForkDetermination(com: CommonRef): bool =
let t = com.forkTransitionTable.mergeForkTransitionThreshold
t.blockNumber.isNone and t.ttd.isSome
proc getTdIfNecessary(com: CommonRef, blockHash: Hash256): Option[DifficultyInt] =
if needTdForHardForkDetermination(com):
getTd(com, blockHash)
else:
none[DifficultyInt]()
# ------------------------------------------------------------------------------
# Public constructors
# ------------------------------------------------------------------------------
@ -201,8 +217,7 @@ proc clone*(com: CommonRef, db: TrieDatabaseRef): CommonRef =
db : ChainDBRef.new(db),
pruneTrie : com.pruneTrie,
config : com.config,
forkToBlock : com.forkToBlock,
blockToFork : com.blockToFork,
forkTransitionTable: com.forkTransitionTable,
forkIds : com.forkIds,
genesisHash : com.genesisHash,
genesisHeader: com.genesisHeader,
@ -222,85 +237,41 @@ proc clone*(com: CommonRef): CommonRef =
# Public functions
# ------------------------------------------------------------------------------
func toHardFork*(com: CommonRef, number: BlockNumber): HardFork =
## doesn't do transition
## only want to know a particular block number
## belongs to which fork without considering TD or TTD
## MergeFork: assume the MergeForkBlock isSome,
## if there is a MergeFork, thus bypassing the TD >= TTD
## comparison
toHardFork(com.forkToBlock, number)
func toHardFork*(com: CommonRef, forkDeterminer: ForkDeterminationInfo): HardFork =
toHardFork(com.forkTransitionTable, forkDeterminer)
proc hardForkTransition(com: CommonRef,
number: BlockNumber, td: Option[DifficultyInt])
proc hardForkTransition(com: CommonRef, forkDeterminer: ForkDeterminationInfo)
{.gcsafe, raises: [Defect, CatchableError].} =
## When consensus type already transitioned to POS,
## the storage can choose not to store TD anymore,
## at that time, TD is no longer needed to find a fork
## TD only needed during transition from POW/POA to POS.
## Same thing happen before London block, TD can be ignored.
let fork = com.toHardFork(forkDeterminer)
com.currentFork = fork
com.consensusTransition(fork)
if td.isNone:
# fork transition ignoring TD
let fork = com.toHardFork(number)
com.currentFork = fork
com.consensusTransition(fork)
return
proc hardForkTransition*(com: CommonRef,
number: BlockNumber,
td: Option[DifficultyInt],
time: Option[EthTime])
{.gcsafe, raises: [Defect, CatchableError].} =
com.hardForkTransition(ForkDeterminationInfo(blockNumber: number, time: time, td: td))
# fork transition considering TD
let td = td.get()
for fork in countdown(HardFork.high, HardFork.low):
let x = com.blockToFork[fork]
if x.toFork(x.data, number, td):
com.currentFork = fork
com.consensusTransition(fork)
return
# should always have a match
doAssert(false, "unreachable code")
proc hardForkTransition*(com: CommonRef, parentHash: Hash256,
number: BlockNumber)
{.gcsafe, raises: [CatchableError].} =
# if mergeForkBlock is present, it has higher
# priority than TTD
if com.config.mergeForkBlock.isSome or
com.config.terminalTotalDifficulty.isNone:
let fork = com.toHardFork(number)
com.currentFork = fork
com.consensusTransition(fork)
return
var td: DifficultyInt
if not com.db.getTd(parentHash, td):
# TODO: Is this really ok?
let fork = com.toHardFork(number)
com.currentFork = fork
com.consensusTransition(fork)
return
for fork in countdown(HardFork.high, HardFork.low):
let x = com.blockToFork[fork]
if x.toFork(x.data, number, td):
com.currentFork = fork
com.consensusTransition(fork)
return
# should always have a match
doAssert(false, "unreachable code")
proc hardForkTransition*(com: CommonRef,
parentHash: Hash256,
number: BlockNumber,
time: Option[EthTime])
{.gcsafe, raises: [Defect, CatchableError].} =
com.hardForkTransition(number, getTdIfNecessary(com, parentHash), time)
proc hardForkTransition*(com: CommonRef, header: BlockHeader)
{.gcsafe, raises: [CatchableError].} =
{.gcsafe, raises: [Defect, CatchableError].} =
com.hardForkTransition(header.parentHash, header.blockNumber, some(header.timestamp))
com.hardForkTransition(header.parentHash, header.blockNumber)
func toEVMFork*(com: CommonRef, number: BlockNumber): EVMFork =
func toEVMFork*(com: CommonRef, forkDeterminer: ForkDeterminationInfo): EVMFork =
## similar to toFork, but produce EVMFork
## be aware that if MergeFork is not set in
## chain config, this function probably give wrong
## result because no TD is put into consideration
let fork = com.toHardFork(number)
let fork = com.toHardFork(forkDeterminer)
ToEVMFork[fork]
func toEVMFork*(com: CommonRef): EVMFork =
@ -308,7 +279,7 @@ func toEVMFork*(com: CommonRef): EVMFork =
func isLondon*(com: CommonRef, number: BlockNumber): bool =
# TODO: Fixme, use only London comparator
com.toHardFork(number) >= London
com.toHardFork(number.blockNumberToForkDeterminationInfo) >= London
func forkGTE*(com: CommonRef, fork: HardFork): bool =
com.currentFork >= fork
@ -328,9 +299,9 @@ proc minerAddress*(com: CommonRef; header: BlockHeader): EthAddress
account.value
func forkId*(com: CommonRef, number: BlockNumber): ForkID {.gcsafe.} =
func forkId*(com: CommonRef, forkDeterminer: ForkDeterminationInfo): ForkID {.gcsafe.} =
## EIP 2364/2124
let fork = com.toHardFork(number)
let fork = com.toHardFork(forkDeterminer)
com.forkIds[fork]
func isEIP155*(com: CommonRef, number: BlockNumber): bool =
@ -470,9 +441,8 @@ proc `syncHighest=`*(com: CommonRef, number: BlockNumber) =
proc setTTD*(com: CommonRef, ttd: Option[DifficultyInt]) =
## useful for testing
com.config.terminalTotalDifficulty = ttd
# rebuild blockToFork
# TODO: Fix me, only need to rebuild MergeFork comparator
com.blockToFork = com.config.blockToForks(com.forkToBlock)
# rebuild the MergeFork piece of the forkTransitionTable
com.forkTransitionTable.mergeForkTransitionThreshold = com.config.mergeForkTransitionThreshold
proc setFork*(com: CommonRef, fork: HardFork): Hardfork =
## useful for testing

View File

@ -112,8 +112,8 @@ proc toGenesisHeader*(
): BlockHeader
{.raises: [RlpError].} =
## Generate the genesis block header from the `genesis` and `config` argument value.
let map = toForkToBlockNumber(params.config)
let fork = map.toHardFork(0.toBlockNumber)
let map = toForkTransitionTable(params.config)
let fork = map.toHardFork(forkDeterminationInfo(0.toBlockNumber, params.genesis.timestamp))
toGenesisHeader(params.genesis, fork, db)
# End

View File

@ -8,7 +8,7 @@
# those terms.
import
std/options,
std/[options, times],
eth/common,
json_serialization,
../utils/utils,
@ -49,10 +49,103 @@ type
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]
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 blockNumberToForkDeterminationInfo.
ForkDeterminationInfo* = object
blockNumber*: BlockNumber
time*: Option[EthTime]
td*: Option[DifficultyInt]
func blockNumberToForkDeterminationInfo*(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))
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?
fromUnix(t.toUnix + 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;
# block number takes precedence.
let t = map.mergeForkTransitionThreshold
if 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
@ -74,18 +167,24 @@ type
arrowGlacierBlock* : Option[BlockNumber]
grayGlacierBlock* : Option[BlockNumber]
mergeForkBlock* : Option[BlockNumber]
shanghaiBlock* : Option[BlockNumber]
cancunBlock* : Option[BlockNumber]
shanghaiTime* : Option[EthTime]
cancunTime* : Option[EthTime]
clique* : CliqueOptions
terminalTotalDifficulty*: Option[UInt256]
consensusType*
{.dontSerialize.} : ConsensusType
ForkToBlockNumber* = array[HardFork, Option[BlockNumber]]
ForkOptional* = object
# 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]
const
# this table is used for generate
@ -107,32 +206,62 @@ const
"arrowGlacierBlock",
"grayGlacierBlock",
"mergeForkBlock",
"shanghaiBlock",
"cancunBlock",
]
# this table is used to generate
# code to build fork to block number
# array
forkBlockNumber* = [
Homestead: "homesteadBlock",
DAOFork: "daoForkBlock",
Tangerine: "eip150Block",
Spurious: "eip158Block",
Byzantium: "byzantiumBlock",
Constantinople: "constantinopleBlock",
Petersburg: "petersburgBlock",
Istanbul: "istanbulBlock",
MuirGlacier: "muirGlacierBlock",
Berlin: "berlinBlock",
London: "londonBlock",
ArrowGlacier: "arrowGlacierBlock",
GrayGlacier: "grayGlacierBlock",
MergeFork: "mergeForkBlock",
Shanghai: "shanghaiBlock",
Cancun: "cancunBlock",
forkTimeField* = [
"shanghaiTime",
"cancunTime",
]
func mergeForkTransitionThreshold*(conf: ChainConfig): MergeForkTransitionThreshold =
MergeForkTransitionThreshold(blockNumber: conf.mergeForkBlock, ttd: conf.terminalTotalDifficulty)
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.shanghaiTime = t.timeThresholds[HardFork.Shanghai]
conf.cancunTime = t.timeThresholds[HardFork.Cancun]
# ------------------------------------------------------------------------------
# Map HardFork to EVM/EVMC Fork
# ------------------------------------------------------------------------------
@ -201,8 +330,15 @@ func toNextFork(n: Option[BlockNumber]): uint64 =
else:
0'u64
func getNextFork(c: ChainConfig, fork: HardFork): uint64 =
let next: array[HardFork, uint64] = [
# EIP-6122: ForkID now works with timestamps too.
func toNextFork(t: Option[EthTime]): uint64 =
if t.isSome:
t.get.toUnix.uint64
else:
0'u64
func arrayMappingHardForkToNextFork(c: ChainConfig): array[HardFork, uint64] =
return [
0'u64,
toNextFork(c.homesteadBlock),
toNextFork(c.daoForkBlock),
@ -218,10 +354,11 @@ func getNextFork(c: ChainConfig, fork: HardFork): uint64 =
toNextFork(c.arrowGlacierBlock),
toNextFork(c.grayGlacierBlock),
toNextFork(c.mergeForkBlock),
toNextFork(c.shanghaiBlock),
toNextFork(c.cancunBlock),
toNextFork(c.shanghaiTime),
toNextFork(c.cancunTime),
]
func getNextFork(next: array[HardFork, uint64], fork: HardFork): uint64 =
if fork == high(HardFork):
result = 0
return
@ -232,9 +369,9 @@ func getNextFork(c: ChainConfig, fork: HardFork): uint64 =
result = next[x]
break
func calculateForkId(c: ChainConfig, fork: HardFork,
func calculateForkId(next: array[HardFork, uint64], fork: HardFork,
prevCRC: uint32, prevFork: uint64): ForkID =
result.nextFork = c.getNextFork(fork)
result.nextFork = getNextFork(next, fork)
if result.nextFork != prevFork:
result.crc = crc32(prevCRC, toBytesBE(prevFork))
@ -243,70 +380,16 @@ func calculateForkId(c: ChainConfig, fork: HardFork,
func calculateForkIds*(c: ChainConfig,
genesisCRC: uint32): array[HardFork, ForkID] =
let next = arrayMappingHardForkToNextFork(c)
var prevCRC = genesisCRC
var prevFork = c.getNextFork(Frontier)
var prevFork = getNextFork(next, Frontier)
for fork in HardFork:
result[fork] = calculateForkId(c, fork, prevCRC, prevFork)
result[fork] = calculateForkId(next, fork, prevCRC, prevFork)
prevFork = result[fork].nextFork
prevCRC = result[fork].crc
# ------------------------------------------------------------------------------
# BlockNumber + TD comparator
# ------------------------------------------------------------------------------
type
BlockToForkFunc* = proc(data, number, td: UInt256): bool
{.gcsafe, noSideEffect, nimcall, raises: [Defect, CatchableError].}
BlockToFork* = object
# `data` can be blockNumber or TTD
data* : UInt256
toFork*: BlockToForkFunc
BlockToForks* = array[HardFork, BlockToFork]
func forkTrue(data, number, td: UInt256): bool
{.gcsafe, nimcall, raises: [].} =
# frontier always return true
true
func forkFalse(data, number, td: UInt256): bool
{.gcsafe, nimcall, raises: [].} =
# forkBlock.isNone always return false
false
func forkMaybe(data, number, td: UInt256): bool
{.gcsafe, nimcall, raises: [].} =
# data is a blockNumber
number >= data
func mergeMaybe(data, number, td: UInt256): bool
{.gcsafe, nimcall, raises: [].} =
# data is a TTD
td >= data
proc blockToForks*(conf: ChainConfig, map: ForkToBlockNumber): BlockToForks =
# between Frontier and latest HardFork
# can be a match or not
for fork, number in map:
if number.isSome:
result[fork].data = number.get()
result[fork].toFork = forkMaybe
else:
result[fork].toFork = forkFalse
# Frontier always return true
result[Frontier].toFork = forkTrue
# special case for MergeFork
# if MergeForkBlock.isSome, it takes precedence over TTD
# if MergeForkBlock.isNone, compare TD with TTD
if map[MergeFork].isNone and
conf.terminalTotalDifficulty.isSome:
result[MergeFork].data = conf.terminalTotalDifficulty.get()
result[MergeFork].toFork = mergeMaybe
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------

View File

@ -127,7 +127,7 @@ proc processTransaction*(
{.gcsafe, raises: [CatchableError].} =
## Variant of `processTransaction()` with `*fork* derived
## from the `vmState` argument.
let fork = vmState.com.toEVMFork(header.blockNumber)
let fork = vmState.com.toEVMFork(header.forkDeterminationInfoForHeader)
vmState.processTransaction(tx, sender, header, fork)
# ------------------------------------------------------------------------------

View File

@ -172,7 +172,7 @@ template calcDifficultyGrayGlacier*(timeStamp: EthTime, parent: BlockHeader): Di
makeDifficultyCalculator(11_400_000, timeStamp, parent)
func calcDifficulty*(com: CommonRef, timeStamp: EthTime, parent: BlockHeader): DifficultyInt =
let next = com.toHardFork(parent.blockNumber + bigOne)
let next = com.toHardFork(parent.forkDeterminationInfoForHeader.adjustForNextBlock)
if next >= GrayGlacier:
result = calcDifficultyGrayGlacier(timeStamp, parent)
elif next >= ArrowGlacier:

View File

@ -106,7 +106,7 @@ proc resetTxEnv(dh: TxChainRef; parent: BlockHeader; fee: Option[UInt256])
# do hardfork transition before
# BaseVMState querying any hardfork/consensus from CommonRef
dh.com.hardForkTransition(parent.blockHash, parent.blockNumber+1)
dh.com.hardForkTransition(parent.blockHash, parent.blockNumber+1, some(parent.timestamp.adjustForNextBlock))
dh.prepareHeader(parent)
# we don't consider PoS difficulty here
@ -249,7 +249,7 @@ proc baseFee*(dh: TxChainRef): GasPrice =
proc nextFork*(dh: TxChainRef): EVMFork =
## Getter, fork of next block
dh.com.toEVMFork(dh.txEnv.vmState.blockNumber)
dh.com.toEVMFork(dh.txEnv.vmState.forkDeterminationInfoForVMState)
proc gasUsed*(dh: TxChainRef): GasInt =
## Getter, accumulated gas burned for collected blocks

View File

@ -35,8 +35,9 @@ proc baseFeeGet*(com: CommonRef; parent: BlockHeader): GasPrice =
# Note that the baseFee is calculated for the next header
let
parentFork = com.toEVMFork(parent.blockNumber)
nextFork = com.toEVMFork(parent.blockNumber + 1)
forkDeterminer = forkDeterminationInfoForHeader(parent)
parentFork = com.toEVMFork(forkDeterminer)
nextFork = com.toEVMFork(forkDeterminer.adjustForNextBlock)
if nextFork < FkLondon:
return 0.GasPrice

View File

@ -379,3 +379,9 @@ proc buildWitness*(vmState: BaseVMState): seq[byte]
# build witness from tree
var wb = initWitnessBuilder(vmState.com.db.db, rootHash, flags)
wb.buildWitness(mkeys)
func forkDeterminationInfoForVMState*(vmState: BaseVMState): ForkDeterminationInfo =
# FIXME-Adam: Is this timestamp right? Note that up above in blockNumber we add 1;
# should timestamp be adding 12 or something?
# Also, can I get the TD? Do I need to?
forkDeterminationInfo(vmState.blockNumber, vmState.timestamp)

View File

@ -33,7 +33,7 @@ proc setupTxContext*(vmState: BaseVMState, origin: EthAddress, gasPrice: GasInt,
if forkOverride.isSome:
forkOverride.get
else:
vmState.com.toEVMFork(vmState.blockNumber)
vmState.com.toEVMFork(vmState.forkDeterminationInfoForVMState)
vmState.gasCosts = vmState.fork.forkToSchedule

View File

@ -393,7 +393,7 @@ proc setupEthRpc*(
var
idx = 0
prevGasUsed = GasInt(0)
fork = com.toEVMFork(header.blockNumber)
fork = com.toEVMFork(header.forkDeterminationInfoForHeader)
for receipt in chainDB.getReceipts(header.receiptRoot):
let gasUsed = receipt.cumulativeGasUsed - prevGasUsed

View File

@ -385,7 +385,7 @@ method getStatus*(ctx: EthWireRef): EthState
db = ctx.db
com = ctx.chain.com
bestBlock = db.getCanonicalHead()
forkId = com.forkId(bestBlock.blockNumber)
forkId = com.forkId(bestBlock.forkDeterminationInfoForHeader)
EthState(
totalDifficulty: db.headTotalDifficulty,

View File

@ -101,7 +101,7 @@ proc rpcEstimateGas*(cd: RpcCallData, header: BlockHeader, com: CommonRef, gasCa
gasLimit: 0.GasInt, ## ???
fee: UInt256.none()) ## ???
let vmState = BaseVMState.new(topHeader, com)
let fork = com.toEVMFork(header.blockNumber)
let fork = com.toEVMFork(vmState.forkDeterminationInfoForVMState)
let txGas = gasFees[fork][GasTransaction] # txGas always 21000, use constants?
var params = toCallParams(vmState, cd, gasCap, header.fee)

View File

@ -24,6 +24,7 @@ export
vms.enableTracing,
vms.tracingEnabled,
vms.baseFee,
vms.forkDeterminationInfoForVMState,
vms.generateWitness,
vms.`generateWitness=`,
vms.getAncestorHash,

View File

@ -174,7 +174,7 @@ proc importBlock(tester: var Tester, com: CommonRef,
let parentHeader = com.db.getBlockHeader(tb.header.parentHash)
let td = some(com.db.getScore(tb.header.parentHash))
com.hardForkTransition(tb.header.blockNumber, td)
com.hardForkTransition(tb.header.blockNumber, td, some(tb.header.timestamp))
tester.vmState = BaseVMState.new(
parentHeader,

View File

@ -237,7 +237,7 @@ proc genesisLoadRunner(noisy = true;
params = params)
check mcom.ttd.get == sSpcs.termTotalDff
check mcom.toHardFork(sSpcs.mergeFork.toBlockNumber) == MergeFork
check mcom.toHardFork(sSpcs.mergeFork.toBlockNumber.blockNumberToForkDeterminationInfo) == MergeFork
test &"Construct persistent ChainDBRef on {tmpDir}, {persistPruneInfo}":
if disablePersistentDB:
@ -257,7 +257,7 @@ proc genesisLoadRunner(noisy = true;
params = params)
check dcom.ttd.get == sSpcs.termTotalDff
check dcom.toHardFork(sSpcs.mergeFork.toBlockNumber) == MergeFork
check dcom.toHardFork(sSpcs.mergeFork.toBlockNumber.blockNumberToForkDeterminationInfo) == MergeFork
test "Initialise in-memory Genesis":
mcom.initializeEmptyDb

View File

@ -92,7 +92,7 @@ template runTest(network: untyped, name: string) =
com = CommonRef.new(newMemoryDB(), true, network, params)
for x in `network IDs`:
let id = com.forkId(x.blockNumber.toBlockNumber)
let id = com.forkId(x.blockNumber.toBlockNumber.blockNumberToForkDeterminationInfo)
check id.crc == x.id.crc
check id.nextFork == x.id.nextFork

View File

@ -98,7 +98,7 @@ proc testFixtureIndexes(tester: Tester, testStatusIMPL: var TestStatus) =
var gasUsed: GasInt
let sender = tester.tx.getSender()
let fork = com.toEVMFork(tester.header.blockNumber)
let fork = com.toEVMFork(tester.header.forkDeterminationInfoForHeader)
vmState.mutateStateDB:
setupStateDB(tester.pre, db)

View File

@ -9,104 +9,112 @@
# according to those terms.
import
std/times,
../../nimbus/common/common,
./types
export
types
const
BlockNumberZero: BlockNumber = 0.toBlockNumber
BlockNumberFive: BlockNumber = 5.toBlockNumber
TimeZero: EthTime = fromUnix(0)
proc createForkTransitionTable(transitionFork: HardFork, b: Option[BlockNumber], t: Option[EthTime], ttd: Option[DifficultyInt]): ForkTransitionTable =
proc blockNumberToUse(f: HardFork): Option[BlockNumber] =
if f < transitionFork:
some(BlockNumberZero)
elif f == transitionFork:
b
else:
none(BlockNumber)
proc timeToUse(f: HardFork): Option[EthTime] =
if f < transitionFork:
some(TimeZero)
elif f == transitionFork:
t
else:
none(EthTime)
for f in low(HardFork) .. lastPurelyBlockNumberBasedFork:
result.blockNumberThresholds[f] = blockNumberToUse(f)
result.mergeForkTransitionThreshold.blockNumber = blockNumberToUse(HardFork.MergeFork)
result.mergeForkTransitionThreshold.ttd = ttd
for f in firstTimeBasedFork .. high(HardFork):
result.timeThresholds[f] = timeToUse(f)
proc assignNumber(c: ChainConfig, transitionFork: HardFork, n: BlockNumber) =
let table = createForkTransitionTable(transitionFork, some(n), none(EthTime), none(DifficultyInt))
c.populateFromForkTransitionTable(table)
proc assignTime(c: ChainConfig, transitionFork: HardFork, t: EthTime) =
let table = createForkTransitionTable(transitionFork, none(BlockNumber), some(t), none(DifficultyInt))
c.populateFromForkTransitionTable(table)
func getChainConfig*(network: string, c: ChainConfig) =
const
Zero = 0.toBlockNumber
Five = 5.toBlockNumber
proc assignNumber(c: ChainConfig,
fork: HardFork, n: BlockNumber) =
var number: array[HardFork, Option[BlockNumber]]
var z = low(HardFork)
while z < fork:
number[z] = some(Zero)
z = z.succ
number[fork] = some(n)
z = high(HardFork)
while z > fork:
number[z] = none(BlockNumber)
z = z.pred
c.homesteadBlock = number[HardFork.Homestead]
c.daoForkBlock = number[HardFork.DAOFork]
c.eip150Block = number[HardFork.Tangerine]
c.eip155Block = number[HardFork.Spurious]
c.eip158Block = number[HardFork.Spurious]
c.byzantiumBlock = number[HardFork.Byzantium]
c.constantinopleBlock = number[HardFork.Constantinople]
c.petersburgBlock = number[HardFork.Petersburg]
c.istanbulBlock = number[HardFork.Istanbul]
c.muirGlacierBlock = number[HardFork.MuirGlacier]
c.berlinBlock = number[HardFork.Berlin]
c.londonBlock = number[HardFork.London]
c.arrowGlacierBlock = number[HardFork.ArrowGlacier]
c.grayGlacierBlock = number[HardFork.GrayGlacier]
c.mergeForkBlock = number[HardFork.MergeFork]
c.shanghaiBlock = number[HardFork.Shanghai]
c.cancunBlock = number[HardFork.Cancun]
c.daoForkSupport = false
c.chainId = 1.ChainId
c.terminalTotalDifficulty = none(UInt256)
case network
of $TestFork.Frontier:
c.assignNumber(HardFork.Frontier, Zero)
c.assignNumber(HardFork.Frontier, BlockNumberZero)
of $TestFork.Homestead:
c.assignNumber(HardFork.Homestead, Zero)
c.assignNumber(HardFork.Homestead, BlockNumberZero)
of $TestFork.EIP150:
c.assignNumber(HardFork.Tangerine, Zero)
c.assignNumber(HardFork.Tangerine, BlockNumberZero)
of $TestFork.EIP158:
c.assignNumber(HardFork.Spurious, Zero)
c.assignNumber(HardFork.Spurious, BlockNumberZero)
of $TestFork.Byzantium:
c.assignNumber(HardFork.Byzantium, Zero)
c.assignNumber(HardFork.Byzantium, BlockNumberZero)
of $TestFork.Constantinople:
c.assignNumber(HardFork.Constantinople, Zero)
c.assignNumber(HardFork.Constantinople, BlockNumberZero)
of $TestFork.ConstantinopleFix:
c.assignNumber(HardFork.Petersburg, Zero)
c.assignNumber(HardFork.Petersburg, BlockNumberZero)
of $TestFork.Istanbul:
c.assignNumber(HardFork.Istanbul, Zero)
c.assignNumber(HardFork.Istanbul, BlockNumberZero)
of $TestFork.FrontierToHomesteadAt5:
c.assignNumber(HardFork.Homestead, Five)
c.assignNumber(HardFork.Homestead, BlockNumberFive)
of $TestFork.HomesteadToEIP150At5:
c.assignNumber(HardFork.Tangerine, Five)
c.assignNumber(HardFork.Tangerine, BlockNumberFive)
of $TestFork.HomesteadToDaoAt5:
c.assignNumber(HardFork.DAOFork, Five)
c.assignNumber(HardFork.DAOFork, BlockNumberFive)
c.daoForkSupport = true
of $TestFork.EIP158ToByzantiumAt5:
c.assignNumber(HardFork.Byzantium, Five)
c.assignNumber(HardFork.Byzantium, BlockNumberFive)
of $TestFork.ByzantiumToConstantinopleAt5:
c.assignNumber(HardFork.Constantinople, Five)
c.assignNumber(HardFork.Constantinople, BlockNumberFive)
of $TestFork.ByzantiumToConstantinopleFixAt5:
c.assignNumber(HardFork.Petersburg, Five)
c.constantinopleBlock = some(Five)
c.assignNumber(HardFork.Petersburg, BlockNumberFive)
c.constantinopleBlock = some(BlockNumberFive)
of $TestFork.ConstantinopleFixToIstanbulAt5:
c.assignNumber(HardFork.Istanbul, Five)
c.assignNumber(HardFork.Istanbul, BlockNumberFive)
of $TestFork.Berlin:
c.assignNumber(HardFork.Berlin, Zero)
c.assignNumber(HardFork.Berlin, BlockNumberZero)
of $TestFork.BerlinToLondonAt5:
c.assignNumber(HardFork.London, Five)
c.assignNumber(HardFork.London, BlockNumberFive)
of $TestFork.London:
c.assignNumber(HardFork.London, Zero)
c.assignNumber(HardFork.London, BlockNumberZero)
of $TestFork.ArrowGlacier:
c.assignNumber(HardFork.ArrowGlacier, Zero)
c.assignNumber(HardFork.ArrowGlacier, BlockNumberZero)
of $TestFork.GrayGlacier:
c.assignNumber(HardFork.GrayGlacier, Zero)
c.assignNumber(HardFork.GrayGlacier, BlockNumberZero)
of $TestFork.Merge:
c.assignNumber(HardFork.MergeFork, Zero)
c.assignNumber(HardFork.MergeFork, BlockNumberZero)
of $TestFork.ArrowGlacierToMergeAtDiffC0000:
c.assignNumber(HardFork.GrayGlacier, Zero)
c.assignNumber(HardFork.GrayGlacier, BlockNumberZero)
c.terminalTotalDifficulty = some(0xC0000.u256)
of $TestFork.Shanghai:
c.assignNumber(HardFork.Shanghai, Zero)
c.assignTime(HardFork.Shanghai, TimeZero)
of $TestFork.MergeToShanghaiAtTime15k:
c.assignTime(HardFork.Shanghai, fromUnix(15000))
of $TestFork.Cancun:
c.assignNumber(HardFork.Cancun, Zero)
c.assignTime(HardFork.Cancun, TimeZero)
else:
raise newException(ValueError, "unsupported network " & network)

View File

@ -33,6 +33,7 @@ type
Merge
ArrowGlacierToMergeAtDiffC0000
Shanghai
MergeToShanghaiAtTime15k
Cancun
LogLevel* = enum

View File

@ -200,7 +200,7 @@ proc runExecution(ctx: var StateContext, conf: StateConf, pre: JsonNode): StateR
let
com = CommonRef.new(newMemoryDB(), ctx.chainConfig, pruneTrie = false)
parent = BlockHeader(stateRoot: emptyRlpHash)
fork = com.toEVMFork(ctx.header.blockNumber)
fork = com.toEVMFork(ctx.header.forkDeterminationInfoForHeader)
let vmState = TestVMState()
vmState.init(