Skip to content

Commit 8f200db

Browse files
committed
all: add block access list construction via flag --experimentalbal. When enabled, post-Cancun blocks which lack access lists will have them constructed on execution during import. When importing blocks which contain access lists, transaction execution and state root calculation is performed in parallel.
1 parent 5ebd803 commit 8f200db

34 files changed

+1597
-185
lines changed

cmd/evm/blockrunner.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import (
2525
"regexp"
2626
"slices"
2727

28+
"github.com/ethereum/go-ethereum/cmd/utils"
29+
2830
"github.com/ethereum/go-ethereum/core"
2931
"github.com/ethereum/go-ethereum/core/rawdb"
3032
"github.com/ethereum/go-ethereum/tests"
@@ -89,7 +91,7 @@ func runBlockTest(ctx *cli.Context, fname string) ([]testResult, error) {
8991
continue
9092
}
9193
result := &testResult{Name: name, Pass: true}
92-
if err := tests[name].Run(false, rawdb.HashScheme, ctx.Bool(WitnessCrossCheckFlag.Name), tracer, func(res error, chain *core.BlockChain) {
94+
if err := tests[name].Run(false, rawdb.HashScheme, ctx.Bool(WitnessCrossCheckFlag.Name), ctx.Bool(utils.ExperimentalBALFlag.Name), tracer, func(res error, chain *core.BlockChain) {
9395
if ctx.Bool(DumpFlag.Name) {
9496
if s, _ := chain.State(); s != nil {
9597
result.State = dump(s)

cmd/geth/main.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ var (
151151
utils.BeaconGenesisTimeFlag,
152152
utils.BeaconCheckpointFlag,
153153
utils.BeaconCheckpointFileFlag,
154+
utils.ExperimentalBALFlag,
154155
}, utils.NetworkFlags, utils.DatabaseFlags)
155156

156157
rpcFlags = []cli.Flag{
@@ -338,6 +339,10 @@ func startNode(ctx *cli.Context, stack *node.Node, isConsole bool) {
338339
log.Warn(`The "unlock" flag has been deprecated and has no effect`)
339340
}
340341

342+
if ctx.IsSet(utils.ExperimentalBALFlag.Name) {
343+
log.Warn(`block-access-list construction enabled. This is an experimental feature that shouldn't be enabled outside of a Geth development context.'`)
344+
}
345+
341346
// Register wallet event handlers to open and auto-derive wallets
342347
events := make(chan accounts.WalletEvent, 16)
343348
stack.AccountManager().Subscribe(events)

cmd/utils/flags.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -966,6 +966,14 @@ Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server.
966966
Value: metrics.DefaultConfig.InfluxDBOrganization,
967967
Category: flags.MetricsCategory,
968968
}
969+
970+
// Block Access List flags
971+
972+
ExperimentalBALFlag = &cli.BoolFlag{
973+
Name: "experimentalbal",
974+
Usage: "Enable block-access-list building when executing blocks, and validation of post-cancun blocks that contain access lists. This is used for development purposes only. Do not enable this flag on a live node.",
975+
Category: flags.MiscCategory,
976+
}
969977
)
970978

971979
var (
@@ -1852,6 +1860,8 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
18521860
cfg.VMTraceJsonConfig = ctx.String(VMTraceJsonConfigFlag.Name)
18531861
}
18541862
}
1863+
1864+
cfg.ExperimentalBAL = ctx.Bool(ExperimentalBALFlag.Name)
18551865
}
18561866

18571867
// MakeBeaconLightConfig constructs a beacon light client config based on the
@@ -2244,6 +2254,7 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh
22442254
}
22452255
options.VmConfig = vmcfg
22462256

2257+
options.EnableBAL = ctx.Bool(ExperimentalBALFlag.Name)
22472258
chain, err := core.NewBlockChain(chainDb, gspec, engine, options)
22482259
if err != nil {
22492260
Fatalf("Can't create BlockChain: %v", err)

core/bal_prefetcher.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package core
2+
3+
import (
4+
"github.com/ethereum/go-ethereum/core/state"
5+
"github.com/ethereum/go-ethereum/core/types"
6+
"golang.org/x/sync/errgroup"
7+
"runtime"
8+
"sync/atomic"
9+
)
10+
11+
// balPrefetcher implements a state prefetcher for block access lists.
12+
// State present in the block access list is retrieved in parallel for
13+
// each account.
14+
type balPrefetcher struct{}
15+
16+
// Prefetch retrieves state contained in the block's access list to warm state
17+
// caches before executing the block.
18+
func (p *balPrefetcher) Prefetch(block *types.Block, db *state.StateDB, interrupt *atomic.Bool) {
19+
al := block.Body().AccessList
20+
21+
var workers errgroup.Group
22+
23+
workers.SetLimit(runtime.NumCPU() / 2)
24+
25+
for _, accesses := range al.Accesses {
26+
statedb := db.Copy()
27+
workers.Go(func() error {
28+
statedb.GetBalance(accesses.Address)
29+
for _, storageAccess := range accesses.StorageWrites {
30+
if interrupt != nil && interrupt.Load() {
31+
return nil
32+
}
33+
statedb.GetState(accesses.Address, storageAccess.Slot)
34+
}
35+
for _, storageRead := range accesses.StorageReads {
36+
if interrupt != nil && interrupt.Load() {
37+
return nil
38+
}
39+
statedb.GetState(accesses.Address, storageRead)
40+
}
41+
if interrupt != nil && interrupt.Load() {
42+
return nil
43+
}
44+
statedb.GetCode(accesses.Address)
45+
return nil
46+
})
47+
}
48+
workers.Wait()
49+
}

core/block_validator.go

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ package core
1919
import (
2020
"errors"
2121
"fmt"
22-
2322
"github.com/ethereum/go-ethereum/consensus"
2423
"github.com/ethereum/go-ethereum/core/state"
2524
"github.com/ethereum/go-ethereum/core/types"
@@ -121,6 +120,52 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
121120
return nil
122121
}
123122

123+
func (v *BlockValidator) ValidateProcessResult(block *types.Block, resCh chan *ProcessResult, stateless bool) (*ProcessResult, error) {
124+
// Validate the state root against the received state root and throw
125+
// an error if they don't match.
126+
header := block.Header()
127+
128+
res := <-resCh
129+
if res.Error != nil {
130+
return nil, res.Error
131+
}
132+
133+
if block.GasUsed() != res.GasUsed {
134+
return res, fmt.Errorf("invalid gas used (remote: %d local: %d)", block.GasUsed(), res.GasUsed)
135+
}
136+
// Validate the received block's bloom with the one derived from the generated receipts.
137+
// For valid blocks this should always validate to true.
138+
//
139+
// Receipts must go through MakeReceipt to calculate the receipt's bloom
140+
// already. Merge the receipt's bloom together instead of recalculating
141+
// everything.
142+
rbloom := types.MergeBloom(res.Receipts)
143+
if rbloom != header.Bloom {
144+
return res, fmt.Errorf("invalid bloom (remote: %x local: %x)", header.Bloom, rbloom)
145+
}
146+
// In stateless mode, return early because the receipt and state root are not
147+
// provided through the witness, rather the cross validator needs to return it.
148+
if stateless {
149+
return res, nil
150+
}
151+
// The receipt Trie's root (R = (Tr [[H1, R1], ... [Hn, Rn]]))
152+
receiptSha := types.DeriveSha(res.Receipts, trie.NewStackTrie(nil))
153+
if receiptSha != header.ReceiptHash {
154+
return res, fmt.Errorf("invalid receipt root hash (remote: %x local: %x)", header.ReceiptHash, receiptSha)
155+
}
156+
// Validate the parsed requests match the expected header value.
157+
if header.RequestsHash != nil {
158+
reqhash := types.CalcRequestsHash(res.Requests)
159+
if reqhash != *header.RequestsHash {
160+
return res, fmt.Errorf("invalid requests hash (remote: %x local: %x)", *header.RequestsHash, reqhash)
161+
}
162+
} else if res.Requests != nil {
163+
return res, errors.New("block has requests before prague fork")
164+
}
165+
166+
return res, nil
167+
}
168+
124169
// ValidateState validates the various changes that happen after a state transition,
125170
// such as amount of used gas, the receipt roots and the state root itself.
126171
func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateDB, res *ProcessResult, stateless bool) error {

core/blockchain.go

Lines changed: 99 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,9 @@ type BlockChainConfig struct {
193193
// If the value is zero, all transactions of the entire chain will be indexed.
194194
// If the value is -1, indexing is disabled.
195195
TxLookupLimit int64
196+
197+
// EnableBAL enables block access list creation and verification (TODO better wording here)
198+
EnableBAL bool
196199
}
197200

198201
// DefaultConfig returns the default config.
@@ -325,11 +328,12 @@ type BlockChain struct {
325328
stopping atomic.Bool // false if chain is running, true when stopped
326329
procInterrupt atomic.Bool // interrupt signaler for block processing
327330

328-
engine consensus.Engine
329-
validator Validator // Block and state validator interface
330-
prefetcher Prefetcher
331-
processor Processor // Block transaction processor interface
332-
logger *tracing.Hooks
331+
engine consensus.Engine
332+
validator Validator // Block and state validator interface
333+
prefetcher Prefetcher
334+
balPrefetcher balPrefetcher
335+
processor Processor // Block transaction processor interface
336+
logger *tracing.Hooks
333337

334338
lastForkReadyAlert time.Time // Last time there was a fork readiness print out
335339
}
@@ -388,6 +392,7 @@ func NewBlockChain(db ethdb.Database, genesis *Genesis, engine consensus.Engine,
388392
bc.statedb = state.NewDatabase(bc.triedb, nil)
389393
bc.validator = NewBlockValidator(chainConfig, bc)
390394
bc.prefetcher = newStatePrefetcher(chainConfig, bc.hc)
395+
bc.balPrefetcher = balPrefetcher{}
391396
bc.processor = NewStateProcessor(chainConfig, bc.hc)
392397

393398
genesisHeader := bc.GetHeaderByNumber(0)
@@ -1881,12 +1886,18 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness
18811886
}
18821887
// The traced section of block import.
18831888
start := time.Now()
1884-
res, err := bc.processBlock(parent.Root, block, setHead, makeWitness && len(chain) == 1)
1889+
1890+
blockHasAccessList := block.Body().AccessList != nil
1891+
// BAL generation/verification not enabled pre-selfdestruct removal
1892+
forkSupportsBAL := bc.chainConfig.IsCancun(block.Number(), block.Time())
1893+
makeBAL := forkSupportsBAL && !blockHasAccessList
1894+
validateBAL := forkSupportsBAL && blockHasAccessList
1895+
1896+
res, err := bc.processBlock(parent.Root, block, setHead, makeWitness && len(chain) == 1, makeBAL, validateBAL)
18851897
if err != nil {
18861898
return nil, it.index, err
18871899
}
18881900
// Report the import stats before returning the various results
1889-
stats.processed++
18901901
stats.usedGas += res.usedGas
18911902
witness = res.witness
18921903

@@ -1949,7 +1960,7 @@ type blockProcessingResult struct {
19491960

19501961
// processBlock executes and validates the given block. If there was no error
19511962
// it writes the block and associated state to database.
1952-
func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, setHead bool, makeWitness bool) (_ *blockProcessingResult, blockEndErr error) {
1963+
func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, setHead bool, makeWitness bool, constructBAL bool, validateBALTesting bool) (bpr *blockProcessingResult, blockEndErr error) {
19531964
var (
19541965
err error
19551966
startTime = time.Now()
@@ -1960,6 +1971,9 @@ func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, s
19601971

19611972
if bc.cfg.NoPrefetch {
19621973
statedb, err = state.New(parentRoot, bc.statedb)
1974+
if constructBAL || validateBALTesting {
1975+
statedb.EnableStateDiffRecording()
1976+
}
19631977
if err != nil {
19641978
return nil, err
19651979
}
@@ -1981,6 +1995,9 @@ func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, s
19811995
if err != nil {
19821996
return nil, err
19831997
}
1998+
if constructBAL || validateBALTesting {
1999+
statedb.EnableStateDiffRecording()
2000+
}
19842001
// Upload the statistics of reader at the end
19852002
defer func() {
19862003
stats := prefetch.GetStats()
@@ -1999,7 +2016,11 @@ func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, s
19992016
// Disable tracing for prefetcher executions.
20002017
vmCfg := bc.cfg.VmConfig
20012018
vmCfg.Tracer = nil
2002-
bc.prefetcher.Prefetch(block, throwaway, vmCfg, &interrupt)
2019+
if block.Body().AccessList != nil {
2020+
bc.balPrefetcher.Prefetch(block, throwaway, &interrupt)
2021+
} else {
2022+
bc.prefetcher.Prefetch(block, throwaway, vmCfg, &interrupt)
2023+
}
20032024

20042025
blockPrefetchExecuteTimer.Update(time.Since(start))
20052026
if interrupt.Load() {
@@ -2008,6 +2029,10 @@ func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, s
20082029
}(time.Now(), throwaway, block)
20092030
}
20102031

2032+
if constructBAL {
2033+
statedb.EnableBALConstruction()
2034+
}
2035+
20112036
// If we are past Byzantium, enable prefetching to pull in trie node paths
20122037
// while processing transactions. Before Byzantium the prefetcher is mostly
20132038
// useless due to the intermediate root hashing after each transaction.
@@ -2022,8 +2047,15 @@ func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, s
20222047
return nil, err
20232048
}
20242049
}
2025-
statedb.StartPrefetcher("chain", witness)
2026-
defer statedb.StopPrefetcher()
2050+
2051+
// access-list containing blocks don't use the prefetcher because
2052+
// state root computation proceeds concurrently with transaction
2053+
// execution, meaning the prefetcher doesn't have any time to run
2054+
// before the trie nodes are needed for state root computation.
2055+
if block.Body().AccessList == nil {
2056+
statedb.StartPrefetcher("chain", witness)
2057+
defer statedb.StopPrefetcher()
2058+
}
20272059
}
20282060

20292061
if bc.logger != nil && bc.logger.OnBlockStart != nil {
@@ -2039,21 +2071,65 @@ func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, s
20392071
}()
20402072
}
20412073

2042-
// Process block using the parent state as reference point
2043-
pstart := time.Now()
2044-
res, err := bc.processor.Process(block, statedb, bc.cfg.VmConfig)
2045-
if err != nil {
2046-
bc.reportBlock(block, res, err)
2047-
return nil, err
2074+
var res *ProcessResult
2075+
var ptime, vtime time.Duration
2076+
if block.Body().AccessList != nil {
2077+
if block.NumberU64() == 0 {
2078+
return nil, fmt.Errorf("genesis block cannot have a block access list")
2079+
}
2080+
if !validateBALTesting && !bc.chainConfig.IsGlamsterdam(block.Number(), block.Time()) {
2081+
bc.reportBlock(block, res, fmt.Errorf("received block containing access list before glamsterdam activated"))
2082+
return nil, err
2083+
}
2084+
// Process block using the parent state as reference point
2085+
pstart := time.Now()
2086+
var resCh chan *ProcessResult
2087+
resCh, err = bc.processor.ProcessWithAccessList(block, statedb, bc.cfg.VmConfig)
2088+
if err != nil {
2089+
// TODO: okay to pass nil here as execution result?
2090+
bc.reportBlock(block, nil, err)
2091+
return nil, err
2092+
}
2093+
ptime = time.Since(pstart)
2094+
2095+
vstart := time.Now()
2096+
var err error
2097+
res, err = bc.validator.ValidateProcessResult(block, resCh, false)
2098+
if err != nil {
2099+
// TODO: okay to pass nil here as execution result?
2100+
bc.reportBlock(block, nil, err)
2101+
return nil, err
2102+
}
2103+
vtime = time.Since(vstart)
2104+
2105+
} else {
2106+
// Process block using the parent state as reference point
2107+
pstart := time.Now()
2108+
res, err = bc.processor.Process(block, statedb, bc.cfg.VmConfig)
2109+
if err != nil {
2110+
bc.reportBlock(block, res, err)
2111+
return nil, err
2112+
}
2113+
ptime = time.Since(pstart)
2114+
2115+
vstart := time.Now()
2116+
if err := bc.validator.ValidateState(block, statedb, res, false); err != nil {
2117+
bc.reportBlock(block, res, err)
2118+
return nil, err
2119+
}
2120+
vtime = time.Since(vstart)
2121+
20482122
}
2049-
ptime := time.Since(pstart)
20502123

2051-
vstart := time.Now()
2052-
if err := bc.validator.ValidateState(block, statedb, res, false); err != nil {
2053-
bc.reportBlock(block, res, err)
2054-
return nil, err
2124+
if constructBAL {
2125+
// very ugly... deep-copy the block body before setting the block access
2126+
// list on it to prevent mutating the block instance passed by the caller.
2127+
existingBody := block.Body()
2128+
block = block.WithBody(*existingBody)
2129+
existingBody = block.Body()
2130+
existingBody.AccessList = statedb.BlockAccessList().ToEncodingObj()
2131+
block = block.WithBody(*existingBody)
20552132
}
2056-
vtime := time.Since(vstart)
20572133

20582134
// If witnesses was generated and stateless self-validation requested, do
20592135
// that now. Self validation should *never* run in production, it's more of

core/genesis.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -681,7 +681,7 @@ func DeveloperGenesisBlock(gasLimit uint64, faucet *common.Address) *Genesis {
681681
},
682682
}
683683
if faucet != nil {
684-
genesis.Alloc[*faucet] = types.Account{Balance: new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(9))}
684+
genesis.Alloc[*faucet] = types.Account{Balance: new(big.Int).Lsh(big.NewInt(1), 95)}
685685
}
686686
return genesis
687687
}

0 commit comments

Comments
 (0)