core, consensus, internal, eth, miner: construct block accessList

This commit is contained in:
Gary Rong 2026-05-13 10:39:04 +08:00
parent b2aa6987de
commit 8274c95fdc
17 changed files with 306 additions and 99 deletions

View file

@ -35,6 +35,7 @@ import (
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "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/core/vm"
"github.com/ethereum/go-ethereum/crypto/keccak" "github.com/ethereum/go-ethereum/crypto/keccak"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
@ -172,6 +173,9 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
includedTxs types.Transactions includedTxs types.Transactions
blobGasUsed = uint64(0) blobGasUsed = uint64(0)
receipts = make(types.Receipts, 0) receipts = make(types.Receipts, 0)
// TODO return blockAccessList as a part of result
blockAccessList = bal.NewConstructionBlockAccessList()
) )
vmContext := vm.BlockContext{ vmContext := vm.BlockContext{
CanTransfer: core.CanTransfer, CanTransfer: core.CanTransfer,
@ -231,14 +235,14 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
} }
evm := vm.NewEVM(vmContext, statedb, chainConfig, vmConfig) evm := vm.NewEVM(vmContext, statedb, chainConfig, vmConfig)
if beaconRoot := pre.Env.ParentBeaconBlockRoot; beaconRoot != nil { 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) { if pre.Env.BlockHashes != nil && chainConfig.IsPrague(new(big.Int).SetUint64(pre.Env.Number), pre.Env.Timestamp) {
var ( var (
prevNumber = pre.Env.Number - 1 prevNumber = pre.Env.Number - 1
prevHash = pre.Env.BlockHashes[math.HexOrDecimal64(prevNumber)] prevHash = pre.Env.BlockHashes[math.HexOrDecimal64(prevNumber)]
) )
core.ProcessParentBlockHash(prevHash, evm) core.ProcessParentBlockHash(prevHash, evm, blockAccessList)
} }
for i := 0; txIt.Next(); i++ { for i := 0; txIt.Next(); i++ {
tx, err := txIt.Tx() 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)) statedb.SetTxContext(tx.Hash(), len(receipts), uint32(len(receipts)+1))
var ( var (
snapshot = statedb.Snapshot() snapshot = statedb.Snapshot()
gp = gaspool.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 { if err != nil {
statedb.RevertToSnapshot(snapshot) statedb.RevertToSnapshot(snapshot)
log.Info("rejected tx", "index", i, "hash", tx.Hash(), "from", msg.From, "error", err) 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 blobGasUsed += txBlobGas
receipts = append(receipts, receipt) receipts = append(receipts, receipt)
blockAccessList.Merge(bal)
} }
statedb.IntermediateRoot(chainConfig.IsEIP158(vmContext.BlockNumber)) statedb.IntermediateRoot(chainConfig.IsEIP158(vmContext.BlockNumber))
@ -336,10 +342,12 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
for _, receipt := range receipts { for _, receipt := range receipts {
allLogs = append(allLogs, receipt.Logs...) 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 { if err != nil {
return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("failed to process post-execution: %v", err)) return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("failed to process post-execution: %v", err))
} }
blockAccessList.Merge(bal)
// Commit block // Commit block
root, err := statedb.Commit(vmContext.BlockNumber.Uint64(), chainConfig.IsEIP158(vmContext.BlockNumber), chainConfig.IsCancun(vmContext.BlockNumber, vmContext.Time)) root, err := statedb.Commit(vmContext.BlockNumber.Uint64(), chainConfig.IsEIP158(vmContext.BlockNumber), chainConfig.IsCancun(vmContext.BlockNumber, vmContext.Time))
if err != nil { if err != nil {

View file

@ -27,6 +27,7 @@ import (
"github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/consensus/misc/eip4844"
"github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "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/core/vm"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256" "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. // 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) { if !beacon.IsPoSHeader(header) {
beacon.ethone.Finalize(chain, header, state, body) beacon.ethone.Finalize(chain, header, state, body, blockAccessIndex, bal)
return return
} }
// Withdrawals processing. // Withdrawals processing.
@ -352,7 +353,20 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types.
// Convert amount from gwei to wei. // Convert amount from gwei to wei.
amount := new(uint256.Int).SetUint64(w.Amount) amount := new(uint256.Int).SetUint64(w.Amount)
amount = amount.Mul(amount, uint256.NewInt(params.GWei)) 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. // No block reward which is issued by consensus layer instead.
} }

View file

@ -34,6 +34,7 @@ import (
"github.com/ethereum/go-ethereum/consensus/misc" "github.com/ethereum/go-ethereum/consensus/misc"
"github.com/ethereum/go-ethereum/consensus/misc/eip1559" "github.com/ethereum/go-ethereum/consensus/misc/eip1559"
"github.com/ethereum/go-ethereum/core/types" "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/core/vm"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/keccak" "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 // Finalize implements consensus.Engine. There is no post-transaction
// consensus rules in clique, do nothing here. // 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 // No block rewards in PoA, so the state remains as is
} }

