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"
"slices"
"github.com/ethereum/go-ethereum/core/types/bal"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
@ -82,24 +84,25 @@ type payloadAttributesMarshaling struct {
// ExecutableData is the data necessary to execute an EL payload.
type ExecutableData struct {
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"`
StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
LogsBloom []byte `json:"logsBloom" gencodec:"required"`
Random common.Hash `json:"prevRandao" gencodec:"required"`
Number uint64 `json:"blockNumber" gencodec:"required"`
GasLimit uint64 `json:"gasLimit" gencodec:"required"`
GasUsed uint64 `json:"gasUsed" gencodec:"required"`
Timestamp uint64 `json:"timestamp" gencodec:"required"`
ExtraData []byte `json:"extraData" gencodec:"required"`
BaseFeePerGas *big.Int `json:"baseFeePerGas" gencodec:"required"`
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
Transactions [][]byte `json:"transactions" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"`
BlobGasUsed *uint64 `json:"blobGasUsed"`
ExcessBlobGas *uint64 `json:"excessBlobGas"`
SlotNumber *uint64 `json:"slotNumber,omitempty"`
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"`
StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
LogsBloom []byte `json:"logsBloom" gencodec:"required"`
Random common.Hash `json:"prevRandao" gencodec:"required"`
Number uint64 `json:"blockNumber" gencodec:"required"`
GasLimit uint64 `json:"gasLimit" gencodec:"required"`
GasUsed uint64 `json:"gasUsed" gencodec:"required"`
Timestamp uint64 `json:"timestamp" gencodec:"required"`
ExtraData []byte `json:"extraData" gencodec:"required"`
BaseFeePerGas *big.Int `json:"baseFeePerGas" gencodec:"required"`
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
Transactions [][]byte `json:"transactions" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"`
BlobGasUsed *uint64 `json:"blobGasUsed"`
ExcessBlobGas *uint64 `json:"excessBlobGas"`
BlockAccessList *bal.BlockAccessList `json:"blockAccessList"`
SlotNumber *uint64 `json:"slotNumber,omitempty"`
}
// JSON type overrides for executableData.
@ -303,6 +306,8 @@ func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.H
requestsHash = &h
}
body := types.Body{Transactions: txs, Uncles: nil, Withdrawals: data.Withdrawals}
header := &types.Header{
ParentHash: data.ParentHash,
UncleHash: types.EmptyUncleHash,
@ -326,33 +331,40 @@ func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.H
RequestsHash: requestsHash,
SlotNumber: data.SlotNumber,
}
return types.NewBlockWithHeader(header).
WithBody(types.Body{Transactions: txs, Uncles: nil, Withdrawals: data.Withdrawals}),
nil
if data.BlockAccessList != 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
// 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 {
data := &ExecutableData{
BlockHash: block.Hash(),
ParentHash: block.ParentHash(),
FeeRecipient: block.Coinbase(),
StateRoot: block.Root(),
Number: block.NumberU64(),
GasLimit: block.GasLimit(),
GasUsed: block.GasUsed(),
BaseFeePerGas: block.BaseFee(),
Timestamp: block.Time(),
ReceiptsRoot: block.ReceiptHash(),
LogsBloom: block.Bloom().Bytes(),
Transactions: encodeTransactions(block.Transactions()),
Random: block.MixDigest(),
ExtraData: block.Extra(),
Withdrawals: block.Withdrawals(),
BlobGasUsed: block.BlobGasUsed(),
ExcessBlobGas: block.ExcessBlobGas(),
SlotNumber: block.SlotNumber(),
BlockHash: block.Hash(),
ParentHash: block.ParentHash(),
FeeRecipient: block.Coinbase(),
StateRoot: block.Root(),
Number: block.NumberU64(),
GasLimit: block.GasLimit(),
GasUsed: block.GasUsed(),
BaseFeePerGas: block.BaseFee(),
Timestamp: block.Time(),
ReceiptsRoot: block.ReceiptHash(),
LogsBloom: block.Bloom().Bytes(),
Transactions: encodeTransactions(block.Transactions()),
Random: block.MixDigest(),
ExtraData: block.Extra(),
Withdrawals: block.Withdrawals(),
BlobGasUsed: block.BlobGasUsed(),
ExcessBlobGas: block.ExcessBlobGas(),
BlockAccessList: block.AccessList(),
SlotNumber: block.SlotNumber(),
}
// 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
type ExecutionPayloadBody struct {
TransactionData []hexutil.Bytes `json:"transactions"`
Withdrawals []*types.Withdrawal `json:"withdrawals"`
TransactionData []hexutil.Bytes `json:"transactions"`
Withdrawals []*types.Withdrawal `json:"withdrawals"`
AccessList *bal.BlockAccessList `json:"blockAccessList"`
}
// Client identifiers to support ClientVersionV1.

View file

@ -5,6 +5,11 @@
# https://github.com/ethereum/execution-spec-tests/releases/download/v5.1.0
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
# https://go.dev/dl/
0ec9ef8ebcea097aac37decae9f09a7218b451cd96be7d6ed513d8e4bcf909cf go1.25.9.src.tar.gz

View file

@ -176,6 +176,9 @@ var (
// This is where the tests should be unpacked.
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"))
@ -384,6 +387,7 @@ func doTest(cmdline []string) {
// Get test fixtures.
if !*short {
downloadSpecTestFixtures(csdb, *cachedir)
downloadBALSpecTestFixtures(csdb, *cachedir)
}
// Configure the toolchain.
@ -449,6 +453,19 @@ func downloadSpecTestFixtures(csdb *download.ChecksumDB, cachedir string) string
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
// any mutations in the source file tree.
func doCheckGenerate() {

View file

@ -258,7 +258,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
snapshot = statedb.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 {
statedb.RevertToSnapshot(snapshot)
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))
}
// 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))
}
// 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))
}
}

View file

@ -21,6 +21,8 @@ import (
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/core/types/bal"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
"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.
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) {
beacon.ethone.Finalize(chain, header, state, body)
return
return beacon.ethone.Finalize(chain, header, state, body)
}
// Withdrawals processing.
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.
amount := new(uint256.Int).SetUint64(w.Amount)
amount = amount.Mul(amount, uint256.NewInt(params.GWei))
state.AddBalance(w.Address, amount, tracing.BalanceIncreaseWithdrawal)
}
return state.Finalise(true)
// No block reward which is issued by consensus layer instead.
}

View file

@ -27,6 +27,8 @@ import (
"sync"
"time"
"github.com/ethereum/go-ethereum/core/types/bal"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"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
// 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
return nil, nil
}
// 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/core/types"
"github.com/ethereum/go-ethereum/core/types/bal"
"github.com/ethereum/go-ethereum/core/vm"
"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
// 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
// the result into the given channel.

