core/types: add optional bal construction in statedb when triggered by a flag

This commit is contained in:
Jared Wasinger 2025-07-02 21:18:54 +09:00
parent b992b105ef
commit b5948eeb38
33 changed files with 363 additions and 105 deletions

View file

@ -89,7 +89,7 @@ func runBlockTest(ctx *cli.Context, fname string) ([]testResult, error) {
continue continue
} }
result := &testResult{Name: name, Pass: true} result := &testResult{Name: name, Pass: true}
if err := tests[name].Run(false, rawdb.HashScheme, ctx.Bool(WitnessCrossCheckFlag.Name), tracer, func(res error, chain *core.BlockChain) { if err := tests[name].Run(false, rawdb.HashScheme, ctx.Bool(WitnessCrossCheckFlag.Name), false, tracer, func(res error, chain *core.BlockChain) {
if ctx.Bool(DumpFlag.Name) { if ctx.Bool(DumpFlag.Name) {
if s, _ := chain.State(); s != nil { if s, _ := chain.State(); s != nil {
result.State = dump(s) result.State = dump(s)

View file

@ -151,6 +151,7 @@ var (
utils.BeaconGenesisTimeFlag, utils.BeaconGenesisTimeFlag,
utils.BeaconCheckpointFlag, utils.BeaconCheckpointFlag,
utils.BeaconCheckpointFileFlag, utils.BeaconCheckpointFileFlag,
utils.BuildBALFlag,
}, utils.NetworkFlags, utils.DatabaseFlags) }, utils.NetworkFlags, utils.DatabaseFlags)
rpcFlags = []cli.Flag{ rpcFlags = []cli.Flag{
@ -338,6 +339,10 @@ func startNode(ctx *cli.Context, stack *node.Node, isConsole bool) {
log.Warn(`The "unlock" flag has been deprecated and has no effect`) log.Warn(`The "unlock" flag has been deprecated and has no effect`)
} }
if ctx.IsSet(utils.BuildBALFlag.Name) {
log.Warn(`block-access-list construction enabled. This is an experimental feature that shouldn't be enabled outside of a Geth development context.'`)
}
// Register wallet event handlers to open and auto-derive wallets // Register wallet event handlers to open and auto-derive wallets
events := make(chan accounts.WalletEvent, 16) events := make(chan accounts.WalletEvent, 16)
stack.AccountManager().Subscribe(events) stack.AccountManager().Subscribe(events)

View file

@ -966,6 +966,13 @@ Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server.
Value: metrics.DefaultConfig.InfluxDBOrganization, Value: metrics.DefaultConfig.InfluxDBOrganization,
Category: flags.MetricsCategory, Category: flags.MetricsCategory,
} }
// Block Access List flags
BuildBALFlag = &cli.BoolFlag{
Name: "buildbal",
Usage: "Enable block-access-list building when executing blocks (experimental)",
Category: flags.MiscCategory,
}
) )
var ( var (
@ -1852,6 +1859,11 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
cfg.VMTraceJsonConfig = ctx.String(VMTraceJsonConfigFlag.Name) cfg.VMTraceJsonConfig = ctx.String(VMTraceJsonConfigFlag.Name)
} }
} }
if ctx.IsSet(BuildBALFlag.Name) {
enabled := ctx.Bool(BuildBALFlag.Name)
cfg.BuildBAL = &enabled
}
} }
// MakeBeaconLightConfig constructs a beacon light client config based on the // MakeBeaconLightConfig constructs a beacon light client config based on the

View file

@ -210,7 +210,7 @@ func testHeaderVerificationForMerging(t *testing.T, isClique bool) {
t.Fatalf("post-block %d: unexpected result returned: %v", i, result) t.Fatalf("post-block %d: unexpected result returned: %v", i, result)
case <-time.After(25 * time.Millisecond): case <-time.After(25 * time.Millisecond):
} }
chain.InsertBlockWithoutSetHead(postBlocks[i], false) chain.InsertBlockWithoutSetHead(postBlocks[i], false, false)
} }
// Verify the blocks with pre-merge blocks and post-merge blocks // Verify the blocks with pre-merge blocks and post-merge blocks

View file

@ -1709,7 +1709,7 @@ func (bc *BlockChain) InsertChain(chain types.Blocks) (int, error) {
} }
defer bc.chainmu.Unlock() defer bc.chainmu.Unlock()
_, n, err := bc.insertChain(chain, true, false) // No witness collection for mass inserts (would get super large) _, n, err := bc.insertChain(chain, true, false, true) // No witness collection for mass inserts (would get super large)
return n, err return n, err
} }
@ -1721,7 +1721,7 @@ func (bc *BlockChain) InsertChain(chain types.Blocks) (int, error) {
// racey behaviour. If a sidechain import is in progress, and the historic state // racey behaviour. If a sidechain import is in progress, and the historic state
// is imported, but then new canon-head is added before the actual sidechain // is imported, but then new canon-head is added before the actual sidechain
// completes, then the historic state could be pruned again // completes, then the historic state could be pruned again
func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness bool) (*stateless.Witness, int, error) { func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness bool, makeBAL bool) (*stateless.Witness, int, error) {
// If the chain is terminating, don't even bother starting up. // If the chain is terminating, don't even bother starting up.
if bc.insertStopped() { if bc.insertStopped() {
return nil, 0, nil return nil, 0, nil
@ -1879,7 +1879,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness
} }
// The traced section of block import. // The traced section of block import.
start := time.Now() start := time.Now()
res, err := bc.processBlock(parent.Root, block, setHead, makeWitness && len(chain) == 1) res, err := bc.processBlock(parent.Root, block, setHead, makeWitness && len(chain) == 1, makeBAL)
if err != nil { if err != nil {
return nil, it.index, err return nil, it.index, err
} }
@ -1947,7 +1947,7 @@ type blockProcessingResult struct {
// processBlock executes and validates the given block. If there was no error // processBlock executes and validates the given block. If there was no error
// it writes the block and associated state to database. // it writes the block and associated state to database.
func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, setHead bool, makeWitness bool) (_ *blockProcessingResult, blockEndErr error) { func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, setHead bool, makeWitness bool, makeBAL bool) (_ *blockProcessingResult, blockEndErr error) {
var ( var (
err error err error
startTime = time.Now() startTime = time.Now()
@ -2006,6 +2006,11 @@ func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, s
}(time.Now(), throwaway, block) }(time.Now(), throwaway, block)
} }
constructBAL := bc.chainConfig.IsByzantium(block.Number()) && makeBAL && bc.cfg.VmConfig.BALConstruction
if constructBAL {
statedb.EnableBALConstruction()
}
// If we are past Byzantium, enable prefetching to pull in trie node paths // If we are past Byzantium, enable prefetching to pull in trie node paths
// while processing transactions. Before Byzantium the prefetcher is mostly // while processing transactions. Before Byzantium the prefetcher is mostly
// useless due to the intermediate root hashing after each transaction. // useless due to the intermediate root hashing after each transaction.
@ -2053,6 +2058,16 @@ func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, s
} }
vtime := time.Since(vstart) vtime := time.Since(vstart)
if constructBAL {
// very ugly... deep-copy the block body before setting the block access
// list on it to prevent mutating the block instance passed by the caller.
existingBody := block.Body()
block = block.WithBody(*existingBody)
existingBody = block.Body()
existingBody.AccessList = statedb.BlockAccessList().ToEncodingObj()
block = block.WithBody(*existingBody)
}
// If witnesses was generated and stateless self-validation requested, do // If witnesses was generated and stateless self-validation requested, do
// that now. Self validation should *never* run in production, it's more of // that now. Self validation should *never* run in production, it's more of
// a tight integration to enable running *all* consensus tests through the // a tight integration to enable running *all* consensus tests through the
@ -2226,7 +2241,7 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator, ma
// memory here. // memory here.
if len(blocks) >= 2048 || memory > 64*1024*1024 { if len(blocks) >= 2048 || memory > 64*1024*1024 {
log.Info("Importing heavy sidechain segment", "blocks", len(blocks), "start", blocks[0].NumberU64(), "end", block.NumberU64()) log.Info("Importing heavy sidechain segment", "blocks", len(blocks), "start", blocks[0].NumberU64(), "end", block.NumberU64())
if _, _, err := bc.insertChain(blocks, true, false); err != nil { if _, _, err := bc.insertChain(blocks, true, false, false); err != nil {
return nil, 0, err return nil, 0, err
} }
blocks, memory = blocks[:0], 0 blocks, memory = blocks[:0], 0
@ -2240,7 +2255,7 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator, ma
} }
if len(blocks) > 0 { if len(blocks) > 0 {
log.Info("Importing sidechain segment", "start", blocks[0].NumberU64(), "end", blocks[len(blocks)-1].NumberU64()) log.Info("Importing sidechain segment", "start", blocks[0].NumberU64(), "end", blocks[len(blocks)-1].NumberU64())
return bc.insertChain(blocks, true, makeWitness) return bc.insertChain(blocks, true, makeWitness, false)
} }
return nil, 0, nil return nil, 0, nil
} }
@ -2289,7 +2304,7 @@ func (bc *BlockChain) recoverAncestors(block *types.Block, makeWitness bool) (co
} else { } else {
b = bc.GetBlock(hashes[i], numbers[i]) b = bc.GetBlock(hashes[i], numbers[i])
} }
if _, _, err := bc.insertChain(types.Blocks{b}, false, makeWitness && i == 0); err != nil { if _, _, err := bc.insertChain(types.Blocks{b}, false, makeWitness && i == 0, false); err != nil {
return b.ParentHash(), err return b.ParentHash(), err
} }
} }
@ -2509,13 +2524,13 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Header) error
// The key difference between the InsertChain is it won't do the canonical chain // The key difference between the InsertChain is it won't do the canonical chain
// updating. It relies on the additional SetCanonical call to finalize the entire // updating. It relies on the additional SetCanonical call to finalize the entire
// procedure. // procedure.
func (bc *BlockChain) InsertBlockWithoutSetHead(block *types.Block, makeWitness bool) (*stateless.Witness, error) { func (bc *BlockChain) InsertBlockWithoutSetHead(block *types.Block, makeWitness bool, makeBAL bool) (*stateless.Witness, error) {
if !bc.chainmu.TryLock() { if !bc.chainmu.TryLock() {
return nil, errChainStopped return nil, errChainStopped
} }
defer bc.chainmu.Unlock() defer bc.chainmu.Unlock()
witness, _, err := bc.insertChain(types.Blocks{block}, false, makeWitness) witness, _, err := bc.insertChain(types.Blocks{block}, false, makeWitness, makeBAL)
return witness, err return witness, err
} }

