Skip to content

Commit 1e48b00

Browse files
committed
wip: tracer-based BAL creation
1 parent cbf0b5b commit 1e48b00

File tree

20 files changed

+905
-417
lines changed

20 files changed

+905
-417
lines changed

cmd/geth/main.go

Lines changed: 1 addition & 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{

cmd/utils/flags.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -972,6 +972,14 @@ Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server.
972972
Value: metrics.DefaultConfig.InfluxDBOrganization,
973973
Category: flags.MetricsCategory,
974974
}
975+
976+
// Block Access List flags
977+
978+
ExperimentalBALFlag = &cli.BoolFlag{
979+
Name: "experimental.bal",
980+
Usage: "Enable block-access-list building when importing post-Cancun blocks, and validation that access lists contained in post-Cancun blocks correctly correspond to the state changes in those blocks. This is used for development purposes only. Do not enable it otherwise.",
981+
Category: flags.MiscCategory,
982+
}
975983
)
976984

977985
var (
@@ -1861,6 +1869,8 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
18611869
cfg.VMTraceJsonConfig = ctx.String(VMTraceJsonConfigFlag.Name)
18621870
}
18631871
}
1872+
1873+
cfg.ExperimentalBAL = ctx.Bool(ExperimentalBALFlag.Name)
18641874
}
18651875

18661876
// MakeBeaconLightConfig constructs a beacon light client config based on the
@@ -2256,6 +2266,7 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh
22562266
}
22572267
options.VmConfig = vmcfg
22582268

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

core/block_access_list_creation.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package core
2+
3+
import (
4+
"github.com/ethereum/go-ethereum/common"
5+
"github.com/ethereum/go-ethereum/core/tracing"
6+
"github.com/ethereum/go-ethereum/core/types"
7+
"github.com/ethereum/go-ethereum/core/types/bal"
8+
"math/big"
9+
)
10+
11+
type AccessListTracer struct {
12+
accessList bal.ConstructionBlockAccessList
13+
14+
curAccessList []*bal.ConstructionBlockAccessList
15+
txIdx int
16+
17+
lastCallSelfdestructed bool
18+
}
19+
20+
func (a *AccessListTracer) TxEndHook(receipt *types.Receipt, err error) {
21+
a.txIdx++
22+
}
23+
24+
func (a *AccessListTracer) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
25+
a.curAccessList = append(a.curAccessList, bal.NewConstructionBlockAccessList())
26+
}
27+
28+
func (a *AccessListTracer) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) {
29+
if a.lastCallSelfdestructed {
30+
a.lastCallSelfdestructed = false
31+
a.curAccessList = a.curAccessList[:len(a.curAccessList)-1]
32+
return
33+
}
34+
// TODO: can we guarantee that there is a top-most call context we are exiting into here?
35+
// i.e. len(a.curAccessList) > 1 ?
36+
parentAccessList := a.curAccessList[len(a.curAccessList)-2]
37+
scopeAccessList := a.curAccessList[len(a.curAccessList)-1]
38+
// TODO: remove the need to directly assign the array here. looks ugly
39+
a.curAccessList[len(a.curAccessList)-2] = parentAccessList.Merge(scopeAccessList)
40+
a.curAccessList = a.curAccessList[:len(a.curAccessList)-1]
41+
}
42+
43+
func (a *AccessListTracer) OnCodeChange(addr common.Address, prevCodeHash common.Hash, prevCode []byte, codeHash common.Hash, code []byte, reason tracing.CodeChangeReason) {
44+
if reason == tracing.CodeChangeSelfDestruct {
45+
a.lastCallSelfdestructed = true
46+
return
47+
}
48+
a.curAccessList[len(a.curAccessList)-1].CodeChange()
49+
}
50+
51+
func (a *AccessListTracer) OnBalanceChange(addr common.Address, prev, new *big.Int, _ tracing.BalanceChangeReason) {
52+
// TODO: implement this method on the StateDiff object
53+
//a.stateDiffs[len(a.stateDiffs)-1].BalanceChange(addr, new)
54+
}
55+
56+
func (a *AccessListTracer) OnNonceChange(addr common.Address, prev uint64, new uint64) {
57+
//a.stateDiffs[len(a.stateDiffs)-1].NonceChange(addr, new)
58+
}
59+
60+
func (a *AccessListTracer) OnSystemCallStart() {
61+
// create a new entry on the state diff stack
62+
}
63+
64+
func (a *AccessListTracer) OnSystemCallEnd() {
65+
// add the contents of the state diff stack to the block access list and clear the state diff stack
66+
}
67+
68+
func (a *AccessListTracer) OnColdStorageRead(addr common.Address, key common.Hash) {
69+
//a.accessList.StorageRead(addr, key)
70+
}
71+
72+
func (a *AccessListTracer) OnColdAccountRead(addr common.Address) {
73+
//a.accessList.AccountRead(addr)
74+
}
75+
76+
// TODO: figure out if we need the system call hooks?

