diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index a2de58ad46..043e675494 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -35,6 +35,7 @@ import ( "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/types/bal" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto/keccak" "github.com/ethereum/go-ethereum/ethdb" @@ -172,6 +173,9 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, includedTxs types.Transactions blobGasUsed = uint64(0) receipts = make(types.Receipts, 0) + + // TODO return blockAccessList as a part of result + blockAccessList = bal.NewConstructionBlockAccessList() ) vmContext := vm.BlockContext{ CanTransfer: core.CanTransfer, @@ -231,14 +235,14 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, } evm := vm.NewEVM(vmContext, statedb, chainConfig, vmConfig) if beaconRoot := pre.Env.ParentBeaconBlockRoot; beaconRoot != nil { - core.ProcessBeaconBlockRoot(*beaconRoot, evm) + core.ProcessBeaconBlockRoot(*beaconRoot, evm, blockAccessList) } if pre.Env.BlockHashes != nil && chainConfig.IsPrague(new(big.Int).SetUint64(pre.Env.Number), pre.Env.Timestamp) { var ( prevNumber = pre.Env.Number - 1 prevHash = pre.Env.BlockHashes[math.HexOrDecimal64(prevNumber)] ) - core.ProcessParentBlockHash(prevHash, evm) + core.ProcessParentBlockHash(prevHash, evm, blockAccessList) } for i := 0; txIt.Next(); i++ { tx, err := txIt.Tx() @@ -271,11 +275,12 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, } } statedb.SetTxContext(tx.Hash(), len(receipts), uint32(len(receipts)+1)) + var ( snapshot = statedb.Snapshot() gp = gaspool.Snapshot() ) - receipt, err := core.ApplyTransactionWithEVM(msg, gaspool, statedb, vmContext.BlockNumber, blockHash, pre.Env.Timestamp, tx, evm) + receipt, bal, err := core.ApplyTransactionWithEVM(msg, gaspool, statedb, vmContext.BlockNumber, blockHash, pre.Env.Timestamp, tx, evm) if err != nil { statedb.RevertToSnapshot(snapshot) log.Info("rejected tx", "index", i, "hash", tx.Hash(), "from", msg.From, "error", err) @@ -292,6 +297,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, } blobGasUsed += txBlobGas receipts = append(receipts, receipt) + blockAccessList.Merge(bal) } statedb.IntermediateRoot(chainConfig.IsEIP158(vmContext.BlockNumber)) @@ -336,10 +342,12 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, for _, receipt := range receipts { allLogs = append(allLogs, receipt.Logs...) } - requests, err := core.PostExecution(context.Background(), chainConfig, vmContext.BlockNumber, vmContext.Time, allLogs, evm, uint32(len(receipts)+1)) + requests, bal, err := core.PostExecution(context.Background(), chainConfig, vmContext.BlockNumber, vmContext.Time, allLogs, evm, uint32(len(receipts)+1)) if err != nil { return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("failed to process post-execution: %v", err)) } + blockAccessList.Merge(bal) + // Commit block root, err := statedb.Commit(vmContext.BlockNumber.Uint64(), chainConfig.IsEIP158(vmContext.BlockNumber), chainConfig.IsCancun(vmContext.BlockNumber, vmContext.Time)) if err != nil { diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index 72ac75c036..0dff36a29c 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -27,6 +27,7 @@ import ( "github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/types/bal" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/params" "github.com/holiman/uint256" @@ -342,9 +343,9 @@ func (beacon *Beacon) Prepare(chain consensus.ChainHeaderReader, header *types.H } // Finalize implements consensus.Engine and processes withdrawals on top. -func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state vm.StateDB, body *types.Body) { +func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state vm.StateDB, body *types.Body, blockAccessIndex uint32, bal *bal.ConstructionBlockAccessList) { if !beacon.IsPoSHeader(header) { - beacon.ethone.Finalize(chain, header, state, body) + beacon.ethone.Finalize(chain, header, state, body, blockAccessIndex, bal) return } // Withdrawals processing. @@ -352,7 +353,20 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types. // Convert amount from gwei to wei. amount := new(uint256.Int).SetUint64(w.Amount) amount = amount.Mul(amount, uint256.NewInt(params.GWei)) - state.AddBalance(w.Address, amount, tracing.BalanceIncreaseWithdrawal) + prev := state.AddBalance(w.Address, amount, tracing.BalanceIncreaseWithdrawal) + + // Populate the block-level accessList if Amsterdam is enabled + if bal != nil { + if w.Amount == 0 { + // Zero amount withdrawal, account is accessed potential + // without state changes. + bal.AccountRead(w.Address) + } else { + // Non-zero amount withdrawal, account is accessed with + // a balance change. + bal.BalanceChange(blockAccessIndex, w.Address, new(uint256.Int).Add(&prev, amount)) + } + } } // No block reward which is issued by consensus layer instead. } diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index ceaec44656..f44afde241 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -34,6 +34,7 @@ import ( "github.com/ethereum/go-ethereum/consensus/misc" "github.com/ethereum/go-ethereum/consensus/misc/eip1559" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/types/bal" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/keccak" @@ -573,7 +574,7 @@ func (c *Clique) Prepare(chain consensus.ChainHeaderReader, header *types.Header // Finalize implements consensus.Engine. There is no post-transaction // consensus rules in clique, do nothing here. -func (c *Clique) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state vm.StateDB, body *types.Body) { +func (c *Clique) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state vm.StateDB, body *types.Body, blockAccessIndex uint32, bal *bal.ConstructionBlockAccessList) { // No block rewards in PoA, so the state remains as is } diff --git a/consensus/consensus.go b/consensus/consensus.go index 4ba389292f..e4f7b7a6a1 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -22,6 +22,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/types/bal" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/params" ) @@ -79,12 +80,12 @@ type Engine interface { // rules of a particular engine. The changes are executed inline. Prepare(chain ChainHeaderReader, header *types.Header) error - // Finalize runs any post-transaction state modifications (e.g. block rewards - // or process withdrawals) but does not assemble the block. + // Finalize runs any post-transaction consensus-specific state modifications + // (e.g. block rewards or process withdrawals) but does not assemble the block. // // Note: The state database might be updated to reflect any consensus rules // that happen at finalization (e.g. block rewards). - Finalize(chain ChainHeaderReader, header *types.Header, state vm.StateDB, body *types.Body) + Finalize(chain ChainHeaderReader, header *types.Header, state vm.StateDB, body *types.Body, blockAccessIndex uint32, bal *bal.ConstructionBlockAccessList) // Seal generates a new sealing request for the given input block and pushes // the result into the given channel. diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index ee9d9d97d6..21adc9d279 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -29,6 +29,7 @@ import ( "github.com/ethereum/go-ethereum/consensus/misc/eip1559" "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/types/bal" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto/keccak" "github.com/ethereum/go-ethereum/params" @@ -504,7 +505,7 @@ func (ethash *Ethash) Prepare(chain consensus.ChainHeaderReader, header *types.H } // Finalize implements consensus.Engine, accumulating the block and uncle rewards. -func (ethash *Ethash) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state vm.StateDB, body *types.Body) { +func (ethash *Ethash) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state vm.StateDB, body *types.Body, blockAccessIndex uint32, bal *bal.ConstructionBlockAccessList) { // Accumulate any block and uncle rewards accumulateRewards(chain.Config(), state, header, body.Uncles) } diff --git a/core/bintrie_witness_test.go b/core/bintrie_witness_test.go index 5f6239e4fa..b49ac83bb5 100644 --- a/core/bintrie_witness_test.go +++ b/core/bintrie_witness_test.go @@ -29,6 +29,7 @@ import ( "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/types/bal" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" @@ -202,7 +203,7 @@ func TestProcessParentBlockHash(t *testing.T) { } vmContext := NewEVMBlockContext(header, nil, new(common.Address)) evm := vm.NewEVM(vmContext, statedb, chainConfig, vm.Config{}) - ProcessParentBlockHash(header.ParentHash, evm) + ProcessParentBlockHash(header.ParentHash, evm, bal.NewConstructionBlockAccessList()) } // Read block hashes for block 0 .. num-1 for i := 0; i < num; i++ { diff --git a/core/block_validator.go b/core/block_validator.go index 008444fbbc..f5c5f11d14 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -111,6 +111,27 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error { } } + // Block access list hash must be present in header after the + // Amsterdam hard fork. + if v.config.IsAmsterdam(block.Number(), block.Time()) { + if block.Header().BlockAccessListHash == nil { + return fmt.Errorf("block access list hash not set in header") + } + // If the block does not come with an access list, we compute the access list + // locally as part of execution and validate against the header's access list + // hash. + if block.AccessList() != nil { + computed := block.AccessList().Hash() + if *block.Header().BlockAccessListHash != computed { + return fmt.Errorf("access list hash mismatch, computed: %x, remote: %x", computed, *block.Header().BlockAccessListHash) + } else if err := block.AccessList().Validate(); err != nil { + return fmt.Errorf("invalid block access list: %v", err) + } + } + } else if block.Header().BlockAccessListHash != nil || block.AccessList() != nil { + return fmt.Errorf("block had access list before Amsterdam") + } + // Ancestor block must be known. if !v.bc.HasBlockAndState(block.ParentHash(), block.NumberU64()-1) { if !v.bc.HasBlock(block.ParentHash(), block.NumberU64()-1) { @@ -160,6 +181,13 @@ func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateD } else if res.Requests != nil { return errors.New("block has requests before prague fork") } + // Verify Block-level accessList once Amsterdam is enabled + if v.config.IsAmsterdam(block.Number(), block.Time()) { + local, remote := res.Bal.ToEncodingObj().Hash(), *block.Header().BlockAccessListHash + if local != remote { + return fmt.Errorf("access list hash mismatch, local: %x, remote: %x", local, remote) + } + } // Validate the state root against the received state root and throw // an error if they don't match. if root := statedb.IntermediateRoot(v.config.IsEIP158(header.Number)); header.Root != root { diff --git a/core/chain_makers.go b/core/chain_makers.go index cfd6302794..8bab68d131 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -29,6 +29,7 @@ import ( "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/types/bal" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/params" @@ -50,6 +51,7 @@ type BlockGen struct { receipts []*types.Receipt uncles []*types.Header withdrawals []*types.Withdrawal + bal *bal.ConstructionBlockAccessList engine consensus.Engine } @@ -65,6 +67,7 @@ func (b *BlockGen) SetCoinbase(addr common.Address) { } b.header.Coinbase = addr b.gasPool = NewGasPool(b.header.GasLimit) + b.bal = bal.NewConstructionBlockAccessList() } // SetExtra sets the extra data field of the generated block. @@ -99,7 +102,7 @@ func (b *BlockGen) Difficulty() *big.Int { func (b *BlockGen) SetParentBeaconRoot(root common.Hash) { b.header.ParentBeaconRoot = &root blockContext := NewEVMBlockContext(b.header, b.cm, &b.header.Coinbase) - ProcessBeaconBlockRoot(root, vm.NewEVM(blockContext, b.statedb, b.cm.config, vm.Config{})) + ProcessBeaconBlockRoot(root, vm.NewEVM(blockContext, b.statedb, b.cm.config, vm.Config{}), b.bal) } // addTx adds a transaction to the generated block. If no coinbase has @@ -118,7 +121,7 @@ func (b *BlockGen) addTx(bc *BlockChain, vmConfig vm.Config, tx *types.Transacti evm = vm.NewEVM(blockContext, b.statedb, b.cm.config, vmConfig) ) b.statedb.SetTxContext(tx.Hash(), len(b.txs), uint32(len(b.txs)+1)) - receipt, err := ApplyTransaction(evm, b.gasPool, b.statedb, b.header, tx) + receipt, bal, err := ApplyTransaction(evm, b.gasPool, b.statedb, b.header, tx) if err != nil { panic(err) } @@ -134,6 +137,7 @@ func (b *BlockGen) addTx(bc *BlockChain, vmConfig vm.Config, tx *types.Transacti if b.header.BlobGasUsed != nil { *b.header.BlobGasUsed += receipt.BlobGasUsed } + b.bal.Merge(bal) } // AddTx adds a transaction to the generated block. If no coinbase has @@ -304,10 +308,11 @@ func (b *BlockGen) OffsetTime(seconds int64) { // ConsensusLayerRequests returns the EIP-7685 requests which have accumulated so far. func (b *BlockGen) ConsensusLayerRequests() [][]byte { - return b.collectRequests(true) + requests, _ := b.collectRequests(true) + return requests } -func (b *BlockGen) collectRequests(readonly bool) (requests [][]byte) { +func (b *BlockGen) collectRequests(readonly bool) (requests [][]byte, bal *bal.ConstructionBlockAccessList) { statedb := b.statedb if readonly { // The system contracts clear themselves on a system-initiated read. @@ -323,11 +328,11 @@ func (b *BlockGen) collectRequests(readonly bool) (requests [][]byte) { blockContext := NewEVMBlockContext(b.header, b.cm, &b.header.Coinbase) evm := vm.NewEVM(blockContext, statedb, b.cm.config, vm.Config{}) - requests, err := PostExecution(context.Background(), b.cm.config, b.header.Number, b.header.Time, blockLogs, evm, uint32(len(b.txs)+1)) + requests, bal, err := PostExecution(context.Background(), b.cm.config, b.header.Number, b.header.Time, blockLogs, evm, uint32(len(b.txs)+1)) if err != nil { panic(fmt.Sprintf("failed to run post-execution: %v", err)) } - return requests + return requests, bal } // GenerateChain creates a chain of n blocks. The first block's @@ -386,7 +391,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse blockContext := NewEVMBlockContext(b.header, cm, &b.header.Coinbase) blockContext.Random = &common.Hash{} // enable post-merge instruction set evm := vm.NewEVM(blockContext, statedb, cm.config, vm.Config{}) - ProcessParentBlockHash(b.header.ParentHash, evm) + ProcessParentBlockHash(b.header.ParentHash, evm, b.bal) } // Execute any user modifications to the block @@ -394,11 +399,12 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse gen(i, b) } - requests := b.collectRequests(false) + requests, bal := b.collectRequests(false) if requests != nil { reqHash := types.CalcRequestsHash(requests) b.header.RequestsHash = &reqHash } + b.bal.Merge(bal) body := types.Body{ Transactions: b.txs, @@ -414,8 +420,11 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse body.Withdrawals = make([]*types.Withdrawal, 0) } } + // Apply the consensus-specific post-transaction changes + b.engine.Finalize(cm, b.header, statedb, &body, uint32(len(b.txs)+1), b.bal) + // Assemble the block for delivery. - block := AssembleBlock(b.engine, cm, b.header, statedb, &body, b.receipts) + block := AssembleBlock(cm, b.header, statedb, &body, b.receipts, b.bal) // Write state changes to db root, err := statedb.Commit(b.header.Number.Uint64(), config.IsEIP158(b.header.Number), config.IsCancun(b.header.Number, b.header.Time)) diff --git a/core/state_processor.go b/core/state_processor.go index 13466b7815..eec5be9ff8 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -27,6 +27,7 @@ import ( "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/types/bal" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/internal/telemetry" @@ -81,13 +82,16 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated misc.ApplyDAOHardFork(tracingStateDB) } var ( - context = NewEVMBlockContext(header, p.chain, nil) - signer = types.MakeSigner(config, header.Number, header.Time) - evm = vm.NewEVM(context, tracingStateDB, config, cfg) + context = NewEVMBlockContext(header, p.chain, nil) + signer = types.MakeSigner(config, header.Number, header.Time) + evm = vm.NewEVM(context, tracingStateDB, config, cfg) + blockAccessList = bal.NewConstructionBlockAccessList() ) defer evm.Release() + // Run the pre-execution system calls - PreExecution(ctx, block.BeaconRoot(), block.ParentHash(), config, evm, block.Number(), block.Time()) + blockAccessList.Merge(PreExecution(ctx, block.BeaconRoot(), block.ParentHash(), config, evm, block.Number(), block.Time())) + // Iterate over and process the individual transactions for i, tx := range block.Transactions() { msg, err := TransactionToMessage(tx, signer, header.BaseFee) @@ -99,76 +103,89 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated telemetry.StringAttribute("tx.hash", tx.Hash().Hex()), telemetry.Int64Attribute("tx.index", int64(i)), ) - - receipt, err := ApplyTransactionWithEVM(msg, gp, statedb, blockNumber, blockHash, context.Time, tx, evm) + receipt, bal, err := ApplyTransactionWithEVM(msg, gp, statedb, blockNumber, blockHash, context.Time, tx, evm) if err != nil { spanEnd(&err) return nil, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) } receipts = append(receipts, receipt) allLogs = append(allLogs, receipt.Logs...) + blockAccessList.Merge(bal) spanEnd(nil) } - requests, err := PostExecution(ctx, config, block.Number(), block.Time(), allLogs, evm, uint32(len(block.Transactions())+1)) + requests, bal, err := PostExecution(ctx, config, block.Number(), block.Time(), allLogs, evm, uint32(len(block.Transactions())+1)) if err != nil { return nil, err } + blockAccessList.Merge(bal) + // Finalize the block, applying any consensus engine specific extras (e.g. block rewards) - p.chain.Engine().Finalize(p.chain, header, tracingStateDB, block.Body()) + // + // TODO(rjl493456442) integrate it into the PostExecution. + p.chain.Engine().Finalize(p.chain, header, tracingStateDB, block.Body(), uint32(len(block.Transactions())+1), blockAccessList) return &ProcessResult{ Receipts: receipts, Requests: requests, Logs: allLogs, GasUsed: gp.Used(), + Bal: blockAccessList, }, nil } // PreExecution processes pre-execution system calls. -func PreExecution(ctx context.Context, beaconRoot *common.Hash, parent common.Hash, config *params.ChainConfig, evm *vm.EVM, number *big.Int, time uint64) { +func PreExecution(ctx context.Context, beaconRoot *common.Hash, parent common.Hash, config *params.ChainConfig, evm *vm.EVM, number *big.Int, time uint64) *bal.ConstructionBlockAccessList { _, _, spanEnd := telemetry.StartSpan(ctx, "core.preExecution") defer spanEnd(nil) + var blockAccessList *bal.ConstructionBlockAccessList + if config.IsAmsterdam(number, time) { + blockAccessList = bal.NewConstructionBlockAccessList() + } // EIP-4788 if beaconRoot != nil { - ProcessBeaconBlockRoot(*beaconRoot, evm) + ProcessBeaconBlockRoot(*beaconRoot, evm, blockAccessList) } // EIP-2935 if config.IsPrague(number, time) || config.IsUBT(number, time) { - ProcessParentBlockHash(parent, evm) + ProcessParentBlockHash(parent, evm, blockAccessList) } + return blockAccessList } // PostExecution processes post-execution system calls when Prague is enabled. // If Prague is not activated, it returns null requests to differentiate from // empty requests. -func PostExecution(ctx context.Context, config *params.ChainConfig, number *big.Int, time uint64, allLogs []*types.Log, evm *vm.EVM, blockAccessIndex uint32) (requests [][]byte, err error) { +func PostExecution(ctx context.Context, config *params.ChainConfig, number *big.Int, time uint64, allLogs []*types.Log, evm *vm.EVM, blockAccessIndex uint32) (requests [][]byte, blockAccessList *bal.ConstructionBlockAccessList, err error) { _, _, spanEnd := telemetry.StartSpan(ctx, "core.postExecution") defer spanEnd(&err) + if config.IsAmsterdam(number, time) { + blockAccessList = bal.NewConstructionBlockAccessList() + } // Read requests if Prague is enabled. if config.IsPrague(number, time) { requests = [][]byte{} // EIP-6110 if err := ParseDepositLogs(&requests, allLogs, config); err != nil { - return nil, fmt.Errorf("failed to parse deposit logs: %w", err) + return nil, nil, fmt.Errorf("failed to parse deposit logs: %w", err) } // EIP-7002 - if err := ProcessWithdrawalQueue(&requests, evm, blockAccessIndex); err != nil { - return nil, fmt.Errorf("failed to process withdrawal queue: %w", err) + if err := ProcessWithdrawalQueue(&requests, evm, blockAccessIndex, blockAccessList); err != nil { + return nil, nil, fmt.Errorf("failed to process withdrawal queue: %w", err) } // EIP-7251 - if err := ProcessConsolidationQueue(&requests, evm, blockAccessIndex); err != nil { - return nil, fmt.Errorf("failed to process consolidation queue: %w", err) + if err := ProcessConsolidationQueue(&requests, evm, blockAccessIndex, blockAccessList); err != nil { + return nil, nil, fmt.Errorf("failed to process consolidation queue: %w", err) } } - return requests, nil + return requests, blockAccessList, nil } // ApplyTransactionWithEVM attempts to apply a transaction to the given state database // and uses the input parameters for its environment similar to ApplyTransaction. However, // this method takes an already created EVM instance as input. -func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, blockTime uint64, tx *types.Transaction, evm *vm.EVM) (receipt *types.Receipt, err error) { +func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, blockTime uint64, tx *types.Transaction, evm *vm.EVM) (receipt *types.Receipt, bal *bal.ConstructionBlockAccessList, err error) { if hooks := evm.Config.Tracer; hooks != nil { if hooks.OnTxStart != nil { hooks.OnTxStart(evm.GetVMContext(), tx, msg.From) @@ -180,12 +197,12 @@ func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb *state.StateDB, // Apply the transaction to the current state (included in the env). result, err := ApplyMessage(evm, msg, gp) if err != nil { - return nil, err + return nil, nil, err } // Update the state with pending changes. var root []byte if evm.ChainConfig().IsByzantium(blockNumber) { - evm.StateDB.Finalise(true) + bal = evm.StateDB.Finalise(true) } else { root = statedb.IntermediateRoot(evm.ChainConfig().IsEIP158(blockNumber)).Bytes() } @@ -194,7 +211,7 @@ func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb *state.StateDB, if statedb.Database().Type().Is(state.TypeUBT) { statedb.AccessEvents().Merge(evm.AccessEvents) } - return MakeReceipt(evm, result, statedb, blockNumber, blockHash, blockTime, tx, gp.CumulativeUsed(), root), nil + return MakeReceipt(evm, result, statedb, blockNumber, blockHash, blockTime, tx, gp.CumulativeUsed(), root), bal, nil } // MakeReceipt generates the receipt object for a transaction given its execution result. @@ -239,10 +256,10 @@ func MakeReceipt(evm *vm.EVM, result *ExecutionResult, statedb *state.StateDB, b // and uses the input parameters for its environment. It returns the receipt // for the transaction and an error if the transaction failed, // indicating the block was invalid. -func ApplyTransaction(evm *vm.EVM, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction) (*types.Receipt, error) { +func ApplyTransaction(evm *vm.EVM, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction) (*types.Receipt, *bal.ConstructionBlockAccessList, error) { msg, err := TransactionToMessage(tx, types.MakeSigner(evm.ChainConfig(), header.Number, header.Time), header.BaseFee) if err != nil { - return nil, err + return nil, nil, err } // Create a new context to be used in the EVM environment return ApplyTransactionWithEVM(msg, gp, statedb, header.Number, header.Hash(), header.Time, tx, evm) @@ -250,7 +267,7 @@ func ApplyTransaction(evm *vm.EVM, gp *GasPool, statedb *state.StateDB, header * // ProcessBeaconBlockRoot applies the EIP-4788 system call to the beacon block root // contract. This method is exported to be used in tests. -func ProcessBeaconBlockRoot(beaconRoot common.Hash, evm *vm.EVM) { +func ProcessBeaconBlockRoot(beaconRoot common.Hash, evm *vm.EVM, blockAccessList *bal.ConstructionBlockAccessList) { if tracer := evm.Config.Tracer; tracer != nil { onSystemCallStart(tracer, evm.GetVMContext()) if tracer.OnSystemCallEnd != nil { @@ -273,12 +290,12 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, evm *vm.EVM) { if evm.StateDB.AccessEvents() != nil { evm.StateDB.AccessEvents().Merge(evm.AccessEvents) } - evm.StateDB.Finalise(true) + blockAccessList.Merge(evm.StateDB.Finalise(true)) } // ProcessParentBlockHash stores the parent block hash in the history storage contract // as per EIP-2935/7709. -func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM) { +func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM, blockAccessList *bal.ConstructionBlockAccessList) { if tracer := evm.Config.Tracer; tracer != nil { onSystemCallStart(tracer, evm.GetVMContext()) if tracer.OnSystemCallEnd != nil { @@ -304,22 +321,22 @@ func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM) { if evm.StateDB.AccessEvents() != nil { evm.StateDB.AccessEvents().Merge(evm.AccessEvents) } - evm.StateDB.Finalise(true) + blockAccessList.Merge(evm.StateDB.Finalise(true)) } // ProcessWithdrawalQueue calls the EIP-7002 withdrawal queue contract. // It returns the opaque request data returned by the contract. -func ProcessWithdrawalQueue(requests *[][]byte, evm *vm.EVM, blockAccessIndex uint32) error { - return processRequestsSystemCall(requests, evm, 0x01, params.WithdrawalQueueAddress, blockAccessIndex) +func ProcessWithdrawalQueue(requests *[][]byte, evm *vm.EVM, blockAccessIndex uint32, blockAccessList *bal.ConstructionBlockAccessList) error { + return processRequestsSystemCall(requests, evm, 0x01, params.WithdrawalQueueAddress, blockAccessIndex, blockAccessList) } // ProcessConsolidationQueue calls the EIP-7251 consolidation queue contract. // It returns the opaque request data returned by the contract. -func ProcessConsolidationQueue(requests *[][]byte, evm *vm.EVM, blockAccessIndex uint32) error { - return processRequestsSystemCall(requests, evm, 0x02, params.ConsolidationQueueAddress, blockAccessIndex) +func ProcessConsolidationQueue(requests *[][]byte, evm *vm.EVM, blockAccessIndex uint32, blockAccessList *bal.ConstructionBlockAccessList) error { + return processRequestsSystemCall(requests, evm, 0x02, params.ConsolidationQueueAddress, blockAccessIndex, blockAccessList) } -func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte, addr common.Address, blockAccessIndex uint32) error { +func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte, addr common.Address, blockAccessIndex uint32, blockAccessList *bal.ConstructionBlockAccessList) error { if tracer := evm.Config.Tracer; tracer != nil { onSystemCallStart(tracer, evm.GetVMContext()) if tracer.OnSystemCallEnd != nil { @@ -341,7 +358,7 @@ func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte if evm.StateDB.AccessEvents() != nil { evm.StateDB.AccessEvents().Merge(evm.AccessEvents) } - evm.StateDB.Finalise(true) + bal := evm.StateDB.Finalise(true) if err != nil { return fmt.Errorf("system call failed to execute: %v", err) } @@ -353,6 +370,7 @@ func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte requestsData[0] = requestType copy(requestsData[1:], ret) *requests = append(*requests, requestsData) + blockAccessList.Merge(bal) return nil } @@ -387,8 +405,19 @@ func onSystemCallStart(tracer *tracing.Hooks, ctx *tracing.VMContext) { // AssembleBlock finalizes the state and assembles the block with provided // body and receipts. -func AssembleBlock(engine consensus.Engine, chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) *types.Block { - engine.Finalize(chain, header, state, body) +func AssembleBlock(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt, blockAccessList *bal.ConstructionBlockAccessList) *types.Block { + var bal *bal.BlockAccessList + if chain.Config().IsAmsterdam(header.Number, header.Time) { + bal = blockAccessList.ToEncodingObj() + } header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) - return types.NewBlock(header, body, receipts, trie.NewStackTrie(nil)) + if bal != nil { + balHash := bal.Hash() + header.BlockAccessListHash = &balHash + } + block := types.NewBlock(header, body, receipts, trie.NewStackTrie(nil)) + if bal != nil { + block = block.WithAccessListUnsafe(bal) + } + return block } diff --git a/core/types.go b/core/types.go index 87bbfcff58..4b99191771 100644 --- a/core/types.go +++ b/core/types.go @@ -22,6 +22,7 @@ import ( "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/types/bal" "github.com/ethereum/go-ethereum/core/vm" ) @@ -58,4 +59,5 @@ type ProcessResult struct { Requests [][]byte Logs []*types.Log GasUsed uint64 + Bal *bal.ConstructionBlockAccessList } diff --git a/core/types/bal/bal.go b/core/types/bal/bal.go index 9cbc1faeb9..2eb5fe93cd 100644 --- a/core/types/bal/bal.go +++ b/core/types/bal/bal.go @@ -138,10 +138,62 @@ func (b *ConstructionBlockAccessList) BalanceChange(txIdx uint32, address common // PrettyPrint returns a human-readable representation of the access list func (b *ConstructionBlockAccessList) PrettyPrint() string { - enc := b.toEncodingObj() + enc := b.ToEncodingObj() return enc.PrettyPrint() } +// Merge applies other on top of the local block access list. For colliding +// entries (a (slot, txIdx) write or a txIdx-keyed balance/nonce/code change), +// the value from other wins, matching the semantics of applying the local +// effects first and then other's. Storage reads are unioned; any slot +// written by either side is dropped from StorageReads. +// +// Typically each list covers its own tx index, so txIdx-level collisions are +// not expected; the exception is pre/post-transition system calls, which +// share a single tx index. In that case callers must pass block-accessList +// in order strictly. +// +// other is referenced (not deep copied), after the call both lists share +// inner maps and other must not be mutated. +func (b *ConstructionBlockAccessList) Merge(other *ConstructionBlockAccessList) { + if other == nil { + return + } + for addr, otherAcc := range other.Accounts { + acc, ok := b.Accounts[addr] + if !ok { + b.Accounts[addr] = otherAcc + continue + } + for key, writes := range otherAcc.StorageWrites { + existing, ok := acc.StorageWrites[key] + if !ok { + acc.StorageWrites[key] = writes + } else { + for txIdx, value := range writes { + existing[txIdx] = value + } + } + delete(acc.StorageReads, key) + } + for key := range otherAcc.StorageReads { + if _, ok := acc.StorageWrites[key]; ok { + continue + } + acc.StorageReads[key] = struct{}{} + } + for txIdx, balance := range otherAcc.BalanceChanges { + acc.BalanceChanges[txIdx] = balance + } + for txIdx, nonce := range otherAcc.NonceChanges { + acc.NonceChanges[txIdx] = nonce + } + for txIdx, code := range otherAcc.CodeChange { + acc.CodeChange[txIdx] = code + } + } +} + // Copy returns a deep copy of the access list. func (b *ConstructionBlockAccessList) Copy() *ConstructionBlockAccessList { res := NewConstructionBlockAccessList() diff --git a/core/types/bal/bal_encoding.go b/core/types/bal/bal_encoding.go index 03f97f3809..ff36612eb0 100644 --- a/core/types/bal/bal_encoding.go +++ b/core/types/bal/bal_encoding.go @@ -78,14 +78,14 @@ func (e *BlockAccessList) DecodeRLP(s *rlp.Stream) error { // Validate returns an error if the contents of the access list are not ordered // according to the spec or any code changes are contained which exceed protocol // max code size. -func (e *BlockAccessList) Validate(rules params.Rules) error { +func (e *BlockAccessList) Validate() error { if !slices.IsSortedFunc(*e, func(a, b AccountAccess) int { return bytes.Compare(a.Address[:], b.Address[:]) }) { return errors.New("block access list accounts not in lexicographic order") } for _, entry := range *e { - if err := entry.validate(rules); err != nil { + if err := entry.validate(); err != nil { return err } } @@ -159,7 +159,7 @@ type AccountAccess struct { // validate converts the account accesses out of encoding format. // If any of the keys in the encoding object are not ordered according to the // spec, an error is returned. -func (e *AccountAccess) validate(rules params.Rules) error { +func (e *AccountAccess) validate() error { // Check the storage write slots are sorted in order if !slices.IsSortedFunc(e.StorageWrites, func(a, b encodingSlotWrites) int { return a.Slot.Cmp(b.Slot) @@ -200,14 +200,7 @@ func (e *AccountAccess) validate(rules params.Rules) error { return errors.New("code changes not in ascending order by tx index") } for _, change := range e.CodeChanges { - var sizeLimit int - switch { - case rules.IsAmsterdam: - sizeLimit = params.MaxCodeSizeAmsterdam - default: - sizeLimit = params.MaxCodeSize - } - if len(change.Code) > sizeLimit { + if len(change.Code) > params.MaxCodeSizeAmsterdam { return errors.New("code change contained oversized code") } } @@ -257,7 +250,7 @@ func (e *AccountAccess) Copy() AccountAccess { // EncodeRLP returns the RLP-encoded access list func (b *ConstructionBlockAccessList) EncodeRLP(wr io.Writer) error { - return b.toEncodingObj().EncodeRLP(wr) + return b.ToEncodingObj().EncodeRLP(wr) } var _ rlp.Encoder = &ConstructionBlockAccessList{} @@ -340,9 +333,9 @@ func (a *ConstructionAccountAccess) toEncodingObj(addr common.Address) AccountAc return res } -// toEncodingObj returns an instance of the access list expressed as the type +// ToEncodingObj returns an instance of the access list expressed as the type // which is used as input for the encoding/decoding. -func (b *ConstructionBlockAccessList) toEncodingObj() *BlockAccessList { +func (b *ConstructionBlockAccessList) ToEncodingObj() *BlockAccessList { var addresses []common.Address for addr := range b.Accounts { addresses = append(addresses, addr) diff --git a/core/types/bal/bal_test.go b/core/types/bal/bal_test.go index 32a0292f2e..4bd705034b 100644 --- a/core/types/bal/bal_test.go +++ b/core/types/bal/bal_test.go @@ -25,7 +25,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/internal/testrand" - "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/holiman/uint256" ) @@ -98,14 +97,65 @@ func TestBALEncoding(t *testing.T) { if err := dec.DecodeRLP(rlp.NewStream(bytes.NewReader(buf.Bytes()), 0)); err != nil { t.Fatalf("decoding failed: %v\n", err) } - if dec.Hash() != bal.toEncodingObj().Hash() { + if dec.Hash() != bal.ToEncodingObj().Hash() { t.Fatalf("encoded block hash doesn't match decoded") } - if !reflect.DeepEqual(bal.toEncodingObj(), &dec) { + if !reflect.DeepEqual(bal.ToEncodingObj(), &dec) { t.Fatal("decoded BAL doesn't match") } } +func TestConstructionBALMerge(t *testing.T) { + var ( + addrA = common.BytesToAddress([]byte{0xAA}) + addrB = common.BytesToAddress([]byte{0xBB}) + slot1 = common.BytesToHash([]byte{0x01}) + slot2 = common.BytesToHash([]byte{0x02}) + slot3 = common.BytesToHash([]byte{0x03}) + ) + a := NewConstructionBlockAccessList() + a.StorageWrite(1, addrA, slot1, common.BytesToHash([]byte{0x11})) + a.StorageRead(addrA, slot2) // demoted by other's write below + a.BalanceChange(1, addrA, uint256.NewInt(100)) + a.NonceChange(addrA, 1, 7) + + b := NewConstructionBlockAccessList() + b.StorageWrite(2, addrA, slot1, common.BytesToHash([]byte{0x22})) // same slot, disjoint txIdx + b.StorageWrite(2, addrA, slot2, common.BytesToHash([]byte{0x33})) + b.StorageRead(addrA, slot3) + b.BalanceChange(2, addrA, uint256.NewInt(200)) + b.NonceChange(addrA, 2, 8) + b.CodeChange(addrB, 2, []byte{0xde, 0xad}) // account only in other + + a.Merge(b) + + accA := a.Accounts[addrA] + wantWrites := map[common.Hash]map[uint32]common.Hash{ + slot1: {1: common.BytesToHash([]byte{0x11}), 2: common.BytesToHash([]byte{0x22})}, + slot2: {2: common.BytesToHash([]byte{0x33})}, + } + if !reflect.DeepEqual(accA.StorageWrites, wantWrites) { + t.Fatalf("storage writes mismatch: got %v, want %v", accA.StorageWrites, wantWrites) + } + wantReads := map[common.Hash]struct{}{slot3: {}} + if !reflect.DeepEqual(accA.StorageReads, wantReads) { + t.Fatalf("storage reads mismatch: got %v, want %v", accA.StorageReads, wantReads) + } + if accA.BalanceChanges[1].Uint64() != 100 || accA.BalanceChanges[2].Uint64() != 200 { + t.Fatalf("balance changes mismatch: %v", accA.BalanceChanges) + } + if accA.NonceChanges[1] != 7 || accA.NonceChanges[2] != 8 { + t.Fatalf("nonce changes mismatch: %v", accA.NonceChanges) + } + accB, ok := a.Accounts[addrB] + if !ok { + t.Fatal("account only present in other was not adopted") + } + if !bytes.Equal(accB.CodeChange[2], []byte{0xde, 0xad}) { + t.Fatalf("code change for adopted account missing: %x", accB.CodeChange[2]) + } +} + func makeTestAccountAccess(sort bool) AccountAccess { var ( storageWrites []encodingSlotWrites @@ -234,7 +284,7 @@ func TestBlockAccessListCopy(t *testing.T) { func TestBlockAccessListValidation(t *testing.T) { // Validate the block access list after RLP decoding enc := makeTestBAL(true) - if err := enc.Validate(params.Rules{}); err != nil { + if err := enc.Validate(); err != nil { t.Fatalf("Unexpected validation error: %v", err) } var buf bytes.Buffer @@ -246,14 +296,14 @@ func TestBlockAccessListValidation(t *testing.T) { if err := dec.DecodeRLP(rlp.NewStream(bytes.NewReader(buf.Bytes()), 0)); err != nil { t.Fatalf("Unexpected RLP-decode error: %v", err) } - if err := dec.Validate(params.Rules{}); err != nil { + if err := dec.Validate(); err != nil { t.Fatalf("Unexpected validation error: %v", err) } // Validate the derived block access list cBAL := makeTestConstructionBAL() - listB := cBAL.toEncodingObj() - if err := listB.Validate(params.Rules{}); err != nil { + listB := cBAL.ToEncodingObj() + if err := listB.Validate(); err != nil { t.Fatalf("Unexpected validation error: %v", err) } } diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 0df02388b3..88132b4b63 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -1018,7 +1018,7 @@ func (api *API) traceTx(ctx context.Context, tx *types.Transaction, message *cor // Call Prepare to clear out the statedb access list statedb.SetTxContext(txctx.TxHash, txctx.TxIndex, uint32(txctx.TxIndex+1)) - _, err = core.ApplyTransactionWithEVM(message, core.NewGasPool(message.GasLimit), statedb, vmctx.BlockNumber, txctx.BlockHash, vmctx.Time, tx, evm) + _, _, err = core.ApplyTransactionWithEVM(message, core.NewGasPool(message.GasLimit), statedb, vmctx.BlockNumber, txctx.BlockHash, vmctx.Time, tx, evm) if err != nil { return nil, fmt.Errorf("tracing failed: %w", err) } diff --git a/eth/tracers/internal/tracetest/selfdestruct_state_test.go b/eth/tracers/internal/tracetest/selfdestruct_state_test.go index 692c5eb775..39067e8efc 100644 --- a/eth/tracers/internal/tracetest/selfdestruct_state_test.go +++ b/eth/tracers/internal/tracetest/selfdestruct_state_test.go @@ -620,7 +620,7 @@ func TestSelfdestructStateTracer(t *testing.T) { } context := core.NewEVMBlockContext(block.Header(), blockchain, nil) evm := vm.NewEVM(context, hookedState, tt.genesis.Config, vm.Config{Tracer: tracer.Hooks()}) - _, err = core.ApplyTransactionWithEVM(msg, core.NewGasPool(msg.GasLimit), statedb, block.Number(), block.Hash(), block.Time(), tx, evm) + _, _, err = core.ApplyTransactionWithEVM(msg, core.NewGasPool(msg.GasLimit), statedb, block.Number(), block.Hash(), block.Time(), tx, evm) if err != nil { t.Fatalf("failed to execute transaction: %v", err) } diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index fa2ff2c32b..8462194b1d 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -33,6 +33,7 @@ import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/types/bal" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/internal/ethapi/override" "github.com/ethereum/go-ethereum/params" @@ -292,9 +293,10 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, gp = core.NewGasPool(blockContext.GasLimit) blobGasUsed uint64 - txes = make([]*types.Transaction, len(block.Calls)) - callResults = make([]simCallResult, len(block.Calls)) - receipts = make([]*types.Receipt, len(block.Calls)) + txes = make([]*types.Transaction, len(block.Calls)) + callResults = make([]simCallResult, len(block.Calls)) + receipts = make([]*types.Receipt, len(block.Calls)) + blockAccessList = bal.NewConstructionBlockAccessList() // Block hash will be repaired after execution. tracer = newTracer(sim.traceTransfers, blockContext.BlockNumber.Uint64(), blockContext.Time, common.Hash{}, common.Hash{}, 0) @@ -313,13 +315,14 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, } evm := vm.NewEVM(blockContext, tracingStateDB, sim.chainConfig, *vmConfig) defer evm.Release() + // It is possible to override precompiles with EVM bytecode, or // move them to another address. if precompiles != nil { evm.SetPrecompiles(precompiles) } // Run pre-execution system calls - core.PreExecution(ctx, header.ParentBeaconRoot, header.ParentHash, sim.chainConfig, evm, header.Number, header.Time) + blockAccessList.Merge(core.PreExecution(ctx, header.ParentBeaconRoot, header.ParentHash, sim.chainConfig, evm, header.Number, header.Time)) var allLogs []*types.Log for i, call := range block.Calls { @@ -350,7 +353,7 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, // Update the state with pending changes. var root []byte if sim.chainConfig.IsByzantium(blockContext.BlockNumber) { - tracingStateDB.Finalise(true) + blockAccessList.Merge(tracingStateDB.Finalise(true)) } else { root = sim.state.IntermediateRoot(sim.chainConfig.IsEIP158(blockContext.BlockNumber)).Bytes() } @@ -391,7 +394,7 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, } // Process EIP-7685 requests - requests, err := core.PostExecution(ctx, sim.chainConfig, header.Number, header.Time, allLogs, evm, uint32(len(block.Calls)+1)) + requests, bal, err := core.PostExecution(ctx, sim.chainConfig, header.Number, header.Time, allLogs, evm, uint32(len(block.Calls)+1)) if err != nil { return nil, nil, nil, err } @@ -399,6 +402,7 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, reqHash := types.CalcRequestsHash(requests) header.RequestsHash = &reqHash } + blockAccessList.Merge(bal) blockBody := &types.Body{ Transactions: txes, @@ -411,8 +415,11 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, } chainHeadReader := &simChainHeadReader{ctx, sim.b} + // Apply the consensus-specific post-transaction changes + sim.b.Engine().Finalize(chainHeadReader, header, sim.state, blockBody, uint32(len(block.Calls)+1), blockAccessList) + // Assemble the block - b := core.AssembleBlock(sim.b.Engine(), chainHeadReader, header, sim.state, blockBody, receipts) + b := core.AssembleBlock(chainHeadReader, header, sim.state, blockBody, receipts, blockAccessList) repairLogs(callResults, b.Hash()) return b, callResults, senders, nil diff --git a/miner/worker.go b/miner/worker.go index 026bafc4e5..a92e75fc57 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -32,6 +32,7 @@ import ( "github.com/ethereum/go-ethereum/core/stateless" "github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/types/bal" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/internal/telemetry" "github.com/ethereum/go-ethereum/log" @@ -71,6 +72,7 @@ type environment struct { receipts []*types.Receipt sidecars []*types.BlobTxSidecar blobs int + bal *bal.ConstructionBlockAccessList witness *stateless.Witness } @@ -208,7 +210,7 @@ func (miner *Miner) generateWork(ctx context.Context, genParam *generateParams, } // Collect consensus-layer requests if Prague is enabled. - requests, err := core.PostExecution(ctx, miner.chainConfig, work.header.Number, work.header.Time, allLogs, work.evm, uint32(work.tcount+1)) + requests, bal, err := core.PostExecution(ctx, miner.chainConfig, work.header.Number, work.header.Time, allLogs, work.evm, uint32(work.tcount+1)) if err != nil { return &newPayloadResult{err: err} } @@ -216,9 +218,14 @@ func (miner *Miner) generateWork(ctx context.Context, genParam *generateParams, reqHash := types.CalcRequestsHash(requests) work.header.RequestsHash = &reqHash } + work.bal.Merge(bal) + + // Apply the consensus-specific post-transaction changes + miner.engine.Finalize(miner.chain, work.header, work.state, &body, uint32(work.tcount+1), work.bal) + // Assemble the block for delivery. _, _, assembleSpanEnd := telemetry.StartSpan(ctx, "miner.AssembleBlock") - block := core.AssembleBlock(miner.engine, miner.chain, work.header, work.state, &body, work.receipts) + block := core.AssembleBlock(miner.chain, work.header, work.state, &body, work.receipts, work.bal) assembleSpanEnd(nil) return &newPayloadResult{ @@ -318,7 +325,7 @@ func (miner *Miner) prepareWork(ctx context.Context, genParams *generateParams, return nil, err } // Run pre-execution system calls - core.PreExecution(ctx, header.ParentBeaconRoot, header.ParentHash, miner.chainConfig, env.evm, header.Number, header.Time) + env.bal.Merge(core.PreExecution(ctx, header.ParentBeaconRoot, header.ParentHash, miner.chainConfig, env.evm, header.Number, header.Time)) return env, nil } @@ -337,6 +344,7 @@ func (miner *Miner) makeEnv(parent *types.Header, header *types.Header, coinbase } } state.StartPrefetcher("miner", bundle) + // Note the passed coinbase may be different with header.Coinbase. return &environment{ signer: types.MakeSigner(miner.chainConfig, header.Number, header.Time), @@ -345,6 +353,7 @@ func (miner *Miner) makeEnv(parent *types.Header, header *types.Header, coinbase coinbase: coinbase, gasPool: core.NewGasPool(header.GasLimit), header: header, + bal: bal.NewConstructionBlockAccessList(), witness: state.Witness(), evm: vm.NewEVM(core.NewEVMBlockContext(header, miner.chain, &coinbase), state, miner.chainConfig, vm.Config{}), }, nil @@ -356,7 +365,7 @@ func (miner *Miner) commitTransaction(ctx context.Context, env *environment, tx if tx.Type() == types.BlobTxType { return miner.commitBlobTransaction(env, tx) } - receipt, err := miner.applyTransaction(env, tx) + receipt, bal, err := miner.applyTransaction(env, tx) if err != nil { return err } @@ -364,6 +373,7 @@ func (miner *Miner) commitTransaction(ctx context.Context, env *environment, tx env.receipts = append(env.receipts, receipt) env.size += tx.Size() env.tcount++ + env.bal.Merge(bal) return nil } @@ -380,7 +390,7 @@ func (miner *Miner) commitBlobTransaction(env *environment, tx *types.Transactio if env.blobs+len(sc.Blobs) > maxBlobs { return errors.New("max data blobs reached") } - receipt, err := miner.applyTransaction(env, tx) + receipt, bal, err := miner.applyTransaction(env, tx) if err != nil { return err } @@ -392,23 +402,24 @@ func (miner *Miner) commitBlobTransaction(env *environment, tx *types.Transactio env.size += txNoBlob.Size() *env.header.BlobGasUsed += receipt.BlobGasUsed env.tcount++ + env.bal.Merge(bal) return nil } // applyTransaction runs the transaction. If execution fails, state and gas pool are reverted. -func (miner *Miner) applyTransaction(env *environment, tx *types.Transaction) (*types.Receipt, error) { +func (miner *Miner) applyTransaction(env *environment, tx *types.Transaction) (*types.Receipt, *bal.ConstructionBlockAccessList, error) { var ( snap = env.state.Snapshot() gp = env.gasPool.Snapshot() ) - receipt, err := core.ApplyTransaction(env.evm, env.gasPool, env.state, env.header, tx) + receipt, bal, err := core.ApplyTransaction(env.evm, env.gasPool, env.state, env.header, tx) if err != nil { env.state.RevertToSnapshot(snap) env.gasPool.Set(gp) - return nil, err + return nil, nil, err } env.header.GasUsed = env.gasPool.Used() - return receipt, nil + return receipt, bal, nil } func (miner *Miner) commitTransactions(ctx context.Context, env *environment, plainTxs, blobTxs *transactionsByPriceAndNonce, interrupt *atomic.Int32) error {