Skip to content

Commit 961f517

Browse files
committed
wip: tracer-based BAL creation
1 parent 1cfe624 commit 961f517

File tree

19 files changed

+880
-391
lines changed

19 files changed

+880
-391
lines changed

cmd/geth/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ var (
155155
utils.BeaconGenesisTimeFlag,
156156
utils.BeaconCheckpointFlag,
157157
utils.BeaconCheckpointFileFlag,
158+
utils.ExperimentalBALFlag,
158159
}, utils.NetworkFlags, utils.DatabaseFlags)
159160

160161
rpcFlags = []cli.Flag{

cmd/utils/flags.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -997,6 +997,14 @@ Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server.
997997
Value: metrics.DefaultConfig.InfluxDBOrganization,
998998
Category: flags.MetricsCategory,
999999
}
1000+
1001+
// Block Access List flags
1002+
1003+
ExperimentalBALFlag = &cli.BoolFlag{
1004+
Name: "experimental.bal",
1005+
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.",
1006+
Category: flags.MiscCategory,
1007+
}
10001008
)
10011009

10021010
var (
@@ -1899,6 +1907,8 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
18991907
cfg.VMTraceJsonConfig = ctx.String(VMTraceJsonConfigFlag.Name)
19001908
}
19011909
}
1910+
1911+
cfg.ExperimentalBAL = ctx.Bool(ExperimentalBALFlag.Name)
19021912
}
19031913

19041914
// MakeBeaconLightConfig constructs a beacon light client config based on the
@@ -2301,6 +2311,7 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh
23012311
}
23022312
options.VmConfig = vmcfg
23032313

2314+
options.EnableBALForTesting = ctx.Bool(ExperimentalBALFlag.Name)
23042315
chain, err := core.NewBlockChain(chainDb, gspec, engine, options)
23052316
if err != nil {
23062317
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
@@ -196,6 +196,11 @@ type BlockChainConfig struct {
196196
// If the value is -1, indexing is disabled.
197197
TxLookupLimit int64
198198

199+
// If EnableBALForTesting is enabled, block access lists will be created as part of
200+
// block processing and embedded in the block body. The block access list hash will
201+
// not be set in the header.
202+
EnableBALForTesting bool
203+
199204
// StateSizeTracking indicates whether the state size tracking is enabled.
200205
StateSizeTracking bool
201206
}
@@ -1904,9 +1909,17 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness
19041909
if parent == nil {
19051910
parent = bc.GetHeader(block.ParentHash(), block.NumberU64()-1)
19061911
}
1912+
1913+
// construct or verify block access lists if BALs are enabled and
1914+
// we are post-selfdestruct removal fork.
1915+
enableBAL := (bc.cfg.EnableBALForTesting && bc.chainConfig.IsCancun(block.Number(), block.Time())) || bc.chainConfig.IsAmsterdam(block.Number(), block.Time())
1916+
blockHasAccessList := block.Body().AccessList != nil
1917+
makeBAL := enableBAL && !blockHasAccessList
1918+
validateBAL := enableBAL && blockHasAccessList
1919+
19071920
// The traced section of block import.
19081921
start := time.Now()
1909-
res, err := bc.ProcessBlock(parent.Root, block, setHead, makeWitness && len(chain) == 1)
1922+
res, err := bc.ProcessBlock(parent.Root, block, setHead, makeWitness && len(chain) == 1, makeBAL, validateBAL)
19101923
if err != nil {
19111924
return nil, it.index, err
19121925
}
@@ -1978,7 +1991,7 @@ func (bpr *blockProcessingResult) Witness() *stateless.Witness {
19781991

19791992
// ProcessBlock executes and validates the given block. If there was no error
19801993
// it writes the block and associated state to database.
1981-
func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, setHead bool, makeWitness bool) (_ *blockProcessingResult, blockEndErr error) {
1994+
func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, setHead bool, makeWitness bool, constructBALForTesting bool, validateBAL bool) (_ *blockProcessingResult, blockEndErr error) {
19821995
var (
19831996
err error
19841997
startTime = time.Now()
@@ -2074,6 +2087,12 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s
20742087
}()
20752088
}
20762089

2090+
var balTracer *BlockAccessListTracer
2091+
// Process block using the parent state as reference point
2092+
if constructBALForTesting {
2093+
balTracer, bc.cfg.VmConfig.Tracer = NewBlockAccessListTracer()
2094+
}
2095+
20772096
// Process block using the parent state as reference point
20782097
pstart := time.Now()
20792098
res, err := bc.processor.Process(block, statedb, bc.cfg.VmConfig)
@@ -2090,6 +2109,16 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s
20902109
}
20912110
vtime := time.Since(vstart)
20922111

2112+
if constructBALForTesting {
2113+
// very ugly... deep-copy the block body before setting the block access
2114+
// list on it to prevent mutating the block instance passed by the caller.
2115+
existingBody := block.Body()
2116+
block = block.WithBody(*existingBody)
2117+
existingBody = block.Body()
2118+
existingBody.AccessList = balTracer.AccessList().ToEncodingObj()
2119+
block = block.WithBody(*existingBody)
2120+
}
2121+
20932122
// If witnesses was generated and stateless self-validation requested, do
20942123
// that now. Self validation should *never* run in production, it's more of
20952124
// 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
}
@@ -469,18 +471,19 @@ func (g *Genesis) ToBlock() *types.Block {
469471
// toBlockWithRoot constructs the genesis block with the given genesis state root.
470472
func (g *Genesis) toBlockWithRoot(root common.Hash) *types.Block {
471473
head := &types.Header{
472-
Number: new(big.Int).SetUint64(g.Number),
473-
Nonce: types.EncodeNonce(g.Nonce),
474-
Time: g.Timestamp,
475-
ParentHash: g.ParentHash,
476-
Extra: g.ExtraData,
477-
GasLimit: g.GasLimit,
478-
GasUsed: g.GasUsed,
479-
BaseFee: g.BaseFee,
480-
Difficulty: g.Difficulty,
481-
MixDigest: g.Mixhash,
482-
Coinbase: g.Coinbase,
483-
Root: root,
474+
Number: new(big.Int).SetUint64(g.Number),
475+
Nonce: types.EncodeNonce(g.Nonce),
476+
Time: g.Timestamp,
477+
ParentHash: g.ParentHash,
478+
Extra: g.ExtraData,
479+
GasLimit: g.GasLimit,
480+
GasUsed: g.GasUsed,
481+
BaseFee: g.BaseFee,
482+
Difficulty: g.Difficulty,
483+
MixDigest: g.Mixhash,
484+
Coinbase: g.Coinbase,
485+
BlockAccessListHash: g.BlockAccessListHash,
486+
Root: root,
484487
}
485488
if g.GasLimit == 0 {
486489
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)