View file

@ -22,6 +22,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "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/core/vm"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
) )
@ -79,12 +80,12 @@ type Engine interface {
// rules of a particular engine. The changes are executed inline. // rules of a particular engine. The changes are executed inline.
Prepare(chain ChainHeaderReader, header *types.Header) error Prepare(chain ChainHeaderReader, header *types.Header) error
// Finalize runs any post-transaction state modifications (e.g. block rewards // Finalize runs any post-transaction consensus-specific state modifications
// or process withdrawals) but does not assemble the block. // (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 // Note: The state database might be updated to reflect any consensus rules
// that happen at finalization (e.g. block rewards). // 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 // Seal generates a new sealing request for the given input block and pushes
// the result into the given channel. // the result into the given channel.

View file

@ -29,6 +29,7 @@ import (
"github.com/ethereum/go-ethereum/consensus/misc/eip1559" "github.com/ethereum/go-ethereum/consensus/misc/eip1559"
"github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "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/core/vm"
"github.com/ethereum/go-ethereum/crypto/keccak" "github.com/ethereum/go-ethereum/crypto/keccak"
"github.com/ethereum/go-ethereum/params" "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. // 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 // Accumulate any block and uncle rewards
accumulateRewards(chain.Config(), state, header, body.Uncles) accumulateRewards(chain.Config(), state, header, body.Uncles)
} }

View file

