diff --git a/core/state_processor.go b/core/state_processor.go index cf2520a7b..027db1808 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -98,6 +98,11 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg isMiko := p.config.IsMiko(blockNumber) isSystemTxsSection := false + // EIP-2935: Store the parent block hash in the history storage contract + if p.config.IsPrague(block.Number()) { + ProcessParentBlockHash(block.ParentHash(), vmenv) + } + for i, tx := range block.Transactions() { if isPoSA { if isSystemTx, err := posa.IsSystemTransaction(tx, block.Header()); err != nil { @@ -143,6 +148,19 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg return receipts, allLogs, *blockContext.InternalTransactions, *usedGas, nil } +// ProcessParentBlockHash stores the parent block hash in the history storage contract +// as per EIP-2935. +func ProcessParentBlockHash(prevHash common.Hash, vmenv *vm.EVM) { + msg := types.NewMessage(consensus.SystemAddress, ¶ms.HistoryStorageAddress, 0, common.Big0, 30_000_000, common.Big0, common.Big0, common.Big0, prevHash.Bytes(), nil, false, nil, nil) + vmenv.Reset(NewEVMTxContext(msg), vmenv.StateDB) + vmenv.StateDB.AddAddressToAccessList(params.HistoryStorageAddress) + _, _, err := vmenv.Call(vm.AccountRef(msg.From()), *msg.To(), msg.Data(), 30_000_000, common.Big0) + if err != nil { + log.Error("Failed to store parent block hash in history storage contract", "err", err) + } + vmenv.StateDB.Finalise(true) +} + func applyTransaction( msg types.Message, config *params.ChainConfig, diff --git a/core/state_processor_test.go b/core/state_processor_test.go index 122f5d0bf..05f359224 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -18,6 +18,7 @@ package core import ( "crypto/ecdsa" + "encoding/binary" "fmt" "math/big" "strings" @@ -33,6 +34,7 @@ import ( "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" @@ -623,3 +625,64 @@ func TestBaseFee(t *testing.T) { t.Fatalf("Treasury balance mismatches, expect %d got %d", fee, treasuryBalance) } } + +func TestProcessParentBlockHash(t *testing.T) { + 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), + CancunBlock: big.NewInt(0), + VenokiBlock: big.NewInt(0), + PragueBlock: big.NewInt(0), + Ethash: new(params.EthashConfig), + } + hashA = common.Hash{0x01} + hashB = common.Hash{0x02} + header = &types.Header{ParentHash: hashA, Number: big.NewInt(2), Difficulty: big.NewInt(0)} + parent = &types.Header{ParentHash: hashB, Number: big.NewInt(1), Difficulty: big.NewInt(0)} + coinbase = common.Address{} + ) + test := func(statedb *state.StateDB) { + statedb.SetNonce(params.HistoryStorageAddress, 1) + statedb.SetCode(params.HistoryStorageAddress, params.HistoryStorageCode) + statedb.IntermediateRoot(true) + + vmContext := NewEVMBlockContext(header, nil, &coinbase) + evm := vm.NewEVM(vmContext, vm.TxContext{}, statedb, chainConfig, vm.Config{}) + ProcessParentBlockHash(header.ParentHash, evm) + + vmContext = NewEVMBlockContext(parent, nil, &coinbase) + evm = vm.NewEVM(vmContext, vm.TxContext{}, statedb, chainConfig, vm.Config{}) + ProcessParentBlockHash(parent.ParentHash, evm) + + // make sure that the state is correct + if have := getParentBlockHash(statedb, 1); have != hashA { + t.Errorf("want parent hash %v, have %v", hashA, have) + } + if have := getParentBlockHash(statedb, 0); have != hashB { + t.Errorf("want parent hash %v, have %v", hashB, 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) +} diff --git a/core/vm/interface.go b/core/vm/interface.go index bb10a69f0..c97b76bd7 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -84,6 +84,9 @@ type StateDB interface { ValidDeployer(common.Address) bool ValidDeployerV2(common.Address, uint64, *common.Address) bool Blacklisted(*common.Address, *common.Address) bool + + // Finalise must be invoked at the end of a transaction + Finalise(bool) } // CallContext provides a basic interface for the EVM calling conventions. The EVM diff --git a/eth/state_accessor.go b/eth/state_accessor.go index c4cac850b..098c44257 100644 --- a/eth/state_accessor.go +++ b/eth/state_accessor.go @@ -234,6 +234,12 @@ func (eth *Ethereum) stateAtTransaction(ctx context.Context, block *types.Block, if err != nil { return nil, vm.BlockContext{}, nil, nil, err } + // If prague hardfork, insert parent block hash in the state as per EIP-2935. + if eth.blockchain.Config().IsPrague(block.Number()) { + context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil) + vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, eth.blockchain.Config(), vm.Config{}) + core.ProcessParentBlockHash(block.ParentHash(), vmenv) + } if txIndex == 0 && len(block.Transactions()) == 0 { return nil, vm.BlockContext{}, statedb, release, nil } diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 4443db66b..b941cbc90 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -415,6 +415,12 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed failed = err break } + // Insert parent hash in history contract. + if api.backend.ChainConfig().IsPrague(next.Number()) { + context := core.NewEVMBlockContext(next.Header(), api.chainContext(ctx), nil) + vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, api.backend.ChainConfig(), vm.Config{}) + core.ProcessParentBlockHash(next.ParentHash(), vmenv) + } // Clean out any pending release functions of trace state. Note this // step must be done after constructing tracing state, because the // tracing state of block next depends on the parent state and construction @@ -574,6 +580,10 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) deleteEmptyObjects = chainConfig.IsEIP158(block.Number()) ) + if chainConfig.IsPrague(block.Number()) { + vevm := vm.NewEVM(vmctx, vm.TxContext{}, statedb, chainConfig, vm.Config{}) + core.ProcessParentBlockHash(block.ParentHash(), vevm) + } for i, tx := range block.Transactions() { if err := ctx.Err(); err != nil { return nil, err @@ -654,6 +664,10 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac signer = types.MakeSigner(api.backend.ChainConfig(), block.Number()) results = make([]*txTraceResult, len(txs)) ) + if api.backend.ChainConfig().IsPrague(block.Number()) { + vmenv := vm.NewEVM(blockCtx, vm.TxContext{}, statedb, api.backend.ChainConfig(), vm.Config{}) + core.ProcessParentBlockHash(block.ParentHash(), vmenv) + } for i, tx := range txs { // Generate the next state snapshot fast without tracing msg, _ := tx.AsMessage(signer, block.BaseFee()) @@ -920,6 +934,10 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block // Note: This copies the config, to not screw up the main config chainConfig, canon = overrideConfig(chainConfig, config.Overrides) } + if chainConfig.IsPrague(block.Number()) { + vmenv := vm.NewEVM(vmctx, vm.TxContext{}, statedb, chainConfig, vm.Config{}) + core.ProcessParentBlockHash(block.ParentHash(), vmenv) + } for i, tx := range block.Transactions() { // Prepare the transaction for un-traced execution var ( diff --git a/miner/worker.go b/miner/worker.go index a9b829b71..468df6fd9 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -35,6 +35,7 @@ import ( "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" @@ -1199,6 +1200,13 @@ func (w *worker) commitNewWork(interrupt *int32, noempty bool, timestamp int64) misc.ApplyDAOHardFork(env.state) } + // EIP-2935: Store the parent block hash in the history storage contract + if w.chainConfig.IsPrague(env.header.Number) { + vmctx := core.NewEVMBlockContext(env.header, w.chain, &w.coinbase) + vevm := vm.NewEVM(vmctx, vm.TxContext{}, env.state, w.chainConfig, vm.Config{}) + core.ProcessParentBlockHash(env.header.ParentHash, vevm) + } + // Accumulate the uncles for the current block uncles := make([]*types.Header, 0, 2) commitUncles := func(blocks map[common.Hash]*types.Block) { @@ -1300,6 +1308,7 @@ func (w *worker) commit(uncles []*types.Header, interval func(), update bool, st if w.isRunning() { // Deep copy receipts here to avoid interaction between different tasks. env := w.current.copy() + // As consortium does not use uncles, we don't care about copying uncles here block, receipts, err := w.engine.FinalizeAndAssemble(w.chain, env.header, env.state, env.txs, uncles, env.receipts) if err != nil { diff --git a/params/protocol_params.go b/params/protocol_params.go index eb14a90fd..3fa3731cd 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -19,6 +19,8 @@ package params import ( "math/big" "time" + + "github.com/ethereum/go-ethereum/common" ) const ( @@ -187,6 +189,8 @@ const ( MaxBlobsPerBlock = 6 // Maximum number of data blobs per block BlobPrunePeriod = 518400 // Number of blocks after which to prune old blob BlobKeepPeriod = 18 * 24 * time.Hour // The approximate period of time before which the blob is pruned + + 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 @@ -198,3 +202,10 @@ var ( MinimumDifficulty = big.NewInt(131072) // The minimum that the difficulty may ever be. DurationLimit = big.NewInt(13) // The decision boundary on the blocktime duration used to determine whether difficulty should go up or not. ) + +var ( + // HistoryStorageAddress is where the historical block hashes are stored. + HistoryStorageAddress = common.HexToAddress("0x0000F90827F1C53a10cb7A02335B175320002935") + // HistoryStorageCode is the code with getters for historical block hashes. + HistoryStorageCode = common.FromHex("3373fffffffffffffffffffffffffffffffffffffffe14604657602036036042575f35600143038111604257611fff81430311604257611fff9006545f5260205ff35b5f5ffd5b5f35611fff60014303065500") +)