all: implement BAL single-threaded execution and validation

This commit is contained in:
Jared Wasinger 2026-04-13 17:52:55 -04:00 committed by MariusVanDerWijden
parent 3c21252fec
commit e66e51e04f
38 changed files with 1634 additions and 681 deletions

View file

@ -21,6 +21,8 @@ import (
"math/big" "math/big"
"slices" "slices"
"github.com/ethereum/go-ethereum/core/types/bal"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
@ -82,24 +84,25 @@ type payloadAttributesMarshaling struct {
// ExecutableData is the data necessary to execute an EL payload. // ExecutableData is the data necessary to execute an EL payload.
type ExecutableData struct { type ExecutableData struct {
ParentHash common.Hash `json:"parentHash" gencodec:"required"` ParentHash common.Hash `json:"parentHash" gencodec:"required"`
FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"` FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"`
StateRoot common.Hash `json:"stateRoot" gencodec:"required"` StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"` ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
LogsBloom []byte `json:"logsBloom" gencodec:"required"` LogsBloom []byte `json:"logsBloom" gencodec:"required"`
Random common.Hash `json:"prevRandao" gencodec:"required"` Random common.Hash `json:"prevRandao" gencodec:"required"`
Number uint64 `json:"blockNumber" gencodec:"required"` Number uint64 `json:"blockNumber" gencodec:"required"`
GasLimit uint64 `json:"gasLimit" gencodec:"required"` GasLimit uint64 `json:"gasLimit" gencodec:"required"`
GasUsed uint64 `json:"gasUsed" gencodec:"required"` GasUsed uint64 `json:"gasUsed" gencodec:"required"`
Timestamp uint64 `json:"timestamp" gencodec:"required"` Timestamp uint64 `json:"timestamp" gencodec:"required"`
ExtraData []byte `json:"extraData" gencodec:"required"` ExtraData []byte `json:"extraData" gencodec:"required"`
BaseFeePerGas *big.Int `json:"baseFeePerGas" gencodec:"required"` BaseFeePerGas *big.Int `json:"baseFeePerGas" gencodec:"required"`
BlockHash common.Hash `json:"blockHash" gencodec:"required"` BlockHash common.Hash `json:"blockHash" gencodec:"required"`
Transactions [][]byte `json:"transactions" gencodec:"required"` Transactions [][]byte `json:"transactions" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"` Withdrawals []*types.Withdrawal `json:"withdrawals"`
BlobGasUsed *uint64 `json:"blobGasUsed"` BlobGasUsed *uint64 `json:"blobGasUsed"`
ExcessBlobGas *uint64 `json:"excessBlobGas"` ExcessBlobGas *uint64 `json:"excessBlobGas"`
SlotNumber *uint64 `json:"slotNumber,omitempty"` BlockAccessList *bal.BlockAccessList `json:"blockAccessList"`
SlotNumber *uint64 `json:"slotNumber,omitempty"`
} }
// JSON type overrides for executableData. // JSON type overrides for executableData.
@ -303,6 +306,8 @@ func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.H
requestsHash = &h requestsHash = &h
} }
body := types.Body{Transactions: txs, Uncles: nil, Withdrawals: data.Withdrawals}
header := &types.Header{ header := &types.Header{
ParentHash: data.ParentHash, ParentHash: data.ParentHash,
UncleHash: types.EmptyUncleHash, UncleHash: types.EmptyUncleHash,
@ -326,33 +331,40 @@ func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.H
RequestsHash: requestsHash, RequestsHash: requestsHash,
SlotNumber: data.SlotNumber, SlotNumber: data.SlotNumber,
} }
return types.NewBlockWithHeader(header).
WithBody(types.Body{Transactions: txs, Uncles: nil, Withdrawals: data.Withdrawals}), if data.BlockAccessList != nil {
nil balHash := data.BlockAccessList.Hash()
header.BlockAccessListHash = &balHash
block := types.NewBlockWithHeader(header).WithBody(body).WithAccessList(data.BlockAccessList)
return block, nil
}
return types.NewBlockWithHeader(header).WithBody(body), nil
} }
// BlockToExecutableData constructs the ExecutableData structure by filling the // BlockToExecutableData constructs the ExecutableData structure by filling the
// fields from the given block. It assumes the given block is post-merge block. // fields from the given block. It assumes the given block is post-merge block.
func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types.BlobTxSidecar, requests [][]byte) *ExecutionPayloadEnvelope { func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types.BlobTxSidecar, requests [][]byte) *ExecutionPayloadEnvelope {
data := &ExecutableData{ data := &ExecutableData{
BlockHash: block.Hash(), BlockHash: block.Hash(),
ParentHash: block.ParentHash(), ParentHash: block.ParentHash(),
FeeRecipient: block.Coinbase(), FeeRecipient: block.Coinbase(),
StateRoot: block.Root(), StateRoot: block.Root(),
Number: block.NumberU64(), Number: block.NumberU64(),
GasLimit: block.GasLimit(), GasLimit: block.GasLimit(),
GasUsed: block.GasUsed(), GasUsed: block.GasUsed(),
BaseFeePerGas: block.BaseFee(), BaseFeePerGas: block.BaseFee(),
Timestamp: block.Time(), Timestamp: block.Time(),
ReceiptsRoot: block.ReceiptHash(), ReceiptsRoot: block.ReceiptHash(),
LogsBloom: block.Bloom().Bytes(), LogsBloom: block.Bloom().Bytes(),
Transactions: encodeTransactions(block.Transactions()), Transactions: encodeTransactions(block.Transactions()),
Random: block.MixDigest(), Random: block.MixDigest(),
ExtraData: block.Extra(), ExtraData: block.Extra(),
Withdrawals: block.Withdrawals(), Withdrawals: block.Withdrawals(),
BlobGasUsed: block.BlobGasUsed(), BlobGasUsed: block.BlobGasUsed(),
ExcessBlobGas: block.ExcessBlobGas(), ExcessBlobGas: block.ExcessBlobGas(),
SlotNumber: block.SlotNumber(), BlockAccessList: block.AccessList(),
SlotNumber: block.SlotNumber(),
} }
// Add blobs. // Add blobs.
@ -391,8 +403,9 @@ func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types.
// ExecutionPayloadBody is used in the response to GetPayloadBodiesByHash and GetPayloadBodiesByRange // ExecutionPayloadBody is used in the response to GetPayloadBodiesByHash and GetPayloadBodiesByRange
type ExecutionPayloadBody struct { type ExecutionPayloadBody struct {
TransactionData []hexutil.Bytes `json:"transactions"` TransactionData []hexutil.Bytes `json:"transactions"`
Withdrawals []*types.Withdrawal `json:"withdrawals"` Withdrawals []*types.Withdrawal `json:"withdrawals"`
AccessList *bal.BlockAccessList `json:"blockAccessList"`
} }
// Client identifiers to support ClientVersionV1. // Client identifiers to support ClientVersionV1.

View file

@ -5,6 +5,11 @@
# https://github.com/ethereum/execution-spec-tests/releases/download/v5.1.0 # https://github.com/ethereum/execution-spec-tests/releases/download/v5.1.0
a3192784375acec7eaec492799d5c5d0c47a2909a3cc40178898e4ecd20cc416 fixtures_develop.tar.gz a3192784375acec7eaec492799d5c5d0c47a2909a3cc40178898e4ecd20cc416 fixtures_develop.tar.gz
# version:spec-tests-bal v5.6.1
# https://github.com/ethereum/execution-spec-tests/releases
# https://github.com/ethereum/execution-spec-tests/releases/download/bal%40v5.6.1
741530c88f6a48c15184d1504316c02c3a76c2322c410a04b643a85185dc62e9 fixtures_bal.tar.gz
# version:golang 1.25.9 # version:golang 1.25.9
# https://go.dev/dl/ # https://go.dev/dl/
0ec9ef8ebcea097aac37decae9f09a7218b451cd96be7d6ed513d8e4bcf909cf go1.25.9.src.tar.gz 0ec9ef8ebcea097aac37decae9f09a7218b451cd96be7d6ed513d8e4bcf909cf go1.25.9.src.tar.gz

View file

@ -176,6 +176,9 @@ var (
// This is where the tests should be unpacked. // This is where the tests should be unpacked.
executionSpecTestsDir = "tests/spec-tests" executionSpecTestsDir = "tests/spec-tests"
// This is where the bal-specific release of the tests should be unpacked.
executionSpecTestsBALDir = "tests/spec-tests-bal"
) )
var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin")) var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin"))
@ -384,6 +387,7 @@ func doTest(cmdline []string) {
// Get test fixtures. // Get test fixtures.
if !*short { if !*short {
downloadSpecTestFixtures(csdb, *cachedir) downloadSpecTestFixtures(csdb, *cachedir)
downloadBALSpecTestFixtures(csdb, *cachedir)
} }
// Configure the toolchain. // Configure the toolchain.
@ -449,6 +453,19 @@ func downloadSpecTestFixtures(csdb *download.ChecksumDB, cachedir string) string
return filepath.Join(cachedir, base) return filepath.Join(cachedir, base)
} }
func downloadBALSpecTestFixtures(csdb *download.ChecksumDB, cachedir string) string {
ext := ".tar.gz"
base := "fixtures_bal"
archivePath := filepath.Join(cachedir, base+ext)
if err := csdb.DownloadFileFromKnownURL(archivePath); err != nil {
log.Fatal(err)
}
if err := build.ExtractArchive(archivePath, executionSpecTestsBALDir); err != nil {
log.Fatal(err)
}
return filepath.Join(cachedir, base)
}
// doCheckGenerate ensures that re-generating generated files does not cause // doCheckGenerate ensures that re-generating generated files does not cause
// any mutations in the source file tree. // any mutations in the source file tree.
func doCheckGenerate() { func doCheckGenerate() {

View file

@ -258,7 +258,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
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, 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)
@ -327,11 +327,11 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not parse requests logs: %v", err)) return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not parse requests logs: %v", err))
} }
// EIP-7002 // EIP-7002
if err := core.ProcessWithdrawalQueue(&requests, evm); err != nil { if _, _, err := core.ProcessWithdrawalQueue(&requests, evm); err != nil {
return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not process withdrawal requests: %v", err)) return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not process withdrawal requests: %v", err))
} }
// EIP-7251 // EIP-7251
if err := core.ProcessConsolidationQueue(&requests, evm); err != nil { if _, _, err := core.ProcessConsolidationQueue(&requests, evm); err != nil {
return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not process consolidation requests: %v", err)) return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not process consolidation requests: %v", err))
} }
} }

View file

@ -21,6 +21,8 @@ import (
"fmt" "fmt"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/core/types/bal"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/misc/eip1559" "github.com/ethereum/go-ethereum/consensus/misc/eip1559"
@ -342,18 +344,21 @@ 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) (*bal.StateAccessList, *bal.StateMutations) {
if !beacon.IsPoSHeader(header) { if !beacon.IsPoSHeader(header) {
beacon.ethone.Finalize(chain, header, state, body) return beacon.ethone.Finalize(chain, header, state, body)
return
} }
// Withdrawals processing. // Withdrawals processing.
for _, w := range body.Withdrawals { for _, w := range body.Withdrawals {
// ensure that target account is included as a read in the BAL even if the withdrawal amount is zero
state.GetBalance(w.Address)
// 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) state.AddBalance(w.Address, amount, tracing.BalanceIncreaseWithdrawal)
} }
return state.Finalise(true)
// No block reward which is issued by consensus layer instead. // No block reward which is issued by consensus layer instead.
} }

View file

@ -27,6 +27,8 @@ import (
"sync" "sync"
"time" "time"
"github.com/ethereum/go-ethereum/core/types/bal"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/lru" "github.com/ethereum/go-ethereum/common/lru"
@ -573,8 +575,9 @@ 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) (*bal.StateAccessList, *bal.StateMutations) {
// No block rewards in PoA, so the state remains as is // No block rewards in PoA, so the state remains as is
return nil, nil
} }
// Authorize injects a private key into the consensus engine to mint new blocks // Authorize injects a private key into the consensus engine to mint new blocks

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"
) )
@ -84,7 +85,7 @@ type Engine interface {
// //
// 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) (*bal.StateAccessList, *bal.StateMutations)
// 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

@ -22,6 +22,8 @@ import (
"math/big" "math/big"
"time" "time"
"github.com/ethereum/go-ethereum/core/types/bal"
mapset "github.com/deckarep/golang-set/v2" mapset "github.com/deckarep/golang-set/v2"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus"
@ -504,9 +506,10 @@ 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) (*bal.StateAccessList, *bal.StateMutations) {
// 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)
return nil, nil
} }
// SealHash returns the hash of a block prior to it being sealed. // SealHash returns the hash of a block prior to it being sealed.

View file

@ -111,6 +111,25 @@ 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 {
// TODO: verify that this check isn't also done elsewhere
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 {
if *block.Header().BlockAccessListHash != block.AccessList().Hash() {
return fmt.Errorf("access list hash mismatch. local: %x. remote: %x\n", block.AccessList().Hash(), *block.Header().BlockAccessListHash)
} else if err := block.AccessList().Validate(len(block.Transactions()), block.GasLimit()); err != nil {
return fmt.Errorf("invalid block access list: %v", err)
}
}
} else if 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) {

View file

@ -2112,11 +2112,12 @@ type ExecuteConfig struct {
// it writes the block and associated state to database. // it writes the block and associated state to database.
func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash, block *types.Block, config ExecuteConfig) (result *blockProcessingResult, blockEndErr error) { func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash, block *types.Block, config ExecuteConfig) (result *blockProcessingResult, blockEndErr error) {
var ( var (
err error err error
startTime = time.Now() startTime = time.Now()
statedb *state.StateDB statedb *state.StateDB
interrupt atomic.Bool interrupt atomic.Bool
sdb state.Database sdb state.Database
isAmsterdam = bc.chainConfig.IsAmsterdam(block.Number(), block.Time())
) )
defer interrupt.Store(true) // terminate the prefetch at the end defer interrupt.Store(true) // terminate the prefetch at the end
@ -2240,6 +2241,37 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
} }
vtime := time.Since(vstart) vtime := time.Since(vstart)
if isAmsterdam {
computedAccessList := res.AccessList.ToEncodingObj()
computedAccessListHash := computedAccessList.Hash()
if *block.Header().BlockAccessListHash != computedAccessListHash {
err := fmt.Errorf("block header access list hash mismatch (remote =%x local=%x)", *block.Header().BlockAccessListHash, computedAccessListHash)
bc.reportBadBlock(block, res, err)
return nil, err
}
// note that we don't validate that the computed BAL's size aligns with the gas
// limit here because it should be impossible case if the parameters in 7928
// are tuned correctly.
if block.AccessList() == nil {
// attach the computed access list to the block so it gets persisted
// when the block is written to disk
block = block.WithAccessList(computedAccessList)
}
// Failing the access list max size validation should be impossible here
// better safe than sorry.
if err := computedAccessList.ValidateGasLimit(block.GasLimit()); err != nil {
err := fmt.Errorf("block access list validation failed: %v", err)
bc.reportBadBlock(block, res, err)
return nil, err
} else if block.AccessList().Hash() != computedAccessListHash {
err := fmt.Errorf("block access list hash mismatch (remote=%x computed=%x)", block.AccessList().Hash(), computedAccessListHash)
bc.reportBadBlock(block, res, err)
return nil, err
}
}
// If witnesses was generated and stateless self-validation requested, do // If witnesses was generated and stateless self-validation requested, do
// that now. Self validation should *never* run in production, it's more of // that now. Self validation should *never* run in production, it's more of
// a tight integration to enable running *all* consensus tests through the // a tight integration to enable running *all* consensus tests through the

View file

