Skip to content

Commit 8d479ef

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

File tree

20 files changed

+900
-406
lines changed

20 files changed

+900
-406
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.EnableBALForTesting = 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: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
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+
"github.com/holiman/uint256"
9+
"math/big"
10+
)
11+
12+
// BlockAccessListTracer constructs an EIP-7928 block access list from the
13+
// execution of a block
14+
type BlockAccessListTracer struct {
15+
// this is a set of access lists for each call scope. the overall block access lists
16+
// is accrued at index 0, while the access lists of various nested execution
17+
// scopes are in the proceeding indices.
18+
// When an execution scope terminates in a non-reverting fashion, the changes are
19+
// merged into the access list of the parent scope.
20+
callAccessLists []*bal.ConstructionBlockAccessList
21+
txIdx uint16
22+
23+
// if non-nil, it's the address of the account which just self-destructed.
24+
// reset at the end of the call-scope which self-destructed.
25+
selfdestructedAccount *common.Address
26+
}
27+
28+
// NewBlockAccessListTracer returns an BlockAccessListTracer and a set of hooks
29+
func NewBlockAccessListTracer() (*BlockAccessListTracer, *tracing.Hooks) {
30+
balTracer := &BlockAccessListTracer{
31+
callAccessLists: []*bal.ConstructionBlockAccessList{bal.NewConstructionBlockAccessList()},
32+
txIdx: 0,
33+
}
34+
hooks := &tracing.Hooks{
35+
OnTxEnd: balTracer.TxEndHook,
36+
OnTxStart: balTracer.TxStartHook,
37+
OnEnter: balTracer.OnEnter,
38+
OnExit: balTracer.OnExit,
39+
OnCodeChangeV2: balTracer.OnCodeChange,
40+
OnBalanceChange: balTracer.OnBalanceChange,
41+
OnNonceChange: balTracer.OnNonceChange,
42+
OnStorageChange: balTracer.OnStorageChange,
43+
OnColdAccountRead: balTracer.OnColdAccountRead,
44+
OnColdStorageRead: balTracer.OnColdStorageRead,
45+
}
46+
return balTracer, hooks
47+
}
48+
49+
// AccessList returns the constructed access list
50+
func (a *BlockAccessListTracer) AccessList() *bal.ConstructionBlockAccessList {
51+
return a.callAccessLists[0]
52+
}
53+
54+
func (a *BlockAccessListTracer) TxEndHook(receipt *types.Receipt, err error) {
55+
a.txIdx++
56+
}
57+
58+
func (a *BlockAccessListTracer) TxStartHook(vm *tracing.VMContext, tx *types.Transaction, from common.Address) {
59+
if a.txIdx == 0 {
60+
a.txIdx++
61+
}
62+
}
63+
64+
func (a *BlockAccessListTracer) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
65+
a.callAccessLists = append(a.callAccessLists, bal.NewConstructionBlockAccessList())
66+
}
67+
68+
func (a *BlockAccessListTracer) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) {
69+
// any self-destructed accounts must have been created in the same transaction
70+
// so there is no difference between the pre/post tx state of those accounts
71+
if a.selfdestructedAccount != nil {
72+
delete(a.callAccessLists[len(a.callAccessLists)-1].Accounts, *a.selfdestructedAccount)
73+
}
74+
if !reverted {
75+
parentAccessList := a.callAccessLists[len(a.callAccessLists)-2]
76+
scopeAccessList := a.callAccessLists[len(a.callAccessLists)-1]
77+
parentAccessList.Merge(scopeAccessList)
78+
}
79+
80+
a.callAccessLists = a.callAccessLists[:len(a.callAccessLists)-1]
81+
}
82+
83+
func (a *BlockAccessListTracer) OnCodeChange(addr common.Address, prevCodeHash common.Hash, prevCode []byte, codeHash common.Hash, code []byte, reason tracing.CodeChangeReason) {
84+
if reason == tracing.CodeChangeSelfDestruct {
85+
a.selfdestructedAccount = &addr
86+
return
87+
}
88+
a.callAccessLists[len(a.callAccessLists)-1].CodeChange(addr, uint16(a.txIdx), code)
89+
}
90+
91+
func (a *BlockAccessListTracer) OnBalanceChange(addr common.Address, prevBalance, newBalance *big.Int, _ tracing.BalanceChangeReason) {
92+
a.callAccessLists[len(a.callAccessLists)-1].BalanceChange(a.txIdx, addr, new(uint256.Int).SetBytes(newBalance.Bytes()))
93+
}
94+
95+
func (a *BlockAccessListTracer) OnNonceChange(addr common.Address, prev uint64, new uint64) {
96+
a.callAccessLists[len(a.callAccessLists)-1].NonceChange(addr, a.txIdx, new)
97+
}
98+
99+
func (a *BlockAccessListTracer) OnColdStorageRead(addr common.Address, key common.Hash) {
100+
a.callAccessLists[len(a.callAccessLists)-1].StorageRead(addr, key)
101+
}
102+
103+
func (a *BlockAccessListTracer) OnColdAccountRead(addr common.Address) {
104+
a.callAccessLists[len(a.callAccessLists)-1].AccountRead(addr)
105+
}
106+
107+
func (a *BlockAccessListTracer) OnStorageChange(addr common.Address, slot common.Hash, prev common.Hash, new common.Hash) {
108+
a.callAccessLists[len(a.callAccessLists)-1].StorageWrite(a.txIdx, addr, slot, new)
109+
}

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.EnableBALForTesting {
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: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,11 @@ type BlockChainConfig struct {
197197
// If the value is -1, indexing is disabled.
198198
TxLookupLimit int64
199199

200+
// If EnableBALForTesting is enabled, block access lists will be created as part of
201+
// block processing and embedded in the block body. The block access list hash will
202+
// not be set in the header.
203+
EnableBALForTesting bool
204+
200205
// StateSizeTracking indicates whether the state size tracking is enabled.
201206
StateSizeTracking bool
202207
}
@@ -1905,9 +1910,17 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness
19051910
if parent == nil {
19061911
parent = bc.GetHeader(block.ParentHash(), block.NumberU64()-1)
19071912
}
1913+
1914+
// construct or verify block access lists if BALs are enabled and
1915+
// we are post-selfdestruct removal fork.
1916+
enableBAL := (bc.cfg.EnableBALForTesting && bc.chainConfig.IsCancun(block.Number(), block.Time())) || bc.chainConfig.IsAmsterdam(block.Number(), block.Time())
1917+
blockHasAccessList := block.Body().AccessList != nil
1918+
makeBAL := enableBAL && !blockHasAccessList
1919+
validateBAL := enableBAL && blockHasAccessList
1920+
19081921
// The traced section of block import.
19091922
start := time.Now()
1910-
res, err := bc.processBlock(parent.Root, block, setHead, makeWitness && len(chain) == 1)
1923+
res, err := bc.processBlock(parent.Root, block, setHead, makeWitness && len(chain) == 1, makeBAL, validateBAL)
19111924
if err != nil {
19121925
return nil, it.index, err
19131926
}
@@ -1975,7 +1988,7 @@ type blockProcessingResult struct {
19751988

19761989
// processBlock executes and validates the given block. If there was no error
19771990
// 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) {
1991+
func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, setHead bool, makeWitness bool, constructBALForTesting bool, validateBAL bool) (bpr *blockProcessingResult, blockEndErr error) {
19791992
var (
19801993
err error
19811994
startTime = time.Now()
@@ -2071,6 +2084,12 @@ func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, s
20712084
}()
20722085
}
20732086

2087+
var balTracer *BlockAccessListTracer
2088+
// Process block using the parent state as reference point
2089+
if constructBALForTesting {
2090+
balTracer, bc.cfg.VmConfig.Tracer = NewBlockAccessListTracer()
2091+
}
2092+
20742093
// Process block using the parent state as reference point
20752094
pstart := time.Now()
20762095
res, err := bc.processor.Process(block, statedb, bc.cfg.VmConfig)
@@ -2087,6 +2106,16 @@ func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, s
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 = balTracer.AccessList().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

core/tracing/hooks.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,12 @@ type (
183183
// StorageChangeHook is called when the storage of an account changes.
184184
StorageChangeHook = func(addr common.Address, slot common.Hash, prev, new common.Hash)
185185

186+
// ColdStorageReadHook is called before a previously-unread storage slot is read.
187+
ColdStorageReadHook = func(common.Address, common.Hash)
188+
189+
// ColdAccountReadHook is called before an previously-unread account is read.
190+
ColdAccountReadHook = func(address common.Address)
191+
186192
// LogHook is called when a log is emitted.
187193
LogHook = func(log *types.Log)
188194

@@ -209,6 +215,7 @@ type Hooks struct {
209215
OnSystemCallStart OnSystemCallStartHook
210216
OnSystemCallStartV2 OnSystemCallStartHookV2
211217
OnSystemCallEnd OnSystemCallEndHook
218+
212219
// State events
213220
OnBalanceChange BalanceChangeHook
214221
OnNonceChange NonceChangeHook
@@ -217,6 +224,9 @@ type Hooks struct {
217224
OnCodeChangeV2 CodeChangeHookV2
218225
OnStorageChange StorageChangeHook
219226
OnLog LogHook
227+
//State read events
228+
OnColdStorageRead ColdStorageReadHook
229+
OnColdAccountRead ColdAccountReadHook
220230
// Block hash read
221231
OnBlockHashRead BlockHashReadHook
222232
}

0 commit comments

Comments
 (0)