core/block_validator.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,31 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
111111
}
112112
}
113113

114+
// block access lists must be present after the Amsterdam hard fork
115+
if v.config.IsAmsterdam(block.Number(), block.Time()) {
116+
if block.Body().AccessList == nil {
117+
return fmt.Errorf("access list not present in block body")
118+
} else if block.Header().BlockAccessListHash == nil {
119+
return fmt.Errorf("access list hash not present in block header")
120+
} else if *block.Header().BlockAccessListHash != block.Body().AccessList.Hash() {
121+
return fmt.Errorf("access list hash mismatch. local: %x. remote: %x\n", block.Body().AccessList.Hash(), *block.Header().BlockAccessListHash)
122+
}
123+
} else if !v.bc.cfg.EnableBAL {
124+
// if --experimental-bal is not enabled, block headers cannot have access list hash and bodies cannot have access lists.
125+
if block.Body().AccessList != nil {
126+
return fmt.Errorf("access list not allowed in block body if not in amsterdam or --experimental-bal is set")
127+
} else if block.Header().BlockAccessListHash != nil {
128+
return fmt.Errorf("access list hash in block header not allowed when --experimental-bal is set")
129+
}
130+
} else {
131+
// if --experimental-bal is enabled, the BAL hash is not allowed in the header.
132+
// this is in order that Geth can import pre-existing chains augmented with BALs
133+
// and not have a hash mismatch.
134+
if block.Header().BlockAccessListHash != nil {
135+
return fmt.Errorf("access list hash in block header not allowed pre-amsterdam")
136+
}
137+
}
138+
114139
// Ancestor block must be known.
115140
if !v.bc.HasBlockAndState(block.ParentHash(), block.NumberU64()-1) {
116141
if !v.bc.HasBlock(block.ParentHash(), block.NumberU64()-1) {

core/blockchain.go

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,9 @@ type BlockChainConfig struct {
197197
// If the value is -1, indexing is disabled.
198198
TxLookupLimit int64
199199

200+
// EnableBAL enables block access list creation and verification for post-Cancun blocks which contain access lists.
201+
EnableBAL bool
202+
200203
// StateSizeTracking indicates whether the state size tracking is enabled.
201204
StateSizeTracking bool
202205
}
@@ -1905,9 +1908,17 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness
19051908
if parent == nil {
19061909
parent = bc.GetHeader(block.ParentHash(), block.NumberU64()-1)
19071910
}
1911+
1912+
// construct or verify block access lists if BALs are enabled and
1913+
// we are post-selfdestruct removal fork.
1914+
enableBAL := (bc.cfg.EnableBAL && bc.chainConfig.IsCancun(block.Number(), block.Time())) || bc.chainConfig.IsAmsterdam(block.Number(), block.Time())
1915+
blockHasAccessList := block.Body().AccessList != nil
1916+
makeBAL := enableBAL && !blockHasAccessList
1917+
validateBAL := enableBAL && blockHasAccessList
1918+
19081919
// The traced section of block import.
19091920
start := time.Now()
1910-
res, err := bc.processBlock(parent.Root, block, setHead, makeWitness && len(chain) == 1)
1921+
res, err := bc.processBlock(parent.Root, block, setHead, makeWitness && len(chain) == 1, makeBAL, validateBAL)
19111922
if err != nil {
19121923
return nil, it.index, err
19131924
}
@@ -1975,7 +1986,7 @@ type blockProcessingResult struct {
19751986

19761987
// processBlock executes and validates the given block. If there was no error
19771988
// it writes the block and associated state to database.
1978-
func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, setHead bool, makeWitness bool) (_ *blockProcessingResult, blockEndErr error) {
1989+
func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, setHead bool, makeWitness bool, constructBALForTesting bool, validateBAL bool) (bpr *blockProcessingResult, blockEndErr error) {
19791990
var (
19801991
err error
19811992
startTime = time.Now()
@@ -2034,6 +2045,9 @@ func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, s
20342045
}(time.Now(), throwaway, block)
20352046
}
20362047

2048+
if constructBALForTesting {
2049+
statedb.EnableStateDiffRecording()
2050+
}
20372051
// If we are past Byzantium, enable prefetching to pull in trie node paths
20382052
// while processing transactions. Before Byzantium the prefetcher is mostly
20392053
// useless due to the intermediate root hashing after each transaction.
@@ -2071,22 +2085,37 @@ func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, s
20712085
}()
20722086
}
20732087