@ -20,6 +20,8 @@ import (
"fmt" "fmt"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/core/types/bal"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/misc" "github.com/ethereum/go-ethereum/consensus/misc"
@ -51,6 +53,8 @@ type BlockGen struct {
withdrawals []*types.Withdrawal withdrawals []*types.Withdrawal
engine consensus.Engine engine consensus.Engine
accessList *bal.ConstructionBlockAccessList
} }
// SetCoinbase sets the coinbase of the generated block. // SetCoinbase sets the coinbase of the generated block.
@ -117,11 +121,15 @@ 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)) b.statedb.SetTxContext(tx.Hash(), len(b.txs))
receipt, err := ApplyTransaction(evm, b.gasPool, b.statedb, b.header, tx) txAccesses, txMut, receipt, err := ApplyTransaction(evm, b.gasPool, b.statedb, b.header, tx)
if err != nil { if err != nil {
panic(err) panic(err)
} }
b.header.GasUsed = b.gasPool.Used() b.header.GasUsed = b.gasPool.Used()
if b.accessList != nil {
b.accessList.AddTransactionMutations(txMut, len(b.txs))
b.accessList.AddAccesses(txAccesses)
}
// Merge the tx-local access event into the "block-local" one, in order to collect // Merge the tx-local access event into the "block-local" one, in order to collect
// all values, so that the witness can be built. // all values, so that the witness can be built.
@ -325,17 +333,29 @@ func (b *BlockGen) collectRequests(readonly bool) (requests [][]byte) {
if err := ParseDepositLogs(&requests, blockLogs, b.cm.config); err != nil { if err := ParseDepositLogs(&requests, blockLogs, b.cm.config); err != nil {
panic(fmt.Sprintf("failed to parse deposit log: %v", err)) panic(fmt.Sprintf("failed to parse deposit log: %v", err))
} }
// TODO: these accesses should be accumulated in the BAL
// create EVM for system calls // create EVM for system calls
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{})
mut := new(bal.StateMutations)
// EIP-7002 // EIP-7002
if err := ProcessWithdrawalQueue(&requests, evm); err != nil { withdrawalAccess, withdrawalMut, err := ProcessWithdrawalQueue(&requests, evm)
if err != nil {
panic(fmt.Sprintf("could not process withdrawal requests: %v", err)) panic(fmt.Sprintf("could not process withdrawal requests: %v", err))
} }
mut.Merge(withdrawalMut)
// EIP-7251 // EIP-7251
if err := ProcessConsolidationQueue(&requests, evm); err != nil { consolidationAccess, consolidationMut, err := ProcessConsolidationQueue(&requests, evm)
if err != nil {
panic(fmt.Sprintf("could not process consolidation requests: %v", err)) panic(fmt.Sprintf("could not process consolidation requests: %v", err))
} }
mut.Merge(consolidationMut)
if b.cm.config.IsAmsterdam(b.header.Number, b.header.Time) {
b.accessList.AddAccesses(withdrawalAccess)
b.accessList.AddAccesses(consolidationAccess)
b.accessList.AddBlockFinalizeMutations(mut)
}
} }
return requests return requests
} }
@ -364,7 +384,10 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
genblock := func(i int, parent *types.Block, triedb *triedb.Database, statedb *state.StateDB) (*types.Block, types.Receipts) { genblock := func(i int, parent *types.Block, triedb *triedb.Database, statedb *state.StateDB) (*types.Block, types.Receipts) {
b := &BlockGen{i: i, cm: cm, parent: parent, statedb: statedb, engine: engine} b := &BlockGen{i: i, cm: cm, parent: parent, statedb: statedb, engine: engine}
b.header = cm.makeHeader(parent, statedb, b.engine) b.header = cm.makeHeader(parent, statedb, b.engine)
isAmsterdam := config.IsAmsterdam(b.header.Number, b.header.Time)
if isAmsterdam {
b.accessList = bal.NewConstructionBlockAccessList()
}
// Set the difficulty for clique block. The chain maker doesn't have access // Set the difficulty for clique block. The chain maker doesn't have access
// to a chain, so the difficulty will be left unset (nil). Set it here to the // to a chain, so the difficulty will be left unset (nil). Set it here to the
// correct value. // correct value.
@ -391,14 +414,32 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
misc.ApplyDAOHardFork(statedb) misc.ApplyDAOHardFork(statedb)
} }
preTxMutations := bal.NewStateMutations()
if config.IsPrague(b.header.Number, b.header.Time) || config.IsUBT(b.header.Number, b.header.Time) { if config.IsPrague(b.header.Number, b.header.Time) || config.IsUBT(b.header.Number, b.header.Time) {
// EIP-2935 // EIP-2935
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) accesses, mutations := ProcessParentBlockHash(b.header.ParentHash, evm)
if isAmsterdam {
preTxMutations.Merge(mutations)
b.accessList.AddAccesses(accesses)
}
beaconRoot := common.Hash{}
if b.header.ParentBeaconRoot != nil {
beaconRoot = *b.header.ParentBeaconRoot
}
reads, writes := ProcessBeaconBlockRoot(beaconRoot, evm)
if isAmsterdam {
preTxMutations.Merge(writes)
b.accessList.AddAccesses(reads)
b.accessList.AddBlockInitMutations(preTxMutations)
}
} }
// TODO: what about the parent beacon root, and post-block system contract (forget what it is rn)?
// Execute any user modifications to the block // Execute any user modifications to the block
if gen != nil { if gen != nil {
gen(i, b) gen(i, b)

View file

@ -181,6 +181,175 @@ func TestGeneratePOSChain(t *testing.T) {
} }
} }
func TestGenerateBALChain(t *testing.T) {
var (
keyHex = "9c647b8b7c4e7c3490668fb6c11473619db80c93704c70893d3813af4090c39c"
key, _ = crypto.HexToECDSA(keyHex)
address = crypto.PubkeyToAddress(key.PublicKey) // 658bdf435d810c91414ec09147daa6db62406379
aa = common.Address{0xaa}
bb = common.Address{0xbb}
funds = big.NewInt(0).Mul(big.NewInt(1337), big.NewInt(params.Ether))
config = *params.MergedTestChainConfig
gspec = &Genesis{
Config: &config,
Alloc: types.GenesisAlloc{
address: {Balance: funds},
params.BeaconRootsAddress: {Code: params.BeaconRootsCode},
params.WithdrawalQueueAddress: {Code: params.WithdrawalQueueCode},
params.ConsolidationQueueAddress: {Code: params.ConsolidationQueueCode},
params.HistoryStorageAddress: {Code: params.HistoryStorageCode},
},
BaseFee: big.NewInt(params.InitialBaseFee),
Difficulty: common.Big0,
GasLimit: 5_000_000,
// TODO: why do I have to set the bal hash here, and why does it trigger an issue with the genesis
// being reported as not present if I omit the following line?
BlockAccessListHash: &common.Hash{}, // TODO: probably bad to initialize this to something other than keccak(nil) but idk
}
gendb = rawdb.NewMemoryDatabase()
db = rawdb.NewMemoryDatabase()
)
// init 0xaa with some storage elements
storage := make(map[common.Hash]common.Hash)
storage[common.Hash{0x00}] = common.Hash{0x00}
storage[common.Hash{0x01}] = common.Hash{0x01}
storage[common.Hash{0x02}] = common.Hash{0x02}
storage[common.Hash{0x03}] = common.HexToHash("0303")
gspec.Alloc[aa] = types.Account{
Balance: common.Big1,
Nonce: 1,
Storage: storage,
Code: common.Hex2Bytes("6042"),
}
gspec.Alloc[bb] = types.Account{
Balance: common.Big2,
Nonce: 1,
Storage: storage,
Code: common.Hex2Bytes("600154600354"),
}
genesis := gspec.MustCommit(gendb, triedb.NewDatabase(gendb, triedb.HashDefaults))
engine := beacon.New(ethash.NewFaker())
genchain, genreceipts := GenerateChain(gspec.Config, genesis, engine, gendb, 4, func(i int, gen *BlockGen) {
// TODO: I think we can remove SetBeaconRoot entirely
// and provide a different mechanism to set it?
// gen.SetParentBeaconRoot(common.Hash{byte(i + 1)})
if gspec.Config.IsAmsterdam(gen.header.Number, gen.header.Time) {
// TODO: parameterize the slot num
gen.header.SlotNumber = new(uint64)
*gen.header.SlotNumber = gen.header.Number.Uint64()
}
// Add value transfer tx.
tx := types.MustSignNewTx(key, gen.Signer(), &types.LegacyTx{
Nonce: gen.TxNonce(address),
To: &address,
Value: big.NewInt(1000),
Gas: params.TxGas,
GasPrice: new(big.Int).Add(gen.BaseFee(), common.Big1),
})
gen.AddTx(tx)
// Add withdrawals.
if i == 1 {
gen.AddWithdrawal(&types.Withdrawal{
Validator: 42,
Address: common.Address{0xee},
Amount: 1337,
})
gen.AddWithdrawal(&types.Withdrawal{
Validator: 13,
Address: common.Address{0xee},
Amount: 1,
})
}
if i == 3 {
gen.AddWithdrawal(&types.Withdrawal{
Validator: 42,
Address: common.Address{0xee},
Amount: 1337,
})
gen.AddWithdrawal(&types.Withdrawal{
Validator: 13,
Address: common.Address{0xee},
Amount: 1,
})
}
})
// Import the chain. This runs all block validation rules.
blockchain, err := NewBlockChain(db, gspec, engine, nil)
if err != nil {
fmt.Printf("err is %v\n", err)
}
defer blockchain.Stop()
if i, err := blockchain.InsertChain(genchain); err != nil {
t.Fatalf("insert error (block %d): %v\n", genchain[i].NumberU64(), err)
}
// enforce that withdrawal indexes are monotonically increasing from 0
var (
withdrawalIndex uint64
)
for i := range genchain {
blocknum := genchain[i].NumberU64()
block := blockchain.GetBlockByNumber(blocknum)
if block == nil {
t.Fatalf("block %d not found", blocknum)
}
// Verify receipts.
genBlockReceipts := genreceipts[i]
for _, r := range genBlockReceipts {
if r.BlockNumber.Cmp(block.Number()) != 0 {
t.Errorf("receipt has wrong block number %d, want %d", r.BlockNumber, block.Number())
}
if r.BlockHash != block.Hash() {
t.Errorf("receipt has wrong block hash %v, want %v", r.BlockHash, block.Hash())
}
// patch up empty logs list to make DeepEqual below work
if r.Logs == nil {
r.Logs = []*types.Log{}
}
}
blockchainReceipts := blockchain.GetReceiptsByHash(block.Hash())
if !reflect.DeepEqual(genBlockReceipts, blockchainReceipts) {
t.Fatalf("receipts mismatch\ngenerated: %s\nblockchain: %s", spew.Sdump(genBlockReceipts), spew.Sdump(blockchainReceipts))
}
// Verify withdrawals.
if len(block.Withdrawals()) == 0 {
continue
}
for j := 0; j < len(block.Withdrawals()); j++ {
if block.Withdrawals()[j].Index != withdrawalIndex {
t.Fatalf("withdrawal index %d does not equal expected index %d", block.Withdrawals()[j].Index, withdrawalIndex)
}
withdrawalIndex += 1
}
// TODO: can we reinstate the following?
/*
// Verify parent beacon root.
want := common.Hash{byte(blocknum)}
if got := block.BeaconRoot(); *got != want {
t.Fatalf("block %d, wrong parent beacon root: got %s, want %s", i, got, want)
}
state, _ := blockchain.State()
idx := block.Time()%8191 + 8191
got := state.GetState(params.BeaconRootsAddress, common.BigToHash(new(big.Int).SetUint64(idx)))
if got != want {
t.Fatalf("block %d, wrong parent beacon root in state: got %s, want %s", i, got, want)
}
*/
}
}
func ExampleGenerateChain() { func ExampleGenerateChain() {
var ( var (
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")

View file

@ -67,13 +67,14 @@ type Genesis struct {
// These fields are used for consensus tests. Please don't use them // These fields are used for consensus tests. Please don't use them
// in actual genesis blocks. // in actual genesis blocks.
Number uint64 `json:"number"` Number uint64 `json:"number"`
GasUsed uint64 `json:"gasUsed"` GasUsed uint64 `json:"gasUsed"`
ParentHash common.Hash `json:"parentHash"` ParentHash common.Hash `json:"parentHash"`
BaseFee *big.Int `json:"baseFeePerGas"` // EIP-1559 BaseFee *big.Int `json:"baseFeePerGas"` // EIP-1559
ExcessBlobGas *uint64 `json:"excessBlobGas"` // EIP-4844 ExcessBlobGas *uint64 `json:"excessBlobGas"` // EIP-4844
BlobGasUsed *uint64 `json:"blobGasUsed"` // EIP-4844 BlobGasUsed *uint64 `json:"blobGasUsed"` // EIP-4844
SlotNumber *uint64 `json:"slotNumber"` // EIP-7843 BlockAccessListHash *common.Hash `json:"BlockAccessListHash,omitempty"`
SlotNumber *uint64 `json:"slotNumber"` // EIP-7843
} }
// copy copies the genesis. // copy copies the genesis.
@ -487,18 +488,19 @@ func (g *Genesis) ToBlock() *types.Block {
// toBlockWithRoot constructs the genesis block with the given genesis state root. // toBlockWithRoot constructs the genesis block with the given genesis state root.
func (g *Genesis) toBlockWithRoot(root common.Hash) *types.Block { func (g *Genesis) toBlockWithRoot(root common.Hash) *types.Block {
head := &types.Header{ head := &types.Header{
Number: new(big.Int).SetUint64(g.Number), Number: new(big.Int).SetUint64(g.Number),
Nonce: types.EncodeNonce(g.Nonce), Nonce: types.EncodeNonce(g.Nonce),
Time: g.Timestamp, Time: g.Timestamp,
ParentHash: g.ParentHash, ParentHash: g.ParentHash,
Extra: g.ExtraData, Extra: g.ExtraData,
GasLimit: g.GasLimit, GasLimit: g.GasLimit,
GasUsed: g.GasUsed, GasUsed: g.GasUsed,
BaseFee: g.BaseFee, BaseFee: g.BaseFee,
Difficulty: g.Difficulty, Difficulty: g.Difficulty,
MixDigest: g.Mixhash, MixDigest: g.Mixhash,
Coinbase: g.Coinbase, Coinbase: g.Coinbase,
Root: root, BlockAccessListHash: g.BlockAccessListHash,
Root: root,
} }
if g.GasLimit == 0 { if g.GasLimit == 0 {
head.GasLimit = params.GenesisGasLimit head.GasLimit = params.GenesisGasLimit

View file

@ -32,7 +32,6 @@ import (
"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"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/holiman/uint256"
) )
// Tests block header storage and retrieval operations. // Tests block header storage and retrieval operations.
@ -906,13 +905,17 @@ func makeTestBAL(t *testing.T) (rlp.RawValue, *bal.BlockAccessList) {
t.Helper() t.Helper()
cb := bal.NewConstructionBlockAccessList() cb := bal.NewConstructionBlockAccessList()
addr := common.HexToAddress("0x1111111111111111111111111111111111111111") /*
cb.AccountRead(addr) TODO MariusVanDerWijden fix after rebase
cb.StorageRead(addr, common.BytesToHash([]byte{0x01})) addr := common.HexToAddress("0x1111111111111111111111111111111111111111")
cb.StorageWrite(0, addr, common.BytesToHash([]byte{0x02}), common.BytesToHash([]byte{0xaa}))
cb.BalanceChange(0, addr, uint256.NewInt(100))
cb.NonceChange(addr, 0, 1)
cb.AccountRead(addr)
cb.StorageRead(addr, common.BytesToHash([]byte{0x01}))
cb.StorageWrite(0, addr, common.BytesToHash([]byte{0x02}), common.BytesToHash([]byte{0xaa}))
cb.BalanceChange(0, addr, uint256.NewInt(100))
cb.NonceChange(addr, 0, 1)
*/
var buf bytes.Buffer var buf bytes.Buffer
if err := cb.EncodeRLP(&buf); err != nil { if err := cb.EncodeRLP(&buf); err != nil {
t.Fatalf("failed to encode BAL: %v", err) t.Fatalf("failed to encode BAL: %v", err)

View file

@ -23,6 +23,8 @@ import (
"slices" "slices"
"time" "time"
"github.com/ethereum/go-ethereum/core/types/bal"
"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/crypto" "github.com/ethereum/go-ethereum/crypto"
@ -53,6 +55,9 @@ type stateObject struct {
origin *types.StateAccount // Account original data without any change applied, nil means it was not existent origin *types.StateAccount // Account original data without any change applied, nil means it was not existent
data types.StateAccount // Account data with all mutations applied in the scope of block data types.StateAccount // Account data with all mutations applied in the scope of block
txPreBalance *uint256.Int // the account balance after the last call to finalise
txPreNonce uint64 // the account nonce after the last call to finalise
// Write caches. // Write caches.
trie Trie // storage trie, which becomes non-nil on first access trie Trie // storage trie, which becomes non-nil on first access
code []byte // contract bytecode, which gets set when code is loaded code []byte // contract bytecode, which gets set when code is loaded
@ -75,6 +80,9 @@ type stateObject struct {
// Cache flags. // Cache flags.
dirtyCode bool // true if the code was updated dirtyCode bool // true if the code was updated
nonFinalizedCode bool // true if the code has been changed in the current transaction
txPrestateCode []byte // set to the value of the code at the beginning of the transaction if it changed in the current transaction
// Flag whether the account was marked as self-destructed. The self-destructed // Flag whether the account was marked as self-destructed. The self-destructed
// account is still accessible in the scope of same transaction. // account is still accessible in the scope of same transaction.
selfDestructed bool selfDestructed bool
@ -107,6 +115,8 @@ func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *s
dirtyStorage: make(Storage), dirtyStorage: make(Storage),
pendingStorage: make(Storage), pendingStorage: make(Storage),
uncommittedStorage: make(Storage), uncommittedStorage: make(Storage),
txPreNonce: acct.Nonce,
txPreBalance: acct.Balance.Clone(),
} }
} }
@ -248,20 +258,59 @@ func (s *stateObject) setState(key common.Hash, value common.Hash, origin common
// finalise moves all dirty storage slots into the pending area to be hashed or // finalise moves all dirty storage slots into the pending area to be hashed or
// committed later. It is invoked at the end of every transaction. // committed later. It is invoked at the end of every transaction.
func (s *stateObject) finalise() { func (s *stateObject) finalise() (mutations *bal.AccountMutations) {
mutations = &bal.AccountMutations{}
if s.Balance().Cmp(s.txPreBalance) != 0 {
mutations.Balance = s.Balance()
}
if s.Nonce() != s.txPreNonce {
mutations.Nonce = new(uint64)
*mutations.Nonce = s.Nonce()
}
// include account code changes: created contracts and 7702 delegation authority code changes
if s.nonFinalizedCode {
if s.code == nil {
// code cleared (7702). code must be non-nil in the post to signal that it's part of the diff vs being unchanged.
mutations.Code = []byte{}
} else {
mutations.Code = s.code
}
}
mutations.StorageWrites = make(map[common.Hash]common.Hash)
slotsToPrefetch := make([]common.Hash, 0, len(s.dirtyStorage)) slotsToPrefetch := make([]common.Hash, 0, len(s.dirtyStorage))
for key, value := range s.dirtyStorage { for key, value := range s.dirtyStorage {
if origin, exist := s.uncommittedStorage[key]; exist && origin == value { if origin, exist := s.uncommittedStorage[key]; exist && origin == value {
// non-parallel-execution:
// The slot is reverted to its original value, delete the entry // The slot is reverted to its original value, delete the entry
// to avoid thrashing the data structures. // to avoid thrashing the data structures.
//
// parallel-exec-with-BAL:
// each statedb instance only executes a single transaction so the previous value
// of the slot won't be in uncommittedStorage
txPrestateVal := s.GetCommittedState(key)
if txPrestateVal != value {
mutations.StorageWrites[key] = value
}
delete(s.uncommittedStorage, key) delete(s.uncommittedStorage, key)
} else if exist { } else if exist {
// non-parallel-execution:
// The slot is modified to another value and the slot has been // The slot is modified to another value and the slot has been
// tracked for commit, do nothing here. // tracked for commit in uncommittedStorage.
//
// parallel-exec-with-BAL:
// each statedb instance only executes a single transaction so the previous value
// of the slot won't be in uncommittedStorage
mutations.StorageWrites[key] = value
} else { } else {
// The slot is different from its original value and hasn't been // The slot is different from its original value and hasn't been
// tracked for commit yet. // tracked for commit yet.
s.uncommittedStorage[key] = s.GetCommittedState(key) // Whether executing parallel with BAL or not, the value of the slot before the execution
// of the current transaction is in originStorage
origin := s.GetCommittedState(key)
mutations.StorageWrites[key] = value
s.uncommittedStorage[key] = origin
slotsToPrefetch = append(slotsToPrefetch, key) // Copy needed for closure slotsToPrefetch = append(slotsToPrefetch, key) // Copy needed for closure
} }
// Aggregate the dirty storage slots into the pending area. It might // Aggregate the dirty storage slots into the pending area. It might
@ -284,6 +333,18 @@ func (s *stateObject) finalise() {
// of the newly-created object as it's no longer eligible for self-destruct // of the newly-created object as it's no longer eligible for self-destruct
// by EIP-6780. For non-newly-created objects, it's a no-op. // by EIP-6780. For non-newly-created objects, it's a no-op.
s.newContract = false s.newContract = false
s.nonFinalizedCode = false
s.txPrestateCode = nil
// TODO(jwasinger): I had a bug here where i would set both of these to the value of s.data.* and there were no amsterdam-release test failures. need to figure out why.
s.txPreBalance = s.Balance().Clone()
s.txPreNonce = s.Nonce()
if mutations.Nonce == nil && mutations.Code == nil && mutations.Balance == nil && len(mutations.StorageWrites) == 0 {
return nil
}
return mutations
} }
// updateTrie is responsible for persisting cached storage changes into the // updateTrie is responsible for persisting cached storage changes into the
@ -511,6 +572,7 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject {
dirtyCode: s.dirtyCode, dirtyCode: s.dirtyCode,
selfDestructed: s.selfDestructed, selfDestructed: s.selfDestructed,
newContract: s.newContract, newContract: s.newContract,
txPreBalance: s.txPreBalance.Clone(),
} }
switch s.trie.(type) { switch s.trie.(type) {
@ -587,13 +649,26 @@ func (s *stateObject) SetCode(codeHash common.Hash, code []byte) (prev []byte) {
prev = slices.Clone(s.code) prev = slices.Clone(s.code)
s.db.journal.setCode(s.address, prev) s.db.journal.setCode(s.address, prev)
s.setCode(codeHash, code) s.setCode(codeHash, code)
return prev return prev
} }
func (s *stateObject) setCode(codeHash common.Hash, code []byte) { func (s *stateObject) setCode(codeHash common.Hash, code []byte) {
prevCode := s.code
if s.txPrestateCode == nil {
if prevCode == nil {
prevCode = []byte{}
}
s.txPrestateCode = prevCode
}
if !bytes.Equal(code, s.txPrestateCode) {
s.dirtyCode = true
s.nonFinalizedCode = true
} else {
s.nonFinalizedCode = false
}
s.code = code s.code = code
s.data.CodeHash = codeHash[:] s.data.CodeHash = codeHash[:]
s.dirtyCode = true
} }
func (s *stateObject) SetNonce(nonce uint64) { func (s *stateObject) SetNonce(nonce uint64) {

View file

@ -27,11 +27,12 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/ethereum/go-ethereum/core/types/bal"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/stateless" "github.com/ethereum/go-ethereum/core/stateless"
"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/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
@ -193,6 +194,7 @@ func NewWithReader(root common.Hash, db Database, reader Reader) (*StateDB, erro
journal: newJournal(), journal: newJournal(),
accessList: newAccessList(), accessList: newAccessList(),
transientStorage: newTransientStorage(), transientStorage: newTransientStorage(),
stateReadList: bal.NewStateAccessList(),
} }
if db.Type().Is(TypeUBT) { if db.Type().Is(TypeUBT) {
sdb.accessEvents = NewAccessEvents() sdb.accessEvents = NewAccessEvents()
@ -200,6 +202,13 @@ func NewWithReader(root common.Hash, db Database, reader Reader) (*StateDB, erro
return sdb, nil return sdb, nil
} }
// WithReader returns a copy of the StateDB instance with the specified reader.
func (s *StateDB) WithReader(reader Reader) *StateDB {
cpy := s.Copy()
cpy.reader = reader
return cpy
}
// StartPrefetcher initializes a new trie prefetcher to pull in nodes from the // StartPrefetcher initializes a new trie prefetcher to pull in nodes from the
// state trie concurrently while the state is mutated so that when we reach the // state trie concurrently while the state is mutated so that when we reach the
// commit phase, most of the needed data is already hot. // commit phase, most of the needed data is already hot.
@ -799,8 +808,9 @@ func (s *StateDB) LogsForBurnAccounts() []*types.Log {
// Finalise finalises the state by removing the destructed objects and clears // Finalise finalises the state by removing the destructed objects and clears
// the journal as well as the refunds. Finalise, however, will not push any updates // the journal as well as the refunds. Finalise, however, will not push any updates
// into the tries just yet. Only IntermediateRoot or Commit will do that. // into the tries just yet. Only IntermediateRoot or Commit will do that.
func (s *StateDB) Finalise(deleteEmptyObjects bool) *bal.StateAccessList { func (s *StateDB) Finalise(deleteEmptyObjects bool) (accesses *bal.StateAccessList, mutations *bal.StateMutations) {
addressesToPrefetch := make([]common.Address, 0, len(s.journal.dirties)) addressesToPrefetch := make([]common.Address, 0, len(s.journal.dirties))
mutations = bal.NewStateMutations()
for addr := range s.journal.dirties { for addr := range s.journal.dirties {
obj, exist := s.stateObjects[addr] obj, exist := s.stateObjects[addr]
if !exist { if !exist {
@ -822,8 +832,18 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) *bal.StateAccessList {
if _, ok := s.stateObjectsDestruct[obj.address]; !ok { if _, ok := s.stateObjectsDestruct[obj.address]; !ok {
s.stateObjectsDestruct[obj.address] = obj s.stateObjectsDestruct[obj.address] = obj
} }
// a pre-existing account can only be removed from the state under the following circumstance:
// it had a balance and was the target of a create2 which selfdestructed in the initcode
if !obj.txPreBalance.IsZero() {
mutations.Set(addr, &bal.AccountMutations{
Balance: uint256.NewInt(0),
})
}
} else { } else {
obj.finalise() mut := obj.finalise()
if mut != nil {
mutations.Set(addr, mut)
}
s.markUpdate(addr) s.markUpdate(addr)
} }
// At this point, also ship the address off to the precacher. The precacher // At this point, also ship the address off to the precacher. The precacher
@ -838,8 +858,7 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) *bal.StateAccessList {
} }
// Invalidate journal because reverting across transactions is not allowed. // Invalidate journal because reverting across transactions is not allowed.
s.clearJournalAndRefund() s.clearJournalAndRefund()
return s.stateReadList, mutations
return s.stateReadList
} }
// IntermediateRoot computes the current root hash of the state trie. // IntermediateRoot computes the current root hash of the state trie.

View file

@ -21,11 +21,12 @@ import (
"math/big" "math/big"
"sort" "sort"
"github.com/ethereum/go-ethereum/core/types/bal"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/stateless" "github.com/ethereum/go-ethereum/core/stateless"
"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/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256" "github.com/holiman/uint256"
@ -234,7 +235,7 @@ func (s *hookedStateDB) LogsForBurnAccounts() []*types.Log {
return s.inner.LogsForBurnAccounts() return s.inner.LogsForBurnAccounts()
} }
func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) *bal.StateAccessList { func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) (*bal.StateAccessList, *bal.StateMutations) {
if s.hooks.OnBalanceChange == nil && s.hooks.OnNonceChangeV2 == nil && s.hooks.OnNonceChange == nil && s.hooks.OnCodeChangeV2 == nil && s.hooks.OnCodeChange == nil { if s.hooks.OnBalanceChange == nil && s.hooks.OnNonceChangeV2 == nil && s.hooks.OnNonceChange == nil && s.hooks.OnCodeChangeV2 == nil && s.hooks.OnCodeChange == nil {
// Short circuit if no relevant hooks are set. // Short circuit if no relevant hooks are set.
return s.inner.Finalise(deleteEmptyObjects) return s.inner.Finalise(deleteEmptyObjects)

View file

@ -21,6 +21,8 @@ import (
"fmt" "fmt"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/core/types/bal"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/misc" "github.com/ethereum/go-ethereum/consensus/misc"
@ -63,13 +65,15 @@ func (p *StateProcessor) chainConfig() *params.ChainConfig {
// transactions failed to execute due to insufficient gas it will return an error. // transactions failed to execute due to insufficient gas it will return an error.
func (p *StateProcessor) Process(ctx context.Context, block *types.Block, statedb *state.StateDB, cfg vm.Config) (*ProcessResult, error) { func (p *StateProcessor) Process(ctx context.Context, block *types.Block, statedb *state.StateDB, cfg vm.Config) (*ProcessResult, error) {
var ( var (
config = p.chainConfig() config = p.chainConfig()
receipts = make(types.Receipts, 0, len(block.Transactions())) receipts = make(types.Receipts, 0, len(block.Transactions()))
header = block.Header() header = block.Header()
blockHash = block.Hash() blockHash = block.Hash()
blockNumber = block.Number() blockNumber = block.Number()
allLogs []*types.Log allLogs []*types.Log
gp = NewGasPool(block.GasLimit()) gp = NewGasPool(block.GasLimit())
computedAccessList = bal.NewConstructionBlockAccessList()
isAmsterdam = p.chainConfig().IsAmsterdam(block.Number(), block.Time())
) )
var tracingStateDB = vm.StateDB(statedb) var tracingStateDB = vm.StateDB(statedb)
if hooks := cfg.Tracer; hooks != nil { if hooks := cfg.Tracer; hooks != nil {
@ -90,10 +94,18 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated
evm := vm.NewEVM(context, tracingStateDB, config, cfg) evm := vm.NewEVM(context, tracingStateDB, config, cfg)
if beaconRoot := block.BeaconRoot(); beaconRoot != nil { if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
ProcessBeaconBlockRoot(*beaconRoot, evm) accesses, mutations := ProcessBeaconBlockRoot(*beaconRoot, evm)
if isAmsterdam {
computedAccessList.AddBlockInitMutations(mutations)
computedAccessList.AddAccesses(accesses)
}
} }
if config.IsPrague(block.Number(), block.Time()) || config.IsUBT(block.Number(), block.Time()) { if config.IsPrague(block.Number(), block.Time()) || config.IsUBT(block.Number(), block.Time()) {
ProcessParentBlockHash(block.ParentHash(), evm) accesses, mutations := ProcessParentBlockHash(block.ParentHash(), evm)
if isAmsterdam {
computedAccessList.AddBlockInitMutations(mutations)
computedAccessList.AddAccesses(accesses)
}
} }
// Iterate over and process the individual transactions // Iterate over and process the individual transactions
@ -108,60 +120,81 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated
telemetry.Int64Attribute("tx.index", int64(i)), telemetry.Int64Attribute("tx.index", int64(i)),
) )
receipt, err := ApplyTransactionWithEVM(msg, gp, statedb, blockNumber, blockHash, context.Time, tx, evm) accesses, mutations, 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...)
if isAmsterdam {
computedAccessList.AddTransactionMutations(mutations, i)
computedAccessList.AddAccesses(accesses)
}
spanEnd(nil) spanEnd(nil)
} }
requests, err := postExecution(ctx, config, block, allLogs, evm) postMut := bal.NewStateMutations()
postReads, postExecMut, requests, err := postExecution(ctx, config, block, allLogs, evm)
if err != nil { if err != nil {
return nil, err return nil, err
} }
postMut.Merge(postExecMut)
// 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()) eip4895WithdrawalReads, eip4985WithdrawalMuts := p.chain.Engine().Finalize(p.chain, header, tracingStateDB, block.Body())
postMut.Merge(eip4985WithdrawalMuts)
if isAmsterdam {
computedAccessList.AddBlockFinalizeMutations(postMut)
computedAccessList.AddAccesses(postReads)
computedAccessList.AddAccesses(eip4895WithdrawalReads)
}
return &ProcessResult{ return &ProcessResult{
Receipts: receipts, Receipts: receipts,
Requests: requests, Requests: requests,
Logs: allLogs, Logs: allLogs,
GasUsed: gp.Used(), GasUsed: gp.Used(),
AccessList: computedAccessList,
}, nil }, nil
} }
// postExecution processes the post-execution system calls if Prague is enabled. // postExecution processes the post-execution system calls if Prague is enabled.
func postExecution(ctx context.Context, config *params.ChainConfig, block *types.Block, allLogs []*types.Log, evm *vm.EVM) (requests [][]byte, err error) { func postExecution(ctx context.Context, config *params.ChainConfig, block *types.Block, allLogs []*types.Log, evm *vm.EVM) (accesses *bal.StateAccessList, mutations *bal.StateMutations, requests [][]byte, err error) {
_, _, spanEnd := telemetry.StartSpan(ctx, "core.postExecution") _, _, spanEnd := telemetry.StartSpan(ctx, "core.postExecution")
defer spanEnd(&err) defer spanEnd(&err)
mutations = bal.NewStateMutations()
accesses = bal.NewStateAccessList()
// Read requests if Prague is enabled. // Read requests if Prague is enabled.
if config.IsPrague(block.Number(), block.Time()) { if config.IsPrague(block.Number(), block.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 requests, fmt.Errorf("failed to parse deposit logs: %w", err) return nil, nil, requests, fmt.Errorf("failed to parse deposit logs: %w", err)
} }
// EIP-7002 // EIP-7002
if err := ProcessWithdrawalQueue(&requests, evm); err != nil { if accesses, mutations, err = ProcessWithdrawalQueue(&requests, evm); err != nil {
return requests, fmt.Errorf("failed to process withdrawal queue: %w", err) return nil, nil, requests, fmt.Errorf("failed to process withdrawal queue: %w", err)
} }
// EIP-7251 // EIP-7251
if err := ProcessConsolidationQueue(&requests, evm); err != nil { consolidationAccesses, consolidationMutations, err := ProcessConsolidationQueue(&requests, evm)
return requests, fmt.Errorf("failed to process consolidation queue: %w", err) if err != nil {
return nil, nil, requests, fmt.Errorf("failed to process consolidation queue: %w", err)
} }
mutations.Merge(consolidationMutations)
accesses.Merge(consolidationAccesses)
} }
return requests, nil return accesses, mutations, requests, 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) (accesses *bal.StateAccessList, mutations *bal.StateMutations, receipt *types.Receipt, 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)
@ -173,12 +206,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, 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) accesses, mutations = evm.StateDB.Finalise(true)
} else { } else {
root = statedb.IntermediateRoot(evm.ChainConfig().IsEIP158(blockNumber)).Bytes() root = statedb.IntermediateRoot(evm.ChainConfig().IsEIP158(blockNumber)).Bytes()
} }
@ -187,7 +220,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 accesses, mutations, MakeReceipt(evm, result, statedb, blockNumber, blockHash, blockTime, tx, gp.CumulativeUsed(), root), 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.
@ -231,10 +264,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) (*bal.StateAccessList, *bal.StateMutations, *types.Receipt, 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, 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)
@ -242,7 +275,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) (*bal.StateAccessList, *bal.StateMutations) {
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 {
@ -264,12 +297,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) return 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) (*bal.StateAccessList, *bal.StateMutations) {
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 {
@ -294,22 +327,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) return 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) error { func ProcessWithdrawalQueue(requests *[][]byte, evm *vm.EVM) (*bal.StateAccessList, *bal.StateMutations, error) {
return processRequestsSystemCall(requests, evm, 0x01, params.WithdrawalQueueAddress) return processRequestsSystemCall(requests, evm, 0x01, params.WithdrawalQueueAddress)
} }
// 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) error { func ProcessConsolidationQueue(requests *[][]byte, evm *vm.EVM) (*bal.StateAccessList, *bal.StateMutations, error) {
return processRequestsSystemCall(requests, evm, 0x02, params.ConsolidationQueueAddress) return processRequestsSystemCall(requests, evm, 0x02, params.ConsolidationQueueAddress)
} }
func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte, addr common.Address) error { func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte, addr common.Address) (*bal.StateAccessList, *bal.StateMutations, 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 {
@ -330,19 +363,19 @@ 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) accesses, mutations := evm.StateDB.Finalise(true)
if err != nil { if err != nil {
return fmt.Errorf("system call failed to execute: %v", err) return nil, nil, fmt.Errorf("system call failed to execute: %v", err)
} }
if len(ret) == 0 { if len(ret) == 0 {
return nil // skip empty output return accesses, mutations, nil // skip empty output
} }
// Append prefixed requestsData to the requests list. // Append prefixed requestsData to the requests list.
requestsData := make([]byte, len(ret)+1) requestsData := make([]byte, len(ret)+1)
requestsData[0] = requestType requestsData[0] = requestType
copy(requestsData[1:], ret) copy(requestsData[1:], ret)
*requests = append(*requests, requestsData) *requests = append(*requests, requestsData)
return nil return accesses, mutations, nil
} }
var depositTopic = common.HexToHash("0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5") var depositTopic = common.HexToHash("0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5")

View file

@ -469,6 +469,7 @@ func TestEIP8037MaxRegularGasValidation(t *testing.T) {
Amsterdam: params.DefaultOsakaBlobConfig, Amsterdam: params.DefaultOsakaBlobConfig,
}, },
} }
rules = config.Rules(common.Big0, true, 0)
signer = types.LatestSigner(config) signer = types.LatestSigner(config)
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
) )
@ -486,7 +487,7 @@ func TestEIP8037MaxRegularGasValidation(t *testing.T) {
} }
// Verify that floor data gas exceeds MaxTxGas // Verify that floor data gas exceeds MaxTxGas
floorGas, err := FloorDataGas(largeData) floorGas, err := FloorDataGas(rules, largeData)
if err != nil { if err != nil {
t.Fatalf("Failed to calculate floor data gas: %v", err) t.Fatalf("Failed to calculate floor data gas: %v", err)
} }

