fix: serve blockhash from state (#1224)

* fix: serve blockhash from state

* add test
This commit is contained in:
Péter Garamvölgyi 2025-07-17 16:41:57 +02:00 committed by GitHub
parent c27d82597b
commit d1fcb787b1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 138 additions and 10 deletions

View file

@ -19,6 +19,7 @@ package core
import ( import (
"crypto/ecdsa" "crypto/ecdsa"
"encoding/binary" "encoding/binary"
"hash"
"math/big" "math/big"
"testing" "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 = &params.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 { func getParentBlockHash(statedb *state.StateDB, number uint64) common.Hash {
ringIndex := number % params.HistoryServeWindow ringIndex := number % params.HistoryServeWindow
var key common.Hash var key common.Hash
binary.BigEndian.PutUint64(key[24:], ringIndex) binary.BigEndian.PutUint64(key[24:], ringIndex)
return statedb.GetState(params.HistoryStorageAddress, key) 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
}

View file

@ -485,18 +485,12 @@ func opBlockhashPostFeynman(pc *uint64, interpreter *EVMInterpreter, scope *Scop
lower = upper - 256 lower = upper - 256
} }
if num64 >= lower && num64 < upper { if num64 >= lower && num64 < upper {
res := interpreter.evm.Context.GetHash(num64) // load block hash from the history storage system contract.
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.
ringIndex := num64 % params.HistoryServeWindow ringIndex := num64 % params.HistoryServeWindow
var key common.Hash var key common.Hash
binary.BigEndian.PutUint64(key[24:], ringIndex) 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 { } else {
num.Clear() num.Clear()
} }

View file

@ -24,7 +24,7 @@ import (
const ( const (
VersionMajor = 5 // Major version component of the current release VersionMajor = 5 // Major version component of the current release
VersionMinor = 8 // Minor 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 VersionMeta = "mainnet" // Version metadata to append to the version string
) )