core/state: forward prefetcher read times through the reader aggregator

Without this, the inline interface assertion in processBlockWithAccessList
silently fell through (the prefetchReader returned by ReaderEIP7928 is a
*reader wrapper, not the inner *prefetchStateReader), causing the
prefetcher contribution to state_read_ms to drop to zero in production.

Mirrors the existing GetStateStats forwarding pattern. Adds a regression
test that asserts *reader exposes PrefetchReadTimes via the BAL chain,
plus a fallback test for non-prefetch readers.
This commit is contained in:
CPerezz 2026-04-30 13:41:57 +02:00
parent 812fa198c3
commit 6b1ea9a498
No known key found for this signature in database
GPG key ID: 62045F34B97177DD
2 changed files with 51 additions and 0 deletions

View file

@ -20,6 +20,7 @@ import (
"errors"
"sync"
"sync/atomic"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/overlay"
@ -563,3 +564,15 @@ func (r *reader) GetStats() ReaderStats {
StateStats: r.GetStateStats(),
}
}
// PrefetchReadTimes returns the prefetcher's accumulated read times if the
// underlying state reader exposes them (e.g. *prefetchStateReader). Returns
// zero if the wrapped reader doesn't track these (sequential paths, tests).
func (r *reader) PrefetchReadTimes() (account, storage time.Duration) {
if pr, ok := r.StateReader.(interface {
PrefetchReadTimes() (time.Duration, time.Duration)
}); ok {
return pr.PrefetchReadTimes()
}
return 0, 0
}

View file

@ -290,3 +290,41 @@ func TestPrefetchStateReaderForwardsStats(t *testing.T) {
t.Fatalf("forward mismatch: got %+v, want %+v", gotStats, stats)
}
}
// TestReaderForwardsPrefetchReadTimes locks down that the *reader aggregator
// (the type returned by ReaderEIP7928) exposes PrefetchReadTimes via the
// inner *prefetchStateReader. Without the forwarding method on *reader,
// callers that hold a Reader interface would not see the prefetcher's
// accumulated read times even though the prefetcher tracks them.
func TestReaderForwardsPrefetchReadTimes(t *testing.T) {
stub := newRefStateReader()
cached := newStateReaderWithCache(stub)
withStats := newStateReaderWithStats(cached)
prefetch := newPrefetchStateReaderInternal(withStats, nil, 1)
// Seed timer values directly on the prefetcher.
prefetch.accountReadNS.Store(123)
prefetch.storageReadNS.Store(456)
// Wrap in *reader the way ReaderEIP7928 does (with a nil code reader for
// brevity; PrefetchReadTimes only inspects the state side).
r := newReader(nil, prefetch)
a, s := r.PrefetchReadTimes()
if a != 123 {
t.Errorf("account: got %v, want 123", a)
}
if s != 456 {
t.Errorf("storage: got %v, want 456", s)
}
}
// TestReaderPrefetchReadTimesNonPrefetch verifies the safe zero fallback when
// the wrapped state reader doesn't expose PrefetchReadTimes (sequential path).
func TestReaderPrefetchReadTimesNonPrefetch(t *testing.T) {
r := newReader(nil, newRefStateReader())
a, s := r.PrefetchReadTimes()
if a != 0 || s != 0 {
t.Errorf("expected (0, 0), got (%v, %v)", a, s)
}
}