View file

@ -20,6 +20,8 @@ import (
"context" "context"
"sync/atomic" "sync/atomic"
"github.com/ethereum/go-ethereum/core/types/bal"
"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/vm" "github.com/ethereum/go-ethereum/core/vm"
@ -54,8 +56,9 @@ type Processor interface {
// ProcessResult contains the values computed by Process. // ProcessResult contains the values computed by Process.
type ProcessResult struct { type ProcessResult struct {
Receipts types.Receipts Receipts types.Receipts
Requests [][]byte Requests [][]byte
Logs []*types.Log Logs []*types.Log
GasUsed uint64 GasUsed uint64
AccessList *bal.ConstructionBlockAccessList
} }

View file

@ -92,3 +92,18 @@ func (s *StateAccessList) Copy() *StateAccessList {
} }
return cpy return cpy
} }
func (s *StateAccessList) Eq(other StateAccessList) bool {
if len(s.list) != len(other.list) {
return false
}
for addr, accesses := range s.list {
if _, ok := other.list[addr]; !ok {
return false
}
if !maps.Equal(accesses, other.list[addr]) {
return false
}
}
return true
}

View file

@ -18,156 +18,331 @@ package bal
import ( import (
"bytes" "bytes"
"encoding/json"
"maps" "maps"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/holiman/uint256" "github.com/holiman/uint256"
) )
// ConstructionAccountAccess contains post-block account state for mutations as well as // ConstructionAccountAccesses contains post-block account state for mutations as well as
// all storage keys that were read during execution. It is used when building block // all storage keys that were read during execution. It is used when building block
// access list during execution. // access list during execution.
type ConstructionAccountAccess struct { type ConstructionAccountAccesses struct {
// StorageWrites is the post-state values of an account's storage slots // StorageWrites is the post-state values of an account's storage slots
// that were modified in a block, keyed by the slot key and the tx index // that were modified in a block, keyed by the slot key and the tx index
// where the modification occurred. // where the modification occurred.
StorageWrites map[common.Hash]map[uint16]common.Hash `json:"storageWrites,omitempty"` StorageWrites map[common.Hash]map[uint16]common.Hash
// StorageReads is the set of slot keys that were accessed during block // StorageReads is the set of slot keys that were accessed during block
// execution. // execution.
// //
// Storage slots which are both read and written (with changed values) // storage slots which are both read and written (with changed values)
// appear only in StorageWrites. // appear only in StorageWrites.
StorageReads map[common.Hash]struct{} `json:"storageReads,omitempty"` StorageReads map[common.Hash]struct{}
// BalanceChanges contains the post-transaction balances of an account, // BalanceChanges contains the post-transaction balances of an account,
// keyed by transaction indices where it was changed. // keyed by transaction indices where it was changed.
BalanceChanges map[uint16]*uint256.Int `json:"balanceChanges,omitempty"` BalanceChanges map[uint16]*uint256.Int
// NonceChanges contains the post-state nonce values of an account keyed // NonceChanges contains the post-state nonce values of an account keyed
// by tx index. // by tx index.
NonceChanges map[uint16]uint64 `json:"nonceChanges,omitempty"` NonceChanges map[uint16]uint64
// CodeChange contains the post-state contract code of an account keyed CodeChanges map[uint16][]byte
// by tx index.
CodeChange map[uint16][]byte `json:"codeChange,omitempty"`
} }
// NewConstructionAccountAccess initializes the account access object. func (c *ConstructionAccountAccesses) Copy() (res ConstructionAccountAccesses) {
func NewConstructionAccountAccess() *ConstructionAccountAccess { if c.StorageWrites != nil {
return &ConstructionAccountAccess{ res.StorageWrites = make(map[common.Hash]map[uint16]common.Hash)
for slot, writes := range c.StorageWrites {
res.StorageWrites[slot] = maps.Clone(writes)
}
}
if c.StorageReads != nil {
res.StorageReads = maps.Clone(c.StorageReads)
}
if c.BalanceChanges != nil {
res.BalanceChanges = maps.Clone(c.BalanceChanges)
}
if c.NonceChanges != nil {
res.NonceChanges = maps.Clone(c.NonceChanges)
}
if c.CodeChanges != nil {
res.CodeChanges = maps.Clone(c.CodeChanges)
}
return res
}
type StateMutations struct {
list map[common.Address]AccountMutations
}
func NewStateMutations() *StateMutations {
return &StateMutations{make(map[common.Address]AccountMutations)}
}
func (s StateMutations) String() string {
b, _ := json.MarshalIndent(s, "", " ")
return string(b)
}
// Merge merges the state changes present in next into the caller. After,
// the state of the caller is the aggregate diff through next.
func (s *StateMutations) Merge(next *StateMutations) {
if next == nil {
return
}
for account, diff := range next.list {
if mut, ok := s.list[account]; ok {
if diff.Balance != nil {
mut.Balance = diff.Balance
}
if diff.Code != nil {
mut.Code = diff.Code
}
if diff.Nonce != nil {
mut.Nonce = diff.Nonce
}
if len(diff.StorageWrites) > 0 {
if mut.StorageWrites == nil {
mut.StorageWrites = maps.Clone(diff.StorageWrites)
} else {
for key, val := range diff.StorageWrites {
mut.StorageWrites[key] = val
}
}
}
s.list[account] = mut
} else {
s.list[account] = *diff.Copy()
}
}
}
func (s *StateMutations) Eq(other *StateMutations) bool {
if s == nil && other == nil {
return true
} else if s == nil && other != nil {
return false
} else if s != nil && other == nil {
return false
} else if len(s.list) != len(other.list) {
return false
}
for addr, mut := range s.list {
otherMut, ok := other.list[addr]
if !ok {
return false
}
if !mut.Eq(&otherMut) {
return false
}
}
return true
}
func (s *StateMutations) Set(addr common.Address, mut *AccountMutations) {
s.list[addr] = *mut
}
type ConstructionBlockAccessList struct {
list map[common.Address]*ConstructionAccountAccesses
transactionCount int
}
func NewConstructionBlockAccessList() *ConstructionBlockAccessList {
return &ConstructionBlockAccessList{
make(map[common.Address]*ConstructionAccountAccesses),
0}
}
func (c *ConstructionBlockAccessList) Copy() *ConstructionBlockAccessList {
if c == nil {
return nil
}
res := NewConstructionBlockAccessList()
for addr, accountAccess := range c.list {
aaCopy := accountAccess.Copy()
res.list[addr] = &aaCopy
}
return res
}
// must be called after all txs are added
func (c *ConstructionBlockAccessList) AddBlockFinalizeMutations(muts *StateMutations) {
c.addMutations(muts, c.transactionCount+1)
}
func (c *ConstructionBlockAccessList) AddBlockInitMutations(muts *StateMutations) {
c.addMutations(muts, 0)
}
func (c *ConstructionBlockAccessList) AddTransactionMutations(muts *StateMutations, txIdx int) {
c.transactionCount = max(c.transactionCount, txIdx+1)
c.addMutations(muts, c.transactionCount)
}
func (c *ConstructionBlockAccessList) addMutations(muts *StateMutations, index int) {
if muts == nil {
return
}
// TO
idx := uint16(index)
for addr, mut := range muts.list {
if _, exist := c.list[addr]; !exist {
c.list[addr] = newConstructionAccountAccesses()
}
if mut.Nonce != nil {
if c.list[addr].NonceChanges == nil {
c.list[addr].NonceChanges = make(map[uint16]uint64)
}
c.list[addr].NonceChanges[idx] = *mut.Nonce
}
if mut.Balance != nil {
if c.list[addr].BalanceChanges == nil {
c.list[addr].BalanceChanges = make(map[uint16]*uint256.Int)
}
c.list[addr].BalanceChanges[idx] = mut.Balance.Clone()
}
if mut.Code != nil {
if c.list[addr].CodeChanges == nil {
c.list[addr].CodeChanges = make(map[uint16][]byte)
}
c.list[addr].CodeChanges[idx] = bytes.Clone(mut.Code)
}
if len(mut.StorageWrites) > 0 {
for key, val := range mut.StorageWrites {
if c.list[addr].StorageWrites[key] == nil {
c.list[addr].StorageWrites[key] = make(map[uint16]common.Hash)
}
c.list[addr].StorageWrites[key][idx] = val
// delete the key from the tracked reads if it was previously read.
delete(c.list[addr].StorageReads, key)
}
}
}
}
func (c *ConstructionBlockAccessList) AddAccesses(reads *StateAccessList) {
if reads == nil {
return
}
for addr, addrReads := range reads.list {
if _, ok := c.list[addr]; !ok {
c.list[addr] = newConstructionAccountAccesses()
}
for storageKey, _ := range addrReads {
if c.list[addr].StorageWrites != nil {
if _, ok := c.list[addr].StorageWrites[storageKey]; ok {
continue
}
}
if c.list[addr].StorageReads == nil {
c.list[addr].StorageReads = make(map[common.Hash]struct{})
}
c.list[addr].StorageReads[storageKey] = struct{}{}
}
}
}
func newConstructionAccountAccesses() *ConstructionAccountAccesses {
return &ConstructionAccountAccesses{
StorageWrites: make(map[common.Hash]map[uint16]common.Hash), StorageWrites: make(map[common.Hash]map[uint16]common.Hash),
StorageReads: make(map[common.Hash]struct{}), StorageReads: make(map[common.Hash]struct{}),
BalanceChanges: make(map[uint16]*uint256.Int), BalanceChanges: make(map[uint16]*uint256.Int),
NonceChanges: make(map[uint16]uint64), NonceChanges: make(map[uint16]uint64),
CodeChange: make(map[uint16][]byte), CodeChanges: make(map[uint16][]byte),
} }
} }
// ConstructionBlockAccessList contains post-block modified state and some state accessed type StorageMutations map[common.Hash]common.Hash
// in execution (account addresses and storage keys).
type ConstructionBlockAccessList struct { // AccountMutations contains mutations that were made to an account across
Accounts map[common.Address]*ConstructionAccountAccess // one or more access list indices.
type AccountMutations struct {
Balance *uint256.Int `json:"Balance,omitempty"`
Nonce *uint64 `json:"Nonce,omitempty"`
Code ContractCode `json:"Code,omitempty"`
StorageWrites StorageMutations `json:"StorageWrites,omitempty"`
} }
// NewConstructionBlockAccessList instantiates an empty access list. // String returns a human-readable JSON representation of the account mutations.
func NewConstructionBlockAccessList() ConstructionBlockAccessList { func (a *AccountMutations) String() string {
return ConstructionBlockAccessList{ var res bytes.Buffer
Accounts: make(map[common.Address]*ConstructionAccountAccess), enc := json.NewEncoder(&res)
} enc.SetIndent("", " ")
enc.Encode(a)
return res.String()
} }
// AccountRead records the address of an account that has been read during execution. // Copy returns a deep-copy of the instance.
func (b *ConstructionBlockAccessList) AccountRead(addr common.Address) { func (a *AccountMutations) Copy() *AccountMutations {
if _, ok := b.Accounts[addr]; !ok { res := &AccountMutations{
b.Accounts[addr] = NewConstructionAccountAccess() nil,
nil,
nil,
nil,
} }
if a.Nonce != nil {
res.Nonce = new(uint64)
*res.Nonce = *a.Nonce
}
if a.Code != nil {
res.Code = bytes.Clone(a.Code)
}
if a.Balance != nil {
res.Balance = new(uint256.Int).Set(a.Balance)
}
if a.StorageWrites != nil {
res.StorageWrites = maps.Clone(a.StorageWrites)
}
return res
} }
// StorageRead records a storage key read during execution. // Copy returns a deep copy of the access list
func (b *ConstructionBlockAccessList) StorageRead(address common.Address, key common.Hash) { func (e BlockAccessList) Copy() *BlockAccessList {
if _, ok := b.Accounts[address]; !ok { var res BlockAccessList
b.Accounts[address] = NewConstructionAccountAccess() for _, accountAccess := range e {
} res = append(res, accountAccess.Copy())
if _, ok := b.Accounts[address].StorageWrites[key]; ok {
return
}
b.Accounts[address].StorageReads[key] = struct{}{}
}
// StorageWrite records the post-transaction value of a mutated storage slot.
// The storage slot is removed from the list of read slots.
func (b *ConstructionBlockAccessList) StorageWrite(txIdx uint16, address common.Address, key, value common.Hash) {
if _, ok := b.Accounts[address]; !ok {
b.Accounts[address] = NewConstructionAccountAccess()
}
if _, ok := b.Accounts[address].StorageWrites[key]; !ok {
b.Accounts[address].StorageWrites[key] = make(map[uint16]common.Hash)
}
b.Accounts[address].StorageWrites[key][txIdx] = value
delete(b.Accounts[address].StorageReads, key)
}
// CodeChange records the code of a newly-created contract.
func (b *ConstructionBlockAccessList) CodeChange(address common.Address, txIndex uint16, code []byte) {
if _, ok := b.Accounts[address]; !ok {
b.Accounts[address] = NewConstructionAccountAccess()
}
// TODO(rjl493456442) is it essential to deep-copy the code?
b.Accounts[address].CodeChange[txIndex] = bytes.Clone(code)
}
// NonceChange records tx post-state nonce of any contract-like accounts whose
// nonce was incremented.
func (b *ConstructionBlockAccessList) NonceChange(address common.Address, txIdx uint16, postNonce uint64) {
if _, ok := b.Accounts[address]; !ok {
b.Accounts[address] = NewConstructionAccountAccess()
}
b.Accounts[address].NonceChanges[txIdx] = postNonce
}
// BalanceChange records the post-transaction balance of an account whose
// balance changed.
func (b *ConstructionBlockAccessList) BalanceChange(txIdx uint16, address common.Address, balance *uint256.Int) {
if _, ok := b.Accounts[address]; !ok {
b.Accounts[address] = NewConstructionAccountAccess()
}
b.Accounts[address].BalanceChanges[txIdx] = balance.Clone()
}
// PrettyPrint returns a human-readable representation of the access list
func (b *ConstructionBlockAccessList) PrettyPrint() string {
enc := b.toEncodingObj()
return enc.PrettyPrint()
}
// Copy returns a deep copy of the access list.
func (b *ConstructionBlockAccessList) Copy() *ConstructionBlockAccessList {
res := NewConstructionBlockAccessList()
for addr, aa := range b.Accounts {
var aaCopy ConstructionAccountAccess
slotWrites := make(map[common.Hash]map[uint16]common.Hash, len(aa.StorageWrites))
for key, m := range aa.StorageWrites {
slotWrites[key] = maps.Clone(m)
}
aaCopy.StorageWrites = slotWrites
aaCopy.StorageReads = maps.Clone(aa.StorageReads)
balances := make(map[uint16]*uint256.Int, len(aa.BalanceChanges))
for index, balance := range aa.BalanceChanges {
balances[index] = balance.Clone()
}
aaCopy.BalanceChanges = balances
aaCopy.NonceChanges = maps.Clone(aa.NonceChanges)
codes := make(map[uint16][]byte, len(aa.CodeChange))
for index, code := range aa.CodeChange {
codes[index] = bytes.Clone(code)
}
aaCopy.CodeChange = codes
res.Accounts[addr] = &aaCopy
} }
return &res return &res
} }
// Eq returns whether the calling instance is equal to the provided one.
func (a *AccountMutations) Eq(other *AccountMutations) bool {
if a.Balance != nil || other.Balance != nil {
if a.Balance == nil || other.Balance == nil {
return false
}
if !a.Balance.Eq(other.Balance) {
return false
}
}
if (len(a.Code) != 0 || len(other.Code) != 0) && !bytes.Equal(a.Code, other.Code) {
return false
}
if a.Nonce != nil || other.Nonce != nil {
if a.Nonce == nil || other.Nonce == nil {
return false
}
if *a.Nonce != *other.Nonce {
return false
}
}
if a.StorageWrites != nil || other.StorageWrites != nil {
if !maps.Equal(a.StorageWrites, other.StorageWrites) {
return false
}
}
return true
}

