diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index d81688935135..af7a40f345f8 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -199,6 +199,16 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, evm := vm.NewEVM(vmContext, vm.TxContext{}, statedb, chainConfig, vmConfig) core.ProcessBeaconBlockRoot(*beaconRoot, evm, statedb) } + if pre.Env.BlockHashes != nil && chainConfig.IsPrague(new(big.Int).SetUint64(pre.Env.Number), pre.Env.Timestamp) { + // insert all parent hashes in the contract + for i, h := range pre.Env.BlockHashes { + n := uint64(i) + if n >= pre.Env.Number || pre.Env.Number > (n+params.HistoryServeWindow) { + continue + } + core.ProcessParentBlockHash(statedb, n, h) + } + } for i := 0; txIt.Next(); i++ { tx, err := txIt.Tx() diff --git a/core/state_processor.go b/core/state_processor.go index 91695a3aaa8a..b24ddeeabb8e 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -82,6 +82,9 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg if beaconRoot := block.BeaconRoot(); beaconRoot != nil { ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb) } + if p.config.IsPrague(block.Number(), block.Time()) { + ProcessBlockHashHistory(statedb, block.Header(), p.config, p.bc) + } // Iterate over and process the individual transactions for i, tx := range block.Transactions() { msg, err := TransactionToMessage(tx, signer, header.BaseFee) @@ -274,3 +277,37 @@ func ProcessDequeueWithdrawalRequests(vmenv *vm.EVM, statedb *state.StateDB) typ } return reqs } + +// ProcessBlockHashHistory is called at every block to insert the parent block hash +// in the history storage contract as per EIP-2935. At the EIP-2935 fork block, it +// populates the whole buffer with block hashes. +func ProcessBlockHashHistory(statedb *state.StateDB, header *types.Header, chainConfig *params.ChainConfig, chain consensus.ChainHeaderReader) { + var ( + prevHash = header.ParentHash + parent = chain.GetHeaderByHash(prevHash) + number = header.Number.Uint64() + prevNumber = parent.Number.Uint64() + ) + ProcessParentBlockHash(statedb, prevNumber, prevHash) + // History already inserted. + if chainConfig.IsPrague(parent.Number, parent.Time) || prevNumber == 0 { + return + } + var low uint64 + if number > params.HistoryServeWindow { + low = number - params.HistoryServeWindow + } + for i := prevNumber; i > low; i-- { + ProcessParentBlockHash(statedb, i-1, parent.ParentHash) + parent = chain.GetHeader(parent.ParentHash, i-1) + } +} + +// ProcessParentBlockHash stores the parent block hash in the history storage contract +// as per EIP-2935. +func ProcessParentBlockHash(statedb *state.StateDB, prevNumber uint64, prevHash common.Hash) { + ringIndex := prevNumber % params.HistoryServeWindow + var key common.Hash + binary.BigEndian.PutUint64(key[24:], ringIndex) + statedb.SetState(params.HistoryStorageAddress, key, prevHash) +} diff --git a/core/state_processor_test.go b/core/state_processor_test.go index af4d29b604da..2caea37ecd82 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -18,6 +18,7 @@ package core import ( "crypto/ecdsa" + "encoding/binary" "math/big" "testing" @@ -29,9 +30,11 @@ import ( "github.com/ethereum/go-ethereum/consensus/misc/eip1559" "github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb/memorydb" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/trie" "github.com/holiman/uint256" @@ -528,3 +531,55 @@ func TestProcessVerkle(t *testing.T) { } } } + +type MockChain struct { + chain map[common.Hash]*types.Header +} + +func (m *MockChain) Config() *params.ChainConfig { return nil } + +func (m *MockChain) CurrentHeader() *types.Header { return nil } + +func (m *MockChain) GetHeaderByNumber(number uint64) *types.Header { return nil } + +func (m *MockChain) GetTd(hash common.Hash, number uint64) *big.Int { return nil } + +func (m *MockChain) GetHeaderByHash(hash common.Hash) *types.Header { + return m.chain[hash] +} + +func (m *MockChain) GetHeader(hash common.Hash, number uint64) *types.Header { + return m.chain[hash] +} + +func TestProcessBlockHashHistory(t *testing.T) { + hashA := common.Hash{0x01} + hashB := common.Hash{0x02} + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil) + header := &types.Header{ParentHash: hashA, Number: big.NewInt(2)} + parent := &types.Header{ParentHash: hashB, Number: big.NewInt(1)} + parentParent := &types.Header{ParentHash: common.Hash{}, Number: big.NewInt(0)} + chainConfig := params.AllDevChainProtocolChanges + chainConfig.PragueTime = nil + chain := new(MockChain) + chain.chain = make(map[common.Hash]*types.Header) + chain.chain[hashA] = parent + chain.chain[hashB] = parentParent + + ProcessBlockHashHistory(statedb, header, chainConfig, chain) + + // make sure that the state is correct + if have := getParentBlockHash(statedb, 1); have != hashA { + t.Fail() + } + if have := getParentBlockHash(statedb, 0); have != hashB { + t.Fail() + } +} + +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) +} diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 10cdd72e0c57..12971090e863 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -434,13 +434,17 @@ func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ( num.Clear() return nil, nil } - + historySize := uint64(256) + // EIP-2935 extends the observable history window. + if interpreter.evm.ChainConfig().IsPrague(interpreter.evm.Context.BlockNumber, interpreter.evm.Context.Time) { + historySize = params.HistoryServeWindow + } var upper, lower uint64 upper = interpreter.evm.Context.BlockNumber.Uint64() - if upper < 257 { + if upper < historySize+1 { lower = 0 } else { - lower = upper - 256 + lower = upper - historySize } if num64 >= lower && num64 < upper { num.SetBytes(interpreter.evm.Context.GetHash(num64).Bytes()) diff --git a/miner/worker.go b/miner/worker.go index 297f02f7b1ce..26f7eb28582e 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -213,6 +213,9 @@ func (miner *Miner) prepareWork(genParams *generateParams) (*environment, error) vmenv := vm.NewEVM(context, vm.TxContext{}, env.state, miner.chainConfig, vm.Config{}) core.ProcessBeaconBlockRoot(*header.ParentBeaconRoot, vmenv, env.state) } + if miner.chainConfig.IsPrague(header.Number, header.Time) { + core.ProcessBlockHashHistory(env.state, header, miner.chainConfig, miner.chain) + } return env, nil } diff --git a/params/protocol_params.go b/params/protocol_params.go index d33902022d45..143f1b58654f 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -174,6 +174,8 @@ const ( BlobTxTargetBlobGasPerBlock = 3 * BlobTxBlobGasPerBlob // Target consumable blob gas for data blobs per block (for 1559-like pricing) MaxBlobGasPerBlock = 6 * BlobTxBlobGasPerBlob // Maximum consumable blob gas for data blobs per block + + HistoryServeWindow = 8192 // Number of blocks to serve historical block hashes for, EIP-2935. ) // Gas discount table for BLS12-381 G1 and G2 multi exponentiation operations @@ -191,4 +193,6 @@ var ( WithdrawalRequestsAddress = common.HexToAddress("0x00A3ca265EBcb825B45F985A16CEFB49958cE017") // SystemAddress is where the system-transaction is sent from as per EIP-4788 SystemAddress = common.HexToAddress("0xfffffffffffffffffffffffffffffffffffffffe") + // HistoryStorageAddress is where the historical block hashes are stored. + HistoryStorageAddress = common.HexToAddress("0x25a219378dad9b3503c8268c9ca836a52427a4fb") )