View file

@ -22,6 +22,8 @@ import (
"math/big"
"time"
"github.com/ethereum/go-ethereum/core/types/bal"
mapset "github.com/deckarep/golang-set/v2"
"github.com/ethereum/go-ethereum/common"
"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.
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
accumulateRewards(chain.Config(), state, header, body.Uncles)
return nil, nil
}
// 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.
if !v.bc.HasBlockAndState(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.
func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash, block *types.Block, config ExecuteConfig) (result *blockProcessingResult, blockEndErr error) {
var (
err error
startTime = time.Now()
statedb *state.StateDB
interrupt atomic.Bool
sdb state.Database
err error
startTime = time.Now()
statedb *state.StateDB
interrupt atomic.Bool
sdb state.Database
isAmsterdam = bc.chainConfig.IsAmsterdam(block.Number(), block.Time())
)
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)
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
// that now. Self validation should *never* run in production, it's more of
// a tight integration to enable running *all* consensus tests through the

View file

@ -20,6 +20,8 @@ import (
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/core/types/bal"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/misc"
@ -51,6 +53,8 @@ type BlockGen struct {
withdrawals []*types.Withdrawal
engine consensus.Engine
accessList *bal.ConstructionBlockAccessList
}
// 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)
)
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 {
panic(err)
}
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
// 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 {
panic(fmt.Sprintf("failed to parse deposit log: %v", err))
}
// TODO: these accesses should be accumulated in the BAL
// create EVM for system calls
blockContext := NewEVMBlockContext(b.header, b.cm, &b.header.Coinbase)
evm := vm.NewEVM(blockContext, statedb, b.cm.config, vm.Config{})
mut := new(bal.StateMutations)
// 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))
}
mut.Merge(withdrawalMut)
// 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))
}
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
}
@ -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) {
b := &BlockGen{i: i, cm: cm, parent: parent, statedb: statedb, engine: 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
// to a chain, so the difficulty will be left unset (nil). Set it here to the
// correct value.
@ -391,14 +414,32 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
misc.ApplyDAOHardFork(statedb)
}
preTxMutations := bal.NewStateMutations()
if config.IsPrague(b.header.Number, b.header.Time) || config.IsUBT(b.header.Number, b.header.Time) {
// EIP-2935
blockContext := NewEVMBlockContext(b.header, cm, &b.header.Coinbase)
blockContext.Random = &common.Hash{} // enable post-merge instruction set
evm := vm.NewEVM(blockContext, statedb, cm.config, vm.Config{})
ProcessParentBlockHash(b.header.ParentHash, evm)
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
if gen != nil {
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() {
var (
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
// in actual genesis blocks.
Number uint64 `json:"number"`
GasUsed uint64 `json:"gasUsed"`
ParentHash common.Hash `json:"parentHash"`
BaseFee *big.Int `json:"baseFeePerGas"` // EIP-1559
ExcessBlobGas *uint64 `json:"excessBlobGas"` // EIP-4844
BlobGasUsed *uint64 `json:"blobGasUsed"` // EIP-4844
SlotNumber *uint64 `json:"slotNumber"` // EIP-7843
Number uint64 `json:"number"`
GasUsed uint64 `json:"gasUsed"`
ParentHash common.Hash `json:"parentHash"`
BaseFee *big.Int `json:"baseFeePerGas"` // EIP-1559
ExcessBlobGas *uint64 `json:"excessBlobGas"` // EIP-4844
BlobGasUsed *uint64 `json:"blobGasUsed"` // EIP-4844
BlockAccessListHash *common.Hash `json:"BlockAccessListHash,omitempty"`
SlotNumber *uint64 `json:"slotNumber"` // EIP-7843
}
// 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.
func (g *Genesis) toBlockWithRoot(root common.Hash) *types.Block {
head := &types.Header{
Number: new(big.Int).SetUint64(g.Number),
Nonce: types.EncodeNonce(g.Nonce),
Time: g.Timestamp,
ParentHash: g.ParentHash,
Extra: g.ExtraData,
GasLimit: g.GasLimit,
GasUsed: g.GasUsed,
BaseFee: g.BaseFee,
Difficulty: g.Difficulty,
MixDigest: g.Mixhash,
Coinbase: g.Coinbase,
Root: root,
Number: new(big.Int).SetUint64(g.Number),
Nonce: types.EncodeNonce(g.Nonce),
Time: g.Timestamp,
ParentHash: g.ParentHash,
Extra: g.ExtraData,
GasLimit: g.GasLimit,
GasUsed: g.GasUsed,
BaseFee: g.BaseFee,
Difficulty: g.Difficulty,
MixDigest: g.Mixhash,
Coinbase: g.Coinbase,
BlockAccessListHash: g.BlockAccessListHash,
Root: root,
}
if g.GasLimit == 0 {
head.GasLimit = params.GenesisGasLimit

View file

@ -32,7 +32,6 @@ import (
"github.com/ethereum/go-ethereum/crypto/keccak"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/holiman/uint256"
)
// Tests block header storage and retrieval operations.
@ -906,13 +905,17 @@ func makeTestBAL(t *testing.T) (rlp.RawValue, *bal.BlockAccessList) {
t.Helper()
cb := bal.NewConstructionBlockAccessList()
addr := common.HexToAddress("0x1111111111111111111111111111111111111111")
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)
/*
TODO MariusVanDerWijden fix after rebase
addr := common.HexToAddress("0x1111111111111111111111111111111111111111")
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
if err := cb.EncodeRLP(&buf); err != nil {
t.Fatalf("failed to encode BAL: %v", err)

View file

@ -23,6 +23,8 @@ import (
"slices"
"time"
"github.com/ethereum/go-ethereum/core/types/bal"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"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
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.
trie Trie // storage trie, which becomes non-nil on first access
code []byte // contract bytecode, which gets set when code is loaded
@ -75,6 +80,9 @@ type stateObject struct {
// Cache flags.
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
// account is still accessible in the scope of same transaction.
selfDestructed bool
@ -107,6 +115,8 @@ func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *s
dirtyStorage: make(Storage),
pendingStorage: 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
// 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))
for key, value := range s.dirtyStorage {
if origin, exist := s.uncommittedStorage[key]; exist && origin == value {
// non-parallel-execution:
// The slot is reverted to its original value, delete the entry
// 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)
} else if exist {
// non-parallel-execution:
// 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 {
// The slot is different from its original value and hasn't been
// 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
}
// 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
// by EIP-6780. For non-newly-created objects, it's a no-op.
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
@ -511,6 +572,7 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject {
dirtyCode: s.dirtyCode,
selfDestructed: s.selfDestructed,
newContract: s.newContract,
txPreBalance: s.txPreBalance.Clone(),
}
switch s.trie.(type) {
@ -587,13 +649,26 @@ func (s *stateObject) SetCode(codeHash common.Hash, code []byte) (prev []byte) {
prev = slices.Clone(s.code)
s.db.journal.setCode(s.address, prev)
s.setCode(codeHash, code)
return prev
}
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.data.CodeHash = codeHash[:]
s.dirtyCode = true
}
func (s *stateObject) SetNonce(nonce uint64) {

View file

@ -27,11 +27,12 @@ import (
"sync/atomic"
"time"
"github.com/ethereum/go-ethereum/core/types/bal"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/stateless"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/types/bal"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
@ -193,6 +194,7 @@ func NewWithReader(root common.Hash, db Database, reader Reader) (*StateDB, erro
journal: newJournal(),
accessList: newAccessList(),
transientStorage: newTransientStorage(),
stateReadList: bal.NewStateAccessList(),
}
if db.Type().Is(TypeUBT) {
sdb.accessEvents = NewAccessEvents()
@ -200,6 +202,13 @@ func NewWithReader(root common.Hash, db Database, reader Reader) (*StateDB, erro
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
// state trie concurrently while the state is mutated so that when we reach the
// 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
// 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.
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))
mutations = bal.NewStateMutations()
for addr := range s.journal.dirties {
obj, exist := s.stateObjects[addr]
if !exist {
@ -822,8 +832,18 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) *bal.StateAccessList {
if _, ok := s.stateObjectsDestruct[obj.address]; !ok {
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 {
obj.finalise()
mut := obj.finalise()
if mut != nil {
mutations.Set(addr, mut)
}
s.markUpdate(addr)
}
// 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.
s.clearJournalAndRefund()
return s.stateReadList
return s.stateReadList, mutations
}
// IntermediateRoot computes the current root hash of the state trie.

View file

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

View file

@ -21,6 +21,8 @@ import (
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/core/types/bal"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
"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.
func (p *StateProcessor) Process(ctx context.Context, block *types.Block, statedb *state.StateDB, cfg vm.Config) (*ProcessResult, error) {
var (
config = p.chainConfig()
receipts = make(types.Receipts, 0, len(block.Transactions()))
header = block.Header()
blockHash = block.Hash()
blockNumber = block.Number()
allLogs []*types.Log
gp = NewGasPool(block.GasLimit())
config = p.chainConfig()
receipts = make(types.Receipts, 0, len(block.Transactions()))
header = block.Header()
blockHash = block.Hash()
blockNumber = block.Number()
allLogs []*types.Log
gp = NewGasPool(block.GasLimit())
computedAccessList = bal.NewConstructionBlockAccessList()
isAmsterdam = p.chainConfig().IsAmsterdam(block.Number(), block.Time())
)
var tracingStateDB = vm.StateDB(statedb)
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)
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()) {
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
@ -108,60 +120,81 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated
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 {
spanEnd(&err)
return nil, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
}
receipts = append(receipts, receipt)
allLogs = append(allLogs, receipt.Logs...)
if isAmsterdam {
computedAccessList.AddTransactionMutations(mutations, i)
computedAccessList.AddAccesses(accesses)
}
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 {
return nil, err
}
postMut.Merge(postExecMut)
// 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{
Receipts: receipts,
Requests: requests,
Logs: allLogs,
GasUsed: gp.Used(),
Receipts: receipts,
Requests: requests,
Logs: allLogs,
GasUsed: gp.Used(),
AccessList: computedAccessList,
}, nil
}
// 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")
defer spanEnd(&err)
mutations = bal.NewStateMutations()
accesses = bal.NewStateAccessList()
// Read requests if Prague is enabled.
if config.IsPrague(block.Number(), block.Time()) {
requests = [][]byte{}
// EIP-6110
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
if err := ProcessWithdrawalQueue(&requests, evm); err != nil {
return requests, fmt.Errorf("failed to process withdrawal queue: %w", err)
if accesses, mutations, err = ProcessWithdrawalQueue(&requests, evm); err != nil {
return nil, nil, requests, fmt.Errorf("failed to process withdrawal queue: %w", err)
}
// EIP-7251
if err := ProcessConsolidationQueue(&requests, evm); err != nil {
return requests, fmt.Errorf("failed to process consolidation queue: %w", err)
consolidationAccesses, consolidationMutations, err := ProcessConsolidationQueue(&requests, evm)
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
// and uses the input parameters for its environment similar to ApplyTransaction. However,
// this method takes an already created EVM instance as input.
func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, blockTime uint64, tx *types.Transaction, evm *vm.EVM) (receipt *types.Receipt, err error) {
func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, blockTime uint64, tx *types.Transaction, evm *vm.EVM) (accesses *bal.StateAccessList, mutations *bal.StateMutations, receipt *types.Receipt, err error) {
if hooks := evm.Config.Tracer; hooks != nil {
if hooks.OnTxStart != nil {
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).
result, err := ApplyMessage(evm, msg, gp)
if err != nil {
return nil, err
return nil, nil, nil, err
}
// Update the state with pending changes.
var root []byte
if evm.ChainConfig().IsByzantium(blockNumber) {
evm.StateDB.Finalise(true)
accesses, mutations = evm.StateDB.Finalise(true)
} else {
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) {
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.
@ -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
// for the transaction and an error if the transaction failed,
// indicating the block was invalid.
func ApplyTransaction(evm *vm.EVM, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction) (*types.Receipt, error) {
func ApplyTransaction(evm *vm.EVM, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction) (*bal.StateAccessList, *bal.StateMutations, *types.Receipt, error) {
msg, err := TransactionToMessage(tx, types.MakeSigner(evm.ChainConfig(), header.Number, header.Time), header.BaseFee)
if err != nil {
return nil, err
return nil, nil, nil, err
}
// Create a new context to be used in the EVM environment
return ApplyTransactionWithEVM(msg, gp, statedb, header.Number, header.Hash(), header.Time, tx, evm)
@ -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
// 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 {
onSystemCallStart(tracer, evm.GetVMContext())
if tracer.OnSystemCallEnd != nil {
@ -264,12 +297,12 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, evm *vm.EVM) {
if evm.StateDB.AccessEvents() != nil {
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
// 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 {
onSystemCallStart(tracer, evm.GetVMContext())
if tracer.OnSystemCallEnd != nil {
@ -294,22 +327,22 @@ func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM) {
if evm.StateDB.AccessEvents() != nil {
evm.StateDB.AccessEvents().Merge(evm.AccessEvents)
}
evm.StateDB.Finalise(true)
return evm.StateDB.Finalise(true)
}
// ProcessWithdrawalQueue calls the EIP-7002 withdrawal queue contract.
// It returns the opaque request data returned by the contract.
func ProcessWithdrawalQueue(requests *[][]byte, evm *vm.EVM) error {
func ProcessWithdrawalQueue(requests *[][]byte, evm *vm.EVM) (*bal.StateAccessList, *bal.StateMutations, error) {
return processRequestsSystemCall(requests, evm, 0x01, params.WithdrawalQueueAddress)
}
// ProcessConsolidationQueue calls the EIP-7251 consolidation queue contract.
// It returns the opaque request data returned by the contract.
func ProcessConsolidationQueue(requests *[][]byte, evm *vm.EVM) error {
func ProcessConsolidationQueue(requests *[][]byte, evm *vm.EVM) (*bal.StateAccessList, *bal.StateMutations, error) {
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 {
onSystemCallStart(tracer, evm.GetVMContext())
if tracer.OnSystemCallEnd != nil {
@ -330,19 +363,19 @@ func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte
if evm.StateDB.AccessEvents() != nil {
evm.StateDB.AccessEvents().Merge(evm.AccessEvents)
}
evm.StateDB.Finalise(true)
accesses, mutations := evm.StateDB.Finalise(true)
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 {
return nil // skip empty output
return accesses, mutations, nil // skip empty output
}
// Append prefixed requestsData to the requests list.
requestsData := make([]byte, len(ret)+1)
requestsData[0] = requestType
copy(requestsData[1:], ret)
*requests = append(*requests, requestsData)
return nil
return accesses, mutations, nil
}
var depositTopic = common.HexToHash("0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5")

View file

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

View file

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

View file

@ -92,3 +92,18 @@ func (s *StateAccessList) Copy() *StateAccessList {
}
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 (
"bytes"
"encoding/json"
"maps"
"github.com/ethereum/go-ethereum/common"
"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
// access list during execution.
type ConstructionAccountAccess struct {
type ConstructionAccountAccesses struct {
// 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
// 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
// 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.
StorageReads map[common.Hash]struct{} `json:"storageReads,omitempty"`
StorageReads map[common.Hash]struct{}
// BalanceChanges contains the post-transaction balances of an account,
// 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
// 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
// by tx index.
CodeChange map[uint16][]byte `json:"codeChange,omitempty"`
CodeChanges map[uint16][]byte
}
// NewConstructionAccountAccess initializes the account access object.
func NewConstructionAccountAccess() *ConstructionAccountAccess {
return &ConstructionAccountAccess{
func (c *ConstructionAccountAccesses) Copy() (res ConstructionAccountAccesses) {
if c.StorageWrites != nil {
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),
StorageReads: make(map[common.Hash]struct{}),
BalanceChanges: make(map[uint16]*uint256.Int),
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
// in execution (account addresses and storage keys).
type ConstructionBlockAccessList struct {
Accounts map[common.Address]*ConstructionAccountAccess
type StorageMutations map[common.Hash]common.Hash
// AccountMutations contains mutations that were made to an account across
// 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.
func NewConstructionBlockAccessList() ConstructionBlockAccessList {
return ConstructionBlockAccessList{
Accounts: make(map[common.Address]*ConstructionAccountAccess),
}
// String returns a human-readable JSON representation of the account mutations.
func (a *AccountMutations) String() string {
var res bytes.Buffer
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.
func (b *ConstructionBlockAccessList) AccountRead(addr common.Address) {
if _, ok := b.Accounts[addr]; !ok {
b.Accounts[addr] = NewConstructionAccountAccess()
// Copy returns a deep-copy of the instance.
func (a *AccountMutations) Copy() *AccountMutations {
res := &AccountMutations{
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.
func (b *ConstructionBlockAccessList) StorageRead(address common.Address, key common.Hash) {
if _, ok := b.Accounts[address]; !ok {
b.Accounts[address] = NewConstructionAccountAccess()
}
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
// Copy returns a deep copy of the access list
func (e BlockAccessList) Copy() *BlockAccessList {
var res BlockAccessList
for _, accountAccess := range e {
res = append(res, accountAccess.Copy())
}
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 (
"bytes"
"cmp"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
@ -28,35 +30,132 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"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
// the spec format.
// BlockAccessList is the encoding format of ConstructionBlockAccessList.
type BlockAccessList struct {
Accesses []AccountAccess `ssz-max:"300000"`
// BlockAccessList is the encoding format of AccessListBuilder.
type BlockAccessList []AccountAccess
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
// max code size.
func (e *BlockAccessList) Validate() error {
if !slices.IsSortedFunc(e.Accesses, func(a, b AccountAccess) int {
// * the total accounts and storage slots in the access list exceed the protocol max
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 errors.New("block access list accounts not in lexicographic order")
}
for _, entry := range e.Accesses {
if err := entry.validate(); err != nil {
// check that the accounts are unique
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
}
}
// 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
}
@ -70,53 +169,135 @@ func (e *BlockAccessList) Hash() common.Hash {
// under reasonable conditions.
panic(err)
}
/*
bal, err := json.MarshalIndent(e.StringableRepresentation(), "", " ")
if err != nil {
panic(err)
}
*/
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.
type encodingBalanceChange struct {
TxIdx uint16 `ssz-size:"2"`
Balance [16]byte `ssz-size:"16"`
TxIdx uint16 `json:"txIndex"`
Balance *uint256.Int `json:"balance"`
}
// encodingAccountNonce is the encoding format of NonceChange.
type encodingAccountNonce struct {
TxIdx uint16 `ssz-size:"2"`
Nonce uint64 `ssz-size:"8"`
TxIdx uint16 `json:"txIndex"`
Nonce uint64 `json:"nonce"`
}
// encodingStorageWrite is the encoding format of StorageWrites.
type encodingStorageWrite struct {
TxIdx uint16
ValueAfter [32]byte `ssz-size:"32"`
TxIdx uint16 `json:"txIndex"`
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.
type encodingSlotWrites struct {
Slot [32]byte `ssz-size:"32"`
Accesses []encodingStorageWrite `ssz-max:"300000"`
Slot *EncodedStorage `json:"slot"`
Accesses []encodingStorageWrite `json:"accesses"`
}
// validate returns an instance of the encoding-representation slot writes in
// working representation.
func (e *encodingSlotWrites) validate() error {
if slices.IsSortedFunc(e.Accesses, func(a, b encodingStorageWrite) int {
func (e *encodingSlotWrites) validate(blockTxCount int) error {
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 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
@ -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
}
// AccountAccess is the encoding format of ConstructionAccountAccess.
// AccountAccess is the encoding format of ConstructionAccountAccesses.
type AccountAccess struct {
Address [20]byte `ssz-size:"20"` // 20-byte Ethereum address
StorageWrites []encodingSlotWrites `ssz-max:"300000"` // Storage changes (slot -> [tx_index -> new_value])
StorageReads [][32]byte `ssz-max:"300000"` // Read-only storage keys
BalanceChanges []encodingBalanceChange `ssz-max:"300000"` // Balance changes ([tx_index -> post_balance])
NonceChanges []encodingAccountNonce `ssz-max:"300000"` // Nonce changes ([tx_index -> new_nonce])
CodeChanges []encodingCodeChange `ssz-max:"300000"` // Code changes ([tx_index -> new_code])
Address common.Address `json:"address,omitempty"` // 20-byte Ethereum address
StorageChanges []encodingSlotWrites `json:"storageChanges,omitempty"` // EncodedStorage changes (slot -> [tx_index -> new_value])
StorageReads []*EncodedStorage `json:"storageReads,omitempty"` // Read-only storage keys
BalanceChanges []encodingBalanceChange `json:"balanceChanges,omitempty"` // Balance changes ([tx_index -> post_balance])
NonceChanges []encodingAccountNonce `json:"nonceChanges,omitempty"` // Nonce changes ([tx_index -> new_nonce])
CodeChanges []encodingCodeChange `json:"code,omitempty"` // CodeChanges changes ([tx_index -> new_code])
}
// validate converts the account accesses out of encoding format.
// If any of the keys in the encoding object are not ordered according to the
// spec, an error is returned.
func (e *AccountAccess) validate() error {
func (e *AccountAccess) validate(blockTxCount int) error {
// Check the storage write slots are sorted in order
if !slices.IsSortedFunc(e.StorageWrites, func(a, b encodingSlotWrites) int {
return bytes.Compare(a.Slot[:], b.Slot[:])
if !slices.IsSortedFunc(e.StorageChanges, func(a, b encodingSlotWrites) int {
aHash, bHash := a.Slot.ToHash(), b.Slot.ToHash()
return bytes.Compare(aHash[:], bHash[:])
}) {
return errors.New("storage writes slots not in lexicographic order")
}
for _, write := range e.StorageWrites {
if err := write.validate(); err != nil {
for _, write := range e.StorageChanges {
if err := write.validate(blockTxCount); err != nil {
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
if !slices.IsSortedFunc(e.StorageReads, func(a, b [32]byte) int {
return bytes.Compare(a[:], b[:])
if !slices.IsSortedFunc(e.StorageReads, func(a, b *EncodedStorage) int {
aHash, bHash := a.ToHash(), b.ToHash()
return bytes.Compare(aHash[:], bHash[:])
}) {
return errors.New("storage read slots not in lexicographic 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 {
return cmp.Compare[uint16](a.TxIdx, b.TxIdx)
}) {
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
// and that none of them report an index above what is allowed
if !slices.IsSortedFunc(e.NonceChanges, func(a, b encodingAccountNonce) int {
return cmp.Compare[uint16](a.TxIdx, b.TxIdx)
}) {
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 {
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 {
// TODO(rjl493456442): This check should be fork-aware, since the limit may
// differ across forks.
if len(change.Code) > params.MaxCodeSize {
return errors.New("code change contained oversized code")
if len(e.CodeChanges) > 0 && int(e.CodeChanges[len(e.CodeChanges)-1].TxIndex) >= blockTxCount+2 {
return errors.New("highest code change index beyond what is allowed")
}
for i, codeChange := range e.CodeChanges {
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
@ -196,41 +433,35 @@ func (e *AccountAccess) Copy() AccountAccess {
StorageReads: slices.Clone(e.StorageReads),
BalanceChanges: slices.Clone(e.BalanceChanges),
NonceChanges: slices.Clone(e.NonceChanges),
StorageWrites: make([]encodingSlotWrites, 0, len(e.StorageWrites)),
CodeChanges: make([]encodingCodeChange, 0, len(e.CodeChanges)),
}
for _, storageWrite := range e.StorageWrites {
res.StorageWrites = append(res.StorageWrites, encodingSlotWrites{
for _, storageWrite := range e.StorageChanges {
res.StorageChanges = append(res.StorageChanges, encodingSlotWrites{
Slot: storageWrite.Slot,
Accesses: slices.Clone(storageWrite.Accesses),
})
}
for _, codeChange := range e.CodeChanges {
res.CodeChanges = append(res.CodeChanges, encodingCodeChange{
TxIndex: codeChange.TxIndex,
Code: bytes.Clone(codeChange.Code),
})
res.CodeChanges = append(res.CodeChanges,
encodingCodeChange{
codeChange.TxIndex,
bytes.Clone(codeChange.Code),
})
}
return res
}
// EncodeRLP returns the RLP-encoded access list
func (b *ConstructionBlockAccessList) EncodeRLP(wr io.Writer) error {
return b.toEncodingObj().EncodeRLP(wr)
func (c ConstructionBlockAccessList) EncodeRLP(wr io.Writer) error {
return c.ToEncodingObj().EncodeRLP(wr)
}
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.
func (a *ConstructionAccountAccess) toEncodingObj(addr common.Address) AccountAccess {
func (a *ConstructionAccountAccesses) toEncodingObj(addr common.Address) AccountAccess {
res := AccountAccess{
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)),
Address: addr,
}
// Convert write slots
@ -238,7 +469,7 @@ func (a *ConstructionAccountAccess) toEncodingObj(addr common.Address) AccountAc
slices.SortFunc(writeSlots, common.Hash.Cmp)
for _, slot := range writeSlots {
var obj encodingSlotWrites
obj.Slot = slot
obj.Slot = NewEncodedStorageFromHash(slot)
slotWrites := a.StorageWrites[slot]
obj.Accesses = make([]encodingStorageWrite, 0, len(slotWrites))
@ -248,17 +479,17 @@ func (a *ConstructionAccountAccess) toEncodingObj(addr common.Address) AccountAc
for _, index := range indices {
obj.Accesses = append(obj.Accesses, encodingStorageWrite{
TxIdx: index,
ValueAfter: slotWrites[index],
ValueAfter: NewEncodedStorageFromHash(slotWrites[index]),
})
}
res.StorageWrites = append(res.StorageWrites, obj)
res.StorageChanges = append(res.StorageChanges, obj)
}
// Convert read slots
readSlots := slices.Collect(maps.Keys(a.StorageReads))
slices.SortFunc(readSlots, common.Hash.Cmp)
for _, slot := range readSlots {
res.StorageReads = append(res.StorageReads, slot)
res.StorageReads = append(res.StorageReads, NewEncodedStorageFromHash(slot))
}
// Convert balance changes
@ -267,7 +498,7 @@ func (a *ConstructionAccountAccess) toEncodingObj(addr common.Address) AccountAc
for _, idx := range balanceIndices {
res.BalanceChanges = append(res.BalanceChanges, encodingBalanceChange{
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
codeIndices := slices.Collect(maps.Keys(a.CodeChange))
slices.SortFunc(codeIndices, cmp.Compare[uint16])
for _, idx := range codeIndices {
codeChangeIdxs := slices.Collect(maps.Keys(a.CodeChanges))
slices.SortFunc(codeChangeIdxs, cmp.Compare[uint16])
for _, idx := range codeChangeIdxs {
res.CodeChanges = append(res.CodeChanges, encodingCodeChange{
TxIndex: idx,
Code: a.CodeChange[idx],
idx,
bytes.Clone(a.CodeChanges[idx]),
})
}
return res
}
// toEncodingObj returns an instance of the access list expressed as the type
// ToEncodingObj returns an instance of the access list expressed as the type
// which is used as input for the encoding/decoding.
func (b *ConstructionBlockAccessList) toEncodingObj() *BlockAccessList {
func (c *ConstructionBlockAccessList) ToEncodingObj() *BlockAccessList {
if c == nil {
return nil
}
var addresses []common.Address
for addr := range b.Accounts {
for addr := range c.list {
addresses = append(addresses, addr)
}
slices.SortFunc(addresses, common.Address.Cmp)
var res BlockAccessList
for _, addr := range addresses {
res.Accesses = append(res.Accesses, b.Accounts[addr].toEncodingObj(addr))
res = append(res, c.list[addr].toEncodingObj(addr))
}
return &res
}
func (e *BlockAccessList) PrettyPrint() string {
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
}
type ContractCode []byte

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
import "github.com/ethereum/go-ethereum/common"
import "github.com/ethereum/go-ethereum/rlp"
import "github.com/holiman/uint256"
import "io"
func (obj *BlockAccessList) EncodeRLP(_w io.Writer) error {
func (obj *AccountAccess) EncodeRLP(_w io.Writer) error {
w := rlp.NewEncoderBuffer(_w)
_tmp0 := w.List()
w.WriteBytes(obj.Address[:])
_tmp1 := w.List()
for _, _tmp2 := range obj.Accesses {
for _, _tmp2 := range obj.StorageChanges {
_tmp3 := w.List()
w.WriteBytes(_tmp2.Address[:])
if err := _tmp2.Slot.EncodeRLP(w); err != nil {
return err
}
_tmp4 := w.List()
for _, _tmp5 := range _tmp2.StorageWrites {
for _, _tmp5 := range _tmp2.Accesses {
_tmp6 := w.List()
w.WriteBytes(_tmp5.Slot[:])
_tmp7 := w.List()
for _, _tmp8 := range _tmp5.Accesses {
_tmp9 := w.List()
w.WriteUint64(uint64(_tmp8.TxIdx))
w.WriteBytes(_tmp8.ValueAfter[:])
w.ListEnd(_tmp9)
w.WriteUint64(uint64(_tmp5.TxIdx))
if err := _tmp5.ValueAfter.EncodeRLP(w); err != nil {
return err
}
w.ListEnd(_tmp7)
w.ListEnd(_tmp6)
}
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(_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)
return w.Flush()
}
func (obj *BlockAccessList) DecodeRLP(dec *rlp.Stream) error {
var _tmp0 BlockAccessList
func (obj *AccountAccess) DecodeRLP(dec *rlp.Stream) error {
var _tmp0 AccountAccess
{
if _, err := dec.List(); err != nil {
return err
}
// Accesses:
var _tmp1 []AccountAccess
// Address:
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 {
return err
}
for dec.MoreDataInList() {
var _tmp2 AccountAccess
var _tmp3 encodingSlotWrites
{
if _, err := dec.List(); err != nil {
return err
}
// Address:
var _tmp3 [20]byte
if err := dec.ReadBytes(_tmp3[:]); err != nil {
// Slot:
_tmp4 := new(EncodedStorage)
if err := _tmp4.DecodeRLP(dec); err != nil {
return err
}
_tmp2.Address = _tmp3
// StorageWrites:
var _tmp4 []encodingSlotWrites
_tmp3.Slot = _tmp4
// Accesses:
var _tmp5 []encodingStorageWrite
if _, err := dec.List(); err != nil {
return err
}
for dec.MoreDataInList() {
var _tmp5 encodingSlotWrites
{
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
var _tmp6 encodingStorageWrite
{
if _, err := dec.List(); err != nil {
return err
}
// TxIdx:
_tmp15, err := dec.Uint16()
_tmp7, err := dec.Uint16()
if err != nil {
return err
}
_tmp14.TxIdx = _tmp15
// Balance:
var _tmp16 [16]byte
if err := dec.ReadBytes(_tmp16[:]); err != nil {
_tmp6.TxIdx = _tmp7
// ValueAfter:
_tmp8 := new(EncodedStorage)
if err := _tmp8.DecodeRLP(dec); err != nil {
return err
}
_tmp14.Balance = _tmp16
_tmp6.ValueAfter = _tmp8
if err := dec.ListEnd(); err != nil {
return err
}
}
_tmp13 = append(_tmp13, _tmp14)
_tmp5 = append(_tmp5, _tmp6)
}
if err := dec.ListEnd(); err != nil {
return err
}
_tmp2.BalanceChanges = _tmp13
// 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
_tmp3.Accesses = _tmp5
if err := dec.ListEnd(); err != nil {
return err
}
}
_tmp1 = append(_tmp1, _tmp2)
_tmp2 = append(_tmp2, _tmp3)
}
if err := dec.ListEnd(); err != nil {
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 {
return err
}

View file

@ -25,6 +25,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/internal/testrand"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/holiman/uint256"
)
@ -36,9 +37,9 @@ func equalBALs(a *BlockAccessList, b *BlockAccessList) bool {
return true
}
func makeTestConstructionBAL() *ConstructionBlockAccessList {
return &ConstructionBlockAccessList{
map[common.Address]*ConstructionAccountAccess{
func makeTestConstructionBAL() ConstructionBlockAccessList {
return ConstructionBlockAccessList{
list: map[common.Address]*ConstructionAccountAccesses{
common.BytesToAddress([]byte{0xff, 0xff}): {
StorageWrites: map[common.Hash]map[uint16]common.Hash{
common.BytesToHash([]byte{0x01}): {
@ -60,9 +61,7 @@ func makeTestConstructionBAL() *ConstructionBlockAccessList {
1: 2,
2: 6,
},
CodeChange: map[uint16][]byte{
0: common.Hex2Bytes("deadbeef"),
},
CodeChanges: map[uint16][]byte{0: common.Hex2Bytes("deadbeef")},
},
common.BytesToAddress([]byte{0xff, 0xff, 0xff}): {
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}),
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{}{
common.BytesToHash([]byte{1, 2, 3, 4, 5, 6, 7, 8}): {},
@ -84,11 +80,9 @@ func makeTestConstructionBAL() *ConstructionBlockAccessList {
NonceChanges: map[uint16]uint64{
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 {
t.Fatalf("decoding failed: %v\n", err)
}
if dec.Hash() != bal.toEncodingObj().Hash() {
if dec.Hash() != bal.ToEncodingObj().Hash() {
t.Fatalf("encoded block hash doesn't match decoded")
}
if !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")
}
}
@ -115,18 +111,18 @@ func TestBALEncoding(t *testing.T) {
func makeTestAccountAccess(sort bool) AccountAccess {
var (
storageWrites []encodingSlotWrites
storageReads [][32]byte
storageReads []common.Hash
balances []encodingBalanceChange
nonces []encodingAccountNonce
)
for i := 0; i < 5; i++ {
slot := encodingSlotWrites{
Slot: testrand.Hash(),
Slot: NewEncodedStorageFromHash(testrand.Hash()),
}
for j := 0; j < 3; j++ {
slot.Accesses = append(slot.Accesses, encodingStorageWrite{
TxIdx: uint16(2 * j),
ValueAfter: testrand.Hash(),
TxIdx: uint16(i*3 + j),
ValueAfter: NewEncodedStorageFromHash(testrand.Hash()),
})
}
if sort {
@ -138,7 +134,7 @@ func makeTestAccountAccess(sort bool) AccountAccess {
}
if sort {
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())
}
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[:])
})
}
for i := 0; i < 5; i++ {
balances = append(balances, encodingBalanceChange{
TxIdx: uint16(2 * i),
Balance: [16]byte(testrand.Bytes(16)),
TxIdx: uint16(i),
Balance: new(uint256.Int).SetBytes(testrand.Bytes(32)),
})
}
if sort {
@ -165,7 +161,7 @@ func makeTestAccountAccess(sort bool) AccountAccess {
for i := 0; i < 5; i++ {
nonces = append(nonces, encodingAccountNonce{
TxIdx: uint16(2 * i),
TxIdx: uint16(i),
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{
Address: [20]byte(testrand.Bytes(20)),
StorageWrites: storageWrites,
StorageReads: storageReads,
StorageChanges: storageWrites,
StorageReads: encodedStorageReads,
BalanceChanges: balances,
NonceChanges: nonces,
CodeChanges: []encodingCodeChange{
{
TxIndex: 100,
TxIndex: 3,
Code: testrand.Bytes(256),
},
},
}
}
func makeTestBAL(sort bool) *BlockAccessList {
list := &BlockAccessList{}
func makeTestBAL(sort bool) BlockAccessList {
list := BlockAccessList{}
for i := 0; i < 5; i++ {
list.Accesses = append(list.Accesses, makeTestAccountAccess(sort))
list = append(list, makeTestAccountAccess(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[:])
})
}
@ -208,28 +208,29 @@ func TestBlockAccessListCopy(t *testing.T) {
cpy := list.Copy()
cpyCpy := cpy.Copy()
if !reflect.DeepEqual(list, cpy) {
if !reflect.DeepEqual(list, *cpy) {
t.Fatal("block access mismatch")
}
if !reflect.DeepEqual(cpy, cpyCpy) {
if !reflect.DeepEqual(*cpy, *cpyCpy) {
t.Fatal("block access mismatch")
}
// Make sure the mutations on copy won't affect the origin
for _, aa := range cpyCpy.Accesses {
for i := 0; i < len(aa.StorageReads); i++ {
aa.StorageReads[i] = [32]byte(testrand.Bytes(32))
for i := range *cpyCpy {
for j := 0; j < len((*cpyCpy)[i].StorageReads); j++ {
(*cpyCpy)[i].StorageReads[j] = NewEncodedStorageFromHash(testrand.Hash())
}
}
if !reflect.DeepEqual(list, cpy) {
if !reflect.DeepEqual(list, *cpy) {
t.Fatal("block access mismatch")
}
}
func TestBlockAccessListValidation(t *testing.T) {
// Validate the block access list after RLP decoding
testBALMaxIndex := 20
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)
}
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 {
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)
}
// Validate the derived block access list
cBAL := makeTestConstructionBAL()
listB := cBAL.toEncodingObj()
if err := listB.Validate(); err != nil {
listB := cBAL.ToEncodingObj()
if err := listB.Validate(testBALMaxIndex, params.MaxGasLimit); err != nil {
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
// 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
access := bal.AccountAccess{
Address: common.HexToAddress("0x01"),
StorageReads: make([][32]byte, n),
StorageReads: make([]*bal.EncodedStorage, n),
}
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.

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
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 {
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)
evm := vm.NewEVM(context, hookedState, tt.genesis.Config, vm.Config{Tracer: tracer.Hooks()})
_, err = core.ApplyTransactionWithEVM(msg, core.NewGasPool(msg.GasLimit), statedb, block.Number(), block.Hash(), block.Time(), tx, evm)
_, _, _, err = core.ApplyTransactionWithEVM(msg, core.NewGasPool(msg.GasLimit), statedb, block.Number(), block.Hash(), block.Time(), tx, evm)
if err != nil {
t.Fatalf("failed to execute transaction: %v", err)
}

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) {
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.
// Parent hash is needed for evm.GetHashFn to work.
header.ParentHash = parent.Hash()
@ -401,11 +404,11 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
return nil, nil, nil, err
}
// EIP-7002
if err := core.ProcessWithdrawalQueue(&requests, evm); err != nil {
if _, _, err := core.ProcessWithdrawalQueue(&requests, evm); err != nil {
return nil, nil, nil, err
}
// EIP-7251
if err := core.ProcessConsolidationQueue(&requests, evm); err != nil {
if _, _, err := core.ProcessConsolidationQueue(&requests, evm); err != nil {
return nil, nil, nil, err
}
}

View file

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

View file

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

View file

@ -194,8 +194,7 @@ const (
StorageCreationSize = 32
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

View file

@ -98,6 +98,7 @@ type btHeader struct {
ExcessBlobGas *uint64
ParentBeaconBlockRoot *common.Hash
SlotNumber *uint64
BlockAccessListHash *common.Hash
}
type btHeaderMarshaling struct {
@ -225,8 +226,10 @@ func (t *BlockTest) genesis(config *params.ChainConfig) *core.Genesis {
Coinbase: t.json.Genesis.Coinbase,
Alloc: t.json.Pre,
BaseFee: t.json.Genesis.BaseFeePerGas,
BlobGasUsed: t.json.Genesis.BlobGasUsed,
ExcessBlobGas: t.json.Genesis.ExcessBlobGas,
BlobGasUsed: t.json.Genesis.BlobGasUsed,
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
ParentBeaconBlockRoot *common.Hash
SlotNumber *math.HexOrDecimal64
BlockAccessListHash *common.Hash
}
var enc btHeader
enc.Bloom = b.Bloom
@ -63,6 +64,7 @@ func (b btHeader) MarshalJSON() ([]byte, error) {
enc.ExcessBlobGas = (*math.HexOrDecimal64)(b.ExcessBlobGas)
enc.ParentBeaconBlockRoot = b.ParentBeaconBlockRoot
enc.SlotNumber = (*math.HexOrDecimal64)(b.SlotNumber)
enc.BlockAccessListHash = b.BlockAccessListHash
return json.Marshal(&enc)
}
@ -91,6 +93,7 @@ func (b *btHeader) UnmarshalJSON(input []byte) error {
ExcessBlobGas *math.HexOrDecimal64
ParentBeaconBlockRoot *common.Hash
SlotNumber *math.HexOrDecimal64
BlockAccessListHash *common.Hash
}
var dec btHeader
if err := json.Unmarshal(input, &dec); err != nil {
@ -162,5 +165,8 @@ func (b *btHeader) UnmarshalJSON(input []byte) error {
if dec.SlotNumber != nil {
b.SlotNumber = (*uint64)(dec.SlotNumber)
}
if dec.BlockAccessListHash != nil {
b.BlockAccessListHash = dec.BlockAccessListHash
}
return nil
}

View file

@ -490,7 +490,7 @@ var Forks = map[string]*params.ChainConfig{
Cancun: params.DefaultCancunBlobConfig,
Prague: params.DefaultPragueBlobConfig,
Osaka: params.DefaultOsakaBlobConfig,
BPO1: bpo1BlobConfig,
BPO1: params.DefaultBPO1BlobConfig,
},
},
"OsakaToBPO1AtTime15k": {
@ -519,7 +519,7 @@ var Forks = map[string]*params.ChainConfig{
Cancun: params.DefaultCancunBlobConfig,
Prague: params.DefaultPragueBlobConfig,
Osaka: params.DefaultOsakaBlobConfig,
BPO1: bpo1BlobConfig,
BPO1: params.DefaultBPO1BlobConfig,
},
},
"BPO2": {
@ -549,8 +549,8 @@ var Forks = map[string]*params.ChainConfig{
Cancun: params.DefaultCancunBlobConfig,
Prague: params.DefaultPragueBlobConfig,
Osaka: params.DefaultOsakaBlobConfig,
BPO1: bpo1BlobConfig,
BPO2: bpo2BlobConfig,
BPO1: params.DefaultBPO1BlobConfig,
BPO2: params.DefaultBPO2BlobConfig,
},
},
"BPO1ToBPO2AtTime15k": {
@ -580,8 +580,8 @@ var Forks = map[string]*params.ChainConfig{
Cancun: params.DefaultCancunBlobConfig,
Prague: params.DefaultPragueBlobConfig,
Osaka: params.DefaultOsakaBlobConfig,
BPO1: bpo1BlobConfig,
BPO2: bpo2BlobConfig,
BPO1: params.DefaultBPO1BlobConfig,
BPO2: params.DefaultBPO2BlobConfig,
},
},
"BPO3": {
@ -612,8 +612,8 @@ var Forks = map[string]*params.ChainConfig{
Cancun: params.DefaultCancunBlobConfig,
Prague: params.DefaultPragueBlobConfig,
Osaka: params.DefaultOsakaBlobConfig,
BPO1: bpo1BlobConfig,
BPO2: bpo2BlobConfig,
BPO1: params.DefaultBPO1BlobConfig,
BPO2: params.DefaultBPO2BlobConfig,
BPO3: params.DefaultBPO3BlobConfig,
},
},
@ -645,8 +645,8 @@ var Forks = map[string]*params.ChainConfig{
Cancun: params.DefaultCancunBlobConfig,
Prague: params.DefaultPragueBlobConfig,
Osaka: params.DefaultOsakaBlobConfig,
BPO1: bpo1BlobConfig,
BPO2: bpo2BlobConfig,
BPO1: params.DefaultBPO1BlobConfig,
BPO2: params.DefaultBPO2BlobConfig,
BPO3: params.DefaultBPO3BlobConfig,
},
},
@ -679,8 +679,8 @@ var Forks = map[string]*params.ChainConfig{
Cancun: params.DefaultCancunBlobConfig,
Prague: params.DefaultPragueBlobConfig,
Osaka: params.DefaultOsakaBlobConfig,
BPO1: bpo1BlobConfig,
BPO2: bpo2BlobConfig,
BPO1: params.DefaultBPO1BlobConfig,
BPO2: params.DefaultBPO2BlobConfig,
BPO3: params.DefaultBPO3BlobConfig,
BPO4: params.DefaultBPO4BlobConfig,
},
@ -714,8 +714,8 @@ var Forks = map[string]*params.ChainConfig{
Cancun: params.DefaultCancunBlobConfig,
Prague: params.DefaultPragueBlobConfig,
Osaka: params.DefaultOsakaBlobConfig,
BPO1: bpo1BlobConfig,
BPO2: bpo2BlobConfig,
BPO1: params.DefaultBPO1BlobConfig,
BPO2: params.DefaultBPO2BlobConfig,
BPO3: params.DefaultBPO3BlobConfig,
BPO4: params.DefaultBPO4BlobConfig,
},
@ -750,11 +750,44 @@ var Forks = map[string]*params.ChainConfig{
Cancun: params.DefaultCancunBlobConfig,
Prague: params.DefaultPragueBlobConfig,
Osaka: params.DefaultOsakaBlobConfig,
BPO1: bpo1BlobConfig,
BPO2: bpo2BlobConfig,
BPO1: params.DefaultBPO1BlobConfig,
BPO2: params.DefaultBPO2BlobConfig,
BPO3: params.DefaultBPO3BlobConfig,
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": {
@ -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
func AvailableForks() []string {
var availableForks []string

View file

@ -82,7 +82,7 @@ func (tt *TransactionTest) Run() error {
}
// Intrinsic cost
// 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 {
return
}