View file

@ -19,6 +19,8 @@ package bal
import ( import (
"bytes" "bytes"
"cmp" "cmp"
"encoding/hex"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@ -28,35 +30,132 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "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"
) )
//go:generate go run github.com/ethereum/go-ethereum/rlp/rlpgen -out bal_encoding_rlp_generated.go -type BlockAccessList -decoder //go:generate go run github.com/ethereum/go-ethereum/rlp/rlpgen -out bal_encoding_rlp_generated.go -type AccountAccess -decoder
// These are objects used as input for the access list encoding. They mirror // These are objects used as input for the access list encoding. They mirror
// the spec format. // the spec format.
// BlockAccessList is the encoding format of ConstructionBlockAccessList. // BlockAccessList is the encoding format of AccessListBuilder.
type BlockAccessList struct { type BlockAccessList []AccountAccess
Accesses []AccountAccess `ssz-max:"300000"`
func (e BlockAccessList) EncodeRLP(_w io.Writer) error {
w := rlp.NewEncoderBuffer(_w)
l := w.List()
for _, access := range e {
access.EncodeRLP(w)
}
w.ListEnd(l)
return w.Flush()
} }
// Validate returns an error if the contents of the access list are not ordered func (e *BlockAccessList) DecodeRLP(dec *rlp.Stream) error {
if _, err := dec.List(); err != nil {
return err
}
*e = (*e)[:0]
for dec.MoreDataInList() {
var access AccountAccess
if err := access.DecodeRLP(dec); err != nil {
return err
}
*e = append(*e, access)
}
dec.ListEnd()
return nil
}
func (e *BlockAccessList) EncodedSize() int {
b, err := rlp.EncodeToBytes(e)
if err != nil {
// TODO: proper to crit here?
log.Crit("failed to rlp encode access list", "err", err)
}
return len(b)
}
func (e *BlockAccessList) JSONString() string {
res, _ := json.MarshalIndent(e.StringableRepresentation(), "", " ")
return string(res)
}
// StringableRepresentation returns an instance of the block access list
// which can be converted to a human-readable JSON representation.
func (e *BlockAccessList) StringableRepresentation() interface{} {
res := []AccountAccess{}
for _, aa := range *e {
res = append(res, aa)
}
return &res
}
func (e *BlockAccessList) String() string {
var res bytes.Buffer
enc := json.NewEncoder(&res)
enc.SetIndent("", " ")
// TODO: check error
enc.Encode(e)
return res.String()
}
// TODO: check that no fields are nil in Validate (unless it's valid for them to be nil)
// 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() error { // * the total accounts and storage slots in the access list exceed the protocol max
if !slices.IsSortedFunc(e.Accesses, func(a, b AccountAccess) int { func (e BlockAccessList) Validate(blockTxCount int, blockGasLimit uint64) error {
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.Accesses { // check that the accounts are unique
if err := entry.validate(); err != nil { addrs := make(map[common.Address]struct{})
for _, acct := range e {
addr := acct.Address
if _, ok := addrs[addr]; ok {
return fmt.Errorf("duplicate account in block access list: %x", addr)
}
addrs[addr] = struct{}{}
}
// validate individual entries
for _, entry := range e {
if err := entry.validate(blockTxCount); err != nil {
return err return err
} }
} }
// check that the total number of items doesn't exceed max
return e.ValidateGasLimit(blockGasLimit)
}
// ValidateGasLimit checks that the number of BAL items does not exceed the
// block gas limit divided by the per-item cost (EIP-7928).
func (e BlockAccessList) ValidateGasLimit(blockGasLimit uint64) error {
var balItems uint64
for _, account := range e {
// Count each address as one item
balItems++
// Count unique storage keys across both reads and writes
uniqueSlots := make(map[common.Hash]struct{})
for _, sc := range account.StorageChanges {
uniqueSlots[sc.Slot.ToHash()] = struct{}{}
}
for _, sr := range account.StorageReads {
uniqueSlots[sr.ToHash()] = struct{}{}
}
balItems += uint64(len(uniqueSlots))
}
limit := blockGasLimit / params.GasBlockAccessListItem
if balItems > limit {
return fmt.Errorf("block access list exceeds gas limit: %d items exceeds limit of %d", balItems, limit)
}
return nil return nil
} }
@ -70,53 +169,135 @@ func (e *BlockAccessList) Hash() common.Hash {
// under reasonable conditions. // under reasonable conditions.
panic(err) panic(err)
} }
/*
bal, err := json.MarshalIndent(e.StringableRepresentation(), "", " ")
if err != nil {
panic(err)
}
*/
return crypto.Keccak256Hash(enc.Bytes()) return crypto.Keccak256Hash(enc.Bytes())
} }
// encodeBalance encodes the provided balance into 16-bytes.
func encodeBalance(val *uint256.Int) [16]byte {
valBytes := val.Bytes()
if len(valBytes) > 16 {
panic("can't encode value that is greater than 16 bytes in size")
}
var enc [16]byte
copy(enc[16-len(valBytes):], valBytes[:])
return enc
}
// encodingBalanceChange is the encoding format of BalanceChange. // encodingBalanceChange is the encoding format of BalanceChange.
type encodingBalanceChange struct { type encodingBalanceChange struct {
TxIdx uint16 `ssz-size:"2"` TxIdx uint16 `json:"txIndex"`
Balance [16]byte `ssz-size:"16"` Balance *uint256.Int `json:"balance"`
} }
// encodingAccountNonce is the encoding format of NonceChange. // encodingAccountNonce is the encoding format of NonceChange.
type encodingAccountNonce struct { type encodingAccountNonce struct {
TxIdx uint16 `ssz-size:"2"` TxIdx uint16 `json:"txIndex"`
Nonce uint64 `ssz-size:"8"` Nonce uint64 `json:"nonce"`
} }
// encodingStorageWrite is the encoding format of StorageWrites. // encodingStorageWrite is the encoding format of StorageWrites.
type encodingStorageWrite struct { type encodingStorageWrite struct {
TxIdx uint16 TxIdx uint16 `json:"txIndex"`
ValueAfter [32]byte `ssz-size:"32"` ValueAfter *EncodedStorage `json:"valueAfter"`
}
// EncodedStorage can represent either a storage key or value
type EncodedStorage struct {
inner *uint256.Int
}
var _ rlp.Encoder = &EncodedStorage{}
var _ rlp.Decoder = &EncodedStorage{}
func (s *EncodedStorage) ToHash() common.Hash {
if s == nil {
return common.Hash{}
}
return s.inner.Bytes32()
}
func NewEncodedStorageFromHash(hash common.Hash) *EncodedStorage {
return &EncodedStorage{
new(uint256.Int).SetBytes(hash[:]),
}
}
func (s *EncodedStorage) UnmarshalJSON(b []byte) error {
var str string
if err := json.Unmarshal(b, &str); err != nil {
return err
}
str = strings.TrimLeft(str, "0x")
if len(str) == 0 {
return nil
}
if len(str)%2 == 1 {
str = "0" + str
}
val, err := hex.DecodeString(str)
if err != nil {
return err
}
if len(val) > 32 {
return fmt.Errorf("storage key/value cannot be greater than 32 bytes")
}
// TODO: check is s == nil ?? should be programmer error
*s = EncodedStorage{
inner: new(uint256.Int).SetBytes(val),
}
return nil
}
func (s EncodedStorage) MarshalJSON() ([]byte, error) {
return json.Marshal(s.inner.Hex())
}
func (s *EncodedStorage) EncodeRLP(_w io.Writer) error {
return s.inner.EncodeRLP(_w)
}
func (s *EncodedStorage) DecodeRLP(dec *rlp.Stream) error {
if s == nil {
*s = EncodedStorage{}
}
s.inner = uint256.NewInt(0)
return dec.ReadUint256(s.inner)
} }
// encodingStorageWrite is the encoding format of SlotWrites. // encodingStorageWrite is the encoding format of SlotWrites.
type encodingSlotWrites struct { type encodingSlotWrites struct {
Slot [32]byte `ssz-size:"32"` Slot *EncodedStorage `json:"slot"`
Accesses []encodingStorageWrite `ssz-max:"300000"` Accesses []encodingStorageWrite `json:"accesses"`
} }
// validate returns an instance of the encoding-representation slot writes in // validate returns an instance of the encoding-representation slot writes in
// working representation. // working representation.
func (e *encodingSlotWrites) validate() error { func (e *encodingSlotWrites) validate(blockTxCount int) error {
if slices.IsSortedFunc(e.Accesses, func(a, b encodingStorageWrite) int { if e.Slot == nil {
return errors.New("nil slot key")
}
if !slices.IsSortedFunc(e.Accesses, func(a, b encodingStorageWrite) int {
return cmp.Compare[uint16](a.TxIdx, b.TxIdx) return cmp.Compare[uint16](a.TxIdx, b.TxIdx)
}) { }) {
return nil return errors.New("storage write tx indices not in order")
} }
return errors.New("storage write tx indices not in order") for i, access := range e.Accesses {
if access.ValueAfter == nil {
return errors.New("nil storage write post")
}
if i > 0 && e.Accesses[i-1].TxIdx == access.TxIdx {
return errors.New("duplicate storage write index")
}
}
// TODO: add test that covers there are actually storage modifications here
// if there aren't, it should be a bad block
if len(e.Accesses) == 0 {
return fmt.Errorf("empty storage writes")
} else if int(e.Accesses[len(e.Accesses)-1].TxIdx) >= blockTxCount+2 {
return fmt.Errorf("storage access reported index higher than allowed")
}
return nil
} }
// encodingCodeChange contains the runtime bytecode deployed at an address // encodingCodeChange contains the runtime bytecode deployed at an address
@ -126,64 +307,120 @@ type encodingCodeChange struct {
Code []byte `ssz-max:"300000"` // TODO(rjl493456442) shall we put the limit here? The limit will be increased gradually Code []byte `ssz-max:"300000"` // TODO(rjl493456442) shall we put the limit here? The limit will be increased gradually
} }
// AccountAccess is the encoding format of ConstructionAccountAccess. // AccountAccess is the encoding format of ConstructionAccountAccesses.
type AccountAccess struct { type AccountAccess struct {
Address [20]byte `ssz-size:"20"` // 20-byte Ethereum address Address common.Address `json:"address,omitempty"` // 20-byte Ethereum address
StorageWrites []encodingSlotWrites `ssz-max:"300000"` // Storage changes (slot -> [tx_index -> new_value]) StorageChanges []encodingSlotWrites `json:"storageChanges,omitempty"` // EncodedStorage changes (slot -> [tx_index -> new_value])
StorageReads [][32]byte `ssz-max:"300000"` // Read-only storage keys StorageReads []*EncodedStorage `json:"storageReads,omitempty"` // Read-only storage keys
BalanceChanges []encodingBalanceChange `ssz-max:"300000"` // Balance changes ([tx_index -> post_balance]) BalanceChanges []encodingBalanceChange `json:"balanceChanges,omitempty"` // Balance changes ([tx_index -> post_balance])
NonceChanges []encodingAccountNonce `ssz-max:"300000"` // Nonce changes ([tx_index -> new_nonce]) NonceChanges []encodingAccountNonce `json:"nonceChanges,omitempty"` // Nonce changes ([tx_index -> new_nonce])
CodeChanges []encodingCodeChange `ssz-max:"300000"` // Code changes ([tx_index -> new_code]) CodeChanges []encodingCodeChange `json:"code,omitempty"` // CodeChanges changes ([tx_index -> new_code])
} }
// 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() error { func (e *AccountAccess) validate(blockTxCount int) 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.StorageChanges, func(a, b encodingSlotWrites) int {
return bytes.Compare(a.Slot[:], b.Slot[:]) aHash, bHash := a.Slot.ToHash(), b.Slot.ToHash()
return bytes.Compare(aHash[:], bHash[:])
}) { }) {
return errors.New("storage writes slots not in lexicographic order") return errors.New("storage writes slots not in lexicographic order")
} }
for _, write := range e.StorageWrites { for _, write := range e.StorageChanges {
if err := write.validate(); err != nil { if err := write.validate(blockTxCount); err != nil {
return err return err
} }
} }
readKeys := make(map[common.Hash]struct{})
writeKeys := make(map[common.Hash]struct{})
for _, readKey := range e.StorageReads {
if _, ok := readKeys[readKey.ToHash()]; ok {
return errors.New("duplicate read key")
}
readKeys[readKey.ToHash()] = struct{}{}
}
for _, write := range e.StorageChanges {
writeKey := write.Slot
if _, ok := writeKeys[writeKey.ToHash()]; ok {
return errors.New("duplicate write key")
}
writeKeys[writeKey.ToHash()] = struct{}{}
}
for readKey := range readKeys {
if _, ok := writeKeys[readKey]; ok {
return errors.New("storage key reported in both read/write sets")
}
}
// Check the storage read slots are sorted in order // Check the storage read slots are sorted in order
if !slices.IsSortedFunc(e.StorageReads, func(a, b [32]byte) int { if !slices.IsSortedFunc(e.StorageReads, func(a, b *EncodedStorage) int {
return bytes.Compare(a[:], b[:]) aHash, bHash := a.ToHash(), b.ToHash()
return bytes.Compare(aHash[:], bHash[:])
}) { }) {
return errors.New("storage read slots not in lexicographic order") return errors.New("storage read slots not in lexicographic order")
} }
// Check the balance changes are sorted in order // Check the balance changes are sorted in order
// and that none of them report an index above what is allowed
if !slices.IsSortedFunc(e.BalanceChanges, func(a, b encodingBalanceChange) int { if !slices.IsSortedFunc(e.BalanceChanges, func(a, b encodingBalanceChange) int {
return cmp.Compare[uint16](a.TxIdx, b.TxIdx) return cmp.Compare[uint16](a.TxIdx, b.TxIdx)
}) { }) {
return errors.New("balance changes not in ascending order by tx index") return errors.New("balance changes not in ascending order by tx index")
} }
if len(e.BalanceChanges) > 0 && int(e.BalanceChanges[len(e.BalanceChanges)-1].TxIdx) > blockTxCount+1 {
return errors.New("highest balance change index beyond what is allowed")
}
// check that the balance values are set and there are no duplicate index entries
for i, balanceChange := range e.BalanceChanges {
if balanceChange.Balance == nil {
return errors.New("nil balance change value")
}
if i > 0 && e.BalanceChanges[i-1].TxIdx == balanceChange.TxIdx {
return errors.New("duplicate index for balance change")
}
}
// Check the nonce changes are sorted in order // Check the nonce changes are sorted in order
// and that none of them report an index above what is allowed
if !slices.IsSortedFunc(e.NonceChanges, func(a, b encodingAccountNonce) int { if !slices.IsSortedFunc(e.NonceChanges, func(a, b encodingAccountNonce) int {
return cmp.Compare[uint16](a.TxIdx, b.TxIdx) return cmp.Compare[uint16](a.TxIdx, b.TxIdx)
}) { }) {
return errors.New("nonce changes not in ascending order by tx index") return errors.New("nonce changes not in ascending order by tx index")
} }
if len(e.NonceChanges) > 0 && int(e.NonceChanges[len(e.NonceChanges)-1].TxIdx) >= blockTxCount+2 {
return errors.New("highest nonce change index beyond what is allowed")
}
for i, nonceChange := range e.NonceChanges {
if i > 0 && nonceChange.TxIdx == e.NonceChanges[i-1].TxIdx {
return errors.New("duplicate index reported in nonce changes")
}
}
// Check the code changes are sorted in order // TODO: contact testing team to add a test case which has the code changes out of order,
// as it wasn't checked here previously
if !slices.IsSortedFunc(e.CodeChanges, func(a, b encodingCodeChange) int { if !slices.IsSortedFunc(e.CodeChanges, func(a, b encodingCodeChange) int {
return cmp.Compare[uint16](a.TxIndex, b.TxIndex) return cmp.Compare[uint16](a.TxIndex, b.TxIndex)
}) { }) {
return errors.New("code changes not in ascending order by tx index") return errors.New("code changes not in ascending order")
} }
for _, change := range e.CodeChanges { if len(e.CodeChanges) > 0 && int(e.CodeChanges[len(e.CodeChanges)-1].TxIndex) >= blockTxCount+2 {
// TODO(rjl493456442): This check should be fork-aware, since the limit may return errors.New("highest code change index beyond what is allowed")
// differ across forks. }
if len(change.Code) > params.MaxCodeSize { for i, codeChange := range e.CodeChanges {
return errors.New("code change contained oversized code") if i > 0 && codeChange.TxIndex == e.CodeChanges[i-1].TxIndex {
return errors.New("duplicate index reported in code changes")
}
}
// validate that code changes could plausibly be correct (none exceed
// max code size of a contract)
for _, codeChange := range e.CodeChanges {
if len(codeChange.Code) > params.MaxCodeSizeAmsterdam {
return fmt.Errorf("code change contained oversized code")
} }
} }
return nil return nil
@ -196,41 +433,35 @@ func (e *AccountAccess) Copy() AccountAccess {
StorageReads: slices.Clone(e.StorageReads), StorageReads: slices.Clone(e.StorageReads),
BalanceChanges: slices.Clone(e.BalanceChanges), BalanceChanges: slices.Clone(e.BalanceChanges),
NonceChanges: slices.Clone(e.NonceChanges), NonceChanges: slices.Clone(e.NonceChanges),
StorageWrites: make([]encodingSlotWrites, 0, len(e.StorageWrites)),
CodeChanges: make([]encodingCodeChange, 0, len(e.CodeChanges)),
} }
for _, storageWrite := range e.StorageWrites { for _, storageWrite := range e.StorageChanges {
res.StorageWrites = append(res.StorageWrites, encodingSlotWrites{ res.StorageChanges = append(res.StorageChanges, encodingSlotWrites{
Slot: storageWrite.Slot, Slot: storageWrite.Slot,
Accesses: slices.Clone(storageWrite.Accesses), Accesses: slices.Clone(storageWrite.Accesses),
}) })
} }
for _, codeChange := range e.CodeChanges { for _, codeChange := range e.CodeChanges {
res.CodeChanges = append(res.CodeChanges, encodingCodeChange{ res.CodeChanges = append(res.CodeChanges,
TxIndex: codeChange.TxIndex, encodingCodeChange{
Code: bytes.Clone(codeChange.Code), codeChange.TxIndex,
}) bytes.Clone(codeChange.Code),
})
} }
return res return res
} }
// EncodeRLP returns the RLP-encoded access list // EncodeRLP returns the RLP-encoded access list
func (b *ConstructionBlockAccessList) EncodeRLP(wr io.Writer) error { func (c ConstructionBlockAccessList) EncodeRLP(wr io.Writer) error {
return b.toEncodingObj().EncodeRLP(wr) return c.ToEncodingObj().EncodeRLP(wr)
} }
var _ rlp.Encoder = &ConstructionBlockAccessList{} var _ rlp.Encoder = &ConstructionBlockAccessList{}
// toEncodingObj creates an instance of the ConstructionAccountAccess of the type that is // toEncodingObj creates an instance of the ConstructionAccountAccesses of the type that is
// used as input for the encoding. // used as input for the encoding.
func (a *ConstructionAccountAccess) toEncodingObj(addr common.Address) AccountAccess { func (a *ConstructionAccountAccesses) toEncodingObj(addr common.Address) AccountAccess {
res := AccountAccess{ res := AccountAccess{
Address: addr, Address: addr,
StorageWrites: make([]encodingSlotWrites, 0, len(a.StorageWrites)),
StorageReads: make([][32]byte, 0, len(a.StorageReads)),
BalanceChanges: make([]encodingBalanceChange, 0, len(a.BalanceChanges)),
NonceChanges: make([]encodingAccountNonce, 0, len(a.NonceChanges)),
CodeChanges: make([]encodingCodeChange, 0, len(a.CodeChange)),
} }
// Convert write slots // Convert write slots
@ -238,7 +469,7 @@ func (a *ConstructionAccountAccess) toEncodingObj(addr common.Address) AccountAc
slices.SortFunc(writeSlots, common.Hash.Cmp) slices.SortFunc(writeSlots, common.Hash.Cmp)
for _, slot := range writeSlots { for _, slot := range writeSlots {
var obj encodingSlotWrites var obj encodingSlotWrites
obj.Slot = slot obj.Slot = NewEncodedStorageFromHash(slot)
slotWrites := a.StorageWrites[slot] slotWrites := a.StorageWrites[slot]
obj.Accesses = make([]encodingStorageWrite, 0, len(slotWrites)) obj.Accesses = make([]encodingStorageWrite, 0, len(slotWrites))
@ -248,17 +479,17 @@ func (a *ConstructionAccountAccess) toEncodingObj(addr common.Address) AccountAc
for _, index := range indices { for _, index := range indices {
obj.Accesses = append(obj.Accesses, encodingStorageWrite{ obj.Accesses = append(obj.Accesses, encodingStorageWrite{
TxIdx: index, TxIdx: index,
ValueAfter: slotWrites[index], ValueAfter: NewEncodedStorageFromHash(slotWrites[index]),
}) })
} }
res.StorageWrites = append(res.StorageWrites, obj) res.StorageChanges = append(res.StorageChanges, obj)
} }
// Convert read slots // Convert read slots
readSlots := slices.Collect(maps.Keys(a.StorageReads)) readSlots := slices.Collect(maps.Keys(a.StorageReads))
slices.SortFunc(readSlots, common.Hash.Cmp) slices.SortFunc(readSlots, common.Hash.Cmp)
for _, slot := range readSlots { for _, slot := range readSlots {
res.StorageReads = append(res.StorageReads, slot) res.StorageReads = append(res.StorageReads, NewEncodedStorageFromHash(slot))
} }
// Convert balance changes // Convert balance changes
@ -267,7 +498,7 @@ func (a *ConstructionAccountAccess) toEncodingObj(addr common.Address) AccountAc
for _, idx := range balanceIndices { for _, idx := range balanceIndices {
res.BalanceChanges = append(res.BalanceChanges, encodingBalanceChange{ res.BalanceChanges = append(res.BalanceChanges, encodingBalanceChange{
TxIdx: idx, TxIdx: idx,
Balance: encodeBalance(a.BalanceChanges[idx]), Balance: new(uint256.Int).Set(a.BalanceChanges[idx]),
}) })
} }
@ -282,80 +513,34 @@ func (a *ConstructionAccountAccess) toEncodingObj(addr common.Address) AccountAc
} }
// Convert code change // Convert code change
codeIndices := slices.Collect(maps.Keys(a.CodeChange)) codeChangeIdxs := slices.Collect(maps.Keys(a.CodeChanges))
slices.SortFunc(codeIndices, cmp.Compare[uint16]) slices.SortFunc(codeChangeIdxs, cmp.Compare[uint16])
for _, idx := range codeIndices { for _, idx := range codeChangeIdxs {
res.CodeChanges = append(res.CodeChanges, encodingCodeChange{ res.CodeChanges = append(res.CodeChanges, encodingCodeChange{
TxIndex: idx, idx,
Code: a.CodeChange[idx], bytes.Clone(a.CodeChanges[idx]),
}) })
} }
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 (c *ConstructionBlockAccessList) ToEncodingObj() *BlockAccessList {
if c == nil {
return nil
}
var addresses []common.Address var addresses []common.Address
for addr := range b.Accounts { for addr := range c.list {
addresses = append(addresses, addr) addresses = append(addresses, addr)
} }
slices.SortFunc(addresses, common.Address.Cmp) slices.SortFunc(addresses, common.Address.Cmp)
var res BlockAccessList var res BlockAccessList
for _, addr := range addresses { for _, addr := range addresses {
res.Accesses = append(res.Accesses, b.Accounts[addr].toEncodingObj(addr)) res = append(res, c.list[addr].toEncodingObj(addr))
} }
return &res return &res
} }
func (e *BlockAccessList) PrettyPrint() string { type ContractCode []byte
var res bytes.Buffer
printWithIndent := func(indent int, text string) {
fmt.Fprintf(&res, "%s%s\n", strings.Repeat(" ", indent), text)
}
for _, accountDiff := range e.Accesses {
printWithIndent(0, fmt.Sprintf("%x:", accountDiff.Address))
printWithIndent(1, "storage writes:")
for _, sWrite := range accountDiff.StorageWrites {
printWithIndent(2, fmt.Sprintf("%x:", sWrite.Slot))
for _, access := range sWrite.Accesses {
printWithIndent(3, fmt.Sprintf("%d: %x", access.TxIdx, access.ValueAfter))
}
}
printWithIndent(1, "storage reads:")
for _, slot := range accountDiff.StorageReads {
printWithIndent(2, fmt.Sprintf("%x", slot))
}
printWithIndent(1, "balance changes:")
for _, change := range accountDiff.BalanceChanges {
balance := new(uint256.Int).SetBytes(change.Balance[:]).String()
printWithIndent(2, fmt.Sprintf("%d: %s", change.TxIdx, balance))
}
printWithIndent(1, "nonce changes:")
for _, change := range accountDiff.NonceChanges {
printWithIndent(2, fmt.Sprintf("%d: %d", change.TxIdx, change.Nonce))
}
printWithIndent(1, "code changes:")
for _, change := range accountDiff.CodeChanges {
printWithIndent(2, fmt.Sprintf("%d: %x", change.TxIndex, change.Code))
}
}
return res.String()
}
// Copy returns a deep copy of the access list
func (e *BlockAccessList) Copy() *BlockAccessList {
cpy := &BlockAccessList{
Accesses: make([]AccountAccess, 0, len(e.Accesses)),
}
for _, accountAccess := range e.Accesses {
cpy.Accesses = append(cpy.Accesses, accountAccess.Copy())
}
return cpy
}

