From d1fcb787b1b58e95dc356a51d551db19f91663cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Thu, 17 Jul 2025 16:41:57 +0200 Subject: [PATCH] fix: serve blockhash from state (#1224) * fix: serve blockhash from state * add test --- core/state_processor_test.go | 134 +++++++++++++++++++++++++++++++++++ core/vm/instructions.go | 12 +--- params/version.go | 2 +- 3 files changed, 138 insertions(+), 10 deletions(-) diff --git a/core/state_processor_test.go b/core/state_processor_test.go index e3a2f96e3e..a6555800fc 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -19,6 +19,7 @@ package core import ( "crypto/ecdsa" "encoding/binary" + "hash" "math/big" "testing" @@ -516,9 +517,142 @@ func TestProcessParentBlockHash(t *testing.T) { }) } +func TestFeynmanBlockhashOpcode(t *testing.T) { + var ( + feynmanTime uint64 = 100 + + // pragma solidity =0.8.24; + // + // contract GetBlockHash { + // function get(uint256 blockNumber) external view returns (bytes32) { + // return blockhash(blockNumber); + // } + // } + GetBlockHashAddress = common.HexToAddress("0x1230000000000000000000000000000000000001") + GetBlockHashCode = common.FromHex("0x6080604052348015600e575f80fd5b50600436106026575f3560e01c80639507d39a14602a575b5f80fd5b60396035366004604b565b4090565b60405190815260200160405180910390f35b5f60208284031215605a575f80fd5b503591905056fea26469706673582212200a417c99978abf73843291e0f284ce65b3ac99baa31abe6a43952308f3161ca864736f6c63430008180033") + ) + + getBlockHash := func(evm *vm.EVM, statedb *state.StateDB, blockNumber uint64) common.Hash { + // construct contract call calldata + blockNumberBuf := make([]byte, 32) + binary.BigEndian.PutUint64(blockNumberBuf[24:], blockNumber) + calldata := common.Hex2Bytes("9507d39a") // "get(uint256 blockNumber)" selector + calldata = append(calldata, blockNumberBuf...) + + msg := types.NewMessage( + params.SystemAddress, // from + &GetBlockHashAddress, // to + 0, // nonce + common.Big0, // amount + 30_000_000, // gasLimit + common.Big0, // gasPrice + common.Big0, // gasFeeCap + common.Big0, // gasTipCap + calldata, // data + nil, // accessList + false, // isFake + nil, // setCodeAuthorizations + ) + + evm.Reset(NewEVMTxContext(msg), statedb) + ret, _, _ := evm.Call(vm.AccountRef(msg.From()), *msg.To(), msg.Data(), 30_000_000, common.Big0, nil) + return common.BytesToHash(ret) + } + + var ( + chainConfig = ¶ms.ChainConfig{ + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + LondonBlock: big.NewInt(0), + ShanghaiBlock: big.NewInt(0), + BernoulliBlock: big.NewInt(0), + CurieBlock: big.NewInt(0), + DarwinTime: new(uint64), + DarwinV2Time: new(uint64), + EuclidTime: new(uint64), + EuclidV2Time: new(uint64), + FeynmanTime: &feynmanTime, + Ethash: new(params.EthashConfig), + } + + block1Hash = common.Hash{0x01} + block2 = &types.Header{ParentHash: block1Hash, Number: big.NewInt(2), Time: 0, Difficulty: big.NewInt(0)} + block2Hash = block2.Hash() + block3 = &types.Header{ParentHash: block2Hash, Number: big.NewInt(3), Time: feynmanTime, Difficulty: big.NewInt(0)} + + coinbase = common.Address{} + ) + test := func(statedb *state.StateDB) { + statedb.SetNonce(params.HistoryStorageAddress, 1) + statedb.SetCode(params.HistoryStorageAddress, params.HistoryStorageCode) + statedb.SetCode(GetBlockHashAddress, GetBlockHashCode) + statedb.IntermediateRoot(true) + + // pre-Feynman + vmContext := NewEVMBlockContext(block2, nil, chainConfig, &coinbase) + evm := vm.NewEVM(vmContext, vm.TxContext{}, statedb, chainConfig, vm.Config{}) + // note: parent hash (block1Hash) is not stored + + // query 2 ancestors (block1, block0) of current block (block2) + if have, want := getBlockHash(evm, statedb, 1), getBlockHashPreFeynman(chainConfig.ChainID, 1); have != want { + t.Errorf("want block hash %v, have %v", want, have) + } + if have, want := getBlockHash(evm, statedb, 0), getBlockHashPreFeynman(chainConfig.ChainID, 0); have != want { + t.Errorf("want block hash %v, have %v", want, have) + } + + // post-Feynman + vmContext = NewEVMBlockContext(block3, nil, chainConfig, &coinbase) + evm = vm.NewEVM(vmContext, vm.TxContext{}, statedb, chainConfig, vm.Config{}) + ProcessParentBlockHash(block3.ParentHash, evm, statedb) // store parent hash (block2Hash) + + // query 2 ancestors (block2, block1) of current block (block3) + if have, want := getBlockHash(evm, statedb, 2), block2Hash; have != want { + t.Errorf("want block hash %v, have %v", want, have) + } + // querying before the Feynman fork boundary returns the empty hash + if have, want := getBlockHash(evm, statedb, 1), (common.Hash{}); have != want { + t.Errorf("want block hash %v, have %v", want, have) + } + } + t.Run("MPT", func(t *testing.T) { + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil) + test(statedb) + }) +} + func getParentBlockHash(statedb *state.StateDB, number uint64) common.Hash { ringIndex := number % params.HistoryServeWindow var key common.Hash binary.BigEndian.PutUint64(key[24:], ringIndex) return statedb.GetState(params.HistoryStorageAddress, key) } + +type keccakState interface { + hash.Hash + Read([]byte) (int, error) +} + +func getBlockHashPreFeynman(chainId *big.Int, blockNumber uint64) common.Hash { + chainIdBuf := make([]byte, 8) + binary.BigEndian.PutUint64(chainIdBuf, chainId.Uint64()) + num64Buf := make([]byte, 8) + binary.BigEndian.PutUint64(num64Buf, blockNumber) + + hasher := sha3.NewLegacyKeccak256().(keccakState) + hasher.Write(chainIdBuf) + hasher.Write(num64Buf) + + var hasherBuf common.Hash + hasher.Read(hasherBuf[:]) + return hasherBuf +} diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 6bb58fb5ee..238c568a77 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -485,18 +485,12 @@ func opBlockhashPostFeynman(pc *uint64, interpreter *EVMInterpreter, scope *Scop lower = upper - 256 } if num64 >= lower && num64 < upper { - res := interpreter.evm.Context.GetHash(num64) - if witness := interpreter.evm.StateDB.Witness(); witness != nil { - witness.AddBlockHash(num64) - } - num.SetBytes(res[:]) - - // for provability, revm loads block hash from the history storage system contract, - // so we need to ensure that the corresponding slot is present in the execution witness. + // load block hash from the history storage system contract. ringIndex := num64 % params.HistoryServeWindow var key common.Hash binary.BigEndian.PutUint64(key[24:], ringIndex) - interpreter.evm.StateDB.GetState(params.HistoryStorageAddress, key) + res := interpreter.evm.StateDB.GetState(params.HistoryStorageAddress, key) + num.SetBytes(res[:]) } else { num.Clear() } diff --git a/params/version.go b/params/version.go index dc939fa771..9df6874092 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 5 // Major version component of the current release VersionMinor = 8 // Minor version component of the current release - VersionPatch = 70 // Patch version component of the current release + VersionPatch = 71 // Patch version component of the current release VersionMeta = "mainnet" // Version metadata to append to the version string )