Skip to content

Commit 85587d5

Browse files
jwasingerkaralabe
andauthored
cmd, core: prefetch reads too from tries if requested (#29807)
* cmd/utils, consensus/beacon, core/state: when configured via stub flag: prefetch all reads from account/storage tries, terminate prefetcher synchronously. * cmd, core/state: fix nil panic, fix error handling, prefetch nosnap too * core/state: expand prefetcher metrics for reads and writes separately * cmd/utils, eth: fix noop collect witness flag --------- Co-authored-by: Péter Szilágyi <[email protected]>
1 parent 2eb185c commit 85587d5

File tree

10 files changed

+159
-56
lines changed

10 files changed

+159
-56
lines changed

cmd/geth/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ var (
156156
utils.BeaconGenesisRootFlag,
157157
utils.BeaconGenesisTimeFlag,
158158
utils.BeaconCheckpointFlag,
159+
utils.CollectWitnessFlag,
159160
}, utils.NetworkFlags, utils.DatabaseFlags)
160161

161162
rpcFlags = []cli.Flag{

cmd/utils/flags.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,11 @@ var (
604604
Usage: "Disables db compaction after import",
605605
Category: flags.LoggingCategory,
606606
}
607+
CollectWitnessFlag = &cli.BoolFlag{
608+
Name: "collectwitness",
609+
Usage: "Enable state witness generation during block execution. Work in progress flag, don't use.",
610+
Category: flags.MiscCategory,
611+
}
607612

608613
// MISC settings
609614
SyncTargetFlag = &cli.StringFlag{
@@ -1760,6 +1765,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
17601765
// TODO(fjl): force-enable this in --dev mode
17611766
cfg.EnablePreimageRecording = ctx.Bool(VMEnableDebugFlag.Name)
17621767
}
1768+
if ctx.IsSet(CollectWitnessFlag.Name) {
1769+
cfg.EnableWitnessCollection = ctx.Bool(CollectWitnessFlag.Name)
1770+
}
17631771

17641772
if ctx.IsSet(RPCGlobalGasCapFlag.Name) {
17651773
cfg.RPCGasCap = ctx.Uint64(RPCGlobalGasCapFlag.Name)
@@ -2190,7 +2198,10 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh
21902198
if ctx.IsSet(CacheFlag.Name) || ctx.IsSet(CacheGCFlag.Name) {
21912199
cache.TrieDirtyLimit = ctx.Int(CacheFlag.Name) * ctx.Int(CacheGCFlag.Name) / 100
21922200
}
2193-
vmcfg := vm.Config{EnablePreimageRecording: ctx.Bool(VMEnableDebugFlag.Name)}
2201+
vmcfg := vm.Config{
2202+
EnablePreimageRecording: ctx.Bool(VMEnableDebugFlag.Name),
2203+
EnableWitnessCollection: ctx.Bool(CollectWitnessFlag.Name),
2204+
}
21942205
if ctx.IsSet(VMTraceFlag.Name) {
21952206
if name := ctx.String(VMTraceFlag.Name); name != "" {
21962207
var config json.RawMessage

core/blockchain.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1809,7 +1809,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
18091809
// while processing transactions. Before Byzantium the prefetcher is mostly
18101810
// useless due to the intermediate root hashing after each transaction.
18111811
if bc.chainConfig.IsByzantium(block.Number()) {
1812-
statedb.StartPrefetcher("chain")
1812+
statedb.StartPrefetcher("chain", !bc.vmConfig.EnableWitnessCollection)
18131813
}
18141814
activeState = statedb
18151815

core/state/state_object.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,14 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
230230
}
231231
value.SetBytes(val)
232232
}
233+
// Independent of where we loaded the data from, add it to the prefetcher.
234+
// Whilst this would be a bit weird if snapshots are disabled, but we still
235+
// want the trie nodes to end up in the prefetcher too, so just push through.
236+
if s.db.prefetcher != nil && s.data.Root != types.EmptyRootHash {
237+
if err = s.db.prefetcher.prefetch(s.addrHash, s.origin.Root, s.address, [][]byte{key[:]}, true); err != nil {
238+
log.Error("Failed to prefetch storage slot", "addr", s.address, "key", key, "err", err)
239+
}
240+
}
233241
s.originStorage[key] = value
234242
return value
235243
}
@@ -293,7 +301,7 @@ func (s *stateObject) finalise() {
293301
s.pendingStorage[key] = value
294302
}
295303
if s.db.prefetcher != nil && len(slotsToPrefetch) > 0 && s.data.Root != types.EmptyRootHash {
296-
if err := s.db.prefetcher.prefetch(s.addrHash, s.data.Root, s.address, slotsToPrefetch); err != nil {
304+
if err := s.db.prefetcher.prefetch(s.addrHash, s.data.Root, s.address, slotsToPrefetch, false); err != nil {
297305
log.Error("Failed to prefetch slots", "addr", s.address, "slots", len(slotsToPrefetch), "err", err)
298306
}
299307
}

core/state/statedb.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -200,14 +200,14 @@ func (s *StateDB) SetLogger(l *tracing.Hooks) {
200200
// StartPrefetcher initializes a new trie prefetcher to pull in nodes from the
201201
// state trie concurrently while the state is mutated so that when we reach the
202202
// commit phase, most of the needed data is already hot.
203-
func (s *StateDB) StartPrefetcher(namespace string) {
203+
func (s *StateDB) StartPrefetcher(namespace string, noreads bool) {
204204
if s.prefetcher != nil {
205205
s.prefetcher.terminate(false)
206206
s.prefetcher.report()
207207
s.prefetcher = nil
208208
}
209209
if s.snap != nil {
210-
s.prefetcher = newTriePrefetcher(s.db, s.originalRoot, namespace)
210+
s.prefetcher = newTriePrefetcher(s.db, s.originalRoot, namespace, noreads)
211211

212212
// With the switch to the Proof-of-Stake consensus algorithm, block production
213213
// rewards are now handled at the consensus layer. Consequently, a block may
@@ -218,7 +218,7 @@ func (s *StateDB) StartPrefetcher(namespace string) {
218218
// To prevent this, the account trie is always scheduled for prefetching once
219219
// the prefetcher is constructed. For more details, see:
220220
// https://github.com/ethereum/go-ethereum/issues/29880
221-
if err := s.prefetcher.prefetch(common.Hash{}, s.originalRoot, common.Address{}, nil); err != nil {
221+
if err := s.prefetcher.prefetch(common.Hash{}, s.originalRoot, common.Address{}, nil, false); err != nil {
222222
log.Error("Failed to prefetch account trie", "root", s.originalRoot, "err", err)
223223
}
224224
}
@@ -616,6 +616,14 @@ func (s *StateDB) getStateObject(addr common.Address) *stateObject {
616616
return nil
617617
}
618618
}
619+
// Independent of where we loaded the data from, add it to the prefetcher.
620+
// Whilst this would be a bit weird if snapshots are disabled, but we still
621+
// want the trie nodes to end up in the prefetcher too, so just push through.
622+
if s.prefetcher != nil {
623+
if err := s.prefetcher.prefetch(common.Hash{}, s.originalRoot, common.Address{}, [][]byte{addr[:]}, true); err != nil {
624+
log.Error("Failed to prefetch account", "addr", addr, "err", err)
625+
}
626+
}
619627
// Insert into the live set
620628
obj := newObject(s, addr, data)
621629
s.setStateObject(obj)
@@ -792,7 +800,7 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
792800
addressesToPrefetch = append(addressesToPrefetch, common.CopyBytes(addr[:])) // Copy needed for closure
793801
}
794802
if s.prefetcher != nil && len(addressesToPrefetch) > 0 {
795-
if err := s.prefetcher.prefetch(common.Hash{}, s.originalRoot, common.Address{}, addressesToPrefetch); err != nil {
803+
if err := s.prefetcher.prefetch(common.Hash{}, s.originalRoot, common.Address{}, addressesToPrefetch, false); err != nil {
796804
log.Error("Failed to prefetch addresses", "addresses", len(addressesToPrefetch), "err", err)
797805
}
798806
}

core/state/trie_prefetcher.go

Lines changed: 116 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -44,31 +44,49 @@ type triePrefetcher struct {
4444
root common.Hash // Root hash of the account trie for metrics
4545
fetchers map[string]*subfetcher // Subfetchers for each trie
4646
term chan struct{} // Channel to signal interruption
47+
noreads bool // Whether to ignore state-read-only prefetch requests
4748

4849
deliveryMissMeter metrics.Meter
49-
accountLoadMeter metrics.Meter
50-
accountDupMeter metrics.Meter
51-
accountWasteMeter metrics.Meter
52-
storageLoadMeter metrics.Meter
53-
storageDupMeter metrics.Meter
54-
storageWasteMeter metrics.Meter
50+
51+
accountLoadReadMeter metrics.Meter
52+
accountLoadWriteMeter metrics.Meter
53+
accountDupReadMeter metrics.Meter
54+
accountDupWriteMeter metrics.Meter
55+
accountDupCrossMeter metrics.Meter
56+
accountWasteMeter metrics.Meter
57+
58+
storageLoadReadMeter metrics.Meter
59+
storageLoadWriteMeter metrics.Meter
60+
storageDupReadMeter metrics.Meter
61+
storageDupWriteMeter metrics.Meter
62+
storageDupCrossMeter metrics.Meter
63+
storageWasteMeter metrics.Meter
5564
}
5665

57-
func newTriePrefetcher(db Database, root common.Hash, namespace string) *triePrefetcher {
66+
func newTriePrefetcher(db Database, root common.Hash, namespace string, noreads bool) *triePrefetcher {
5867
prefix := triePrefetchMetricsPrefix + namespace
5968
return &triePrefetcher{
6069
db: db,
6170
root: root,
6271
fetchers: make(map[string]*subfetcher), // Active prefetchers use the fetchers map
6372
term: make(chan struct{}),
73+
noreads: noreads,
6474

6575
deliveryMissMeter: metrics.GetOrRegisterMeter(prefix+"/deliverymiss", nil),
66-
accountLoadMeter: metrics.GetOrRegisterMeter(prefix+"/account/load", nil),
67-
accountDupMeter: metrics.GetOrRegisterMeter(prefix+"/account/dup", nil),
68-
accountWasteMeter: metrics.GetOrRegisterMeter(prefix+"/account/waste", nil),
69-
storageLoadMeter: metrics.GetOrRegisterMeter(prefix+"/storage/load", nil),
70-
storageDupMeter: metrics.GetOrRegisterMeter(prefix+"/storage/dup", nil),
71-
storageWasteMeter: metrics.GetOrRegisterMeter(prefix+"/storage/waste", nil),
76+
77+
accountLoadReadMeter: metrics.GetOrRegisterMeter(prefix+"/account/load/read", nil),
78+
accountLoadWriteMeter: metrics.GetOrRegisterMeter(prefix+"/account/load/write", nil),
79+
accountDupReadMeter: metrics.GetOrRegisterMeter(prefix+"/account/dup/read", nil),
80+
accountDupWriteMeter: metrics.GetOrRegisterMeter(prefix+"/account/dup/write", nil),
81+
accountDupCrossMeter: metrics.GetOrRegisterMeter(prefix+"/account/dup/cross", nil),
82+
accountWasteMeter: metrics.GetOrRegisterMeter(prefix+"/account/waste", nil),
83+
84+
storageLoadReadMeter: metrics.GetOrRegisterMeter(prefix+"/storage/load/read", nil),
85+
storageLoadWriteMeter: metrics.GetOrRegisterMeter(prefix+"/storage/load/write", nil),
86+
storageDupReadMeter: metrics.GetOrRegisterMeter(prefix+"/storage/dup/read", nil),
87+
storageDupWriteMeter: metrics.GetOrRegisterMeter(prefix+"/storage/dup/write", nil),
88+
storageDupCrossMeter: metrics.GetOrRegisterMeter(prefix+"/storage/dup/cross", nil),
89+
storageWasteMeter: metrics.GetOrRegisterMeter(prefix+"/storage/waste", nil),
7290
}
7391
}
7492

@@ -98,19 +116,31 @@ func (p *triePrefetcher) report() {
98116
fetcher.wait() // ensure the fetcher's idle before poking in its internals
99117

100118
if fetcher.root == p.root {
101-
p.accountLoadMeter.Mark(int64(len(fetcher.seen)))
102-
p.accountDupMeter.Mark(int64(fetcher.dups))
119+
p.accountLoadReadMeter.Mark(int64(len(fetcher.seenRead)))
120+
p.accountLoadWriteMeter.Mark(int64(len(fetcher.seenWrite)))
121+
122+
p.accountDupReadMeter.Mark(int64(fetcher.dupsRead))
123+
p.accountDupWriteMeter.Mark(int64(fetcher.dupsWrite))
124+
p.accountDupCrossMeter.Mark(int64(fetcher.dupsCross))
125+
103126
for _, key := range fetcher.used {
104-
delete(fetcher.seen, string(key))
127+
delete(fetcher.seenRead, string(key))
128+
delete(fetcher.seenWrite, string(key))
105129
}
106-
p.accountWasteMeter.Mark(int64(len(fetcher.seen)))
130+
p.accountWasteMeter.Mark(int64(len(fetcher.seenRead) + len(fetcher.seenWrite)))
107131
} else {
108-
p.storageLoadMeter.Mark(int64(len(fetcher.seen)))
109-
p.storageDupMeter.Mark(int64(fetcher.dups))
132+
p.storageLoadReadMeter.Mark(int64(len(fetcher.seenRead)))
133+
p.storageLoadWriteMeter.Mark(int64(len(fetcher.seenWrite)))
134+
135+
p.storageDupReadMeter.Mark(int64(fetcher.dupsRead))
136+
p.storageDupWriteMeter.Mark(int64(fetcher.dupsWrite))
137+
p.storageDupCrossMeter.Mark(int64(fetcher.dupsCross))
138+
110139
for _, key := range fetcher.used {
111-
delete(fetcher.seen, string(key))
140+
delete(fetcher.seenRead, string(key))
141+
delete(fetcher.seenWrite, string(key))
112142
}
113-
p.storageWasteMeter.Mark(int64(len(fetcher.seen)))
143+
p.storageWasteMeter.Mark(int64(len(fetcher.seenRead) + len(fetcher.seenWrite)))
114144
}
115145
}
116146
}
@@ -126,7 +156,11 @@ func (p *triePrefetcher) report() {
126156
// upon the same contract, the parameters invoking this method may be
127157
// repeated.
128158
// 2. Finalize of the main account trie. This happens only once per block.
129-
func (p *triePrefetcher) prefetch(owner common.Hash, root common.Hash, addr common.Address, keys [][]byte) error {
159+
func (p *triePrefetcher) prefetch(owner common.Hash, root common.Hash, addr common.Address, keys [][]byte, read bool) error {
160+
// If the state item is only being read, but reads are disabled, return
161+
if read && p.noreads {
162+
return nil
163+
}
130164
// Ensure the subfetcher is still alive
131165
select {
132166
case <-p.term:
@@ -139,7 +173,7 @@ func (p *triePrefetcher) prefetch(owner common.Hash, root common.Hash, addr comm
139173
fetcher = newSubfetcher(p.db, p.root, owner, root, addr)
140174
p.fetchers[id] = fetcher
141175
}
142-
return fetcher.schedule(keys)
176+
return fetcher.schedule(keys, read)
143177
}
144178

145179
// trie returns the trie matching the root hash, blocking until the fetcher of
@@ -186,38 +220,51 @@ type subfetcher struct {
186220
addr common.Address // Address of the account that the trie belongs to
187221
trie Trie // Trie being populated with nodes
188222

189-
tasks [][]byte // Items queued up for retrieval
190-
lock sync.Mutex // Lock protecting the task queue
223+
tasks []*subfetcherTask // Items queued up for retrieval
224+
lock sync.Mutex // Lock protecting the task queue
191225

192226
wake chan struct{} // Wake channel if a new task is scheduled
193227
stop chan struct{} // Channel to interrupt processing
194228
term chan struct{} // Channel to signal interruption
195229

196-
seen map[string]struct{} // Tracks the entries already loaded
197-
dups int // Number of duplicate preload tasks
198-
used [][]byte // Tracks the entries used in the end
230+
seenRead map[string]struct{} // Tracks the entries already loaded via read operations
231+
seenWrite map[string]struct{} // Tracks the entries already loaded via write operations
232+
233+
dupsRead int // Number of duplicate preload tasks via reads only
234+
dupsWrite int // Number of duplicate preload tasks via writes only
235+
dupsCross int // Number of duplicate preload tasks via read-write-crosses
236+
237+
used [][]byte // Tracks the entries used in the end
238+
}
239+
240+
// subfetcherTask is a trie path to prefetch, tagged with whether it originates
241+
// from a read or a write request.
242+
type subfetcherTask struct {
243+
read bool
244+
key []byte
199245
}
200246

201247
// newSubfetcher creates a goroutine to prefetch state items belonging to a
202248
// particular root hash.
203249
func newSubfetcher(db Database, state common.Hash, owner common.Hash, root common.Hash, addr common.Address) *subfetcher {
204250
sf := &subfetcher{
205-
db: db,
206-
state: state,
207-
owner: owner,
208-
root: root,
209-
addr: addr,
210-
wake: make(chan struct{}, 1),
211-
stop: make(chan struct{}),
212-
term: make(chan struct{}),
213-
seen: make(map[string]struct{}),
251+
db: db,
252+
state: state,
253+
owner: owner,
254+
root: root,
255+
addr: addr,
256+
wake: make(chan struct{}, 1),
257+
stop: make(chan struct{}),
258+
term: make(chan struct{}),
259+
seenRead: make(map[string]struct{}),
260+
seenWrite: make(map[string]struct{}),
214261
}
215262
go sf.loop()
216263
return sf
217264
}
218265

219266
// schedule adds a batch of trie keys to the queue to prefetch.
220-
func (sf *subfetcher) schedule(keys [][]byte) error {
267+
func (sf *subfetcher) schedule(keys [][]byte, read bool) error {
221268
// Ensure the subfetcher is still alive
222269
select {
223270
case <-sf.term:
@@ -226,7 +273,10 @@ func (sf *subfetcher) schedule(keys [][]byte) error {
226273
}
227274
// Append the tasks to the current queue
228275
sf.lock.Lock()
229-
sf.tasks = append(sf.tasks, keys...)
276+
for _, key := range keys {
277+
key := key // closure for the append below
278+
sf.tasks = append(sf.tasks, &subfetcherTask{read: read, key: key})
279+
}
230280
sf.lock.Unlock()
231281

232282
// Notify the background thread to execute scheduled tasks
@@ -303,16 +353,36 @@ func (sf *subfetcher) loop() {
303353
sf.lock.Unlock()
304354

305355
for _, task := range tasks {
306-
if _, ok := sf.seen[string(task)]; ok {
307-
sf.dups++
308-
continue
356+
key := string(task.key)
357+
if task.read {
358+
if _, ok := sf.seenRead[key]; ok {
359+
sf.dupsRead++
360+
continue
361+
}
362+
if _, ok := sf.seenWrite[key]; ok {
363+
sf.dupsCross++
364+
continue
365+
}
366+
} else {
367+
if _, ok := sf.seenRead[key]; ok {
368+
sf.dupsCross++
369+
continue
370+
}
371+
if _, ok := sf.seenWrite[key]; ok {
372+
sf.dupsWrite++
373+
continue
374+
}
375+
}
376+
if len(task.key) == common.AddressLength {
377+
sf.trie.GetAccount(common.BytesToAddress(task.key))
378+
} else {
379+
sf.trie.GetStorage(sf.addr, task.key)
309380
}
310-
if len(task) == common.AddressLength {
311-
sf.trie.GetAccount(common.BytesToAddress(task))
381+
if task.read {
382+
sf.seenRead[key] = struct{}{}
312383
} else {
313-
sf.trie.GetStorage(sf.addr, task)
384+
sf.seenWrite[key] = struct{}{}
314385
}
315-
sf.seen[string(task)] = struct{}{}
316386
}
317387

318388
case <-sf.stop:

core/state/trie_prefetcher_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,15 @@ func filledStateDB() *StateDB {
4747

4848
func TestUseAfterTerminate(t *testing.T) {
4949
db := filledStateDB()
50-
prefetcher := newTriePrefetcher(db.db, db.originalRoot, "")
50+
prefetcher := newTriePrefetcher(db.db, db.originalRoot, "", true)
5151
skey := common.HexToHash("aaa")
5252

53-
if err := prefetcher.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()}); err != nil {
53+
if err := prefetcher.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()}, false); err != nil {
5454
t.Errorf("Prefetch failed before terminate: %v", err)
5555
}
5656
prefetcher.terminate(false)
5757

58-
if err := prefetcher.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()}); err == nil {
58+
if err := prefetcher.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()}, false); err == nil {
5959
t.Errorf("Prefetch succeeded after terminate: %v", err)
6060
}
6161
if tr := prefetcher.trie(common.Hash{}, db.originalRoot); tr == nil {

core/vm/interpreter.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ type Config struct {
3333
NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls)
3434
EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages
3535
ExtraEips []int // Additional EIPS that are to be enabled
36+
EnableWitnessCollection bool // true if witness collection is enabled
3637
}
3738

3839
// ScopeContext contains the things that are per-call, such as stack and memory,

0 commit comments

Comments
 (0)