diff --git a/core/blockchain.go b/core/blockchain.go
index d41f301243..9a6a9b6fbf 100644
--- a/core/blockchain.go
+++ b/core/blockchain.go
@@ -2095,7 +2095,7 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
//
// Note: the main processor and prefetcher share the same reader with a local
// cache for mitigating the overhead of state access.
- prefetch, process, err := bc.statedb.ReadersWithCacheStats(parentRoot)
+ prefetch, process, err := bc.statedb.ReadersWithCache(parentRoot)
if err != nil {
return nil, err
}
@@ -2110,8 +2110,12 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
// Upload the statistics of reader at the end
defer func() {
if result != nil {
- result.stats.StatePrefetchCacheStats = prefetch.GetStats()
- result.stats.StateReadCacheStats = process.GetStats()
+ if stater, ok := prefetch.(state.ReaderStater); ok {
+ result.stats.StatePrefetchCacheStats = stater.GetStats()
+ }
+ if stater, ok := process.(state.ReaderStater); ok {
+ result.stats.StateReadCacheStats = stater.GetStats()
+ }
}
}()
go func(start time.Time, throwaway *state.StateDB, block *types.Block) {
diff --git a/core/blockchain_stats.go b/core/blockchain_stats.go
index adc66266c4..f887fc1cbf 100644
--- a/core/blockchain_stats.go
+++ b/core/blockchain_stats.go
@@ -94,15 +94,15 @@ func (s *ExecuteStats) reportMetrics() {
chainMgaspsMeter.Update(time.Duration(s.MgasPerSecond)) // TODO(rjl493456442) generalize the ResettingTimer
// Cache hit rates
- accountCacheHitPrefetchMeter.Mark(s.StatePrefetchCacheStats.AccountCacheHit)
- accountCacheMissPrefetchMeter.Mark(s.StatePrefetchCacheStats.AccountCacheMiss)
- storageCacheHitPrefetchMeter.Mark(s.StatePrefetchCacheStats.StorageCacheHit)
- storageCacheMissPrefetchMeter.Mark(s.StatePrefetchCacheStats.StorageCacheMiss)
+ accountCacheHitPrefetchMeter.Mark(s.StatePrefetchCacheStats.StateStats.AccountCacheHit)
+ accountCacheMissPrefetchMeter.Mark(s.StatePrefetchCacheStats.StateStats.AccountCacheMiss)
+ storageCacheHitPrefetchMeter.Mark(s.StatePrefetchCacheStats.StateStats.StorageCacheHit)
+ storageCacheMissPrefetchMeter.Mark(s.StatePrefetchCacheStats.StateStats.StorageCacheMiss)
- accountCacheHitMeter.Mark(s.StateReadCacheStats.AccountCacheHit)
- accountCacheMissMeter.Mark(s.StateReadCacheStats.AccountCacheMiss)
- storageCacheHitMeter.Mark(s.StateReadCacheStats.StorageCacheHit)
- storageCacheMissMeter.Mark(s.StateReadCacheStats.StorageCacheMiss)
+ accountCacheHitMeter.Mark(s.StateReadCacheStats.StateStats.AccountCacheHit)
+ accountCacheMissMeter.Mark(s.StateReadCacheStats.StateStats.AccountCacheMiss)
+ storageCacheHitMeter.Mark(s.StateReadCacheStats.StateStats.StorageCacheHit)
+ storageCacheMissMeter.Mark(s.StateReadCacheStats.StateStats.StorageCacheMiss)
}
// slowBlockLog represents the JSON structure for slow block logging.
@@ -238,14 +238,14 @@ func (s *ExecuteStats) logSlow(block *types.Block, slowBlockThreshold time.Durat
},
Cache: slowBlockCache{
Account: slowBlockCacheEntry{
- Hits: s.StateReadCacheStats.AccountCacheHit,
- Misses: s.StateReadCacheStats.AccountCacheMiss,
- HitRate: calculateHitRate(s.StateReadCacheStats.AccountCacheHit, s.StateReadCacheStats.AccountCacheMiss),
+ Hits: s.StateReadCacheStats.StateStats.AccountCacheHit,
+ Misses: s.StateReadCacheStats.StateStats.AccountCacheMiss,
+ HitRate: calculateHitRate(s.StateReadCacheStats.StateStats.AccountCacheHit, s.StateReadCacheStats.StateStats.AccountCacheMiss),
},
Storage: slowBlockCacheEntry{
- Hits: s.StateReadCacheStats.StorageCacheHit,
- Misses: s.StateReadCacheStats.StorageCacheMiss,
- HitRate: calculateHitRate(s.StateReadCacheStats.StorageCacheHit, s.StateReadCacheStats.StorageCacheMiss),
+ Hits: s.StateReadCacheStats.StateStats.StorageCacheHit,
+ Misses: s.StateReadCacheStats.StateStats.StorageCacheMiss,
+ HitRate: calculateHitRate(s.StateReadCacheStats.StateStats.StorageCacheHit, s.StateReadCacheStats.StateStats.StorageCacheMiss),
},
Code: slowBlockCodeCacheEntry{
Hits: s.StateReadCacheStats.CodeStats.CacheHit,
diff --git a/core/state/database.go b/core/state/database.go
index 4a5547d075..62888181eb 100644
--- a/core/state/database.go
+++ b/core/state/database.go
@@ -221,21 +221,35 @@ func (db *CachingDB) Reader(stateRoot common.Hash) (Reader, error) {
return newReader(newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache), sr), nil
}
-// ReadersWithCacheStats creates a pair of state readers that share the same
-// underlying state reader and internal state cache, while maintaining separate
-// statistics respectively.
-func (db *CachingDB) ReadersWithCacheStats(stateRoot common.Hash) (ReaderWithStats, ReaderWithStats, error) {
+// ReadersWithCache creates a pair of state readers that share the same
+// underlying state reader and internal state cache, while maintaining
+// separate statistics respectively.
+func (db *CachingDB) ReadersWithCache(stateRoot common.Hash) (Reader, Reader, error) {
r, err := db.StateReader(stateRoot)
if err != nil {
return nil, nil, err
}
sr := newStateReaderWithCache(r)
-
- ra := newReaderWithStats(sr, newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache))
- rb := newReaderWithStats(sr, newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache))
+ ra := newReader(newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache), newStateReaderWithStats(sr))
+ rb := newReader(newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache), newStateReaderWithStats(sr))
return ra, rb, nil
}
+// ReaderEIP7928 creates a state reader with the manner of Block-level accessList.
+func (db *CachingDB) ReaderEIP7928(stateRoot common.Hash, accessList map[common.Address][]common.Hash, threads int) (Reader, error) {
+ base, err := db.StateReader(stateRoot)
+ if err != nil {
+ return nil, err
+ }
+ // Construct the state reader with native cache and associated statistics
+ r := newStateReaderWithStats(newStateReaderWithCache(base))
+
+ // Construct the state reader with background prefetching
+ pr := newPrefetchStateReader(r, accessList, threads)
+
+ return newReader(newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache), pr), nil
+}
+
// OpenTrie opens the main account trie at a specific root hash.
func (db *CachingDB) OpenTrie(root common.Hash) (Trie, error) {
if db.triedb.IsVerkle() {
diff --git a/core/state/reader.go b/core/state/reader.go
index 35b732173b..0835d9a0ef 100644
--- a/core/state/reader.go
+++ b/core/state/reader.go
@@ -18,7 +18,6 @@ package state
import (
"errors"
- "fmt"
"sync"
"sync/atomic"
@@ -38,6 +37,8 @@ import (
)
// ContractCodeReader defines the interface for accessing contract code.
+//
+// ContractCodeReader is supposed to be thread-safe.
type ContractCodeReader interface {
// Has returns the flag indicating whether the contract code with
// specified address and hash exists or not.
@@ -58,35 +59,10 @@ type ContractCodeReader interface {
CodeSize(addr common.Address, codeHash common.Hash) (int, error)
}
-// ContractCodeReaderStats aggregates statistics for the contract code reader.
-type ContractCodeReaderStats struct {
- CacheHit int64 // Number of cache hits
- CacheMiss int64 // Number of cache misses
- CacheHitBytes int64 // Total bytes served from cache
- CacheMissBytes int64 // Total bytes read on cache misses
-}
-
-// HitRate returns the cache hit rate.
-func (s ContractCodeReaderStats) HitRate() float64 {
- if s.CacheHit == 0 {
- return 0
- }
- return float64(s.CacheHit) / float64(s.CacheHit+s.CacheMiss)
-}
-
-// ContractCodeReaderWithStats extends ContractCodeReader by adding GetStats to
-// expose statistics of code reader.
-type ContractCodeReaderWithStats interface {
- ContractCodeReader
-
- GetStats() ContractCodeReaderStats
-}
-
// StateReader defines the interface for accessing accounts and storage slots
// associated with a specific state.
//
-// StateReader is assumed to be thread-safe and implementation must take care
-// of the concurrency issue by themselves.
+// StateReader is supposed to be thread-safe.
type StateReader interface {
// Account retrieves the account associated with a particular address.
//
@@ -114,40 +90,6 @@ type Reader interface {
StateReader
}
-// ReaderStats wraps the statistics of reader.
-type ReaderStats struct {
- AccountCacheHit int64
- AccountCacheMiss int64
- StorageCacheHit int64
- StorageCacheMiss int64
- CodeStats ContractCodeReaderStats
-}
-
-// String implements fmt.Stringer, returning string format statistics.
-func (s ReaderStats) String() string {
- var (
- accountCacheHitRate float64
- storageCacheHitRate float64
- )
- if s.AccountCacheHit > 0 {
- accountCacheHitRate = float64(s.AccountCacheHit) / float64(s.AccountCacheHit+s.AccountCacheMiss) * 100
- }
- if s.StorageCacheHit > 0 {
- storageCacheHitRate = float64(s.StorageCacheHit) / float64(s.StorageCacheHit+s.StorageCacheMiss) * 100
- }
- msg := fmt.Sprintf("Reader statistics\n")
- msg += fmt.Sprintf("account: hit: %d, miss: %d, rate: %.2f\n", s.AccountCacheHit, s.AccountCacheMiss, accountCacheHitRate)
- msg += fmt.Sprintf("storage: hit: %d, miss: %d, rate: %.2f\n", s.StorageCacheHit, s.StorageCacheMiss, storageCacheHitRate)
- msg += fmt.Sprintf("code: hit: %d(%v), miss: %d(%v), rate: %.2f\n", s.CodeStats.CacheHit, common.StorageSize(s.CodeStats.CacheHitBytes), s.CodeStats.CacheMiss, common.StorageSize(s.CodeStats.CacheMissBytes), s.CodeStats.HitRate())
- return msg
-}
-
-// ReaderWithStats wraps the additional method to retrieve the reader statistics from.
-type ReaderWithStats interface {
- Reader
- GetStats() ReaderStats
-}
-
// cachingCodeReader implements ContractCodeReader, accessing contract code either in
// local key-value store or the shared code cache.
//
@@ -210,15 +152,16 @@ func (r *cachingCodeReader) CodeSize(addr common.Address, codeHash common.Hash)
return len(code), nil
}
-// Has returns the flag indicating whether the contract code with
-// specified address and hash exists or not.
+// Has implements ContractCodeReader, returning the flag indicating whether
+// the contract code with specified address and hash exists or not.
func (r *cachingCodeReader) Has(addr common.Address, codeHash common.Hash) bool {
code, _ := r.Code(addr, codeHash)
return len(code) > 0
}
-// GetStats returns the statistics of the code reader.
-func (r *cachingCodeReader) GetStats() ContractCodeReaderStats {
+// GetCodeStats implements ContractCodeReaderStater, returning the statistics
+// of the code reader.
+func (r *cachingCodeReader) GetCodeStats() ContractCodeReaderStats {
return ContractCodeReaderStats{
CacheHit: r.hit.Load(),
CacheMiss: r.miss.Load(),
@@ -495,20 +438,6 @@ func (r *multiStateReader) Storage(addr common.Address, slot common.Hash) (commo
return common.Hash{}, errors.Join(errs...)
}
-// reader is the wrapper of ContractCodeReader and StateReader interface.
-type reader struct {
- ContractCodeReader
- StateReader
-}
-
-// newReader constructs a reader with the supplied code reader and state reader.
-func newReader(codeReader ContractCodeReader, stateReader StateReader) *reader {
- return &reader{
- ContractCodeReader: codeReader,
- StateReader: stateReader,
- }
-}
-
// stateReaderWithCache is a wrapper around StateReader that maintains additional
// state caches to support concurrent state access.
type stateReaderWithCache struct {
@@ -619,9 +548,10 @@ func (r *stateReaderWithCache) Storage(addr common.Address, slot common.Hash) (c
return value, err
}
-type readerWithStats struct {
+// stateReaderWithStats is a wrapper over the stateReaderWithCache, tracking
+// the cache hit statistics of the reader.
+type stateReaderWithStats struct {
*stateReaderWithCache
- ContractCodeReaderWithStats
accountCacheHit atomic.Int64
accountCacheMiss atomic.Int64
@@ -629,11 +559,10 @@ type readerWithStats struct {
storageCacheMiss atomic.Int64
}
-// newReaderWithStats constructs the reader with additional statistics tracked.
-func newReaderWithStats(sr *stateReaderWithCache, cr ContractCodeReaderWithStats) *readerWithStats {
- return &readerWithStats{
- stateReaderWithCache: sr,
- ContractCodeReaderWithStats: cr,
+// newReaderWithStats constructs the state reader with additional statistics tracked.
+func newStateReaderWithStats(sr *stateReaderWithCache) *stateReaderWithStats {
+ return &stateReaderWithStats{
+ stateReaderWithCache: sr,
}
}
@@ -641,7 +570,7 @@ func newReaderWithStats(sr *stateReaderWithCache, cr ContractCodeReaderWithStats
// The returned account might be nil if it's not existent.
//
// An error will be returned if the state is corrupted in the underlying reader.
-func (r *readerWithStats) Account(addr common.Address) (*types.StateAccount, error) {
+func (r *stateReaderWithStats) Account(addr common.Address) (*types.StateAccount, error) {
account, incache, err := r.stateReaderWithCache.account(addr)
if err != nil {
return nil, err
@@ -659,7 +588,7 @@ func (r *readerWithStats) Account(addr common.Address) (*types.StateAccount, err
// existent.
//
// An error will be returned if the state is corrupted in the underlying reader.
-func (r *readerWithStats) Storage(addr common.Address, slot common.Hash) (common.Hash, error) {
+func (r *stateReaderWithStats) Storage(addr common.Address, slot common.Hash) (common.Hash, error) {
value, incache, err := r.stateReaderWithCache.storage(addr, slot)
if err != nil {
return common.Hash{}, err
@@ -672,13 +601,51 @@ func (r *readerWithStats) Storage(addr common.Address, slot common.Hash) (common
return value, nil
}
-// GetStats implements ReaderWithStats, returning the statistics of state reader.
-func (r *readerWithStats) GetStats() ReaderStats {
- return ReaderStats{
+// GetStateStats implements StateReaderStater, returning the statistics of the
+// state reader.
+func (r *stateReaderWithStats) GetStateStats() StateReaderStats {
+ return StateReaderStats{
AccountCacheHit: r.accountCacheHit.Load(),
AccountCacheMiss: r.accountCacheMiss.Load(),
StorageCacheHit: r.storageCacheHit.Load(),
StorageCacheMiss: r.storageCacheMiss.Load(),
- CodeStats: r.ContractCodeReaderWithStats.GetStats(),
+ }
+}
+
+// reader aggregates a code reader and a state reader into a single object.
+type reader struct {
+ ContractCodeReader
+ StateReader
+}
+
+// newReader constructs a reader with the supplied code reader and state reader.
+func newReader(codeReader ContractCodeReader, stateReader StateReader) *reader {
+ return &reader{
+ ContractCodeReader: codeReader,
+ StateReader: stateReader,
+ }
+}
+
+// GetCodeStats returns the statistics of code access.
+func (r *reader) GetCodeStats() ContractCodeReaderStats {
+ if stater, ok := r.ContractCodeReader.(ContractCodeReaderStater); ok {
+ return stater.GetCodeStats()
+ }
+ return ContractCodeReaderStats{}
+}
+
+// GetStateStats returns the statistics of state access.
+func (r *reader) GetStateStats() StateReaderStats {
+ if stater, ok := r.StateReader.(StateReaderStater); ok {
+ return stater.GetStateStats()
+ }
+ return StateReaderStats{}
+}
+
+// GetStats returns the aggregated statistics for both state and code access.
+func (r *reader) GetStats() ReaderStats {
+ return ReaderStats{
+ CodeStats: r.GetCodeStats(),
+ StateStats: r.GetStateStats(),
}
}
diff --git a/core/state/reader_eip_7928.go b/core/state/reader_eip_7928.go
new file mode 100644
index 0000000000..039342801f
--- /dev/null
+++ b/core/state/reader_eip_7928.go
@@ -0,0 +1,312 @@
+// Copyright 2026 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package state
+
+// The EIP27928 reader utilizes a hierarchical architecture to optimize state
+// access during block execution:
+//
+// - Base layer: The reader is initialized with the pre-transition state root,
+// providing the access of the state.
+//
+// - Prefetching Layer: This base reader is wrapped by newPrefetchStateReader.
+// Using an Access List hint, it asynchronously fetches required state data
+// in the background, minimizing I/O blocking during transaction processing.
+//
+// - Execution Layer: To support parallel transaction execution within the EIP
+// 7928 context, readers are wrapped in ReaderWithBlockLevelAccessList.
+// This layer provides a "unified view" by merging the pre-transition state
+// with mutated states from preceding transactions in the block.
+//
+// - Tracking Layer: Finally, the readerTracker wraps the execution reader to
+// capture all state accesses made during a specific transaction. These individual
+// access are subsequently merged to construct a comprehensive access list
+// for the entire block.
+//
+// The architecture can be illustrated by the diagram below:
+
+// [ Block Level Access List ] <────────────────┐
+// ▲ │ (Merge)
+// │ │
+// ┌───────┴───────┐ ┌───────┴───────┐
+// │ readerTracker │ │ readerTracker │ (Access Tracking)
+// └───────┬───────┘ └───────┬───────┘
+// │ │
+// ┌──────────────┴──────────────┐ ┌──────────────┴──────────────┐
+// │ ReaderWithBlockLevelAL │ │ ReaderWithBlockLevelAL │ (Unified View)
+// │ (Pre-state + Mutations) │ │ (Pre-state + Mutations) │
+// └──────────────┬──────────────┘ └──────────────┬──────────────┘
+// │ │
+// └────────────────┬─────────────────┘
+// │
+// ┌──────────────┴──────────────┐
+// │ newPrefetchStateReader │ (Async I/O)
+// │ (Access List Hint driven) │
+// └──────────────┬──────────────┘
+// │
+// ┌──────────────┴──────────────┐
+// │ Base Reader │ (State Root)
+// │ (State & Contract Code) │
+// └─────────────────────────────┘
+
+import (
+ "maps"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/core/types/bal"
+)
+
+type fetchTask struct {
+ addr common.Address
+ slots []common.Hash
+}
+
+func (t *fetchTask) weight() int { return 1 + len(t.slots) }
+
+type prefetchStateReader struct {
+ StateReader
+ tasks []*fetchTask
+ nThreads int
+ done chan struct{}
+ term chan struct{}
+ closeOnce sync.Once
+}
+
+func newPrefetchStateReader(reader StateReader, accessList map[common.Address][]common.Hash, nThreads int) *prefetchStateReader {
+ tasks := make([]*fetchTask, 0, len(accessList))
+ for addr, slots := range accessList {
+ tasks = append(tasks, &fetchTask{
+ addr: addr,
+ slots: slots,
+ })
+ }
+ return newPrefetchStateReaderInternal(reader, tasks, nThreads)
+}
+
+func newPrefetchStateReaderInternal(reader StateReader, tasks []*fetchTask, nThreads int) *prefetchStateReader {
+ r := &prefetchStateReader{
+ StateReader: reader,
+ tasks: tasks,
+ nThreads: nThreads,
+ done: make(chan struct{}),
+ term: make(chan struct{}),
+ }
+ go r.prefetch()
+ return r
+}
+
+func (r *prefetchStateReader) Close() {
+ r.closeOnce.Do(func() {
+ close(r.term)
+ <-r.done
+ })
+}
+
+func (r *prefetchStateReader) Wait() error {
+ select {
+ case <-r.term:
+ return nil
+ case <-r.done:
+ return nil
+ }
+}
+
+func (r *prefetchStateReader) prefetch() {
+ defer close(r.done)
+
+ if len(r.tasks) == 0 {
+ return
+ }
+ var total int
+ for _, t := range r.tasks {
+ total += t.weight()
+ }
+ var (
+ wg sync.WaitGroup
+ unit = (total + r.nThreads - 1) / r.nThreads // round-up the per worker unit
+ )
+ for i := 0; i < r.nThreads; i++ {
+ start := i * unit
+ if start >= total {
+ break
+ }
+ limit := (i + 1) * unit
+ if i == r.nThreads-1 {
+ limit = total
+ }
+ // Schedule the worker for prefetching, the items on the range [start, limit)
+ // is exclusively assigned for this worker.
+ wg.Add(1)
+ go func(workerID, startW, endW int) {
+ r.process(startW, endW)
+ wg.Done()
+ }(i, start, limit)
+ }
+ wg.Wait()
+}
+
+func (r *prefetchStateReader) process(start, limit int) {
+ var total = 0
+ for _, t := range r.tasks {
+ tw := t.weight()
+ if total+tw > start {
+ s := 0
+ if start > total {
+ s = start - total
+ }
+ l := tw
+ if limit < total+tw {
+ l = limit - total
+ }
+ for j := s; j < l; j++ {
+ select {
+ case <-r.term:
+ return
+ default:
+ if j == 0 {
+ r.StateReader.Account(t.addr)
+ } else {
+ r.StateReader.Storage(t.addr, t.slots[j-1])
+ }
+ }
+ }
+ }
+ total += tw
+ if total >= limit {
+ return
+ }
+ }
+}
+
+// ReaderWithBlockLevelAccessList provides state access that reflects the
+// pre-transition state combined with the mutations made by transactions
+// prior to TxIndex.
+type ReaderWithBlockLevelAccessList struct {
+ Reader
+ AccessList *bal.ConstructionBlockAccessList
+ TxIndex int
+}
+
+func NewReaderWithBlockLevelAccessList(base Reader, accessList *bal.ConstructionBlockAccessList, txIndex int) *ReaderWithBlockLevelAccessList {
+ return &ReaderWithBlockLevelAccessList{
+ Reader: base,
+ AccessList: accessList,
+ TxIndex: txIndex,
+ }
+}
+
+// Account implements Reader, returning the account with the specific address.
+func (r *ReaderWithBlockLevelAccessList) Account(addr common.Address) (*types.StateAccount, error) {
+ panic("implement me")
+}
+
+// Storage implements Reader, returning the storage slot with the specific
+// address and slot key.
+func (r *ReaderWithBlockLevelAccessList) Storage(addr common.Address, slot common.Hash) (common.Hash, error) {
+ panic("implement me")
+}
+
+// Has implements Reader, returning the flag indicating whether the contract
+// code with specified address and hash exists or not.
+func (r *ReaderWithBlockLevelAccessList) Has(addr common.Address, codeHash common.Hash) bool {
+ panic("implement me")
+}
+
+// Code implements Reader, returning the contract code with specified address
+// and hash.
+func (r *ReaderWithBlockLevelAccessList) Code(addr common.Address, codeHash common.Hash) ([]byte, error) {
+ panic("implement me")
+}
+
+// CodeSize implements Reader, returning the contract code size with specified
+// address and hash.
+func (r *ReaderWithBlockLevelAccessList) CodeSize(addr common.Address, codeHash common.Hash) (int, error) {
+ panic("implement me")
+}
+
+// StorageAccessList represents a set of storage slots accessed within an account.
+type StorageAccessList map[common.Hash]struct{}
+
+// StateAccessList maps account addresses to their respective accessed storage slots.
+type StateAccessList map[common.Address]StorageAccessList
+
+// Merge merges the entries from the other StateAccessList into the receiver.
+func (s StateAccessList) Merge(other StateAccessList) {
+ for addr, otherSlots := range other {
+ slots, exists := s[addr]
+ if !exists {
+ s[addr] = otherSlots
+ continue
+ }
+ maps.Copy(slots, otherSlots)
+ }
+}
+
+// StateReaderTracker defines the capability to retrieve the access footprint
+// recorded during state reading operations.
+type StateReaderTracker interface {
+ GetStateAccessList() StateAccessList
+}
+
+type readerTracker struct {
+ Reader
+ access StateAccessList
+ lock sync.RWMutex
+}
+
+func newReaderTracker(reader Reader) *readerTracker {
+ return &readerTracker{
+ Reader: reader,
+ access: make(StateAccessList),
+ }
+}
+
+// Account implements StateReader, tracking the accessed address locally.
+func (r *readerTracker) Account(addr common.Address) (*types.StateAccount, error) {
+ r.lock.Lock()
+ defer r.lock.Unlock()
+
+ _, exists := r.access[addr]
+ if !exists {
+ r.access[addr] = make(StorageAccessList)
+ }
+ return r.Reader.Account(addr)
+}
+
+// Storage implements StateReader, tracking the accessed slot identifier locally.
+func (r *readerTracker) Storage(addr common.Address, slot common.Hash) (common.Hash, error) {
+ r.lock.Lock()
+ defer r.lock.Unlock()
+
+ list, exists := r.access[addr]
+ if !exists {
+ list = make(StorageAccessList)
+ r.access[addr] = list
+ }
+ list[slot] = struct{}{}
+
+ return r.Reader.Storage(addr, slot)
+}
+
+// GetStateAccessList implements StateReaderTracker, returning the access footprint.
+func (r *readerTracker) GetStateAccessList() StateAccessList {
+ r.lock.RLock()
+ defer r.lock.RUnlock()
+
+ return r.access
+}
diff --git a/core/state/reader_eip_7928_test.go b/core/state/reader_eip_7928_test.go
new file mode 100644
index 0000000000..d9d20f6d7b
--- /dev/null
+++ b/core/state/reader_eip_7928_test.go
@@ -0,0 +1,201 @@
+// Copyright 2026 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package state
+
+import (
+ "fmt"
+ "maps"
+ "math/rand"
+ "sync"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/internal/testrand"
+)
+
+type countingStateReader struct {
+ accounts map[common.Address]int
+ storages map[common.Address]map[common.Hash]int
+ lock sync.Mutex
+}
+
+func newRefStateReader() *countingStateReader {
+ return &countingStateReader{
+ accounts: make(map[common.Address]int),
+ storages: make(map[common.Address]map[common.Hash]int),
+ }
+}
+
+func (r *countingStateReader) validate(total int) error {
+ var sum int
+ for addr, n := range r.accounts {
+ if n != 1 {
+ return fmt.Errorf("duplicated account access: %x-%d", addr, n)
+ }
+ sum += 1
+
+ slots, exists := r.storages[addr]
+ if !exists {
+ continue
+ }
+ for key, n := range slots {
+ if n != 1 {
+ return fmt.Errorf("duplicated storage access: %x-%x-%d", addr, key, n)
+ }
+ sum += 1
+ }
+ }
+ for addr := range r.storages {
+ _, exists := r.accounts[addr]
+ if !exists {
+ return fmt.Errorf("dangling storage access: %x", addr)
+ }
+ }
+ if sum != total {
+ return fmt.Errorf("unexpected number of access, want: %d, got: %d", total, sum)
+ }
+ return nil
+}
+
+func (r *countingStateReader) Account(addr common.Address) (*types.StateAccount, error) {
+ r.lock.Lock()
+ defer r.lock.Unlock()
+
+ r.accounts[addr] += 1
+ return nil, nil
+}
+func (r *countingStateReader) Storage(addr common.Address, slot common.Hash) (common.Hash, error) {
+ r.lock.Lock()
+ defer r.lock.Unlock()
+
+ slots, exists := r.storages[addr]
+ if !exists {
+ slots = make(map[common.Hash]int)
+ r.storages[addr] = slots
+ }
+ slots[slot] += 1
+ return common.Hash{}, nil
+}
+
+func makeFetchTasks(n int) ([]*fetchTask, int) {
+ var (
+ total int
+ tasks []*fetchTask
+ )
+ for i := 0; i < n; i++ {
+ var slots []common.Hash
+ if rand.Intn(3) != 0 {
+ for j := 0; j < rand.Intn(100); j++ {
+ slots = append(slots, testrand.Hash())
+ }
+ }
+ tasks = append(tasks, &fetchTask{
+ addr: testrand.Address(),
+ slots: slots,
+ })
+ total += len(slots) + 1
+ }
+ return tasks, total
+}
+
+func TestPrefetchReader(t *testing.T) {
+ type suite struct {
+ tasks []*fetchTask
+ threads int
+ total int
+ }
+ var suites []suite
+ for i := 0; i < 100; i++ {
+ tasks, total := makeFetchTasks(100)
+ suites = append(suites, suite{
+ tasks: tasks,
+ threads: rand.Intn(30) + 1,
+ total: total,
+ })
+ }
+ // num(tasks) < num(threads)
+ tasks, total := makeFetchTasks(1)
+ suites = append(suites, suite{
+ tasks: tasks,
+ threads: 100,
+ total: total,
+ })
+ for _, s := range suites {
+ r := newRefStateReader()
+ pr := newPrefetchStateReaderInternal(r, s.tasks, s.threads)
+ pr.Wait()
+ if err := r.validate(s.total); err != nil {
+ t.Fatal(err)
+ }
+ }
+}
+
+func makeFakeSlots(n int) map[common.Hash]struct{} {
+ slots := make(map[common.Hash]struct{})
+ for i := 0; i < n; i++ {
+ slots[testrand.Hash()] = struct{}{}
+ }
+ return slots
+}
+
+type noopStateReader struct{}
+
+func (r *noopStateReader) Account(addr common.Address) (*types.StateAccount, error) { return nil, nil }
+func (r *noopStateReader) Storage(addr common.Address, slot common.Hash) (common.Hash, error) {
+ return common.Hash{}, nil
+}
+
+type noopCodeReader struct{}
+
+func (r *noopCodeReader) Has(addr common.Address, codeHash common.Hash) bool { return false }
+
+func (r *noopCodeReader) Code(addr common.Address, codeHash common.Hash) ([]byte, error) {
+ return nil, nil
+}
+
+func (r *noopCodeReader) CodeSize(addr common.Address, codeHash common.Hash) (int, error) {
+ return 0, nil
+}
+
+func TestReaderWithTracker(t *testing.T) {
+ var r Reader = newReaderTracker(newReader(&noopCodeReader{}, &noopStateReader{}))
+
+ accesses := map[common.Address]map[common.Hash]struct{}{
+ testrand.Address(): makeFakeSlots(10),
+ testrand.Address(): makeFakeSlots(0),
+ }
+ for addr, slots := range accesses {
+ r.Account(addr)
+ for slot := range slots {
+ r.Storage(addr, slot)
+ }
+ }
+ got := r.(StateReaderTracker).GetStateAccessList()
+ if len(got) != len(accesses) {
+ t.Fatalf("Unexpected access list, want: %d, got: %d", len(accesses), len(got))
+ }
+ for addr, slots := range got {
+ entry, ok := accesses[addr]
+ if !ok {
+ t.Fatal("Unexpected access list")
+ }
+ if !maps.Equal(slots, entry) {
+ t.Fatal("Unexpected slots")
+ }
+ }
+}
diff --git a/core/state/reader_stater.go b/core/state/reader_stater.go
new file mode 100644
index 0000000000..5294275953
--- /dev/null
+++ b/core/state/reader_stater.go
@@ -0,0 +1,82 @@
+// Copyright 2026 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package state
+
+// ContractCodeReaderStats aggregates statistics for the contract code reader.
+type ContractCodeReaderStats struct {
+ CacheHit int64 // Number of cache hits
+ CacheMiss int64 // Number of cache misses
+ CacheHitBytes int64 // Total bytes served from cache
+ CacheMissBytes int64 // Total bytes read on cache misses
+}
+
+// HitRate returns the cache hit rate in percentage.
+func (s ContractCodeReaderStats) HitRate() float64 {
+ total := s.CacheHit + s.CacheMiss
+ if total == 0 {
+ return 0
+ }
+ return float64(s.CacheHit) / float64(total) * 100
+}
+
+// ContractCodeReaderStater wraps the method to retrieve the statistics of
+// contract code reader.
+type ContractCodeReaderStater interface {
+ GetCodeStats() ContractCodeReaderStats
+}
+
+// StateReaderStats aggregates statistics for the state reader.
+type StateReaderStats struct {
+ AccountCacheHit int64 // Number of account cache hits
+ AccountCacheMiss int64 // Number of account cache misses
+ StorageCacheHit int64 // Number of storage cache hits
+ StorageCacheMiss int64 // Number of storage cache misses
+}
+
+// AccountCacheHitRate returns the cache hit rate of account requests in percentage.
+func (s StateReaderStats) AccountCacheHitRate() float64 {
+ total := s.AccountCacheHit + s.AccountCacheMiss
+ if total == 0 {
+ return 0
+ }
+ return float64(s.AccountCacheHit) / float64(total) * 100
+}
+
+// StorageCacheHitRate returns the cache hit rate of storage requests in percentage.
+func (s StateReaderStats) StorageCacheHitRate() float64 {
+ total := s.StorageCacheHit + s.StorageCacheMiss
+ if total == 0 {
+ return 0
+ }
+ return float64(s.StorageCacheHit) / float64(total) * 100
+}
+
+// StateReaderStater wraps the method to retrieve the statistics of state reader.
+type StateReaderStater interface {
+ GetStateStats() StateReaderStats
+}
+
+// ReaderStats wraps the statistics of reader.
+type ReaderStats struct {
+ CodeStats ContractCodeReaderStats
+ StateStats StateReaderStats
+}
+
+// ReaderStater defines the capability to retrieve aggregated statistics.
+type ReaderStater interface {
+ GetStats() ReaderStats
+}