Proof-of-stakiness based on block header (#2682)

* Proof-of-stakiness based on block header

* Remove unnecessary PoS check from test_txpool2

* Fix engine api simulator

* Fix indentation

* Fix vmstate debug util

* Fix MainNet ForkId calculation issue
This commit is contained in:
andri lim 2024-10-08 09:37:36 +07:00 committed by GitHub
parent a3bb5d4428
commit 76c2a75a53
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 79 additions and 162 deletions

View File

@ -72,10 +72,6 @@ proc envConfig*(conf: ChainConfig): NimbusConf =
result.networkParams.config = conf
proc newEngineEnv*(conf: var NimbusConf, chainFile: string, enableAuth: bool): EngineEnv =
if chainFile.len > 0:
# disable clique if we are using PoW chain
conf.networkParams.config.consensusType = ConsensusType.POW
let ctx = newEthContext()
ctx.am.importPrivateKey(sealerKey).isOkOr:
echo error

View File

@ -64,7 +64,7 @@ proc processBlock(
discard com.db.persistUncles(blk.uncles)
# EIP-3675: no reward for miner in POA/POS
if com.consensus == ConsensusType.POW:
if com.proofOfStake(header):
vmState.calculateReward(header, blk.uncles)
vmState.mutateStateDB:
@ -95,8 +95,6 @@ proc setBlock*(c: ChainRef; blk: EthBlock): Result[void, string] =
let dbTx = c.db.ctx.newTransaction()
defer: dbTx.dispose()
c.com.hardForkTransition(header)
# Needed for figuring out whether KVT cleanup is due (see at the end)
let
vmState = c.getVmState(header).valueOr:
@ -105,7 +103,7 @@ proc setBlock*(c: ChainRef; blk: EthBlock): Result[void, string] =
? vmState.processBlock(blk)
if not c.db.persistHeader(
header, c.com.consensus == ConsensusType.POS, c.com.startOfHistory):
header, c.com.proofOfStake(header), c.com.startOfHistory):
return err("Could not persist header")
try:

View File

@ -48,7 +48,7 @@ proc setupELClient*(conf: ChainConfig, node: JsonNode): TestEnv =
doAssert stateDB.rootHash == genesisHeader.stateRoot
doAssert com.db.persistHeader(genesisHeader,
com.consensus == ConsensusType.POS)
com.proofOfStake(genesisHeader))
doAssert(com.db.getCanonicalHead().blockHash ==
genesisHeader.blockHash)

View File