View file

@ -0,0 +1,108 @@
package bal
import (
"encoding/json"
"fmt"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/rlp"
)
func (c *ContractCode) MarshalJSON() ([]byte, error) {
hexStr := fmt.Sprintf("%x", *c)
return json.Marshal(hexStr)
}
func (e encodingBalanceChange) MarshalJSON() ([]byte, error) {
type Alias encodingBalanceChange
return json.Marshal(&struct {
TxIdx string `json:"txIndex"`
*Alias
}{
TxIdx: fmt.Sprintf("0x%x", e.TxIdx),
Alias: (*Alias)(&e),
})
}
func (e *encodingBalanceChange) UnmarshalJSON(data []byte) error {
type Alias encodingBalanceChange
aux := &struct {
TxIdx string `json:"txIndex"`
*Alias
}{
Alias: (*Alias)(e),
}
if err := json.Unmarshal(data, &aux); err != nil {
return err
}
if len(aux.TxIdx) >= 2 && aux.TxIdx[:2] == "0x" {
if _, err := fmt.Sscanf(aux.TxIdx, "0x%x", &e.TxIdx); err != nil {
return err
}
}
return nil
}
func (e encodingAccountNonce) MarshalJSON() ([]byte, error) {
type Alias encodingAccountNonce
return json.Marshal(&struct {
TxIdx string `json:"txIndex"`
Nonce string `json:"nonce"`
*Alias
}{
TxIdx: fmt.Sprintf("0x%x", e.TxIdx),
Nonce: fmt.Sprintf("0x%x", e.Nonce),
Alias: (*Alias)(&e),
})
}
func (e *encodingAccountNonce) UnmarshalJSON(data []byte) error {
type Alias encodingAccountNonce
aux := &struct {
TxIdx string `json:"txIndex"`
Nonce string `json:"nonce"`
*Alias
}{
Alias: (*Alias)(e),
}
if err := json.Unmarshal(data, &aux); err != nil {
return err
}
if len(aux.TxIdx) >= 2 && aux.TxIdx[:2] == "0x" {
if _, err := fmt.Sscanf(aux.TxIdx, "0x%x", &e.TxIdx); err != nil {
return err
}
}
if len(aux.Nonce) >= 2 && aux.Nonce[:2] == "0x" {
if _, err := fmt.Sscanf(aux.Nonce, "0x%x", &e.Nonce); err != nil {
return err
}
}
return nil
}
// UnmarshalJSON implements json.Unmarshaler to decode from RLP hex bytes
func (b *BlockAccessList) UnmarshalJSON(input []byte) error {
// Handle both hex string and object formats
var hexBytes hexutil.Bytes
if err := json.Unmarshal(input, &hexBytes); err == nil {
// It's a hex string, decode from RLP
return rlp.DecodeBytes(hexBytes, b)
}
// Otherwise try to unmarshal as structured JSON
var tmp []AccountAccess
if err := json.Unmarshal(input, &tmp); err != nil {
return err
}
*b = BlockAccessList(tmp)
return nil
}
// MarshalJSON implements json.Marshaler to encode as RLP hex bytes
func (b BlockAccessList) MarshalJSON() ([]byte, error) {
// Encode to RLP then to hex
rlpBytes, err := rlp.EncodeToBytes(b)
if err != nil {
return nil, err
}
return json.Marshal(hexutil.Bytes(rlpBytes))
}

