From 20f1d2ceed9006276f06ea0d7725169a115bc39e Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Thu, 4 Apr 2024 06:30:52 +0200 Subject: [PATCH] core: implement eip-2935 Co-authored-by: Guillaume Ballet Co-authored-by: Ignacio Hagopian --- cmd/evm/internal/t8ntool/execution.go | 10 +++++ core/genesis.go | 2 + core/state_processor.go | 38 ++++++++++++++++++ core/state_processor_test.go | 55 +++++++++++++++++++++++++++ core/vm/instructions.go | 20 ++++++++-- core/vm/jump_table.go | 5 +++ miner/worker.go | 3 ++ params/protocol_params.go | 9 +++++ 8 files changed, 138 insertions(+), 4 deletions(-) diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index 3c09229e1c..bf6b6ef3c7 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -196,6 +196,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/genesis.go b/core/genesis.go index 42836e0269..038477bf9f 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -593,6 +593,8 @@ func DeveloperGenesisBlock(gasLimit uint64, faucet *common.Address) *Genesis { common.BytesToAddress([]byte{7}): {Balance: big.NewInt(1)}, // ECScalarMul common.BytesToAddress([]byte{8}): {Balance: big.NewInt(1)}, // ECPairing common.BytesToAddress([]byte{9}): {Balance: big.NewInt(1)}, // BLAKE2b + // Pre-deploy EIP-2935 history contract. + params.HistoryStorageAddress: types.Account{Nonce: 1, Code: params.HistoryStorageCode}, }, } if faucet != nil { diff --git a/core/state_processor.go b/core/state_processor.go index 7166ed8bd8..5bc24c6386 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -17,6 +17,7 @@ package core import ( + "encoding/binary" "errors" "fmt" "math/big" @@ -80,6 +81,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) @@ -209,3 +213,37 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, vmenv *vm.EVM, statedb *stat _, _, _ = vmenv.Call(vm.AccountRef(msg.From), *msg.To, msg.Data, 30_000_000, common.U2560) statedb.Finalise(true) } + +// 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 af4d29b604..2caea37ecd 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 10cdd72e0c..fb82dc1509 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -17,6 +17,7 @@ package vm import ( + "encoding/binary" "math" "github.com/ethereum/go-ethereum/common" @@ -434,16 +435,27 @@ func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ( num.Clear() return nil, nil } - + historySize := uint64(256) + isPrague := interpreter.evm.ChainConfig().IsPrague(interpreter.evm.Context.BlockNumber, interpreter.evm.Context.Time) + // EIP-2935 extends the observable history window. + if isPrague { + 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()) + if isPrague { + var key common.Hash + binary.BigEndian.PutUint64(key[24:], num64 % params.HistoryServeWindow) + num.SetBytes(interpreter.evm.StateDB.GetState(params.HistoryStorageAddress, key).Bytes()) + } else { + num.SetBytes(interpreter.evm.Context.GetHash(num64).Bytes()) + } } else { num.Clear() } diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index 5624f47ba7..229d765847 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -87,6 +87,11 @@ func newVerkleInstructionSet() JumpTable { return validate(instructionSet) } +func newPraugeInstructionSet() JumpTable { + instructionSet := newCancunInstructionSet() + return validate(instructionSet) +} + func newCancunInstructionSet() JumpTable { instructionSet := newShanghaiInstructionSet() enable4844(&instructionSet) // EIP-4844 (BLOBHASH opcode) diff --git a/miner/worker.go b/miner/worker.go index 5dc3e2056b..91c4836e53 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -200,6 +200,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 d375a96642..a07efe48bc 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 @@ -189,4 +191,11 @@ var ( BeaconRootsAddress = common.HexToAddress("0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02") // 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") + // HistoryStorageCode is the code with getters for historical block hashes. + HistoryStorageCode = common.FromHex("60203611603157600143035f35116029575f356120000143116029576120005f3506545f5260205ff35b5f5f5260205ff35b5f5ffd00") + + // Magic prefix for EIP-3074 AUTH messages. + AuthMagic = byte(0x04) )