2088+
// Process block using the parent state as reference point
2089+
var sdb state.BlockProcessingDB = statedb
2090+
if constructBALForTesting {
2091+
sdb = state.NewBlockAccessListBuilder(statedb)
2092+
}
20742093
// Process block using the parent state as reference point
20752094
pstart := time.Now()
2076-
res, err := bc.processor.Process(block, statedb, bc.cfg.VmConfig)
2095+
res, err := bc.processor.Process(block, sdb, bc.cfg.VmConfig)
20772096
if err != nil {
20782097
bc.reportBlock(block, res, err)
20792098
return nil, err
20802099
}
20812100
ptime := time.Since(pstart)
20822101

20832102
vstart := time.Now()
2084-
if err := bc.validator.ValidateState(block, statedb, res, false); err != nil {
2103+
if err := bc.validator.ValidateState(block, sdb, res, false); err != nil {
20852104
bc.reportBlock(block, res, err)
20862105
return nil, err
20872106
}
20882107
vtime := time.Since(vstart)
20892108

2109+
if constructBALForTesting {
2110+
// very ugly... deep-copy the block body before setting the block access
2111+
// list on it to prevent mutating the block instance passed by the caller.
2112+
existingBody := block.Body()
2113+
block = block.WithBody(*existingBody)
2114+
existingBody = block.Body()
2115+
existingBody.AccessList = sdb.(*state.AccessListCreationDB).ConstructedBlockAccessList().ToEncodingObj()
2116+
block = block.WithBody(*existingBody)
2117+
}
2118+
20902119
// If witnesses was generated and stateless self-validation requested, do
20912120
// that now. Self validation should *never* run in production, it's more of
20922121
// a tight integration to enable running *all* consensus tests through the

core/genesis.go

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,13 @@ type Genesis struct {
6767

6868
// These fields are used for consensus tests. Please don't use them
6969
// in actual genesis blocks.
70-
Number uint64 `json:"number"`
71-
GasUsed uint64 `json:"gasUsed"`
72-
ParentHash common.Hash `json:"parentHash"`
73-
BaseFee *big.Int `json:"baseFeePerGas"` // EIP-1559
74-
ExcessBlobGas *uint64 `json:"excessBlobGas"` // EIP-4844
75-
BlobGasUsed *uint64 `json:"blobGasUsed"` // EIP-4844
70+
Number uint64 `json:"number"`
71+
GasUsed uint64 `json:"gasUsed"`
72+
ParentHash common.Hash `json:"parentHash"`
73+
BaseFee *big.Int `json:"baseFeePerGas"` // EIP-1559
74+
ExcessBlobGas *uint64 `json:"excessBlobGas"` // EIP-4844
75+
BlobGasUsed *uint64 `json:"blobGasUsed"` // EIP-4844
76+
BlockAccessListHash *common.Hash `json:"blockAccessListHash,omitempty"` // EIP-7928
7677
}
7778

7879
// copy copies the genesis.
@@ -122,6 +123,7 @@ func ReadGenesis(db ethdb.Database) (*Genesis, error) {
122123
genesis.BaseFee = genesisHeader.BaseFee
123124
genesis.ExcessBlobGas = genesisHeader.ExcessBlobGas
124125
genesis.BlobGasUsed = genesisHeader.BlobGasUsed
126+
genesis.BlockAccessListHash = genesisHeader.BlockAccessListHash
125127

126128
return &genesis, nil
127129
}
@@ -461,18 +463,19 @@ func (g *Genesis) ToBlock() *types.Block {
461463
// toBlockWithRoot constructs the genesis block with the given genesis state root.
462464
func (g *Genesis) toBlockWithRoot(root common.Hash) *types.Block {
463465
head := &types.Header{
464-
Number: new(big.Int).SetUint64(g.Number),
465-
Nonce: types.EncodeNonce(g.Nonce),
466-
Time: g.Timestamp,
467-
ParentHash: g.ParentHash,
468-
Extra: g.ExtraData,
469-
GasLimit: g.GasLimit,
470-
GasUsed: g.GasUsed,
471-
BaseFee: g.BaseFee,
472-
Difficulty: g.Difficulty,
473-
MixDigest: g.Mixhash,
474-
Coinbase: g.Coinbase,
475-
Root: root,
466+
Number: new(big.Int).SetUint64(g.Number),
467+
Nonce: types.EncodeNonce(g.Nonce),
468+
Time: g.Timestamp,
469+
ParentHash: g.ParentHash,
470+
Extra: g.ExtraData,
471+
GasLimit: g.GasLimit,
472+
GasUsed: g.GasUsed,
473+
BaseFee: g.BaseFee,
474+
Difficulty: g.Difficulty,
475+
MixDigest: g.Mixhash,
476+
Coinbase: g.Coinbase,
477+
BlockAccessListHash: g.BlockAccessListHash,
478+
Root: root,
476479
}
477480
if g.GasLimit == 0 {
478481
head.GasLimit = params.GenesisGasLimit

0 commit comments

Comments
 (0)