View file

@ -3456,7 +3456,7 @@ func testSetCanonical(t *testing.T, scheme string) {
gen.AddTx(tx) gen.AddTx(tx)
}) })
for _, block := range side { for _, block := range side {
_, err := chain.InsertBlockWithoutSetHead(block, false) _, err := chain.InsertBlockWithoutSetHead(block, false, false)
if err != nil { if err != nil {
t.Fatalf("Failed to insert into chain: %v", err) t.Fatalf("Failed to insert into chain: %v", err)
} }

View file

@ -681,7 +681,7 @@ func DeveloperGenesisBlock(gasLimit uint64, faucet *common.Address) *Genesis {
}, },
} }
if faucet != nil { if faucet != nil {
genesis.Alloc[*faucet] = types.Account{Balance: new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(9))} genesis.Alloc[*faucet] = types.Account{Balance: new(big.Int).Lsh(big.NewInt(1), 95)}
} }
return genesis return genesis
} }

View file

@ -51,6 +51,10 @@ type stateObject struct {
origin *types.StateAccount // Account original data without any change applied, nil means it was not existent origin *types.StateAccount // Account original data without any change applied, nil means it was not existent
data types.StateAccount // Account data with all mutations applied in the scope of block data types.StateAccount // Account data with all mutations applied in the scope of block
// TODO: figure out whether or not this is first set after system contracts (withdrawals) are applied
txPreBalance *uint256.Int // the account balance at the start of the current transaction
txPreNonce uint64
// Write caches. // Write caches.
trie Trie // storage trie, which becomes non-nil on first access trie Trie // storage trie, which becomes non-nil on first access
code []byte // contract bytecode, which gets set when code is loaded code []byte // contract bytecode, which gets set when code is loaded
@ -102,6 +106,8 @@ func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *s
addrHash: crypto.Keccak256Hash(address[:]), addrHash: crypto.Keccak256Hash(address[:]),
origin: origin, origin: origin,
data: *acct, data: *acct,
txPreBalance: acct.Balance.Clone(),
txPreNonce: acct.Nonce,
originStorage: make(Storage), originStorage: make(Storage),
dirtyStorage: make(Storage), dirtyStorage: make(Storage),
pendingStorage: make(Storage), pendingStorage: make(Storage),
@ -272,6 +278,9 @@ func (s *stateObject) finalise() {
// of the newly-created object as it's no longer eligible for self-destruct // of the newly-created object as it's no longer eligible for self-destruct
// by EIP-6780. For non-newly-created objects, it's a no-op. // by EIP-6780. For non-newly-created objects, it's a no-op.
s.newContract = false s.newContract = false
s.txPreBalance = s.data.Balance.Clone()
s.txPreNonce = s.data.Nonce
} }
// updateTrie is responsible for persisting cached storage changes into the // updateTrie is responsible for persisting cached storage changes into the
@ -493,6 +502,8 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject {
dirtyCode: s.dirtyCode, dirtyCode: s.dirtyCode,
selfDestructed: s.selfDestructed, selfDestructed: s.selfDestructed,
newContract: s.newContract, newContract: s.newContract,
txPreNonce: s.txPreNonce,
txPreBalance: s.txPreBalance.Clone(),
} }
if s.trie != nil { if s.trie != nil {
obj.trie = mustCopyTrie(s.trie) obj.trie = mustCopyTrie(s.trie)

View file

@ -26,6 +26,8 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/ethereum/go-ethereum/core/types/bal"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/core/state/snapshot"
@ -138,6 +140,9 @@ type StateDB struct {
// State witness if cross validation is needed // State witness if cross validation is needed
witness *stateless.Witness witness *stateless.Witness
// block access list, if bal construction is specified
b *bal.ConstructionBlockAccessList
// Measurements gathered during execution for debugging purposes // Measurements gathered during execution for debugging purposes
AccountReads time.Duration AccountReads time.Duration
AccountHashes time.Duration AccountHashes time.Duration
@ -157,6 +162,19 @@ type StateDB struct {
StorageDeleted atomic.Int64 // Number of storage slots deleted during the state transition StorageDeleted atomic.Int64 // Number of storage slots deleted during the state transition
} }
// EnableBALConstruction configures the StateDB instance to construct block
// access lists from state reads/writes recorded during block execution
func (s *StateDB) EnableBALConstruction() {
bal := bal.NewConstructionBlockAccessList()
s.b = &bal
}
// BlockAccessList retrieves the access list that has been constructed
// by the StateDB instance, or nil if BAL construction was not enabled.
func (s *StateDB) BlockAccessList() *bal.ConstructionBlockAccessList {
return s.b
}
// New creates a new state from a given trie. // New creates a new state from a given trie.
func New(root common.Hash, db Database) (*StateDB, error) { func New(root common.Hash, db Database) (*StateDB, error) {
reader, err := db.Reader(root) reader, err := db.Reader(root)
@ -373,6 +391,9 @@ func (s *StateDB) GetCodeHash(addr common.Address) common.Hash {
// GetState retrieves the value associated with the specific key. // GetState retrieves the value associated with the specific key.
func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash { func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash {
stateObject := s.getStateObject(addr) stateObject := s.getStateObject(addr)
if s.b != nil {
s.b.StorageRead(addr, hash)
}
if stateObject != nil { if stateObject != nil {
return stateObject.GetState(hash) return stateObject.GetState(hash)
} }
@ -629,6 +650,12 @@ func (s *StateDB) getOrNewStateObject(addr common.Address) *stateObject {
if obj == nil { if obj == nil {
obj = s.createObject(addr) obj = s.createObject(addr)
} }
if s.b != nil {
// note: when sending a transfer with no value, the target account is loaded here
// we probably want to specify whether this is proper behavior in the EIP
s.b.AccountRead(addr)
}
return obj return obj
} }
@ -765,6 +792,29 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
s.stateObjectsDestruct[obj.address] = obj s.stateObjectsDestruct[obj.address] = obj
} }
} else { } else {
if s.b != nil {
// add written storage keys/values
for key, val := range obj.dirtyStorage {
s.b.StorageWrite(uint16(s.txIndex), obj.address, key, val)
}
// for addresses that changed balance, add the post-change value
if obj.Balance().Cmp(obj.txPreBalance) != 0 {
s.b.BalanceChange(uint16(s.txIndex), obj.address, obj.Balance())
}
// include nonces for any contract-like accounts which incremented them
if common.BytesToHash(obj.CodeHash()) != types.EmptyCodeHash && obj.Nonce() != obj.txPreNonce {
s.b.NonceChange(obj.address, uint16(s.txIndex), obj.Nonce())
}
// include code of created contracts
// TODO: validate that this doesn't trigger on delegated EOAs
if obj.newContract {
s.b.CodeChange(obj.address, uint16(s.txIndex), obj.code)
}
}
obj.finalise() obj.finalise()
s.markUpdate(addr) s.markUpdate(addr)
} }

View file

@ -19,6 +19,8 @@ package state
import ( import (
"math/big" "math/big"
"github.com/ethereum/go-ethereum/core/types/bal"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/stateless" "github.com/ethereum/go-ethereum/core/stateless"
"github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/tracing"
@ -157,6 +159,10 @@ func (s *hookedStateDB) Witness() *stateless.Witness {
return s.inner.Witness() return s.inner.Witness()
} }
func (s *hookedStateDB) BlockAccessList() *bal.ConstructionBlockAccessList {
return s.inner.BlockAccessList()
}
func (s *hookedStateDB) AccessEvents() *AccessEvents { func (s *hookedStateDB) AccessEvents() *AccessEvents {
return s.inner.AccessEvents() return s.inner.AccessEvents()
} }
@ -261,6 +267,10 @@ func (s *hookedStateDB) AddLog(log *types.Log) {
} }
} }
func (s *hookedStateDB) TxIndex() int {
return s.inner.TxIndex()
}
func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) { func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) {
defer s.inner.Finalise(deleteEmptyObjects) defer s.inner.Finalise(deleteEmptyObjects)
if s.hooks.OnBalanceChange == nil { if s.hooks.OnBalanceChange == nil {

View file

@ -82,12 +82,23 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
context = NewEVMBlockContext(header, p.chain, nil) context = NewEVMBlockContext(header, p.chain, nil)
evm := vm.NewEVM(context, tracingStateDB, p.config, cfg) evm := vm.NewEVM(context, tracingStateDB, p.config, cfg)
// process beacon-root and parent block system contracts.
// do not include the storage writes in the BAL:
// * beacon root will be provided as a standalone field in the BAL
// * parent block hash is already in the header field of the block
if statedb.BlockAccessList() != nil {
statedb.BlockAccessList().DisableMutations()
}
if beaconRoot := block.BeaconRoot(); beaconRoot != nil { if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
ProcessBeaconBlockRoot(*beaconRoot, evm) ProcessBeaconBlockRoot(*beaconRoot, evm)
} }
// TODO: set the beacon root on the BAL
if p.config.IsPrague(block.Number(), block.Time()) || p.config.IsVerkle(block.Number(), block.Time()) { if p.config.IsPrague(block.Number(), block.Time()) || p.config.IsVerkle(block.Number(), block.Time()) {
ProcessParentBlockHash(block.ParentHash(), evm) ProcessParentBlockHash(block.ParentHash(), evm)
} }
if statedb.BlockAccessList() != nil {
statedb.BlockAccessList().EnableMutations()
}
// Iterate over and process the individual transactions // Iterate over and process the individual transactions
for i, tx := range block.Transactions() { for i, tx := range block.Transactions() {
@ -104,6 +115,12 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
receipts = append(receipts, receipt) receipts = append(receipts, receipt)
allLogs = append(allLogs, receipt.Logs...) allLogs = append(allLogs, receipt.Logs...)
} }
// don't write post-block state mutations to the BAL to save on size.
// these can be easily computed in BAL verification.
if statedb.BlockAccessList() != nil {
statedb.BlockAccessList().DisableMutations()
}
// Read requests if Prague is enabled. // Read requests if Prague is enabled.
var requests [][]byte var requests [][]byte
if p.config.IsPrague(block.Number(), block.Time()) { if p.config.IsPrague(block.Number(), block.Time()) {

View file

@ -38,26 +38,26 @@ type ConstructionAccountAccess struct {
// StorageWrites is the post-state values of an account's storage slots // StorageWrites is the post-state values of an account's storage slots
// that were modified in a block, keyed by the slot key and the tx index // that were modified in a block, keyed by the slot key and the tx index
// where the modification occurred. // where the modification occurred.
StorageWrites map[common.Hash]map[uint16]common.Hash `json:"storageWrites,omitempty"` StorageWrites map[common.Hash]map[uint16]common.Hash
// StorageReads is the set of slot keys that were accessed during block // StorageReads is the set of slot keys that were accessed during block
// execution. // execution.
// //
// Storage slots which are both read and written (with changed values) // Storage slots which are both read and written (with changed values)
// appear only in StorageWrites. // appear only in StorageWrites.
StorageReads map[common.Hash]struct{} `json:"storageReads,omitempty"` StorageReads map[common.Hash]struct{}
// BalanceChanges contains the post-transaction balances of an account, // BalanceChanges contains the post-transaction balances of an account,
// keyed by transaction indices where it was changed. // keyed by transaction indices where it was changed.
BalanceChanges map[uint16]*uint256.Int `json:"balanceChanges,omitempty"` BalanceChanges map[uint16]*uint256.Int
// NonceChanges contains the post-state nonce values of an account keyed // NonceChanges contains the post-state nonce values of an account keyed
// by tx index. // by tx index.
NonceChanges map[uint16]uint64 `json:"nonceChanges,omitempty"` NonceChanges map[uint16]uint64
// CodeChange is only set for contract accounts which were deployed in // CodeChange is only set for contract accounts which were deployed in
// the block. // the block.
CodeChange *CodeChange `json:"codeChange,omitempty"` CodeChange *CodeChange
} }
// NewConstructionAccountAccess initializes the account access object. // NewConstructionAccountAccess initializes the account access object.
@ -74,6 +74,7 @@ func NewConstructionAccountAccess() *ConstructionAccountAccess {
// in execution (account addresses and storage keys). // in execution (account addresses and storage keys).
type ConstructionBlockAccessList struct { type ConstructionBlockAccessList struct {
Accounts map[common.Address]*ConstructionAccountAccess Accounts map[common.Address]*ConstructionAccountAccess
enabled bool
} }
// NewConstructionBlockAccessList instantiates an empty access list. // NewConstructionBlockAccessList instantiates an empty access list.
@ -83,44 +84,69 @@ func NewConstructionBlockAccessList() ConstructionBlockAccessList {
} }
} }
// EnableMutations enables the modification of the access list via subsequent
// calls to record access events. BALs are mutable by default.
func (c *ConstructionBlockAccessList) EnableMutations() {
c.enabled = true
}
// DisableMutations prevents the mutation of the access list by subsequent calls
// to record access events.
func (c *ConstructionBlockAccessList) DisableMutations() {
c.enabled = false
}
// AccountRead records the address of an account that has been read during execution. // AccountRead records the address of an account that has been read during execution.
func (b *ConstructionBlockAccessList) AccountRead(addr common.Address) { func (c *ConstructionBlockAccessList) AccountRead(addr common.Address) {
if _, ok := b.Accounts[addr]; !ok { if !c.enabled {
b.Accounts[addr] = NewConstructionAccountAccess() return
}
if _, ok := c.Accounts[addr]; !ok {
c.Accounts[addr] = NewConstructionAccountAccess()
} }
} }
// StorageRead records a storage key read during execution. // StorageRead records a storage key read during execution.
func (b *ConstructionBlockAccessList) StorageRead(address common.Address, key common.Hash) { func (c *ConstructionBlockAccessList) StorageRead(address common.Address, key common.Hash) {
if _, ok := b.Accounts[address]; !ok { if !c.enabled {
b.Accounts[address] = NewConstructionAccountAccess()
}
if _, ok := b.Accounts[address].StorageWrites[key]; ok {
return return
} }
b.Accounts[address].StorageReads[key] = struct{}{} if _, ok := c.Accounts[address]; !ok {
c.Accounts[address] = NewConstructionAccountAccess()
}
if _, ok := c.Accounts[address].StorageWrites[key]; ok {
return
}
c.Accounts[address].StorageReads[key] = struct{}{}
} }
// StorageWrite records the post-transaction value of a mutated storage slot. // StorageWrite records the post-transaction value of a mutated storage slot.
// The storage slot is removed from the list of read slots. // The storage slot is removed from the list of read slots.
func (b *ConstructionBlockAccessList) StorageWrite(txIdx uint16, address common.Address, key, value common.Hash) { func (c *ConstructionBlockAccessList) StorageWrite(txIdx uint16, address common.Address, key, value common.Hash) {
if _, ok := b.Accounts[address]; !ok { if !c.enabled {
b.Accounts[address] = NewConstructionAccountAccess() return
} }
if _, ok := b.Accounts[address].StorageWrites[key]; !ok { if _, ok := c.Accounts[address]; !ok {
b.Accounts[address].StorageWrites[key] = make(map[uint16]common.Hash) c.Accounts[address] = NewConstructionAccountAccess()
} }
b.Accounts[address].StorageWrites[key][txIdx] = value if _, ok := c.Accounts[address].StorageWrites[key]; !ok {
c.Accounts[address].StorageWrites[key] = make(map[uint16]common.Hash)
}
c.Accounts[address].StorageWrites[key][txIdx] = value
delete(b.Accounts[address].StorageReads, key) delete(c.Accounts[address].StorageReads, key)
} }
// CodeChange records the code of a newly-created contract. // CodeChange records the code of a newly-created contract.
func (b *ConstructionBlockAccessList) CodeChange(address common.Address, txIndex uint16, code []byte) { func (c *ConstructionBlockAccessList) CodeChange(address common.Address, txIndex uint16, code []byte) {
if _, ok := b.Accounts[address]; !ok { if !c.enabled {
b.Accounts[address] = NewConstructionAccountAccess() return
} }
b.Accounts[address].CodeChange = &CodeChange{ if _, ok := c.Accounts[address]; !ok {
c.Accounts[address] = NewConstructionAccountAccess()
}
c.Accounts[address].CodeChange = &CodeChange{
TxIndex: txIndex, TxIndex: txIndex,
Code: bytes.Clone(code), Code: bytes.Clone(code),
} }
@ -128,32 +154,38 @@ func (b *ConstructionBlockAccessList) CodeChange(address common.Address, txIndex
// NonceChange records tx post-state nonce of any contract-like accounts whose // NonceChange records tx post-state nonce of any contract-like accounts whose
// nonce was incremented. // nonce was incremented.
func (b *ConstructionBlockAccessList) NonceChange(address common.Address, txIdx uint16, postNonce uint64) { func (c *ConstructionBlockAccessList) NonceChange(address common.Address, txIdx uint16, postNonce uint64) {
if _, ok := b.Accounts[address]; !ok { if !c.enabled {
b.Accounts[address] = NewConstructionAccountAccess() return
} }
b.Accounts[address].NonceChanges[txIdx] = postNonce if _, ok := c.Accounts[address]; !ok {
c.Accounts[address] = NewConstructionAccountAccess()
}
c.Accounts[address].NonceChanges[txIdx] = postNonce
} }
// BalanceChange records the post-transaction balance of an account whose // BalanceChange records the post-transaction balance of an account whose
// balance changed. // balance changed.
func (b *ConstructionBlockAccessList) BalanceChange(txIdx uint16, address common.Address, balance *uint256.Int) { func (c *ConstructionBlockAccessList) BalanceChange(txIdx uint16, address common.Address, balance *uint256.Int) {
if _, ok := b.Accounts[address]; !ok { if !c.enabled {
b.Accounts[address] = NewConstructionAccountAccess() return
} }
b.Accounts[address].BalanceChanges[txIdx] = balance.Clone() if _, ok := c.Accounts[address]; !ok {
c.Accounts[address] = NewConstructionAccountAccess()
}
c.Accounts[address].BalanceChanges[txIdx] = balance.Clone()
} }
// PrettyPrint returns a human-readable representation of the access list // PrettyPrint returns a human-readable representation of the access list
func (b *ConstructionBlockAccessList) PrettyPrint() string { func (c *ConstructionBlockAccessList) PrettyPrint() string {
enc := b.toEncodingObj() enc := c.ToEncodingObj()
return enc.PrettyPrint() return enc.PrettyPrint()
} }
// Copy returns a deep copy of the access list. // Copy returns a deep copy of the access list.
func (b *ConstructionBlockAccessList) Copy() *ConstructionBlockAccessList { func (c *ConstructionBlockAccessList) Copy() *ConstructionBlockAccessList {
res := NewConstructionBlockAccessList() res := NewConstructionBlockAccessList()
for addr, aa := range b.Accounts { for addr, aa := range c.Accounts {
var aaCopy ConstructionAccountAccess var aaCopy ConstructionAccountAccess
slotWrites := make(map[common.Hash]map[uint16]common.Hash, len(aa.StorageWrites)) slotWrites := make(map[common.Hash]map[uint16]common.Hash, len(aa.StorageWrites))

View file

@ -40,7 +40,7 @@ import (
// BlockAccessList is the encoding format of ConstructionBlockAccessList. // BlockAccessList is the encoding format of ConstructionBlockAccessList.
type BlockAccessList struct { type BlockAccessList struct {
Accesses []AccountAccess `ssz-max:"300000"` Accesses []AccountAccess `ssz-max:"300000" json:"accesses"`
} }
// Validate returns an error if the contents of the access list are not ordered // Validate returns an error if the contents of the access list are not ordered
@ -86,26 +86,26 @@ func encodeBalance(val *uint256.Int) [16]byte {
// encodingBalanceChange is the encoding format of BalanceChange. // encodingBalanceChange is the encoding format of BalanceChange.
type encodingBalanceChange struct { type encodingBalanceChange struct {
TxIdx uint16 `ssz-size:"2"` TxIdx uint16 `ssz-size:"2" json:"txIndex"`
Balance [16]byte `ssz-size:"16"` Balance [16]byte `ssz-size:"16" json:"balance"`
} }
// encodingAccountNonce is the encoding format of NonceChange. // encodingAccountNonce is the encoding format of NonceChange.
type encodingAccountNonce struct { type encodingAccountNonce struct {
TxIdx uint16 `ssz-size:"2"` TxIdx uint16 `ssz-size:"2" json:"txIndex"`
Nonce uint64 `ssz-size:"8"` Nonce uint64 `ssz-size:"8" json:"nonce"`
} }
// encodingStorageWrite is the encoding format of StorageWrites. // encodingStorageWrite is the encoding format of StorageWrites.
type encodingStorageWrite struct { type encodingStorageWrite struct {
TxIdx uint16 TxIdx uint16 `json:"txIndex"`
ValueAfter [32]byte `ssz-size:"32"` ValueAfter common.Hash `ssz-size:"32" json:"valueAfter"`
} }
// encodingStorageWrite is the encoding format of SlotWrites. // encodingStorageWrite is the encoding format of SlotWrites.
type encodingSlotWrites struct { type encodingSlotWrites struct {
Slot [32]byte `ssz-size:"32"` Slot common.Hash `ssz-size:"32" json:"slot"`
Accesses []encodingStorageWrite `ssz-max:"300000"` Accesses []encodingStorageWrite `ssz-max:"300000" json:"accesses"`
} }
// validate returns an instance of the encoding-representation slot writes in // validate returns an instance of the encoding-representation slot writes in
@ -121,12 +121,12 @@ func (e *encodingSlotWrites) validate() error {
// AccountAccess is the encoding format of ConstructionAccountAccess. // AccountAccess is the encoding format of ConstructionAccountAccess.
type AccountAccess struct { type AccountAccess struct {
Address [20]byte `ssz-size:"20"` // 20-byte Ethereum address Address common.Address `ssz-size:"20" json:"address,omitempty"` // 20-byte Ethereum address
StorageWrites []encodingSlotWrites `ssz-max:"300000"` // Storage changes (slot -> [tx_index -> new_value]) StorageWrites []encodingSlotWrites `ssz-max:"300000" json:"storageWrites,omitempty"` // Storage changes (slot -> [tx_index -> new_value])
StorageReads [][32]byte `ssz-max:"300000"` // Read-only storage keys StorageReads []common.Hash `ssz-max:"300000" json:"storageReads,omitempty"` // Read-only storage keys
BalanceChanges []encodingBalanceChange `ssz-max:"300000"` // Balance changes ([tx_index -> post_balance]) BalanceChanges []encodingBalanceChange `ssz-max:"300000" json:"balanceChanges,omitempty"` // Balance changes ([tx_index -> post_balance])
NonceChanges []encodingAccountNonce `ssz-max:"300000"` // Nonce changes ([tx_index -> new_nonce]) NonceChanges []encodingAccountNonce `ssz-max:"300000" json:"nonceChanges,omitempty"` // Nonce changes ([tx_index -> new_nonce])
Code []CodeChange `ssz-max:"1"` // Code changes ([tx_index -> new_code]) Code []CodeChange `ssz-max:"1" json:"code,omitempty"` // Code changes ([tx_index -> new_code])
} }
// validate converts the account accesses out of encoding format. // validate converts the account accesses out of encoding format.
@ -146,7 +146,7 @@ func (e *AccountAccess) validate() error {
} }
// Check the storage read slots are sorted in order // Check the storage read slots are sorted in order
if !slices.IsSortedFunc(e.StorageReads, func(a, b [32]byte) int { if !slices.IsSortedFunc(e.StorageReads, func(a, b common.Hash) int {
return bytes.Compare(a[:], b[:]) return bytes.Compare(a[:], b[:])
}) { }) {
return errors.New("storage read slots not in lexicographic order") return errors.New("storage read slots not in lexicographic order")
@ -201,8 +201,8 @@ func (e *AccountAccess) Copy() AccountAccess {
} }
// EncodeRLP returns the RLP-encoded access list // EncodeRLP returns the RLP-encoded access list
func (b *ConstructionBlockAccessList) EncodeRLP(wr io.Writer) error { func (c *ConstructionBlockAccessList) EncodeRLP(wr io.Writer) error {
return b.toEncodingObj().EncodeRLP(wr) return c.ToEncodingObj().EncodeRLP(wr)
} }
var _ rlp.Encoder = &ConstructionBlockAccessList{} var _ rlp.Encoder = &ConstructionBlockAccessList{}
@ -213,7 +213,7 @@ func (a *ConstructionAccountAccess) toEncodingObj(addr common.Address) AccountAc
res := AccountAccess{ res := AccountAccess{
Address: addr, Address: addr,
StorageWrites: make([]encodingSlotWrites, 0), StorageWrites: make([]encodingSlotWrites, 0),
StorageReads: make([][32]byte, 0), StorageReads: make([]common.Hash, 0),
BalanceChanges: make([]encodingBalanceChange, 0), BalanceChanges: make([]encodingBalanceChange, 0),
NonceChanges: make([]encodingAccountNonce, 0), NonceChanges: make([]encodingAccountNonce, 0),
Code: nil, Code: nil,
@ -279,18 +279,18 @@ func (a *ConstructionAccountAccess) toEncodingObj(addr common.Address) AccountAc
return res return res
} }
// toEncodingObj returns an instance of the access list expressed as the type // ToEncodingObj returns an instance of the access list expressed as the type
// which is used as input for the encoding/decoding. // which is used as input for the encoding/decoding.
func (b *ConstructionBlockAccessList) toEncodingObj() *BlockAccessList { func (c *ConstructionBlockAccessList) ToEncodingObj() *BlockAccessList {
var addresses []common.Address var addresses []common.Address
for addr := range b.Accounts { for addr := range c.Accounts {
addresses = append(addresses, addr) addresses = append(addresses, addr)
} }
slices.SortFunc(addresses, common.Address.Cmp) slices.SortFunc(addresses, common.Address.Cmp)
var res BlockAccessList var res BlockAccessList
for _, addr := range addresses { for _, addr := range addresses {
res.Accesses = append(res.Accesses, b.Accounts[addr].toEncodingObj(addr)) res.Accesses = append(res.Accesses, c.Accounts[addr].toEncodingObj(addr))
} }
return &res return &res
} }

View file

@ -2,6 +2,7 @@
package bal package bal
import "github.com/ethereum/go-ethereum/common"
import "github.com/ethereum/go-ethereum/rlp" import "github.com/ethereum/go-ethereum/rlp"
import "io" import "io"
@ -81,7 +82,7 @@ func (obj *BlockAccessList) DecodeRLP(dec *rlp.Stream) error {
return err return err
} }
// Address: // Address:
var _tmp3 [20]byte var _tmp3 common.Address
if err := dec.ReadBytes(_tmp3[:]); err != nil { if err := dec.ReadBytes(_tmp3[:]); err != nil {
return err return err
} }
@ -98,7 +99,7 @@ func (obj *BlockAccessList) DecodeRLP(dec *rlp.Stream) error {
return err return err
} }
// Slot: // Slot:
var _tmp6 [32]byte var _tmp6 common.Hash
if err := dec.ReadBytes(_tmp6[:]); err != nil { if err := dec.ReadBytes(_tmp6[:]); err != nil {
return err return err
} }
@ -121,7 +122,7 @@ func (obj *BlockAccessList) DecodeRLP(dec *rlp.Stream) error {
} }
_tmp8.TxIdx = _tmp9 _tmp8.TxIdx = _tmp9
// ValueAfter: // ValueAfter:
var _tmp10 [32]byte var _tmp10 common.Hash
if err := dec.ReadBytes(_tmp10[:]); err != nil { if err := dec.ReadBytes(_tmp10[:]); err != nil {
return err return err
} }
@ -147,12 +148,12 @@ func (obj *BlockAccessList) DecodeRLP(dec *rlp.Stream) error {
} }
_tmp2.StorageWrites = _tmp4 _tmp2.StorageWrites = _tmp4
// StorageReads: // StorageReads:
var _tmp11 [][32]byte var _tmp11 []common.Hash
if _, err := dec.List(); err != nil { if _, err := dec.List(); err != nil {
return err return err
} }
for dec.MoreDataInList() { for dec.MoreDataInList() {
var _tmp12 [32]byte var _tmp12 common.Hash
if err := dec.ReadBytes(_tmp12[:]); err != nil { if err := dec.ReadBytes(_tmp12[:]); err != nil {
return err return err
} }

View file

@ -87,6 +87,7 @@ func makeTestConstructionBAL() *ConstructionBlockAccessList {
}, },
}, },
}, },
true,
} }
} }
@ -102,10 +103,10 @@ func TestBALEncoding(t *testing.T) {
if err := dec.DecodeRLP(rlp.NewStream(bytes.NewReader(buf.Bytes()), 10000000)); err != nil { if err := dec.DecodeRLP(rlp.NewStream(bytes.NewReader(buf.Bytes()), 10000000)); err != nil {
t.Fatalf("decoding failed: %v\n", err) t.Fatalf("decoding failed: %v\n", err)
} }
if dec.Hash() != bal.toEncodingObj().Hash() { if dec.Hash() != bal.ToEncodingObj().Hash() {
t.Fatalf("encoded block hash doesn't match decoded") t.Fatalf("encoded block hash doesn't match decoded")
} }
if !equalBALs(bal.toEncodingObj(), &dec) { if !equalBALs(bal.ToEncodingObj(), &dec) {
t.Fatal("decoded BAL doesn't match") t.Fatal("decoded BAL doesn't match")
} }
} }
@ -113,7 +114,7 @@ func TestBALEncoding(t *testing.T) {
func makeTestAccountAccess(sort bool) AccountAccess { func makeTestAccountAccess(sort bool) AccountAccess {
var ( var (
storageWrites []encodingSlotWrites storageWrites []encodingSlotWrites
storageReads [][32]byte storageReads []common.Hash
balances []encodingBalanceChange balances []encodingBalanceChange
nonces []encodingAccountNonce nonces []encodingAccountNonce
) )
@ -144,7 +145,7 @@ func makeTestAccountAccess(sort bool) AccountAccess {
storageReads = append(storageReads, testrand.Hash()) storageReads = append(storageReads, testrand.Hash())
} }
if sort { if sort {
slices.SortFunc(storageReads, func(a, b [32]byte) int { slices.SortFunc(storageReads, func(a, b common.Hash) int {
return bytes.Compare(a[:], b[:]) return bytes.Compare(a[:], b[:])
}) })
} }
@ -245,7 +246,7 @@ func TestBlockAccessListValidation(t *testing.T) {
// Validate the derived block access list // Validate the derived block access list
cBAL := makeTestConstructionBAL() cBAL := makeTestConstructionBAL()
listB := cBAL.toEncodingObj() listB := cBAL.ToEncodingObj()
if err := listB.Validate(); err != nil { if err := listB.Validate(); err != nil {
t.Fatalf("Unexpected validation error: %v", err) t.Fatalf("Unexpected validation error: %v", err)
} }

View file

@ -18,6 +18,7 @@
package types package types
import ( import (
"bytes"
"crypto/sha256" "crypto/sha256"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
@ -28,6 +29,8 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/ethereum/go-ethereum/core/types/bal"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
@ -106,6 +109,8 @@ type Header struct {
// RequestsHash was added by EIP-7685 and is ignored in legacy headers. // RequestsHash was added by EIP-7685 and is ignored in legacy headers.
RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"` RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"`
BALHash *common.Hash `json:"balHash" rlp:"-"`
} }
// field type overrides for gencodec // field type overrides for gencodec
@ -184,6 +189,7 @@ type Body struct {
Transactions []*Transaction Transactions []*Transaction
Uncles []*Header Uncles []*Header
Withdrawals []*Withdrawal `rlp:"optional"` Withdrawals []*Withdrawal `rlp:"optional"`
AccessList *bal.BlockAccessList `rlp:"optional,nil"`
} }
// Block represents an Ethereum block. // Block represents an Ethereum block.
@ -214,6 +220,8 @@ type Block struct {
// that process it. // that process it.
witness *ExecutionWitness witness *ExecutionWitness
accessList *bal.BlockAccessList
// caches // caches
hash atomic.Pointer[common.Hash] hash atomic.Pointer[common.Hash]
size atomic.Uint64 size atomic.Uint64
@ -230,6 +238,7 @@ type extblock struct {
Txs []*Transaction Txs []*Transaction
Uncles []*Header Uncles []*Header
Withdrawals []*Withdrawal `rlp:"optional"` Withdrawals []*Withdrawal `rlp:"optional"`
BAL []byte `rlp:"optional"`
} }
// NewBlock creates a new block. The input data is copied, changes to header and to the // NewBlock creates a new block. The input data is copied, changes to header and to the
@ -290,6 +299,12 @@ func NewBlock(header *Header, body *Body, receipts []*Receipt, hasher TrieHasher
b.withdrawals = slices.Clone(withdrawals) b.withdrawals = slices.Clone(withdrawals)
} }
if body.AccessList != nil {
balHash := body.AccessList.Hash()
b.header.BALHash = &balHash
b.accessList = body.AccessList
}
return b return b
} }
@ -334,30 +349,50 @@ func CopyHeader(h *Header) *Header {
// DecodeRLP decodes a block from RLP. // DecodeRLP decodes a block from RLP.
func (b *Block) DecodeRLP(s *rlp.Stream) error { func (b *Block) DecodeRLP(s *rlp.Stream) error {
var eb extblock var (
eb extblock
err error
)
_, size, _ := s.Kind() _, size, _ := s.Kind()
if err := s.Decode(&eb); err != nil { if err := s.Decode(&eb); err != nil {
return err return err
} }
b.header, b.uncles, b.transactions, b.withdrawals = eb.Header, eb.Uncles, eb.Txs, eb.Withdrawals b.header, b.uncles, b.transactions, b.withdrawals = eb.Header, eb.Uncles, eb.Txs, eb.Withdrawals
if eb.BAL != nil {
err = b.accessList.DecodeRLP(rlp.NewStream(bytes.NewBuffer(eb.BAL), 10_000_000))
if err != nil {
return err
}
}
// TODO: ensure that BAL is accounted for in size
b.size.Store(rlp.ListSize(size)) b.size.Store(rlp.ListSize(size))
return nil return nil
} }
// EncodeRLP serializes a block as RLP. // EncodeRLP serializes a block as RLP.
func (b *Block) EncodeRLP(w io.Writer) error { func (b *Block) EncodeRLP(w io.Writer) error {
var alEnc []byte
var err error
if b.accessList != nil {
err = b.accessList.EncodeRLP(bytes.NewBuffer(alEnc))
if err != nil {
return err
}
}
return rlp.Encode(w, &extblock{ return rlp.Encode(w, &extblock{
Header: b.header, Header: b.header,
Txs: b.transactions, Txs: b.transactions,
Uncles: b.uncles, Uncles: b.uncles,
Withdrawals: b.withdrawals, Withdrawals: b.withdrawals,
BAL: alEnc,
}) })
} }
// Body returns the non-header content of the block. // Body returns the non-header content of the block.
// Note the returned data is not an independent copy. // Note the returned data is not an independent copy.
func (b *Block) Body() *Body { func (b *Block) Body() *Body {
return &Body{b.transactions, b.uncles, b.withdrawals} return &Body{b.transactions, b.uncles, b.withdrawals, b.accessList}
} }
// Accessors for body data. These do not return a copy because the content // Accessors for body data. These do not return a copy because the content
@ -508,6 +543,10 @@ func (b *Block) WithBody(body Body) *Block {
withdrawals: slices.Clone(body.Withdrawals), withdrawals: slices.Clone(body.Withdrawals),
witness: b.witness, witness: b.witness,
} }
if body.AccessList != nil {
balCopy := body.AccessList.Copy()
block.accessList = &balCopy
}
for i := range body.Uncles { for i := range body.Uncles {
block.uncles[i] = CopyHeader(body.Uncles[i]) block.uncles[i] = CopyHeader(body.Uncles[i])
} }

View file

@ -37,6 +37,7 @@ func (h Header) MarshalJSON() ([]byte, error) {
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"` ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"`
ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"` RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"`
BALHash *common.Hash `json:"balHash" rlp:"-"`
Hash common.Hash `json:"hash"` Hash common.Hash `json:"hash"`
} }
var enc Header var enc Header
@ -61,6 +62,7 @@ func (h Header) MarshalJSON() ([]byte, error) {
enc.ExcessBlobGas = (*hexutil.Uint64)(h.ExcessBlobGas) enc.ExcessBlobGas = (*hexutil.Uint64)(h.ExcessBlobGas)
enc.ParentBeaconRoot = h.ParentBeaconRoot enc.ParentBeaconRoot = h.ParentBeaconRoot
enc.RequestsHash = h.RequestsHash enc.RequestsHash = h.RequestsHash
enc.BALHash = h.BALHash
enc.Hash = h.Hash() enc.Hash = h.Hash()
return json.Marshal(&enc) return json.Marshal(&enc)
} }
@ -89,6 +91,7 @@ func (h *Header) UnmarshalJSON(input []byte) error {
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"` ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"`
ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"` RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"`
BALHash *common.Hash `json:"balHash" rlp:"-"`
} }
var dec Header var dec Header
if err := json.Unmarshal(input, &dec); err != nil { if err := json.Unmarshal(input, &dec); err != nil {
@ -169,5 +172,8 @@ func (h *Header) UnmarshalJSON(input []byte) error {
if dec.RequestsHash != nil { if dec.RequestsHash != nil {
h.RequestsHash = dec.RequestsHash h.RequestsHash = dec.RequestsHash
} }
if dec.BALHash != nil {
h.BALHash = dec.BALHash
}
return nil return nil
} }

View file

@ -76,6 +76,7 @@ type TxContext struct {
BlobHashes []common.Hash // Provides information for BLOBHASH BlobHashes []common.Hash // Provides information for BLOBHASH
BlobFeeCap *big.Int // Is used to zero the blobbasefee if NoBaseFee is set BlobFeeCap *big.Int // Is used to zero the blobbasefee if NoBaseFee is set
AccessEvents *state.AccessEvents // Capture all state accesses for this tx AccessEvents *state.AccessEvents // Capture all state accesses for this tx
Index uint64 // the index of the transaction within the block being executed (0 if executing a standalone call)
} }
// EVM is the Ethereum Virtual Machine base object and provides // EVM is the Ethereum Virtual Machine base object and provides
@ -461,7 +462,9 @@ func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *ui
// - the storage is non-empty // - the storage is non-empty
contractHash := evm.StateDB.GetCodeHash(address) contractHash := evm.StateDB.GetCodeHash(address)
storageRoot := evm.StateDB.GetStorageRoot(address) storageRoot := evm.StateDB.GetStorageRoot(address)
if evm.StateDB.GetNonce(address) != 0 || targetNonce := evm.StateDB.GetNonce(address)
if targetNonce != 0 ||
(contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) || // non-empty code (contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) || // non-empty code
(storageRoot != (common.Hash{}) && storageRoot != types.EmptyRootHash) { // non-empty storage (storageRoot != (common.Hash{}) && storageRoot != types.EmptyRootHash) { // non-empty storage
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {

View file

@ -681,7 +681,7 @@ func TestCreate2Addresses(t *testing.T) {
stack.push(big.NewInt(0)) // memstart stack.push(big.NewInt(0)) // memstart
stack.push(big.NewInt(0)) // value stack.push(big.NewInt(0)) // value
gas, _ := gasCreate2(params.GasTable{}, nil, nil, stack, nil, 0) gas, _ := gasCreate2(params.GasTable{}, nil, nil, stack, nil, 0)
fmt.Printf("Example %d\n* address `0x%x`\n* salt `0x%x`\n* init_code `0x%x`\n* gas (assuming no mem expansion): `%v`\n* result: `%s`\n\n", i,origin, salt, code, gas, address.String()) fmt.Printf("Example %d\n* address `0x%x`\n* salt `0x%x`\n* init_code `0x%x`\n* gas (assuming no mem expansion): `%v`\n* result: `%s`\n\n", i,origin, salt, code, gas, address.PrettyPrint())
*/ */
expected := common.BytesToAddress(common.FromHex(tt.expected)) expected := common.BytesToAddress(common.FromHex(tt.expected))
if !bytes.Equal(expected.Bytes(), address.Bytes()) { if !bytes.Equal(expected.Bytes(), address.Bytes()) {

View file

@ -97,8 +97,12 @@ type StateDB interface {
Witness() *stateless.Witness Witness() *stateless.Witness
//BlockAccessList() *types.BlockAccessList
AccessEvents() *state.AccessEvents AccessEvents() *state.AccessEvents
TxIndex() int
// Finalise must be invoked at the end of a transaction // Finalise must be invoked at the end of a transaction
Finalise(bool) Finalise(bool)
} }

View file

@ -35,6 +35,7 @@ type Config struct {
ExtraEips []int // Additional EIPS that are to be enabled ExtraEips []int // Additional EIPS that are to be enabled
StatelessSelfValidation bool // Generate execution witnesses and self-check against them (testing purpose) StatelessSelfValidation bool // Generate execution witnesses and self-check against them (testing purpose)
BALConstruction bool // Generate BAL for executed blocks (testing purposes)
} }
// ScopeContext contains the things that are per-call, such as stack and memory, // ScopeContext contains the things that are per-call, such as stack and memory,

View file

@ -28,6 +28,8 @@ func LookupInstructionSet(rules params.Rules) (JumpTable, error) {
switch { switch {
case rules.IsVerkle: case rules.IsVerkle:
return newCancunInstructionSet(), errors.New("verkle-fork not defined yet") return newCancunInstructionSet(), errors.New("verkle-fork not defined yet")
case rules.IsGlamsterdam:
return newPragueInstructionSet(), errors.New("glamsterdam-fork not defined yet")
case rules.IsOsaka: case rules.IsOsaka:
return newOsakaInstructionSet(), nil return newOsakaInstructionSet(), nil
case rules.IsPrague: case rules.IsPrague:

View file

@ -669,7 +669,7 @@ func TestColdAccountAccessCost(t *testing.T) {
Tracer: &tracing.Hooks{ Tracer: &tracing.Hooks{
OnOpcode: func(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { OnOpcode: func(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
// Uncomment to investigate failures: // Uncomment to investigate failures:
//t.Logf("%d: %v %d", step, vm.OpCode(op).String(), cost) //t.Logf("%d: %v %d", step, vm.OpCode(op).PrettyPrint(), cost)
if step == tc.step { if step == tc.step {
have = cost have = cost
} }

View file

@ -17,11 +17,14 @@
package eth package eth
import ( import (
"bytes"
"context" "context"
"errors" "errors"
"fmt" "fmt"
"time" "time"
"github.com/ethereum/go-ethereum/core/types/bal"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
@ -443,3 +446,29 @@ func (api *DebugAPI) GetTrieFlushInterval() (string, error) {
} }
return api.eth.blockchain.GetTrieFlushInterval().String(), nil return api.eth.blockchain.GetTrieFlushInterval().String(), nil
} }
func (api *DebugAPI) GetBlockAccessList(number rpc.BlockNumberOrHash) (*bal.BlockAccessList, error) {
var block *types.Block
if num := number.BlockNumber; num != nil {
block = api.eth.blockchain.GetBlockByNumber(uint64(num.Int64()))
} else if hash := number.BlockHash; hash != nil {
block = api.eth.blockchain.GetBlockByHash(*hash)
}
if block == nil {
return nil, fmt.Errorf("block not found")
}
return block.Body().AccessList, nil
}
func (api *DebugAPI) GetEncodedAccessList(number rpc.BlockNumberOrHash) ([]byte, error) {
bal, err := api.GetBlockAccessList(number)
if err != nil {
return nil, err
}
var enc bytes.Buffer
if err = bal.EncodeRLP(&enc); err != nil {
return nil, err
}
return enc.Bytes(), nil
}

View file

@ -220,6 +220,10 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
rawdb.WriteDatabaseVersion(chainDb, core.BlockChainVersion) rawdb.WriteDatabaseVersion(chainDb, core.BlockChainVersion)
} }
} }
var buildBAL bool
if config.BuildBAL != nil {
buildBAL = *config.BuildBAL
}
var ( var (
options = &core.BlockChainConfig{ options = &core.BlockChainConfig{
TrieCleanLimit: config.TrieCleanCache, TrieCleanLimit: config.TrieCleanCache,
@ -235,6 +239,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
TxLookupLimit: int64(min(config.TransactionHistory, math.MaxInt64)), TxLookupLimit: int64(min(config.TransactionHistory, math.MaxInt64)),
VmConfig: vm.Config{ VmConfig: vm.Config{
EnablePreimageRecording: config.EnablePreimageRecording, EnablePreimageRecording: config.EnablePreimageRecording,
BALConstruction: buildBAL,
}, },
} }
) )

View file

@ -748,7 +748,7 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashe
return engine.PayloadStatusV1{Status: engine.ACCEPTED}, nil return engine.PayloadStatusV1{Status: engine.ACCEPTED}, nil
} }
log.Trace("Inserting block without sethead", "hash", block.Hash(), "number", block.Number()) log.Trace("Inserting block without sethead", "hash", block.Hash(), "number", block.Number())
proofs, err := api.eth.BlockChain().InsertBlockWithoutSetHead(block, witness) proofs, err := api.eth.BlockChain().InsertBlockWithoutSetHead(block, witness, true)
if err != nil { if err != nil {
log.Warn("NewPayload: inserting block failed", "error", err) log.Warn("NewPayload: inserting block failed", "error", err)

View file

@ -163,6 +163,8 @@ type Config struct {
// OverrideVerkle (TODO: remove after the fork) // OverrideVerkle (TODO: remove after the fork)
OverrideVerkle *uint64 `toml:",omitempty"` OverrideVerkle *uint64 `toml:",omitempty"`
BuildBAL *bool `toml:",omitempty"`
} }
// CreateConsensusEngine creates a consensus engine for the given chain config. // CreateConsensusEngine creates a consensus engine for the given chain config.

File diff suppressed because one or more lines are too long

View file

@ -468,6 +468,11 @@ web3._extend({
call: 'debug_getTrieFlushInterval', call: 'debug_getTrieFlushInterval',
params: 0 params: 0
}), }),
new web3._extend.Method({
name: 'getBlockAccessList',
call: 'debug_getBlockAccessList',
params: 1
}),
], ],
properties: [] properties: []
}); });

View file

@ -410,6 +410,7 @@ type ChainConfig struct {
CancunTime *uint64 `json:"cancunTime,omitempty"` // Cancun switch time (nil = no fork, 0 = already on cancun) CancunTime *uint64 `json:"cancunTime,omitempty"` // Cancun switch time (nil = no fork, 0 = already on cancun)
PragueTime *uint64 `json:"pragueTime,omitempty"` // Prague switch time (nil = no fork, 0 = already on prague) PragueTime *uint64 `json:"pragueTime,omitempty"` // Prague switch time (nil = no fork, 0 = already on prague)
OsakaTime *uint64 `json:"osakaTime,omitempty"` // Osaka switch time (nil = no fork, 0 = already on osaka) OsakaTime *uint64 `json:"osakaTime,omitempty"` // Osaka switch time (nil = no fork, 0 = already on osaka)
GlamsterdamTime *uint64 `json:"glamsterdamTime,omitempty"` // Glamsterdam switch time (nil = no fork, 0 = already on glamsterdam)
VerkleTime *uint64 `json:"verkleTime,omitempty"` // Verkle switch time (nil = no fork, 0 = already on verkle) VerkleTime *uint64 `json:"verkleTime,omitempty"` // Verkle switch time (nil = no fork, 0 = already on verkle)
// TerminalTotalDifficulty is the amount of total difficulty reached by // TerminalTotalDifficulty is the amount of total difficulty reached by
@ -649,6 +650,11 @@ func (c *ChainConfig) IsOsaka(num *big.Int, time uint64) bool {
return c.IsLondon(num) && isTimestampForked(c.OsakaTime, time) return c.IsLondon(num) && isTimestampForked(c.OsakaTime, time)
} }
// IsGlamsterdam returns whether time is either equal to the Glamsterdam fork time or greater.
func (c *ChainConfig) IsGlamsterdam(num *big.Int, time uint64) bool {
return c.IsLondon(num) && isTimestampForked(c.GlamsterdamTime, time)
}
// IsVerkle returns whether time is either equal to the Verkle fork time or greater. // IsVerkle returns whether time is either equal to the Verkle fork time or greater.
func (c *ChainConfig) IsVerkle(num *big.Int, time uint64) bool { func (c *ChainConfig) IsVerkle(num *big.Int, time uint64) bool {
return c.IsLondon(num) && isTimestampForked(c.VerkleTime, time) return c.IsLondon(num) && isTimestampForked(c.VerkleTime, time)
@ -1067,7 +1073,7 @@ type Rules struct {
IsEIP2929, IsEIP4762 bool IsEIP2929, IsEIP4762 bool
IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool
IsBerlin, IsLondon bool IsBerlin, IsLondon bool
IsMerge, IsShanghai, IsCancun, IsPrague, IsOsaka bool IsMerge, IsShanghai, IsCancun, IsPrague, IsOsaka, IsGlamsterdam bool
IsVerkle bool IsVerkle bool
} }
@ -1098,6 +1104,7 @@ func (c *ChainConfig) Rules(num *big.Int, isMerge bool, timestamp uint64) Rules
IsCancun: isMerge && c.IsCancun(num, timestamp), IsCancun: isMerge && c.IsCancun(num, timestamp),
IsPrague: isMerge && c.IsPrague(num, timestamp), IsPrague: isMerge && c.IsPrague(num, timestamp),
IsOsaka: isMerge && c.IsOsaka(num, timestamp), IsOsaka: isMerge && c.IsOsaka(num, timestamp),
IsGlamsterdam: isMerge && c.IsGlamsterdam(num, timestamp),
IsVerkle: isVerkle, IsVerkle: isVerkle,
IsEIP4762: isVerkle, IsEIP4762: isVerkle,
} }

View file

@ -105,7 +105,7 @@ func execBlockTest(t *testing.T, bt *testMatcher, test *BlockTest) {
} }
for _, snapshot := range snapshotConf { for _, snapshot := range snapshotConf {
for _, dbscheme := range dbschemeConf { for _, dbscheme := range dbschemeConf {
if err := bt.checkFailure(t, test.Run(snapshot, dbscheme, true, nil, nil)); err != nil { if err := bt.checkFailure(t, test.Run(snapshot, dbscheme, true, true, nil, nil)); err != nil {
t.Errorf("test with config {snapshotter:%v, scheme:%v} failed: %v", snapshot, dbscheme, err) t.Errorf("test with config {snapshotter:%v, scheme:%v} failed: %v", snapshot, dbscheme, err)
return return
} }

View file

@ -111,7 +111,7 @@ type btHeaderMarshaling struct {
ExcessBlobGas *math.HexOrDecimal64 ExcessBlobGas *math.HexOrDecimal64
} }
func (t *BlockTest) Run(snapshotter bool, scheme string, witness bool, tracer *tracing.Hooks, postCheck func(error, *core.BlockChain)) (result error) { func (t *BlockTest) Run(snapshotter bool, scheme string, witness bool, bal bool, tracer *tracing.Hooks, postCheck func(error, *core.BlockChain)) (result error) {
config, ok := Forks[t.json.Network] config, ok := Forks[t.json.Network]
if !ok { if !ok {
return UnsupportedForkError{t.json.Network} return UnsupportedForkError{t.json.Network}
@ -159,6 +159,7 @@ func (t *BlockTest) Run(snapshotter bool, scheme string, witness bool, tracer *t
VmConfig: vm.Config{ VmConfig: vm.Config{
Tracer: tracer, Tracer: tracer,
StatelessSelfValidation: witness, StatelessSelfValidation: witness,
BALConstruction: bal,
}, },
} }
if snapshotter { if snapshotter {