diff --git a/core/state/reader_eip_7928.go b/core/state/reader_eip_7928.go index 2f6ee478a4..aff5dd3b3b 100644 --- a/core/state/reader_eip_7928.go +++ b/core/state/reader_eip_7928.go @@ -362,3 +362,13 @@ func (r *readerTracker) TouchStorage(addr common.Address, slot common.Hash) { } list[slot] = struct{}{} } + +// GetStateStats forwards stats from the wrapped *stateReaderWithStats so the +// reader-aggregator type assertion at reader.go:553 succeeds. Without this, +// account/storage cache hit/miss counts emit zero on BAL blocks. +func (r *prefetchStateReader) GetStateStats() StateReaderStats { + if stater, ok := r.StateReader.(StateReaderStater); ok { + return stater.GetStateStats() + } + return StateReaderStats{} +} diff --git a/core/state/reader_eip_7928_test.go b/core/state/reader_eip_7928_test.go index ef67a67444..c143f4863f 100644 --- a/core/state/reader_eip_7928_test.go +++ b/core/state/reader_eip_7928_test.go @@ -263,3 +263,30 @@ func TestTrackerSurvivesStateDBCache(t *testing.T) { t.Fatal("slot must be tracked on cache hit (storage)") } } + +// TestPrefetchStateReaderForwardsStats locks down that prefetchStateReader +// exposes the underlying stateReaderWithStats counters via GetStateStats. +func TestPrefetchStateReaderForwardsStats(t *testing.T) { + stub := newRefStateReader() + addr := testrand.Address() + + cached := newStateReaderWithCache(stub) + withStats := newStateReaderWithStats(cached) + prefetch := newPrefetchStateReaderInternal(withStats, nil, 1) + + if _, err := prefetch.Account(addr); err != nil { + t.Fatalf("Account: %v", err) + } + if _, err := prefetch.Account(addr); err != nil { + t.Fatalf("Account (second): %v", err) + } + + stats := withStats.GetStateStats() + if stats.AccountCacheHit == 0 || stats.AccountCacheMiss == 0 { + t.Fatalf("inner stats not populated: %+v", stats) + } + gotStats := prefetch.GetStateStats() + if gotStats != stats { + t.Fatalf("forward mismatch: got %+v, want %+v", gotStats, stats) + } +}