View file

@ -2,275 +2,260 @@
package bal package bal
import "github.com/ethereum/go-ethereum/common"
import "github.com/ethereum/go-ethereum/rlp" import "github.com/ethereum/go-ethereum/rlp"
import "github.com/holiman/uint256"
import "io" import "io"
func (obj *BlockAccessList) EncodeRLP(_w io.Writer) error { func (obj *AccountAccess) EncodeRLP(_w io.Writer) error {
w := rlp.NewEncoderBuffer(_w) w := rlp.NewEncoderBuffer(_w)
_tmp0 := w.List() _tmp0 := w.List()
w.WriteBytes(obj.Address[:])
_tmp1 := w.List() _tmp1 := w.List()
for _, _tmp2 := range obj.Accesses { for _, _tmp2 := range obj.StorageChanges {
_tmp3 := w.List() _tmp3 := w.List()
w.WriteBytes(_tmp2.Address[:]) if err := _tmp2.Slot.EncodeRLP(w); err != nil {
return err
}
_tmp4 := w.List() _tmp4 := w.List()
for _, _tmp5 := range _tmp2.StorageWrites { for _, _tmp5 := range _tmp2.Accesses {
_tmp6 := w.List() _tmp6 := w.List()
w.WriteBytes(_tmp5.Slot[:]) w.WriteUint64(uint64(_tmp5.TxIdx))
_tmp7 := w.List() if err := _tmp5.ValueAfter.EncodeRLP(w); err != nil {
for _, _tmp8 := range _tmp5.Accesses { return err
_tmp9 := w.List()
w.WriteUint64(uint64(_tmp8.TxIdx))
w.WriteBytes(_tmp8.ValueAfter[:])
w.ListEnd(_tmp9)
} }
w.ListEnd(_tmp7)
w.ListEnd(_tmp6) w.ListEnd(_tmp6)
} }
w.ListEnd(_tmp4) w.ListEnd(_tmp4)
_tmp10 := w.List()
for _, _tmp11 := range _tmp2.StorageReads {
w.WriteBytes(_tmp11[:])
}
w.ListEnd(_tmp10)
_tmp12 := w.List()
for _, _tmp13 := range _tmp2.BalanceChanges {
_tmp14 := w.List()
w.WriteUint64(uint64(_tmp13.TxIdx))
w.WriteBytes(_tmp13.Balance[:])
w.ListEnd(_tmp14)
}
w.ListEnd(_tmp12)
_tmp15 := w.List()
for _, _tmp16 := range _tmp2.NonceChanges {
_tmp17 := w.List()
w.WriteUint64(uint64(_tmp16.TxIdx))
w.WriteUint64(_tmp16.Nonce)
w.ListEnd(_tmp17)
}
w.ListEnd(_tmp15)
_tmp18 := w.List()
for _, _tmp19 := range _tmp2.CodeChanges {
_tmp20 := w.List()
w.WriteUint64(uint64(_tmp19.TxIndex))
w.WriteBytes(_tmp19.Code)
w.ListEnd(_tmp20)
}
w.ListEnd(_tmp18)
w.ListEnd(_tmp3) w.ListEnd(_tmp3)
} }
w.ListEnd(_tmp1) w.ListEnd(_tmp1)
_tmp7 := w.List()
for _, _tmp8 := range obj.StorageReads {
if err := _tmp8.EncodeRLP(w); err != nil {
return err
}
}
w.ListEnd(_tmp7)
_tmp9 := w.List()
for _, _tmp10 := range obj.BalanceChanges {
_tmp11 := w.List()
w.WriteUint64(uint64(_tmp10.TxIdx))
if _tmp10.Balance == nil {
w.Write(rlp.EmptyString)
} else {
w.WriteUint256(_tmp10.Balance)
}
w.ListEnd(_tmp11)
}
w.ListEnd(_tmp9)
_tmp12 := w.List()
for _, _tmp13 := range obj.NonceChanges {
_tmp14 := w.List()
w.WriteUint64(uint64(_tmp13.TxIdx))
w.WriteUint64(_tmp13.Nonce)
w.ListEnd(_tmp14)
}
w.ListEnd(_tmp12)
_tmp15 := w.List()
for _, _tmp16 := range obj.CodeChanges {
_tmp17 := w.List()
w.WriteUint64(uint64(_tmp16.TxIndex))
w.WriteBytes(_tmp16.Code)
w.ListEnd(_tmp17)
}
w.ListEnd(_tmp15)
w.ListEnd(_tmp0) w.ListEnd(_tmp0)
return w.Flush() return w.Flush()
} }
func (obj *BlockAccessList) DecodeRLP(dec *rlp.Stream) error { func (obj *AccountAccess) DecodeRLP(dec *rlp.Stream) error {
var _tmp0 BlockAccessList var _tmp0 AccountAccess
{ {
if _, err := dec.List(); err != nil { if _, err := dec.List(); err != nil {
return err return err
} }
// Accesses: // Address:
var _tmp1 []AccountAccess var _tmp1 common.Address
if err := dec.ReadBytes(_tmp1[:]); err != nil {
return err
}
_tmp0.Address = _tmp1
// StorageChanges:
var _tmp2 []encodingSlotWrites
if _, err := dec.List(); err != nil { if _, err := dec.List(); err != nil {
return err return err
} }
for dec.MoreDataInList() { for dec.MoreDataInList() {
var _tmp2 AccountAccess var _tmp3 encodingSlotWrites
{ {
if _, err := dec.List(); err != nil { if _, err := dec.List(); err != nil {
return err return err
} }
// Address: // Slot:
var _tmp3 [20]byte _tmp4 := new(EncodedStorage)
if err := dec.ReadBytes(_tmp3[:]); err != nil { if err := _tmp4.DecodeRLP(dec); err != nil {
return err return err
} }
_tmp2.Address = _tmp3 _tmp3.Slot = _tmp4
// StorageWrites: // Accesses:
var _tmp4 []encodingSlotWrites var _tmp5 []encodingStorageWrite
if _, err := dec.List(); err != nil { if _, err := dec.List(); err != nil {
return err return err
} }
for dec.MoreDataInList() { for dec.MoreDataInList() {
var _tmp5 encodingSlotWrites var _tmp6 encodingStorageWrite
{
if _, err := dec.List(); err != nil {
return err
}
// Slot:
var _tmp6 [32]byte
if err := dec.ReadBytes(_tmp6[:]); err != nil {
return err
}
_tmp5.Slot = _tmp6
// Accesses:
var _tmp7 []encodingStorageWrite
if _, err := dec.List(); err != nil {
return err
}
for dec.MoreDataInList() {
var _tmp8 encodingStorageWrite
{
if _, err := dec.List(); err != nil {
return err
}
// TxIdx:
_tmp9, err := dec.Uint16()
if err != nil {
return err
}
_tmp8.TxIdx = _tmp9
// ValueAfter:
var _tmp10 [32]byte
if err := dec.ReadBytes(_tmp10[:]); err != nil {
return err
}
_tmp8.ValueAfter = _tmp10
if err := dec.ListEnd(); err != nil {
return err
}
}
_tmp7 = append(_tmp7, _tmp8)
}
if err := dec.ListEnd(); err != nil {
return err
}
_tmp5.Accesses = _tmp7
if err := dec.ListEnd(); err != nil {
return err
}
}
_tmp4 = append(_tmp4, _tmp5)
}
if err := dec.ListEnd(); err != nil {
return err
}
_tmp2.StorageWrites = _tmp4
// StorageReads:
var _tmp11 [][32]byte
if _, err := dec.List(); err != nil {
return err
}
for dec.MoreDataInList() {
var _tmp12 [32]byte
if err := dec.ReadBytes(_tmp12[:]); err != nil {
return err
}
_tmp11 = append(_tmp11, _tmp12)
}
if err := dec.ListEnd(); err != nil {
return err
}
_tmp2.StorageReads = _tmp11
// BalanceChanges:
var _tmp13 []encodingBalanceChange
if _, err := dec.List(); err != nil {
return err
}
for dec.MoreDataInList() {
var _tmp14 encodingBalanceChange
{ {
if _, err := dec.List(); err != nil { if _, err := dec.List(); err != nil {
return err return err
} }
// TxIdx: // TxIdx:
_tmp15, err := dec.Uint16() _tmp7, err := dec.Uint16()
if err != nil { if err != nil {
return err return err
} }
_tmp14.TxIdx = _tmp15 _tmp6.TxIdx = _tmp7
// Balance: // ValueAfter:
var _tmp16 [16]byte _tmp8 := new(EncodedStorage)
if err := dec.ReadBytes(_tmp16[:]); err != nil { if err := _tmp8.DecodeRLP(dec); err != nil {
return err return err
} }
_tmp14.Balance = _tmp16 _tmp6.ValueAfter = _tmp8
if err := dec.ListEnd(); err != nil { if err := dec.ListEnd(); err != nil {
return err return err
} }
} }
_tmp13 = append(_tmp13, _tmp14) _tmp5 = append(_tmp5, _tmp6)
} }
if err := dec.ListEnd(); err != nil { if err := dec.ListEnd(); err != nil {
return err return err
} }
_tmp2.BalanceChanges = _tmp13 _tmp3.Accesses = _tmp5
// NonceChanges:
var _tmp17 []encodingAccountNonce
if _, err := dec.List(); err != nil {
return err
}
for dec.MoreDataInList() {
var _tmp18 encodingAccountNonce
{
if _, err := dec.List(); err != nil {
return err
}
// TxIdx:
_tmp19, err := dec.Uint16()
if err != nil {
return err
}
_tmp18.TxIdx = _tmp19
// Nonce:
_tmp20, err := dec.Uint64()
if err != nil {
return err
}
_tmp18.Nonce = _tmp20
if err := dec.ListEnd(); err != nil {
return err
}
}
_tmp17 = append(_tmp17, _tmp18)
}
if err := dec.ListEnd(); err != nil {
return err
}
_tmp2.NonceChanges = _tmp17
// CodeChanges:
var _tmp21 []encodingCodeChange
if _, err := dec.List(); err != nil {
return err
}
for dec.MoreDataInList() {
var _tmp22 encodingCodeChange
{
if _, err := dec.List(); err != nil {
return err
}
// TxIndex:
_tmp23, err := dec.Uint16()
if err != nil {
return err
}
_tmp22.TxIndex = _tmp23
// Code:
_tmp24, err := dec.Bytes()
if err != nil {
return err
}
_tmp22.Code = _tmp24
if err := dec.ListEnd(); err != nil {
return err
}
}
_tmp21 = append(_tmp21, _tmp22)
}
if err := dec.ListEnd(); err != nil {
return err
}
_tmp2.CodeChanges = _tmp21
if err := dec.ListEnd(); err != nil { if err := dec.ListEnd(); err != nil {
return err return err
} }
} }
_tmp1 = append(_tmp1, _tmp2) _tmp2 = append(_tmp2, _tmp3)
} }
if err := dec.ListEnd(); err != nil { if err := dec.ListEnd(); err != nil {
return err return err
} }
_tmp0.Accesses = _tmp1 _tmp0.StorageChanges = _tmp2
// StorageReads:
var _tmp9 []*EncodedStorage
if _, err := dec.List(); err != nil {
return err
}
for dec.MoreDataInList() {
_tmp10 := new(EncodedStorage)
if err := _tmp10.DecodeRLP(dec); err != nil {
return err
}
_tmp9 = append(_tmp9, _tmp10)
}
if err := dec.ListEnd(); err != nil {
return err
}
_tmp0.StorageReads = _tmp9
// BalanceChanges:
var _tmp11 []encodingBalanceChange
if _, err := dec.List(); err != nil {
return err
}
for dec.MoreDataInList() {
var _tmp12 encodingBalanceChange
{
if _, err := dec.List(); err != nil {
return err
}
// TxIdx:
_tmp13, err := dec.Uint16()
if err != nil {
return err
}
_tmp12.TxIdx = _tmp13
// Balance:
var _tmp14 uint256.Int
if err := dec.ReadUint256(&_tmp14); err != nil {
return err
}
_tmp12.Balance = &_tmp14
if err := dec.ListEnd(); err != nil {
return err
}
}
_tmp11 = append(_tmp11, _tmp12)
}
if err := dec.ListEnd(); err != nil {
return err
}
_tmp0.BalanceChanges = _tmp11
// NonceChanges:
var _tmp15 []encodingAccountNonce
if _, err := dec.List(); err != nil {
return err
}
for dec.MoreDataInList() {
var _tmp16 encodingAccountNonce
{
if _, err := dec.List(); err != nil {
return err
}
// TxIdx:
_tmp17, err := dec.Uint16()
if err != nil {
return err
}
_tmp16.TxIdx = _tmp17
// Nonce:
_tmp18, err := dec.Uint64()
if err != nil {
return err
}
_tmp16.Nonce = _tmp18
if err := dec.ListEnd(); err != nil {
return err
}
}
_tmp15 = append(_tmp15, _tmp16)
}
if err := dec.ListEnd(); err != nil {
return err
}
_tmp0.NonceChanges = _tmp15
// CodeChanges:
var _tmp19 []encodingCodeChange
if _, err := dec.List(); err != nil {
return err
}
for dec.MoreDataInList() {
var _tmp20 encodingCodeChange
{
if _, err := dec.List(); err != nil {
return err
}
// TxIndex:
_tmp21, err := dec.Uint16()
if err != nil {
return err
}
_tmp20.TxIndex = _tmp21
// Code:
_tmp22, err := dec.Bytes()
if err != nil {
return err
}
_tmp20.Code = _tmp22
if err := dec.ListEnd(); err != nil {
return err
}
}
_tmp19 = append(_tmp19, _tmp20)
}
if err := dec.ListEnd(); err != nil {
return err
}
_tmp0.CodeChanges = _tmp19
if err := dec.ListEnd(); err != nil { if err := dec.ListEnd(); err != nil {
return err return err
} }

View file

@ -25,6 +25,7 @@ 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"
) )
@ -36,9 +37,9 @@ func equalBALs(a *BlockAccessList, b *BlockAccessList) bool {
return true return true
} }
func makeTestConstructionBAL() *ConstructionBlockAccessList { func makeTestConstructionBAL() ConstructionBlockAccessList {
return &ConstructionBlockAccessList{ return ConstructionBlockAccessList{
map[common.Address]*ConstructionAccountAccess{ list: map[common.Address]*ConstructionAccountAccesses{
common.BytesToAddress([]byte{0xff, 0xff}): { common.BytesToAddress([]byte{0xff, 0xff}): {
StorageWrites: map[common.Hash]map[uint16]common.Hash{ StorageWrites: map[common.Hash]map[uint16]common.Hash{
common.BytesToHash([]byte{0x01}): { common.BytesToHash([]byte{0x01}): {
@ -60,9 +61,7 @@ func makeTestConstructionBAL() *ConstructionBlockAccessList {
1: 2, 1: 2,
2: 6, 2: 6,
}, },
CodeChange: map[uint16][]byte{ CodeChanges: map[uint16][]byte{0: common.Hex2Bytes("deadbeef")},
0: common.Hex2Bytes("deadbeef"),
},
}, },
common.BytesToAddress([]byte{0xff, 0xff, 0xff}): { common.BytesToAddress([]byte{0xff, 0xff, 0xff}): {
StorageWrites: map[common.Hash]map[uint16]common.Hash{ StorageWrites: map[common.Hash]map[uint16]common.Hash{
@ -70,9 +69,6 @@ func makeTestConstructionBAL() *ConstructionBlockAccessList {
2: common.BytesToHash([]byte{1, 2, 3, 4, 5, 6}), 2: common.BytesToHash([]byte{1, 2, 3, 4, 5, 6}),
3: common.BytesToHash([]byte{1, 2, 3, 4, 5, 6, 7, 8}), 3: common.BytesToHash([]byte{1, 2, 3, 4, 5, 6, 7, 8}),
}, },
common.BytesToHash([]byte{0x10}): {
21: common.BytesToHash([]byte{1, 2, 3, 4, 5}),
},
}, },
StorageReads: map[common.Hash]struct{}{ StorageReads: map[common.Hash]struct{}{
common.BytesToHash([]byte{1, 2, 3, 4, 5, 6, 7, 8}): {}, common.BytesToHash([]byte{1, 2, 3, 4, 5, 6, 7, 8}): {},
@ -84,11 +80,9 @@ func makeTestConstructionBAL() *ConstructionBlockAccessList {
NonceChanges: map[uint16]uint64{ NonceChanges: map[uint16]uint64{
1: 2, 1: 2,
}, },
CodeChange: map[uint16][]byte{
0: common.Hex2Bytes("deadbeef"),
},
}, },
}, },
transactionCount: 1,
} }
} }
@ -104,10 +98,12 @@ func TestBALEncoding(t *testing.T) {
if err := dec.DecodeRLP(rlp.NewStream(bytes.NewReader(buf.Bytes()), 10000000)); err != nil { if err := dec.DecodeRLP(rlp.NewStream(bytes.NewReader(buf.Bytes()), 10000000)); 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 !equalBALs(bal.toEncodingObj(), &dec) {
// TODO (jwasinger): we should have a fuzzer to expand on what this test case does.
if !equalBALs(bal.ToEncodingObj(), &dec) {
t.Fatal("decoded BAL doesn't match") t.Fatal("decoded BAL doesn't match")
} }
} }
@ -115,18 +111,18 @@ func TestBALEncoding(t *testing.T) {
func makeTestAccountAccess(sort bool) AccountAccess { func makeTestAccountAccess(sort bool) AccountAccess {
var ( var (
storageWrites []encodingSlotWrites storageWrites []encodingSlotWrites
storageReads [][32]byte storageReads []common.Hash
balances []encodingBalanceChange balances []encodingBalanceChange
nonces []encodingAccountNonce nonces []encodingAccountNonce
) )
for i := 0; i < 5; i++ { for i := 0; i < 5; i++ {
slot := encodingSlotWrites{ slot := encodingSlotWrites{
Slot: testrand.Hash(), Slot: NewEncodedStorageFromHash(testrand.Hash()),
} }
for j := 0; j < 3; j++ { for j := 0; j < 3; j++ {
slot.Accesses = append(slot.Accesses, encodingStorageWrite{ slot.Accesses = append(slot.Accesses, encodingStorageWrite{
TxIdx: uint16(2 * j), TxIdx: uint16(i*3 + j),
ValueAfter: testrand.Hash(), ValueAfter: NewEncodedStorageFromHash(testrand.Hash()),
}) })
} }
if sort { if sort {
@ -138,7 +134,7 @@ func makeTestAccountAccess(sort bool) AccountAccess {
} }
if sort { if sort {
slices.SortFunc(storageWrites, func(a, b encodingSlotWrites) int { slices.SortFunc(storageWrites, func(a, b encodingSlotWrites) int {
return bytes.Compare(a.Slot[:], b.Slot[:]) return bytes.Compare(a.Slot.inner.Bytes(), b.Slot.inner.Bytes())
}) })
} }
@ -146,15 +142,15 @@ func makeTestAccountAccess(sort bool) AccountAccess {
storageReads = append(storageReads, testrand.Hash()) storageReads = append(storageReads, testrand.Hash())
} }
if sort { if sort {
slices.SortFunc(storageReads, func(a, b [32]byte) int { slices.SortFunc(storageReads, func(a, b common.Hash) int {
return bytes.Compare(a[:], b[:]) return bytes.Compare(a[:], b[:])
}) })
} }
for i := 0; i < 5; i++ { for i := 0; i < 5; i++ {
balances = append(balances, encodingBalanceChange{ balances = append(balances, encodingBalanceChange{
TxIdx: uint16(2 * i), TxIdx: uint16(i),
Balance: [16]byte(testrand.Bytes(16)), Balance: new(uint256.Int).SetBytes(testrand.Bytes(32)),
}) })
} }
if sort { if sort {
@ -165,7 +161,7 @@ func makeTestAccountAccess(sort bool) AccountAccess {
for i := 0; i < 5; i++ { for i := 0; i < 5; i++ {
nonces = append(nonces, encodingAccountNonce{ nonces = append(nonces, encodingAccountNonce{
TxIdx: uint16(2 * i), TxIdx: uint16(i),
Nonce: uint64(i + 100), Nonce: uint64(i + 100),
}) })
} }
@ -175,28 +171,32 @@ func makeTestAccountAccess(sort bool) AccountAccess {
}) })
} }
var encodedStorageReads []*EncodedStorage
for _, slot := range storageReads {
encodedStorageReads = append(encodedStorageReads, NewEncodedStorageFromHash(slot))
}
return AccountAccess{ return AccountAccess{
Address: [20]byte(testrand.Bytes(20)), Address: [20]byte(testrand.Bytes(20)),
StorageWrites: storageWrites, StorageChanges: storageWrites,
StorageReads: storageReads, StorageReads: encodedStorageReads,
BalanceChanges: balances, BalanceChanges: balances,
NonceChanges: nonces, NonceChanges: nonces,
CodeChanges: []encodingCodeChange{ CodeChanges: []encodingCodeChange{
{ {
TxIndex: 100, TxIndex: 3,
Code: testrand.Bytes(256), Code: testrand.Bytes(256),
}, },
}, },
} }
} }
func makeTestBAL(sort bool) *BlockAccessList { func makeTestBAL(sort bool) BlockAccessList {
list := &BlockAccessList{} list := BlockAccessList{}
for i := 0; i < 5; i++ { for i := 0; i < 5; i++ {
list.Accesses = append(list.Accesses, makeTestAccountAccess(sort)) list = append(list, makeTestAccountAccess(sort))
} }
if sort { if sort {
slices.SortFunc(list.Accesses, func(a, b AccountAccess) int { slices.SortFunc(list, func(a, b AccountAccess) int {
return bytes.Compare(a.Address[:], b.Address[:]) return bytes.Compare(a.Address[:], b.Address[:])
}) })
} }
@ -208,28 +208,29 @@ func TestBlockAccessListCopy(t *testing.T) {
cpy := list.Copy() cpy := list.Copy()
cpyCpy := cpy.Copy() cpyCpy := cpy.Copy()
if !reflect.DeepEqual(list, cpy) { if !reflect.DeepEqual(list, *cpy) {
t.Fatal("block access mismatch") t.Fatal("block access mismatch")
} }
if !reflect.DeepEqual(cpy, cpyCpy) { if !reflect.DeepEqual(*cpy, *cpyCpy) {
t.Fatal("block access mismatch") t.Fatal("block access mismatch")
} }
// Make sure the mutations on copy won't affect the origin // Make sure the mutations on copy won't affect the origin
for _, aa := range cpyCpy.Accesses { for i := range *cpyCpy {
for i := 0; i < len(aa.StorageReads); i++ { for j := 0; j < len((*cpyCpy)[i].StorageReads); j++ {
aa.StorageReads[i] = [32]byte(testrand.Bytes(32)) (*cpyCpy)[i].StorageReads[j] = NewEncodedStorageFromHash(testrand.Hash())
} }
} }
if !reflect.DeepEqual(list, cpy) { if !reflect.DeepEqual(list, *cpy) {
t.Fatal("block access mismatch") t.Fatal("block access mismatch")
} }
} }
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
testBALMaxIndex := 20
enc := makeTestBAL(true) enc := makeTestBAL(true)
if err := enc.Validate(); err != nil { if err := enc.Validate(testBALMaxIndex, params.MaxGasLimit); err != nil {
t.Fatalf("Unexpected validation error: %v", err) t.Fatalf("Unexpected validation error: %v", err)
} }
var buf bytes.Buffer var buf bytes.Buffer
@ -241,14 +242,17 @@ 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(); err != nil { if err := dec.Validate(testBALMaxIndex, params.MaxGasLimit); 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(); err != nil { if err := listB.Validate(testBALMaxIndex, params.MaxGasLimit); err != nil {
t.Fatalf("Unexpected validation error: %v", err) t.Fatalf("Unexpected validation error: %v", err)
} }
} }
// BALReader test ideas
// * BAL which doesn't have any pre-tx system contracts should return an empty state diff at idx 0

View file

@ -98,5 +98,5 @@ type StateDB interface {
AccessEvents() *state.AccessEvents AccessEvents() *state.AccessEvents
// Finalise must be invoked at the end of a transaction // Finalise must be invoked at the end of a transaction
Finalise(bool) *bal.StateAccessList Finalise(bool) (*bal.StateAccessList, *bal.StateMutations)
} }

View file

@ -37,12 +37,14 @@ func makeTestBAL(minSize int) *bal.BlockAccessList {
n := minSize/33 + 1 // 33 bytes per storage read slot in RLP n := minSize/33 + 1 // 33 bytes per storage read slot in RLP
access := bal.AccountAccess{ access := bal.AccountAccess{
Address: common.HexToAddress("0x01"), Address: common.HexToAddress("0x01"),
StorageReads: make([][32]byte, n), StorageReads: make([]*bal.EncodedStorage, n),
} }
for i := range access.StorageReads { for i := range access.StorageReads {
binary.BigEndian.PutUint64(access.StorageReads[i][24:], uint64(i)) var slot common.Hash
binary.BigEndian.PutUint64(slot[24:], uint64(i))
access.StorageReads[i] = bal.NewEncodedStorageFromHash(slot)
} }
return &bal.BlockAccessList{Accesses: []bal.AccountAccess{access}} return &bal.BlockAccessList{access}
} }
// getChainWithBALs creates a minimal test chain with BALs stored for each block. // getChainWithBALs creates a minimal test chain with BALs stored for each block.

View file

@ -1023,7 +1023,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) statedb.SetTxContext(txctx.TxHash, txctx.TxIndex)
_, 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

@ -256,6 +256,9 @@ func (sim *simulator) execute(ctx context.Context, blocks []simBlock) ([]*simBlo
} }
func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, parent *types.Header, headers []*types.Header, timeout time.Duration) (*types.Block, []simCallResult, map[common.Hash]common.Address, error) { func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, parent *types.Header, headers []*types.Header, timeout time.Duration) (*types.Block, []simCallResult, map[common.Hash]common.Address, error) {
if sim.chainConfig.IsAmsterdam(header.Number, header.Time) {
return nil, nil, nil, fmt.Errorf("eth simulate does not yet support Amsterdam")
}
// Set header fields that depend only on parent block. // Set header fields that depend only on parent block.
// Parent hash is needed for evm.GetHashFn to work. // Parent hash is needed for evm.GetHashFn to work.
header.ParentHash = parent.Hash() header.ParentHash = parent.Hash()
@ -401,11 +404,11 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
return nil, nil, nil, err return nil, nil, nil, err
} }
// EIP-7002 // EIP-7002
if err := core.ProcessWithdrawalQueue(&requests, evm); err != nil { if _, _, err := core.ProcessWithdrawalQueue(&requests, evm); err != nil {
return nil, nil, nil, err return nil, nil, nil, err
} }
// EIP-7251 // EIP-7251
if err := core.ProcessConsolidationQueue(&requests, evm); err != nil { if _, _, err := core.ProcessConsolidationQueue(&requests, evm); err != nil {
return nil, nil, nil, err return nil, nil, nil, err
} }
} }