@ -449,7 +449,6 @@ func chainConfigForNetwork*(id: NetworkId): ChainConfig =
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.BlockNumber, # 2015-09-07 21:33:09 UTC
@ -469,6 +468,7 @@ func chainConfigForNetwork*(id: NetworkId): ChainConfig =
londonBlock: Opt.some(12_965_000.BlockNumber), # 2021-08-05 12:33:42 UTC
arrowGlacierBlock: Opt.some(13_773_000.BlockNumber), # 2021-12-09 19:55:23 UTC
grayGlacierBlock: Opt.some(15_050_000.BlockNumber), # 2022-06-30 10:54:04 UTC
posBlock: Opt.some(15_537_394.BlockNumber), # 2022-09-15 05:42:42 UTC
terminalTotalDifficulty: Opt.some(mainNetTTD),
shanghaiTime: Opt.some(1_681_338_455.EthTime), # 2023-04-12 10:27:35 UTC
cancunTime: Opt.some(1_710_338_135.EthTime), # 2024-03-13 13:55:35 UTC
@ -476,7 +476,6 @@ func chainConfigForNetwork*(id: NetworkId): ChainConfig =
of SepoliaNet:
const sepoliaTTD = parse("17000000000000000",UInt256)
ChainConfig(
consensusType: ConsensusType.POW,
chainId: SepoliaNet.ChainId,
homesteadBlock: Opt.some(0.BlockNumber),
daoForkSupport: false,
@ -498,7 +497,6 @@ func chainConfigForNetwork*(id: NetworkId): ChainConfig =
)
of HoleskyNet:
ChainConfig(
consensusType: ConsensusType.POS,
chainId: HoleskyNet.ChainId,
homesteadBlock: Opt.some(0.BlockNumber),
eip150Block: Opt.some(0.BlockNumber),

View File

@ -72,9 +72,6 @@ type
syncState: SyncState
# one of POW/POS, updated after calling `hardForkTransition`
consensusType: ConsensusType
syncReqNewHead: SyncReqNewHeadCB
## Call back function for the sync processor. This function stages
## the arguent header to a private aerea for subsequent processing.
@ -102,22 +99,12 @@ type
# Forward declarations
# ------------------------------------------------------------------------------
func hardForkTransition*(
com: CommonRef, forkDeterminer: ForkDeterminationInfo)
{.gcsafe, raises: [].}
proc proofOfStake*(com: CommonRef, header: Header): bool {.gcsafe.}
# ------------------------------------------------------------------------------
# Private helper functions
# ------------------------------------------------------------------------------
func consensusTransition(com: CommonRef, fork: HardFork) =
if fork >= MergeFork:
com.consensusType = ConsensusType.POS
else:
# restore consensus type to original config
# this could happen during reorg
com.consensusType = com.config.consensusType
func setForkId(com: CommonRef, genesis: BlockHeader) =
com.genesisHash = genesis.blockHash
let genesisCRC = crc32(0, com.genesisHash.data)
@ -142,7 +129,7 @@ proc initializeDb(com: CommonRef) =
doAssert(com.genesisHeader.number == 0.BlockNumber,
"can't commit genesis block with number > 0")
doAssert(com.db.persistHeader(com.genesisHeader,
com.consensusType == ConsensusType.POS,
com.proofOfStake(com.genesisHeader),
startOfHistory=com.genesisHeader.parentHash),
"can persist genesis header")
doAssert(canonicalHeadHashKey().toOpenArray in kvt)
@ -196,11 +183,6 @@ proc init(com : CommonRef,
com.pruneHistory= pruneHistory
com.pos = CasperRef.new
# com.consensusType
# is set by hardForkTransition.
# set it before creating genesis block
# TD need to be some(0.u256) because it can be the genesis
# already at the MergeFork
const TimeZero = EthTime(0)
# com.forkIdCalculator and com.genesisHash are set
@ -214,8 +196,6 @@ proc init(com : CommonRef,
)
fork = toHardFork(com.forkTransitionTable, forkDeterminer)
com.consensusTransition(fork)
# Must not overwrite the global state on the single state DB
if not db.getBlockHeader(0.BlockNumber, com.genesisHeader):
com.genesisHeader = toGenesisHeader(genesis,
@ -223,35 +203,22 @@ proc init(com : CommonRef,
com.setForkId(com.genesisHeader)
com.pos.timestamp = genesis.timestamp
else:
com.hardForkTransition(ForkDeterminationInfo(
number: 0.BlockNumber,
td: Opt.some(0.u256),
time: Opt.some(TimeZero)
))
# By default, history begins at genesis.
com.startOfHistory = GENESIS_PARENT_HASH
com.initializeDb()
proc getTd(com: CommonRef, blockHash: Hash256): Opt[DifficultyInt] =
var td: DifficultyInt
if not com.db.getTd(blockHash, td):
# TODO: Is this really ok?
Opt.none(DifficultyInt)
else:
Opt.some(td)
proc isBlockAfterTtd(com: CommonRef, header: Header): bool =
if com.config.terminalTotalDifficulty.isNone:
return false
func needTdForHardForkDetermination(com: CommonRef): bool =
let t = com.forkTransitionTable.mergeForkTransitionThreshold
t.ttdPassed.isNone and t.number.isNone and t.ttd.isSome
proc getTdIfNecessary(com: CommonRef, blockHash: Hash256): Opt[DifficultyInt] =
if needTdForHardForkDetermination(com):
getTd(com, blockHash)
else:
Opt.none(DifficultyInt)
let
ttd = com.config.terminalTotalDifficulty.get()
ptd = com.db.getScore(header.parentHash).valueOr:
return false
td = ptd + header.difficulty
ptd >= ttd and td >= ttd
# ------------------------------------------------------------------------------
# Public constructors
@ -307,7 +274,6 @@ func clone*(com: CommonRef, db: CoreDbRef): CommonRef =
genesisHeader: com.genesisHeader,
syncProgress : com.syncProgress,
networkId : com.networkId,
consensusType: com.consensusType,
pos : com.pos,
pruneHistory : com.pruneHistory)
@ -322,38 +288,6 @@ func toHardFork*(
com: CommonRef, forkDeterminer: ForkDeterminationInfo): HardFork =
toHardFork(com.forkTransitionTable, forkDeterminer)
func hardForkTransition(
com: CommonRef, forkDeterminer: ForkDeterminationInfo) =
## 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 to POS.
## Same thing happen before London block, TD can be ignored.
let fork = com.toHardFork(forkDeterminer)
com.consensusTransition(fork)
func hardForkTransition*(
com: CommonRef,
number: BlockNumber,
td: Opt[DifficultyInt],
time: Opt[EthTime]) =
com.hardForkTransition(ForkDeterminationInfo(
number: number, time: time, td: td))
proc hardForkTransition*(
com: CommonRef,
parentHash: Hash256,
number: BlockNumber,
time: Opt[EthTime]) =
com.hardForkTransition(number, getTdIfNecessary(com, parentHash), time)
proc hardForkTransition*(
com: CommonRef, header: BlockHeader)
{.gcsafe, raises: [].} =
com.hardForkTransition(
header.parentHash, header.number, Opt.some(header.timestamp))
func toEVMFork*(com: CommonRef, forkDeterminer: ForkDeterminationInfo): EVMFork =
## similar to toFork, but produce EVMFork
let fork = com.toHardFork(forkDeterminer)
@ -380,17 +314,6 @@ func forkId*(com: CommonRef, head: BlockNumber, time: EthTime): ForkID {.gcsafe.
func isEIP155*(com: CommonRef, number: BlockNumber): bool =
com.config.eip155Block.isSome and number >= com.config.eip155Block.get
proc isBlockAfterTtd*(com: CommonRef, header: BlockHeader): bool =
if com.config.terminalTotalDifficulty.isNone:
return false
let
ttd = com.config.terminalTotalDifficulty.get()
ptd = com.db.getScore(header.parentHash).valueOr:
return false
td = ptd + header.difficulty
ptd >= ttd and td >= ttd
func isShanghaiOrLater*(com: CommonRef, t: EthTime): bool =
com.config.shanghaiTime.isSome and t >= com.config.shanghaiTime.get
@ -400,11 +323,15 @@ func isCancunOrLater*(com: CommonRef, t: EthTime): bool =
func isPragueOrLater*(com: CommonRef, t: EthTime): bool =
com.config.pragueTime.isSome and t >= com.config.pragueTime.get
proc consensus*(com: CommonRef, header: BlockHeader): ConsensusType =
if com.isBlockAfterTtd(header):
return ConsensusType.POS
return com.config.consensusType
proc proofOfStake*(com: CommonRef, header: Header): bool =
if com.config.posBlock.isSome:
# see comments of posBlock in common/hardforks.nim
header.number >= com.config.posBlock.get
elif com.config.mergeForkBlock.isSome:
header.number >= com.config.mergeForkBlock.get
else:
# This costly check is only executed from test suite
com.isBlockAfterTtd(header)
proc syncReqNewHead*(com: CommonRef; header: BlockHeader)
{.gcsafe, raises: [].} =
@ -441,9 +368,6 @@ func pos*(com: CommonRef): CasperRef =
func db*(com: CommonRef): CoreDbRef =
com.db
func consensus*(com: CommonRef): ConsensusType =
com.consensusType
func eip150Block*(com: CommonRef): Opt[BlockNumber] =
com.config.eip150Block

View File

@ -18,15 +18,6 @@ import
{.push raises: [].}
type
ConsensusType* {.pure.} = enum
# Proof of Work
# algorithm: Ethash
POW
# Proof of Stake
# algorithm: Casper
POS
HardFork* = enum
Frontier
Homestead
@ -166,6 +157,14 @@ type
arrowGlacierBlock* : Opt[BlockNumber]
grayGlacierBlock* : Opt[BlockNumber]
# posBlock does not participate in ForkId
# calculation, and in config file
# specially crafted for network depends
# solely on TTD for transition to PoS
# e.g. MainNet, but now has pass the transition
posBlock*
{.dontSerialize.} : Opt[BlockNumber]
# mergeNetsplitBlock is an alias to mergeForkBlock
# and is used for geth compatibility layer
mergeNetsplitBlock* : Opt[BlockNumber]
@ -177,8 +176,6 @@ type
terminalTotalDifficulty*: Opt[UInt256]
terminalTotalDifficultyPassed*: Opt[bool]
consensusType*
{.dontSerialize.} : ConsensusType
# These are used for checking that the values of the fields
# are in a valid order.

View File

@ -52,14 +52,18 @@ func newChain*(com: CommonRef,
vmState: vmState
)
func newChain*(com: CommonRef): ChainRef =
proc newChain*(com: CommonRef): ChainRef =
## Constructor for the `Chain` descriptor object. All sub-object descriptors
## are initialised with defaults. So is extra block chain validation
let extraValidation = com.consensus == ConsensusType.POS
ChainRef(
try:
let header = com.db.getCanonicalHead()
let extraValidation = com.proofOfStake(header)
return ChainRef(
com: com,
extraValidation: extraValidation,
)
except CatchableError:
doAssert(false, "no canonical head")
# ------------------------------------------------------------------------------
# Public `Chain` getters

View File

@ -74,7 +74,6 @@ proc processBlock(c: ForkedChainRef,
let vmState = BaseVMState()
vmState.init(parent, header, c.com)
c.com.hardForkTransition(header)
if c.extraValidation:
?c.com.validateHeaderAndKinship(blk, vmState.parent, checkSealOK = false)

View File

@ -85,8 +85,6 @@ proc persistBlocksImpl(
defer:
dbTx.dispose()
c.com.hardForkTransition(blocks[0].header)
# Note that `0 < headers.len`, assured when called from `persistBlocks()`
let
vmState =
@ -123,7 +121,6 @@ proc persistBlocksImpl(
let skipValidation =
NoFullValidation in flags and header.number != toBlock or NoValidation in flags
c.com.hardForkTransition(header)
if blks > 0:
template parent(): BlockHeader =
@ -161,7 +158,7 @@ proc persistBlocksImpl(
let blockHash = header.blockHash()
if NoPersistHeader notin flags:
if not c.db.persistHeader(
blockHash, header, c.com.consensus == ConsensusType.POS, c.com.startOfHistory
blockHash, header, c.com.proofOfStake(header), c.com.startOfHistory
):
return err("Could not persist header")

View File

@ -227,7 +227,7 @@ proc processBlock*(
?vmState.procBlkPreamble(blk, skipValidation, skipReceipts, skipUncles)
# EIP-3675: no reward for miner in POA/POS
if vmState.com.consensus == ConsensusType.POW:
if not vmState.com.proofOfStake(blk.header):
vmState.calculateReward(blk.header, blk.uncles)
?vmState.procBlkEpilogue(blk, skipValidation, skipReceipts)

View File

@ -113,8 +113,6 @@ proc setupVMState(com: CommonRef; parent: BlockHeader): BaseVMState =
# BaseVMState querying any hardfork/consensus from CommonRef
let pos = com.pos
com.hardForkTransition(
parent.blockHash, parent.number+1, Opt.some(pos.timestamp))
let blockCtx = BlockContext(
timestamp : pos.timestamp,

View File

@ -76,7 +76,7 @@ proc validateHeader(
if header.extraData != daoForkBlockExtraData:
return err("header extra data should be marked DAO")
if com.consensus == ConsensusType.POS:
if com.proofOfStake(header):
# EIP-4399 and EIP-3675
# no need to check mixHash because EIP-4399 override this field
# checking rule
@ -338,7 +338,7 @@ proc validateHeaderAndKinship*(
if blk.uncles.len > MAX_UNCLES:
return err("Number of uncles exceed limit.")
if com.consensus != ConsensusType.POS:
if not com.proofOfStake(header):
? com.validateUncles(header, blk.uncles, checkSealOK)
ok()

View File

@ -56,6 +56,7 @@ func blockCtx(com: CommonRef, header: BlockHeader):
difficulty : header.difficulty,
coinbase : header.coinbase,
excessBlobGas: header.excessBlobGas.get(0'u64),
parentHash : header.parentHash,
)
# --------------
@ -241,8 +242,15 @@ func blockNumber*(vmState: BaseVMState): BlockNumber =
# and not head.number
vmState.parent.number + 1
func difficultyOrPrevRandao*(vmState: BaseVMState): UInt256 =
if vmState.com.consensus == ConsensusType.POS:
proc proofOfStake*(vmState: BaseVMState): bool =
vmState.com.proofOfStake(Header(
number: vmState.blockNumber,
parentHash: vmState.blockCtx.parentHash,
difficulty: vmState.blockCtx.difficulty,
))
proc difficultyOrPrevRandao*(vmState: BaseVMState): UInt256 =
if vmState.proofOfStake():
# EIP-4399/EIP-3675
UInt256.fromBytesBE(vmState.blockCtx.prevRandao.data)
else:

View File

@ -46,6 +46,7 @@ type
difficulty* : UInt256
coinbase* : EthAddress
excessBlobGas* : uint64
parentHash* : Hash32
TxContext* = object
origin* : EthAddress

View File

@ -18,9 +18,6 @@ import
./utils,
./state_dump
proc `$`(hash: Hash256): string =
hash.data.toHex
proc `$`(bloom: BloomFilter): string =
bloom.toHex
@ -84,7 +81,7 @@ proc debugAccounts*(vmState: BaseVMState): string =
res.pretty
proc debug*(vms: BaseVMState): string =
result.add "com.consensus : " & $vms.com.consensus & "\n"
result.add "proofOfStake : " & $vms.proofOfStake() & "\n"
result.add "parent : " & $vms.parent.blockHash & "\n"
result.add "timestamp : " & $vms.blockCtx.timestamp & "\n"
result.add "gasLimit : " & $vms.blockCtx.gasLimit & "\n"

View File

@ -75,7 +75,7 @@ proc executeCase(node: JsonNode): bool =
stateDB.persist()
if not com.db.persistHeader(env.genesisHeader,
com.consensus == ConsensusType.POS):
com.proofOfStake(env.genesisHeader)):
debugEcho "Failed to put genesis header into database"
return false
@ -147,7 +147,9 @@ when isMainModule:
var testStatusIMPL: TestStatus
let node = json.parseFile(name)
executeFile(node, testStatusIMPL)
if testStatusIMPL == FAILED:
quit(QuitFailure)
executeFile("tests/fixtures/eth_tests/BlockchainTests/ValidBlocks/bcWalletTest/walletReorganizeOwners.json")
executeFile("tests/fixtures/eth_tests/BlockchainTests/GeneralStateTests/stTransactionTest/ValueOverflowParis.json")
else:
blockchainJsonMain()

View File

@ -14,9 +14,6 @@ import
../nimbus/config,
../nimbus/common/common
#import ../nimbus/db/aristo/aristo_debug
const
baseDir = [".", "tests", ".."/"tests", $DirSep] # path containg repo
repoDir = [".", "customgenesis"] # alternative repo paths
@ -31,9 +28,15 @@ proc findFilePath(file: string): string =
proc makeGenesis(networkId: NetworkId): BlockHeader =
let com = CommonRef.new(newCoreDbRef DefaultDbMemory, params = networkParams(networkId))
#debugEcho pp(com.db.defCtx.mpt)
com.genesisHeader
proc proofOfStake(params: NetworkParams): bool =
let com = CommonRef.new(newCoreDbRef DefaultDbMemory,
networkId = params.config.chainId.NetworkId,
params = params)
let header = com.genesisHeader
com.proofOfStake(header)
proc genesisTest() =
suite "Genesis":
test "Correct mainnet hash":
@ -56,9 +59,9 @@ proc customGenesisTest() =
check loadNetworkParams("berlin2000.json".findFilePath, cga)
check loadNetworkParams("chainid7.json".findFilePath, cgb)
check loadNetworkParams("noconfig.json".findFilePath, cgc)
check cga.config.consensusType == ConsensusType.POW
check cgb.config.consensusType == ConsensusType.POW
check cgc.config.consensusType == ConsensusType.POW
check cga.proofOfStake() == false
check cgb.proofOfStake() == false
check cgc.proofOfStake() == false
test "Devnet4.json (aka Kintsugi in all but chainId)":
var cg: NetworkParams
@ -68,7 +71,7 @@ proc customGenesisTest() =
let genesisHash = hash32"a28d8d73e087a01d09d8cb806f60863652f30b6b6dfa4e0157501ff07d422399"
check com.genesisHeader.stateRoot == stateRoot
check com.genesisHeader.blockHash == genesisHash
check com.consensus == ConsensusType.POW
check com.proofOfStake(com.genesisHeader) == false
test "Devnet5.json (aka Kiln in all but chainId and TTD)":
var cg: NetworkParams
@ -78,7 +81,7 @@ proc customGenesisTest() =
let genesisHash = hash32"51c7fe41be669f69c45c33a56982cbde405313342d9e2b00d7c91a7b284dd4f8"
check com.genesisHeader.stateRoot == stateRoot
check com.genesisHeader.blockHash == genesisHash
check com.consensus == ConsensusType.POW
check com.proofOfStake(com.genesisHeader) == false
test "Mainnet shadow fork 1":
var cg: NetworkParams
@ -90,7 +93,7 @@ proc customGenesisTest() =
check com.genesisHeader.stateRoot == stateRoot
check com.genesisHeader.blockHash == genesisHash
check com.ttd.get == ttd
check com.consensus == ConsensusType.POW
check com.proofOfStake(com.genesisHeader) == false
test "Geth shadow fork 1":
# parse using geth format should produce the same result with nimbus format
@ -103,7 +106,7 @@ proc customGenesisTest() =
check com.genesisHeader.stateRoot == stateRoot
check com.genesisHeader.blockHash == genesisHash
check com.ttd.get == ttd
check com.consensus == ConsensusType.POW
check com.proofOfStake(com.genesisHeader) == false
check cg.config.mergeNetsplitBlock.isSome
check cg.config.mergeNetsplitBlock.get == 14660963.BlockNumber
check cg.config.mergeNetsplitBlock == cg.config.mergeForkBlock

View File

@ -96,6 +96,7 @@ proc initEnv(envFork: HardFork): TestEnv =
)
if envFork >= MergeFork:
conf.networkParams.config.mergeForkBlock = Opt.some(0'u64)
conf.networkParams.config.terminalTotalDifficulty = Opt.some(100.u256)
if envFork >= Shanghai:
@ -158,8 +159,6 @@ proc runTxPoolPosTest() =
return
blk = r.get.blk
check com.isBlockAfterTtd(blk.header)
body = BlockBody(
transactions: blk.txs,
uncles: blk.uncles
@ -214,8 +213,6 @@ proc runTxPoolBlobhashTest() =
let bundle = r.get
blk = bundle.blk
check com.isBlockAfterTtd(blk.header)
body = BlockBody(
transactions: blk.txs,
uncles: blk.uncles,
@ -301,8 +298,6 @@ proc runTxHeadDelta(noisy = true) =
return
let blk = r.get.blk
check com.isBlockAfterTtd(blk.header)
let body = BlockBody(
transactions: blk.txs,
uncles: blk.uncles)
@ -333,8 +328,8 @@ proc runGetBlockBodyTest() =
var
env = initEnv(Cancun)
blockTime = EthTime.now()
parentHeader: BlockHeader
currentHeader: BlockHeader
parentHeader: Header
currentHeader: Header
suite "Test get parent transactions after persistBlock":
test "TxPool create first block":