diff --git a/core/state/reader.go b/core/state/reader.go index 6d6971520b..b184c75aeb 100644 --- a/core/state/reader.go +++ b/core/state/reader.go @@ -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 +} diff --git a/core/state/reader_eip_7928_test.go b/core/state/reader_eip_7928_test.go index c143f4863f..57305031be 100644 --- a/core/state/reader_eip_7928_test.go +++ b/core/state/reader_eip_7928_test.go @@ -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) + } +}