View file

@ -213,11 +213,11 @@ func (miner *Miner) generateWork(ctx context.Context, genParam *generateParams,
return &newPayloadResult{err: err} return &newPayloadResult{err: err}
} }
// EIP-7002 // EIP-7002
if err := core.ProcessWithdrawalQueue(&requests, work.evm); err != nil { if _, _, err := core.ProcessWithdrawalQueue(&requests, work.evm); err != nil {
return &newPayloadResult{err: err} return &newPayloadResult{err: err}
} }
// EIP-7251 consolidations // EIP-7251 consolidations
if err := core.ProcessConsolidationQueue(&requests, work.evm); err != nil { if _, _, err := core.ProcessConsolidationQueue(&requests, work.evm); err != nil {
return &newPayloadResult{err: err} return &newPayloadResult{err: err}
} }
} }
@ -414,7 +414,7 @@ func (miner *Miner) applyTransaction(env *environment, tx *types.Transaction) (*
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, 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)

View file

@ -323,14 +323,16 @@ var (
CancunTime: newUint64(0), CancunTime: newUint64(0),
PragueTime: newUint64(0), PragueTime: newUint64(0),
OsakaTime: newUint64(0), OsakaTime: newUint64(0),
AmsterdamTime: newUint64(0),
UBTTime: nil, UBTTime: nil,
TerminalTotalDifficulty: big.NewInt(0), TerminalTotalDifficulty: big.NewInt(0),
Ethash: new(EthashConfig), Ethash: new(EthashConfig),
Clique: nil, Clique: nil,
BlobScheduleConfig: &BlobScheduleConfig{ BlobScheduleConfig: &BlobScheduleConfig{
Cancun: DefaultCancunBlobConfig, Cancun: DefaultCancunBlobConfig,
Prague: DefaultPragueBlobConfig, Prague: DefaultPragueBlobConfig,
Osaka: DefaultOsakaBlobConfig, Osaka: DefaultOsakaBlobConfig,
Amsterdam: DefaultOsakaBlobConfig,
}, },
} }