@ -29,6 +29,7 @@ import (
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "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/core/vm"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
@ -202,7 +203,7 @@ func TestProcessParentBlockHash(t *testing.T) {
} }
vmContext := NewEVMBlockContext(header, nil, new(common.Address)) vmContext := NewEVMBlockContext(header, nil, new(common.Address))
evm := vm.NewEVM(vmContext, statedb, chainConfig, vm.Config{}) 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 // Read block hashes for block 0 .. num-1
for i := 0; i < num; i++ { for i := 0; i < num; i++ {

View file

@ -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. // Ancestor block must be known.
if !v.bc.HasBlockAndState(block.ParentHash(), block.NumberU64()-1) { if !v.bc.HasBlockAndState(block.ParentHash(), block.NumberU64()-1) {
if !v.bc.HasBlock(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 { } else if res.Requests != nil {
return errors.New("block has requests before prague fork") 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 // Validate the state root against the received state root and throw
// an error if they don't match. // an error if they don't match.
if root := statedb.IntermediateRoot(v.config.IsEIP158(header.Number)); header.Root != root { if root := statedb.IntermediateRoot(v.config.IsEIP158(header.Number)); header.Root != root {

View file

@ -29,6 +29,7 @@ import (
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types" "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/core/vm"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
@ -50,6 +51,7 @@ type BlockGen struct {
receipts []*types.Receipt receipts []*types.Receipt
uncles []*types.Header uncles []*types.Header
withdrawals []*types.Withdrawal withdrawals []*types.Withdrawal
bal *bal.ConstructionBlockAccessList
engine consensus.Engine engine consensus.Engine
} }
@ -65,6 +67,7 @@ func (b *BlockGen) SetCoinbase(addr common.Address) {
} }
b.header.Coinbase = addr b.header.Coinbase = addr
b.gasPool = NewGasPool(b.header.GasLimit) b.gasPool = NewGasPool(b.header.GasLimit)
b.bal = bal.NewConstructionBlockAccessList()
} }
// SetExtra sets the extra data field of the generated block. // 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) { func (b *BlockGen) SetParentBeaconRoot(root common.Hash) {
b.header.ParentBeaconRoot = &root b.header.ParentBeaconRoot = &root
blockContext := NewEVMBlockContext(b.header, b.cm, &b.header.Coinbase) 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 // 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) evm = vm.NewEVM(blockContext, b.statedb, b.cm.config, vmConfig)
) )
b.statedb.SetTxContext(tx.Hash(), len(b.txs), uint32(len(b.txs)+1)) 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 { if err != nil {
panic(err) panic(err)
} }
@ -134,6 +137,7 @@ func (b *BlockGen) addTx(bc *BlockChain, vmConfig vm.Config, tx *types.Transacti
if b.header.BlobGasUsed != nil { if b.header.BlobGasUsed != nil {
*b.header.BlobGasUsed += receipt.BlobGasUsed *b.header.BlobGasUsed += receipt.BlobGasUsed
} }
b.bal.Merge(bal)
} }
// AddTx adds a transaction to the generated block. If no coinbase has // 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. // ConsensusLayerRequests returns the EIP-7685 requests which have accumulated so far.
func (b *BlockGen) ConsensusLayerRequests() [][]byte { 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 statedb := b.statedb
if readonly { if readonly {
// The system contracts clear themselves on a system-initiated read. // 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) blockContext := NewEVMBlockContext(b.header, b.cm, &b.header.Coinbase)
evm := vm.NewEVM(blockContext, statedb, b.cm.config, vm.Config{}) 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 { if err != nil {
panic(fmt.Sprintf("failed to run post-execution: %v", err)) 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 // 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 := NewEVMBlockContext(b.header, cm, &b.header.Coinbase)
blockContext.Random = &common.Hash{} // enable post-merge instruction set blockContext.Random = &common.Hash{} // enable post-merge instruction set
evm := vm.NewEVM(blockContext, statedb, cm.config, vm.Config{}) 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 // Execute any user modifications to the block
@ -394,11 +399,12 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
gen(i, b) gen(i, b)
} }
requests := b.collectRequests(false) requests, bal := b.collectRequests(false)
if requests != nil { if requests != nil {
reqHash := types.CalcRequestsHash(requests) reqHash := types.CalcRequestsHash(requests)
b.header.RequestsHash = &reqHash b.header.RequestsHash = &reqHash
} }
b.bal.Merge(bal)
body := types.Body{ body := types.Body{
Transactions: b.txs, Transactions: b.txs,
@ -414,8 +420,11 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
body.Withdrawals = make([]*types.Withdrawal, 0) 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. // 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 // 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)) root, err := statedb.Commit(b.header.Number.Uint64(), config.IsEIP158(b.header.Number), config.IsCancun(b.header.Number, b.header.Time))

View file

@ -27,6 +27,7 @@ import (
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "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/core/vm"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/internal/telemetry" "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) misc.ApplyDAOHardFork(tracingStateDB)
} }
var ( var (
context = NewEVMBlockContext(header, p.chain, nil) context = NewEVMBlockContext(header, p.chain, nil)
signer = types.MakeSigner(config, header.Number, header.Time) signer = types.MakeSigner(config, header.Number, header.Time)
evm = vm.NewEVM(context, tracingStateDB, config, cfg) evm = vm.NewEVM(context, tracingStateDB, config, cfg)
blockAccessList = bal.NewConstructionBlockAccessList()
) )
defer evm.Release() defer evm.Release()
// Run the pre-execution system calls // 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 // Iterate over and process the individual transactions
for i, tx := range block.Transactions() { for i, tx := range block.Transactions() {
msg, err := TransactionToMessage(tx, signer, header.BaseFee) 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.StringAttribute("tx.hash", tx.Hash().Hex()),
telemetry.Int64Attribute("tx.index", int64(i)), telemetry.Int64Attribute("tx.index", int64(i)),
) )
receipt, bal, err := ApplyTransactionWithEVM(msg, gp, statedb, blockNumber, blockHash, context.Time, tx, evm)
receipt, err := ApplyTransactionWithEVM(msg, gp, statedb, blockNumber, blockHash, context.Time, tx, evm)
if err != nil { if err != nil {
spanEnd(&err) spanEnd(&err)
return nil, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) return nil, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
} }
receipts = append(receipts, receipt) receipts = append(receipts, receipt)
allLogs = append(allLogs, receipt.Logs...) allLogs = append(allLogs, receipt.Logs...)
blockAccessList.Merge(bal)
spanEnd(nil) 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 { if err != nil {
return nil, err return nil, err
} }
blockAccessList.Merge(bal)
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards) // 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{ return &ProcessResult{
Receipts: receipts, Receipts: receipts,
Requests: requests, Requests: requests,
Logs: allLogs, Logs: allLogs,
GasUsed: gp.Used(), GasUsed: gp.Used(),
Bal: blockAccessList,
}, nil }, nil
} }
// PreExecution processes pre-execution system calls. // 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") _, _, spanEnd := telemetry.StartSpan(ctx, "core.preExecution")
defer spanEnd(nil) defer spanEnd(nil)
var blockAccessList *bal.ConstructionBlockAccessList
if config.IsAmsterdam(number, time) {
blockAccessList = bal.NewConstructionBlockAccessList()
}
// EIP-4788 // EIP-4788
if beaconRoot != nil { if beaconRoot != nil {
ProcessBeaconBlockRoot(*beaconRoot, evm) ProcessBeaconBlockRoot(*beaconRoot, evm, blockAccessList)
} }
// EIP-2935 // EIP-2935
if config.IsPrague(number, time) || config.IsUBT(number, time) { 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. // PostExecution processes post-execution system calls when Prague is enabled.
// If Prague is not activated, it returns null requests to differentiate from // If Prague is not activated, it returns null requests to differentiate from
// empty requests. // 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") _, _, spanEnd := telemetry.StartSpan(ctx, "core.postExecution")
defer spanEnd(&err) defer spanEnd(&err)
if config.IsAmsterdam(number, time) {
blockAccessList = bal.NewConstructionBlockAccessList()
}
// Read requests if Prague is enabled. // Read requests if Prague is enabled.
if config.IsPrague(number, time) { if config.IsPrague(number, time) {
requests = [][]byte{} requests = [][]byte{}
// EIP-6110 // EIP-6110
if err := ParseDepositLogs(&requests, allLogs, config); err != nil { 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 // EIP-7002
if err := ProcessWithdrawalQueue(&requests, evm, blockAccessIndex); err != nil { if err := ProcessWithdrawalQueue(&requests, evm, blockAccessIndex, blockAccessList); err != nil {
return nil, fmt.Errorf("failed to process withdrawal queue: %w", err) return nil, nil, fmt.Errorf("failed to process withdrawal queue: %w", err)
} }
// EIP-7251 // EIP-7251
if err := ProcessConsolidationQueue(&requests, evm, blockAccessIndex); err != nil { if err := ProcessConsolidationQueue(&requests, evm, blockAccessIndex, blockAccessList); err != nil {
return nil, fmt.Errorf("failed to process consolidation queue: %w", err) 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 // ApplyTransactionWithEVM attempts to apply a transaction to the given state database
// and uses the input parameters for its environment similar to ApplyTransaction. However, // and uses the input parameters for its environment similar to ApplyTransaction. However,
// this method takes an already created EVM instance as input. // 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 := evm.Config.Tracer; hooks != nil {
if hooks.OnTxStart != nil { if hooks.OnTxStart != nil {
hooks.OnTxStart(evm.GetVMContext(), tx, msg.From) 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). // Apply the transaction to the current state (included in the env).
result, err := ApplyMessage(evm, msg, gp) result, err := ApplyMessage(evm, msg, gp)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
// Update the state with pending changes. // Update the state with pending changes.
var root []byte var root []byte
if evm.ChainConfig().IsByzantium(blockNumber) { if evm.ChainConfig().IsByzantium(blockNumber) {
evm.StateDB.Finalise(true) bal = evm.StateDB.Finalise(true)
} else { } else {
root = statedb.IntermediateRoot(evm.ChainConfig().IsEIP158(blockNumber)).Bytes() 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) { if statedb.Database().Type().Is(state.TypeUBT) {
statedb.AccessEvents().Merge(evm.AccessEvents) 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. // 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 // and uses the input parameters for its environment. It returns the receipt
// for the transaction and an error if the transaction failed, // for the transaction and an error if the transaction failed,
// indicating the block was invalid. // 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) msg, err := TransactionToMessage(tx, types.MakeSigner(evm.ChainConfig(), header.Number, header.Time), header.BaseFee)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
// Create a new context to be used in the EVM environment // Create a new context to be used in the EVM environment
return ApplyTransactionWithEVM(msg, gp, statedb, header.Number, header.Hash(), header.Time, tx, evm) 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 // ProcessBeaconBlockRoot applies the EIP-4788 system call to the beacon block root
// contract. This method is exported to be used in tests. // 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 { if tracer := evm.Config.Tracer; tracer != nil {
onSystemCallStart(tracer, evm.GetVMContext()) onSystemCallStart(tracer, evm.GetVMContext())
if tracer.OnSystemCallEnd != nil { if tracer.OnSystemCallEnd != nil {
@ -273,12 +290,12 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, evm *vm.EVM) {
if evm.StateDB.AccessEvents() != nil { if evm.StateDB.AccessEvents() != nil {
evm.StateDB.AccessEvents().Merge(evm.AccessEvents) 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 // ProcessParentBlockHash stores the parent block hash in the history storage contract
// as per EIP-2935/7709. // 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 { if tracer := evm.Config.Tracer; tracer != nil {
onSystemCallStart(tracer, evm.GetVMContext()) onSystemCallStart(tracer, evm.GetVMContext())
if tracer.OnSystemCallEnd != nil { if tracer.OnSystemCallEnd != nil {
@ -304,22 +321,22 @@ func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM) {
if evm.StateDB.AccessEvents() != nil { if evm.StateDB.AccessEvents() != nil {
evm.StateDB.AccessEvents().Merge(evm.AccessEvents) evm.StateDB.AccessEvents().Merge(evm.AccessEvents)
} }
evm.StateDB.Finalise(true) blockAccessList.Merge(evm.StateDB.Finalise(true))
} }
// ProcessWithdrawalQueue calls the EIP-7002 withdrawal queue contract. // ProcessWithdrawalQueue calls the EIP-7002 withdrawal queue contract.
// It returns the opaque request data returned by the contract. // It returns the opaque request data returned by the contract.
func ProcessWithdrawalQueue(requests *[][]byte, evm *vm.EVM, blockAccessIndex uint32) error { func ProcessWithdrawalQueue(requests *[][]byte, evm *vm.EVM, blockAccessIndex uint32, blockAccessList *bal.ConstructionBlockAccessList) error {
return processRequestsSystemCall(requests, evm, 0x01, params.WithdrawalQueueAddress, blockAccessIndex) return processRequestsSystemCall(requests, evm, 0x01, params.WithdrawalQueueAddress, blockAccessIndex, blockAccessList)
} }
// ProcessConsolidationQueue calls the EIP-7251 consolidation queue contract. // ProcessConsolidationQueue calls the EIP-7251 consolidation queue contract.
// It returns the opaque request data returned by the contract. // It returns the opaque request data returned by the contract.
func ProcessConsolidationQueue(requests *[][]byte, evm *vm.EVM, blockAccessIndex uint32) error { func ProcessConsolidationQueue(requests *[][]byte, evm *vm.EVM, blockAccessIndex uint32, blockAccessList *bal.ConstructionBlockAccessList) error {
return processRequestsSystemCall(requests, evm, 0x02, params.ConsolidationQueueAddress, blockAccessIndex) 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 { if tracer := evm.Config.Tracer; tracer != nil {
onSystemCallStart(tracer, evm.GetVMContext()) onSystemCallStart(tracer, evm.GetVMContext())
if tracer.OnSystemCallEnd != nil { if tracer.OnSystemCallEnd != nil {
@ -341,7 +358,7 @@ func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte
if evm.StateDB.AccessEvents() != nil { if evm.StateDB.AccessEvents() != nil {
evm.StateDB.AccessEvents().Merge(evm.AccessEvents) evm.StateDB.AccessEvents().Merge(evm.AccessEvents)
} }
evm.StateDB.Finalise(true) bal := evm.StateDB.Finalise(true)
if err != nil { if err != nil {
return fmt.Errorf("system call failed to execute: %v", err) 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 requestsData[0] = requestType
copy(requestsData[1:], ret) copy(requestsData[1:], ret)
*requests = append(*requests, requestsData) *requests = append(*requests, requestsData)
blockAccessList.Merge(bal)
return nil return nil
} }
@ -387,8 +405,19 @@ func onSystemCallStart(tracer *tracing.Hooks, ctx *tracing.VMContext) {
// AssembleBlock finalizes the state and assembles the block with provided // AssembleBlock finalizes the state and assembles the block with provided
// body and receipts. // 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 { func AssembleBlock(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt, blockAccessList *bal.ConstructionBlockAccessList) *types.Block {
engine.Finalize(chain, header, state, body) var bal *bal.BlockAccessList
if chain.Config().IsAmsterdam(header.Number, header.Time) {
bal = blockAccessList.ToEncodingObj()
}
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) 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
} }

View file

@ -22,6 +22,7 @@ import (
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types" "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/core/vm"
) )
@ -58,4 +59,5 @@ type ProcessResult struct {
Requests [][]byte Requests [][]byte
Logs []*types.Log Logs []*types.Log
GasUsed uint64 GasUsed uint64
Bal *bal.ConstructionBlockAccessList
} }

View file

@ -138,10 +138,62 @@ func (b *ConstructionBlockAccessList) BalanceChange(txIdx uint32, address common
// PrettyPrint returns a human-readable representation of the access list // PrettyPrint returns a human-readable representation of the access list
func (b *ConstructionBlockAccessList) PrettyPrint() string { func (b *ConstructionBlockAccessList) PrettyPrint() string {
enc := b.toEncodingObj() enc := b.ToEncodingObj()
return enc.PrettyPrint() 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. // Copy returns a deep copy of the access list.
func (b *ConstructionBlockAccessList) Copy() *ConstructionBlockAccessList { func (b *ConstructionBlockAccessList) Copy() *ConstructionBlockAccessList {
res := NewConstructionBlockAccessList() res := NewConstructionBlockAccessList()

View file

@ -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 // 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 // according to the spec or any code changes are contained which exceed protocol
// max code size. // 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 { if !slices.IsSortedFunc(*e, func(a, b AccountAccess) int {
return bytes.Compare(a.Address[:], b.Address[:]) return bytes.Compare(a.Address[:], b.Address[:])
}) { }) {
return errors.New("block access list accounts not in lexicographic order") return errors.New("block access list accounts not in lexicographic order")
} }
for _, entry := range *e { for _, entry := range *e {
if err := entry.validate(rules); err != nil { if err := entry.validate(); err != nil {
return err return err
} }
} }
@ -159,7 +159,7 @@ type AccountAccess struct {
// validate converts the account accesses out of encoding format. // validate converts the account accesses out of encoding format.
// If any of the keys in the encoding object are not ordered according to the // If any of the keys in the encoding object are not ordered according to the
// spec, an error is returned. // 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 // Check the storage write slots are sorted in order
if !slices.IsSortedFunc(e.StorageWrites, func(a, b encodingSlotWrites) int { if !slices.IsSortedFunc(e.StorageWrites, func(a, b encodingSlotWrites) int {
return a.Slot.Cmp(b.Slot) 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") return errors.New("code changes not in ascending order by tx index")
} }
for _, change := range e.CodeChanges { for _, change := range e.CodeChanges {
var sizeLimit int if len(change.Code) > params.MaxCodeSizeAmsterdam {
switch {
case rules.IsAmsterdam:
sizeLimit = params.MaxCodeSizeAmsterdam
default:
sizeLimit = params.MaxCodeSize
}
if len(change.Code) > sizeLimit {
return errors.New("code change contained oversized code") return errors.New("code change contained oversized code")
} }
} }
@ -257,7 +250,7 @@ func (e *AccountAccess) Copy() AccountAccess {
// EncodeRLP returns the RLP-encoded access list // EncodeRLP returns the RLP-encoded access list
func (b *ConstructionBlockAccessList) EncodeRLP(wr io.Writer) error { func (b *ConstructionBlockAccessList) EncodeRLP(wr io.Writer) error {
return b.toEncodingObj().EncodeRLP(wr) return b.ToEncodingObj().EncodeRLP(wr)
} }
var _ rlp.Encoder = &ConstructionBlockAccessList{} var _ rlp.Encoder = &ConstructionBlockAccessList{}
@ -340,9 +333,9 @@ func (a *ConstructionAccountAccess) toEncodingObj(addr common.Address) AccountAc
return res 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. // which is used as input for the encoding/decoding.
func (b *ConstructionBlockAccessList) toEncodingObj() *BlockAccessList { func (b *ConstructionBlockAccessList) ToEncodingObj() *BlockAccessList {
var addresses []common.Address var addresses []common.Address
for addr := range b.Accounts { for addr := range b.Accounts {
addresses = append(addresses, addr) addresses = append(addresses, addr)

View file

@ -25,7 +25,6 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/internal/testrand" "github.com/ethereum/go-ethereum/internal/testrand"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/holiman/uint256" "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 { if err := dec.DecodeRLP(rlp.NewStream(bytes.NewReader(buf.Bytes()), 0)); err != nil {
t.Fatalf("decoding failed: %v\n", err) 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") 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") 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 { func makeTestAccountAccess(sort bool) AccountAccess {
var ( var (
storageWrites []encodingSlotWrites storageWrites []encodingSlotWrites
@ -234,7 +284,7 @@ func TestBlockAccessListCopy(t *testing.T) {
func TestBlockAccessListValidation(t *testing.T) { func TestBlockAccessListValidation(t *testing.T) {
// Validate the block access list after RLP decoding // Validate the block access list after RLP decoding
enc := makeTestBAL(true) enc := makeTestBAL(true)
if err := enc.Validate(params.Rules{}); err != nil { if err := enc.Validate(); err != nil {
t.Fatalf("Unexpected validation error: %v", err) t.Fatalf("Unexpected validation error: %v", err)
} }
var buf bytes.Buffer 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 { if err := dec.DecodeRLP(rlp.NewStream(bytes.NewReader(buf.Bytes()), 0)); err != nil {
t.Fatalf("Unexpected RLP-decode error: %v", err) 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) t.Fatalf("Unexpected validation error: %v", err)
} }
// Validate the derived block access list // Validate the derived block access list
cBAL := makeTestConstructionBAL() cBAL := makeTestConstructionBAL()
listB := cBAL.toEncodingObj() listB := cBAL.ToEncodingObj()
if err := listB.Validate(params.Rules{}); err != nil { if err := listB.Validate(); err != nil {
t.Fatalf("Unexpected validation error: %v", err) t.Fatalf("Unexpected validation error: %v", err)
} }
} }

View file

@ -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 // Call Prepare to clear out the statedb access list
statedb.SetTxContext(txctx.TxHash, txctx.TxIndex, uint32(txctx.TxIndex+1)) 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 { if err != nil {
return nil, fmt.Errorf("tracing failed: %w", err) return nil, fmt.Errorf("tracing failed: %w", err)
} }

View file

@ -620,7 +620,7 @@ func TestSelfdestructStateTracer(t *testing.T) {
} }
context := core.NewEVMBlockContext(block.Header(), blockchain, nil) context := core.NewEVMBlockContext(block.Header(), blockchain, nil)
evm := vm.NewEVM(context, hookedState, tt.genesis.Config, vm.Config{Tracer: tracer.Hooks()}) 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 { if err != nil {
t.Fatalf("failed to execute transaction: %v", err) t.Fatalf("failed to execute transaction: %v", err)
} }

View file

@ -33,6 +33,7 @@ import (
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types" "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/core/vm"
"github.com/ethereum/go-ethereum/internal/ethapi/override" "github.com/ethereum/go-ethereum/internal/ethapi/override"
"github.com/ethereum/go-ethereum/params" "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) gp = core.NewGasPool(blockContext.GasLimit)
blobGasUsed uint64 blobGasUsed uint64
txes = make([]*types.Transaction, len(block.Calls)) txes = make([]*types.Transaction, len(block.Calls))
callResults = make([]simCallResult, len(block.Calls)) callResults = make([]simCallResult, len(block.Calls))
receipts = make([]*types.Receipt, len(block.Calls)) receipts = make([]*types.Receipt, len(block.Calls))
blockAccessList = bal.NewConstructionBlockAccessList()
// Block hash will be repaired after execution. // Block hash will be repaired after execution.
tracer = newTracer(sim.traceTransfers, blockContext.BlockNumber.Uint64(), blockContext.Time, common.Hash{}, common.Hash{}, 0) 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) evm := vm.NewEVM(blockContext, tracingStateDB, sim.chainConfig, *vmConfig)
defer evm.Release() defer evm.Release()
// It is possible to override precompiles with EVM bytecode, or // It is possible to override precompiles with EVM bytecode, or
// move them to another address. // move them to another address.
if precompiles != nil { if precompiles != nil {
evm.SetPrecompiles(precompiles) evm.SetPrecompiles(precompiles)
} }
// Run pre-execution system calls // 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 var allLogs []*types.Log
for i, call := range block.Calls { 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. // Update the state with pending changes.
var root []byte var root []byte
if sim.chainConfig.IsByzantium(blockContext.BlockNumber) { if sim.chainConfig.IsByzantium(blockContext.BlockNumber) {
tracingStateDB.Finalise(true) blockAccessList.Merge(tracingStateDB.Finalise(true))
} else { } else {
root = sim.state.IntermediateRoot(sim.chainConfig.IsEIP158(blockContext.BlockNumber)).Bytes() 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 // 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 { if err != nil {
return nil, nil, nil, err return nil, nil, nil, err
} }
@ -399,6 +402,7 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
reqHash := types.CalcRequestsHash(requests) reqHash := types.CalcRequestsHash(requests)
header.RequestsHash = &reqHash header.RequestsHash = &reqHash
} }
blockAccessList.Merge(bal)
blockBody := &types.Body{ blockBody := &types.Body{
Transactions: txes, Transactions: txes,
@ -411,8 +415,11 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
} }
chainHeadReader := &simChainHeadReader{ctx, sim.b} 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 // 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()) repairLogs(callResults, b.Hash())
return b, callResults, senders, nil return b, callResults, senders, nil

View file

@ -32,6 +32,7 @@ import (
"github.com/ethereum/go-ethereum/core/stateless" "github.com/ethereum/go-ethereum/core/stateless"
"github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/txpool"
"github.com/ethereum/go-ethereum/core/types" "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/core/vm"
"github.com/ethereum/go-ethereum/internal/telemetry" "github.com/ethereum/go-ethereum/internal/telemetry"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
@ -71,6 +72,7 @@ type environment struct {
receipts []*types.Receipt receipts []*types.Receipt
sidecars []*types.BlobTxSidecar sidecars []*types.BlobTxSidecar
blobs int blobs int
bal *bal.ConstructionBlockAccessList
witness *stateless.Witness witness *stateless.Witness
} }
@ -208,7 +210,7 @@ func (miner *Miner) generateWork(ctx context.Context, genParam *generateParams,
} }
// Collect consensus-layer requests if Prague is enabled. // 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 { if err != nil {
return &newPayloadResult{err: err} return &newPayloadResult{err: err}
} }
@ -216,9 +218,14 @@ func (miner *Miner) generateWork(ctx context.Context, genParam *generateParams,
reqHash := types.CalcRequestsHash(requests) reqHash := types.CalcRequestsHash(requests)
work.header.RequestsHash = &reqHash 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. // Assemble the block for delivery.
_, _, assembleSpanEnd := telemetry.StartSpan(ctx, "miner.AssembleBlock") _, _, 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) assembleSpanEnd(nil)
return &newPayloadResult{ return &newPayloadResult{
@ -318,7 +325,7 @@ func (miner *Miner) prepareWork(ctx context.Context, genParams *generateParams,
return nil, err return nil, err
} }
// Run pre-execution system calls // 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 return env, nil
} }
@ -337,6 +344,7 @@ func (miner *Miner) makeEnv(parent *types.Header, header *types.Header, coinbase
} }
} }
state.StartPrefetcher("miner", bundle) state.StartPrefetcher("miner", bundle)
// Note the passed coinbase may be different with header.Coinbase. // Note the passed coinbase may be different with header.Coinbase.
return &environment{ return &environment{
signer: types.MakeSigner(miner.chainConfig, header.Number, header.Time), 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, coinbase: coinbase,
gasPool: core.NewGasPool(header.GasLimit), gasPool: core.NewGasPool(header.GasLimit),
header: header, header: header,
bal: bal.NewConstructionBlockAccessList(),
witness: state.Witness(), witness: state.Witness(),
evm: vm.NewEVM(core.NewEVMBlockContext(header, miner.chain, &coinbase), state, miner.chainConfig, vm.Config{}), evm: vm.NewEVM(core.NewEVMBlockContext(header, miner.chain, &coinbase), state, miner.chainConfig, vm.Config{}),
}, nil }, nil
@ -356,7 +365,7 @@ func (miner *Miner) commitTransaction(ctx context.Context, env *environment, tx
if tx.Type() == types.BlobTxType { if tx.Type() == types.BlobTxType {
return miner.commitBlobTransaction(env, tx) return miner.commitBlobTransaction(env, tx)
} }
receipt, err := miner.applyTransaction(env, tx) receipt, bal, err := miner.applyTransaction(env, tx)
if err != nil { if err != nil {
return err return err
} }
@ -364,6 +373,7 @@ func (miner *Miner) commitTransaction(ctx context.Context, env *environment, tx
env.receipts = append(env.receipts, receipt) env.receipts = append(env.receipts, receipt)
env.size += tx.Size() env.size += tx.Size()
env.tcount++ env.tcount++
env.bal.Merge(bal)
return nil return nil
} }
@ -380,7 +390,7 @@ func (miner *Miner) commitBlobTransaction(env *environment, tx *types.Transactio
if env.blobs+len(sc.Blobs) > maxBlobs { if env.blobs+len(sc.Blobs) > maxBlobs {
return errors.New("max data blobs reached") return errors.New("max data blobs reached")
} }
receipt, err := miner.applyTransaction(env, tx) receipt, bal, err := miner.applyTransaction(env, tx)
if err != nil { if err != nil {
return err return err
} }
@ -392,23 +402,24 @@ func (miner *Miner) commitBlobTransaction(env *environment, tx *types.Transactio
env.size += txNoBlob.Size() env.size += txNoBlob.Size()
*env.header.BlobGasUsed += receipt.BlobGasUsed *env.header.BlobGasUsed += receipt.BlobGasUsed
env.tcount++ env.tcount++
env.bal.Merge(bal)
return nil return nil
} }
// applyTransaction runs the transaction. If execution fails, state and gas pool are reverted. // 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 ( var (
snap = env.state.Snapshot() snap = env.state.Snapshot()
gp = env.gasPool.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 { if err != nil {
env.state.RevertToSnapshot(snap) env.state.RevertToSnapshot(snap)
env.gasPool.Set(gp) env.gasPool.Set(gp)
return nil, err return nil, nil, err
} }
env.header.GasUsed = env.gasPool.Used() 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 { func (miner *Miner) commitTransactions(ctx context.Context, env *environment, plainTxs, blobTxs *transactionsByPriceAndNonce, interrupt *atomic.Int32) error {