View file

@ -194,8 +194,7 @@ const (
StorageCreationSize = 32 StorageCreationSize = 32
AuthorizationCreationSize = 23 AuthorizationCreationSize = 23
// TODO: Add when EIP-7928 is implemented GasBlockAccessListItem = 2000 // EIP-7928: gas cost per BAL item for gas limit check
// GasBlockAccessListItem = 2000 // EIP-7928: gas cost per BAL item for gas limit check
) )
// Bls12381G1MultiExpDiscountTable is the gas discount table for BLS12-381 G1 multi exponentiation operation // Bls12381G1MultiExpDiscountTable is the gas discount table for BLS12-381 G1 multi exponentiation operation

View file

@ -98,6 +98,7 @@ type btHeader struct {
ExcessBlobGas *uint64 ExcessBlobGas *uint64
ParentBeaconBlockRoot *common.Hash ParentBeaconBlockRoot *common.Hash
SlotNumber *uint64 SlotNumber *uint64
BlockAccessListHash *common.Hash
} }
type btHeaderMarshaling struct { type btHeaderMarshaling struct {
@ -225,8 +226,10 @@ func (t *BlockTest) genesis(config *params.ChainConfig) *core.Genesis {
Coinbase: t.json.Genesis.Coinbase, Coinbase: t.json.Genesis.Coinbase,
Alloc: t.json.Pre, Alloc: t.json.Pre,
BaseFee: t.json.Genesis.BaseFeePerGas, BaseFee: t.json.Genesis.BaseFeePerGas,
BlobGasUsed: t.json.Genesis.BlobGasUsed, BlobGasUsed: t.json.Genesis.BlobGasUsed,
ExcessBlobGas: t.json.Genesis.ExcessBlobGas, ExcessBlobGas: t.json.Genesis.ExcessBlobGas,
SlotNumber: t.json.Genesis.SlotNumber,
BlockAccessListHash: t.json.Genesis.BlockAccessListHash,
} }
} }

View file

@ -39,6 +39,7 @@ func (b btHeader) MarshalJSON() ([]byte, error) {
ExcessBlobGas *math.HexOrDecimal64 ExcessBlobGas *math.HexOrDecimal64
ParentBeaconBlockRoot *common.Hash ParentBeaconBlockRoot *common.Hash
SlotNumber *math.HexOrDecimal64 SlotNumber *math.HexOrDecimal64
BlockAccessListHash *common.Hash
} }
var enc btHeader var enc btHeader
enc.Bloom = b.Bloom enc.Bloom = b.Bloom
@ -63,6 +64,7 @@ func (b btHeader) MarshalJSON() ([]byte, error) {
enc.ExcessBlobGas = (*math.HexOrDecimal64)(b.ExcessBlobGas) enc.ExcessBlobGas = (*math.HexOrDecimal64)(b.ExcessBlobGas)
enc.ParentBeaconBlockRoot = b.ParentBeaconBlockRoot enc.ParentBeaconBlockRoot = b.ParentBeaconBlockRoot
enc.SlotNumber = (*math.HexOrDecimal64)(b.SlotNumber) enc.SlotNumber = (*math.HexOrDecimal64)(b.SlotNumber)
enc.BlockAccessListHash = b.BlockAccessListHash
return json.Marshal(&enc) return json.Marshal(&enc)
} }
@ -91,6 +93,7 @@ func (b *btHeader) UnmarshalJSON(input []byte) error {
ExcessBlobGas *math.HexOrDecimal64 ExcessBlobGas *math.HexOrDecimal64
ParentBeaconBlockRoot *common.Hash ParentBeaconBlockRoot *common.Hash
SlotNumber *math.HexOrDecimal64 SlotNumber *math.HexOrDecimal64
BlockAccessListHash *common.Hash
} }
var dec btHeader var dec btHeader
if err := json.Unmarshal(input, &dec); err != nil { if err := json.Unmarshal(input, &dec); err != nil {
@ -162,5 +165,8 @@ func (b *btHeader) UnmarshalJSON(input []byte) error {
if dec.SlotNumber != nil { if dec.SlotNumber != nil {
b.SlotNumber = (*uint64)(dec.SlotNumber) b.SlotNumber = (*uint64)(dec.SlotNumber)
} }
if dec.BlockAccessListHash != nil {
b.BlockAccessListHash = dec.BlockAccessListHash
}
return nil return nil
} }

View file

@ -490,7 +490,7 @@ var Forks = map[string]*params.ChainConfig{
Cancun: params.DefaultCancunBlobConfig, Cancun: params.DefaultCancunBlobConfig,
Prague: params.DefaultPragueBlobConfig, Prague: params.DefaultPragueBlobConfig,
Osaka: params.DefaultOsakaBlobConfig, Osaka: params.DefaultOsakaBlobConfig,
BPO1: bpo1BlobConfig, BPO1: params.DefaultBPO1BlobConfig,
}, },
}, },
"OsakaToBPO1AtTime15k": { "OsakaToBPO1AtTime15k": {
@ -519,7 +519,7 @@ var Forks = map[string]*params.ChainConfig{
Cancun: params.DefaultCancunBlobConfig, Cancun: params.DefaultCancunBlobConfig,
Prague: params.DefaultPragueBlobConfig, Prague: params.DefaultPragueBlobConfig,
Osaka: params.DefaultOsakaBlobConfig, Osaka: params.DefaultOsakaBlobConfig,
BPO1: bpo1BlobConfig, BPO1: params.DefaultBPO1BlobConfig,
}, },
}, },
"BPO2": { "BPO2": {
@ -549,8 +549,8 @@ var Forks = map[string]*params.ChainConfig{
Cancun: params.DefaultCancunBlobConfig, Cancun: params.DefaultCancunBlobConfig,
Prague: params.DefaultPragueBlobConfig, Prague: params.DefaultPragueBlobConfig,
Osaka: params.DefaultOsakaBlobConfig, Osaka: params.DefaultOsakaBlobConfig,
BPO1: bpo1BlobConfig, BPO1: params.DefaultBPO1BlobConfig,
BPO2: bpo2BlobConfig, BPO2: params.DefaultBPO2BlobConfig,
}, },
}, },
"BPO1ToBPO2AtTime15k": { "BPO1ToBPO2AtTime15k": {
@ -580,8 +580,8 @@ var Forks = map[string]*params.ChainConfig{
Cancun: params.DefaultCancunBlobConfig, Cancun: params.DefaultCancunBlobConfig,
Prague: params.DefaultPragueBlobConfig, Prague: params.DefaultPragueBlobConfig,
Osaka: params.DefaultOsakaBlobConfig, Osaka: params.DefaultOsakaBlobConfig,
BPO1: bpo1BlobConfig, BPO1: params.DefaultBPO1BlobConfig,
BPO2: bpo2BlobConfig, BPO2: params.DefaultBPO2BlobConfig,
}, },
}, },
"BPO3": { "BPO3": {
@ -612,8 +612,8 @@ var Forks = map[string]*params.ChainConfig{
Cancun: params.DefaultCancunBlobConfig, Cancun: params.DefaultCancunBlobConfig,
Prague: params.DefaultPragueBlobConfig, Prague: params.DefaultPragueBlobConfig,
Osaka: params.DefaultOsakaBlobConfig, Osaka: params.DefaultOsakaBlobConfig,
BPO1: bpo1BlobConfig, BPO1: params.DefaultBPO1BlobConfig,
BPO2: bpo2BlobConfig, BPO2: params.DefaultBPO2BlobConfig,
BPO3: params.DefaultBPO3BlobConfig, BPO3: params.DefaultBPO3BlobConfig,
}, },
}, },
@ -645,8 +645,8 @@ var Forks = map[string]*params.ChainConfig{
Cancun: params.DefaultCancunBlobConfig, Cancun: params.DefaultCancunBlobConfig,
Prague: params.DefaultPragueBlobConfig, Prague: params.DefaultPragueBlobConfig,
Osaka: params.DefaultOsakaBlobConfig, Osaka: params.DefaultOsakaBlobConfig,
BPO1: bpo1BlobConfig, BPO1: params.DefaultBPO1BlobConfig,
BPO2: bpo2BlobConfig, BPO2: params.DefaultBPO2BlobConfig,
BPO3: params.DefaultBPO3BlobConfig, BPO3: params.DefaultBPO3BlobConfig,
}, },
}, },
@ -679,8 +679,8 @@ var Forks = map[string]*params.ChainConfig{
Cancun: params.DefaultCancunBlobConfig, Cancun: params.DefaultCancunBlobConfig,
Prague: params.DefaultPragueBlobConfig, Prague: params.DefaultPragueBlobConfig,
Osaka: params.DefaultOsakaBlobConfig, Osaka: params.DefaultOsakaBlobConfig,
BPO1: bpo1BlobConfig, BPO1: params.DefaultBPO1BlobConfig,
BPO2: bpo2BlobConfig, BPO2: params.DefaultBPO2BlobConfig,
BPO3: params.DefaultBPO3BlobConfig, BPO3: params.DefaultBPO3BlobConfig,
BPO4: params.DefaultBPO4BlobConfig, BPO4: params.DefaultBPO4BlobConfig,
}, },
@ -714,8 +714,8 @@ var Forks = map[string]*params.ChainConfig{
Cancun: params.DefaultCancunBlobConfig, Cancun: params.DefaultCancunBlobConfig,
Prague: params.DefaultPragueBlobConfig, Prague: params.DefaultPragueBlobConfig,
Osaka: params.DefaultOsakaBlobConfig, Osaka: params.DefaultOsakaBlobConfig,
BPO1: bpo1BlobConfig, BPO1: params.DefaultBPO1BlobConfig,
BPO2: bpo2BlobConfig, BPO2: params.DefaultBPO2BlobConfig,
BPO3: params.DefaultBPO3BlobConfig, BPO3: params.DefaultBPO3BlobConfig,
BPO4: params.DefaultBPO4BlobConfig, BPO4: params.DefaultBPO4BlobConfig,
}, },
@ -750,11 +750,44 @@ var Forks = map[string]*params.ChainConfig{
Cancun: params.DefaultCancunBlobConfig, Cancun: params.DefaultCancunBlobConfig,
Prague: params.DefaultPragueBlobConfig, Prague: params.DefaultPragueBlobConfig,
Osaka: params.DefaultOsakaBlobConfig, Osaka: params.DefaultOsakaBlobConfig,
BPO1: bpo1BlobConfig, BPO1: params.DefaultBPO1BlobConfig,
BPO2: bpo2BlobConfig, BPO2: params.DefaultBPO2BlobConfig,
BPO3: params.DefaultBPO3BlobConfig, BPO3: params.DefaultBPO3BlobConfig,
BPO4: params.DefaultBPO4BlobConfig, BPO4: params.DefaultBPO4BlobConfig,
Amsterdam: params.DefaultBPO4BlobConfig, // TODO update when defined Amsterdam: params.DefaultBPO2BlobConfig,
},
},
"BPO2ToAmsterdamAtTime15k": {
ChainID: big.NewInt(1),
HomesteadBlock: big.NewInt(0),
EIP150Block: big.NewInt(0),
EIP155Block: big.NewInt(0),
EIP158Block: big.NewInt(0),
ByzantiumBlock: big.NewInt(0),
ConstantinopleBlock: big.NewInt(0),
PetersburgBlock: big.NewInt(0),
IstanbulBlock: big.NewInt(0),
MuirGlacierBlock: big.NewInt(0),
BerlinBlock: big.NewInt(0),
LondonBlock: big.NewInt(0),
ArrowGlacierBlock: big.NewInt(0),
MergeNetsplitBlock: big.NewInt(0),
TerminalTotalDifficulty: big.NewInt(0),
ShanghaiTime: u64(0),
CancunTime: u64(0),
PragueTime: u64(0),
OsakaTime: u64(0),
BPO1Time: u64(0),
BPO2Time: u64(0),
AmsterdamTime: u64(15_000),
DepositContractAddress: params.MainnetChainConfig.DepositContractAddress,
BlobScheduleConfig: &params.BlobScheduleConfig{
Cancun: params.DefaultCancunBlobConfig,
Prague: params.DefaultPragueBlobConfig,
Osaka: params.DefaultOsakaBlobConfig,
BPO1: params.DefaultBPO1BlobConfig,
BPO2: params.DefaultBPO2BlobConfig,
Amsterdam: params.DefaultBPO2BlobConfig,
}, },
}, },
"Verkle": { "Verkle": {
@ -778,18 +811,6 @@ var Forks = map[string]*params.ChainConfig{
}, },
} }
var bpo1BlobConfig = &params.BlobConfig{
Target: 9,
Max: 14,
UpdateFraction: 8832827,
}
var bpo2BlobConfig = &params.BlobConfig{
Target: 14,
Max: 21,
UpdateFraction: 13739630,
}
// AvailableForks returns the set of defined fork names // AvailableForks returns the set of defined fork names
func AvailableForks() []string { func AvailableForks() []string {
var availableForks []string var availableForks []string

View file

@ -82,7 +82,7 @@ func (tt *TransactionTest) Run() error {
} }
// Intrinsic cost // Intrinsic cost
// TODO (MariusVanDerWijden): correctly set this for post-amsterdam tests. // TODO (MariusVanDerWijden): correctly set this for post-amsterdam tests.
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, *rules, 0) cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules, 0)
if err != nil { if err != nil {
return return
} }