core: split BAL read-time access from cached metrics struct

Replace the cached AccountReadTime/StorageReadTime fields (which had a
snapshot-staleness bug fixed in 16e98f5d9 by re-calling Metrics()) with
a live ReadTimes() accessor. Metrics() now only returns commit/hash-phase
timings — it no longer touches atomics. blockchain.go reads atomics
directly via stateTransition.ReadTimes(), eliminating the refresh hack.

Also resolves the I1 fragility: Metrics() returning &s.metrics no longer
involves any writes inside the function, so concurrent callers can't race
on the read-time field updates.
This commit is contained in:
CPerezz 2026-05-01 00:12:54 +02:00
parent eb4d17595f
commit 546d2b457e
No known key found for this signature in database
GPG key ID: 62045F34B97177DD
2 changed files with 13 additions and 17 deletions

View file

@ -677,10 +677,6 @@ func (bc *BlockChain) processBlockWithAccessList(parentRoot common.Hash, block *
stats.DatabaseCommit = m.TrieDBCommits stats.DatabaseCommit = m.TrieDBCommits
stats.Prefetch = m.StatePrefetch stats.Prefetch = m.StatePrefetch
} }
// Refresh BAL read-time cache: commitAccount runs storage reads during
// writeBlockWithState, after the first Metrics() snapshot.
stateTransition.Metrics()
// Sum read times across per-tx execution, BAL state-transition, and // Sum read times across per-tx execution, BAL state-transition, and
// prefetcher async fetches. Sum-of-CPU-time, not wall-clock. // prefetcher async fetches. Sum-of-CPU-time, not wall-clock.
if w, ok := prefetchReader.(interface{ WaitPrefetch() }); ok { if w, ok := prefetchReader.(interface{ WaitPrefetch() }); ok {
@ -692,13 +688,10 @@ func (bc *BlockChain) processBlockWithAccessList(parentRoot common.Hash, block *
}); ok { }); ok {
prefetchAccountReads, prefetchStorageReads = pr.PrefetchReadTimes() prefetchAccountReads, prefetchStorageReads = pr.PrefetchReadTimes()
} }
stats.AccountReads = res.PerTxAccountReads + prefetchAccountReads balAccountReads, balStorageReads := stateTransition.ReadTimes()
stats.StorageReads = res.PerTxStorageReads + prefetchStorageReads stats.AccountReads = res.PerTxAccountReads + prefetchAccountReads + balAccountReads
stats.StorageReads = res.PerTxStorageReads + prefetchStorageReads + balStorageReads
stats.CodeReads = res.PerTxCodeReads stats.CodeReads = res.PerTxCodeReads
if m := res.StateTransitionMetrics; m != nil {
stats.AccountReads += m.AccountReadTime
stats.StorageReads += m.StorageReadTime
}
// Cache stats from the shared prefetch reader (accumulates centrally). // Cache stats from the shared prefetch reader (accumulates centrally).
if r, ok := prefetchReader.(state.ReaderStater); ok { if r, ok := prefetchReader.(state.ReaderStater); ok {

View file

@ -64,12 +64,20 @@ type BALStateTransition struct {
err error err error
} }
// Metrics returns the cached commit/hash-phase timings. Read-time atomics
// are exposed separately via ReadTimes; that decoupling avoids the
// snapshot-staleness pitfall when commitAccount runs more reads after
// Metrics is first called.
func (s *BALStateTransition) Metrics() *BALStateTransitionMetrics { func (s *BALStateTransition) Metrics() *BALStateTransitionMetrics {
s.metrics.AccountReadTime = time.Duration(s.accountReadNS.Load())
s.metrics.StorageReadTime = time.Duration(s.storageReadNS.Load())
return &s.metrics return &s.metrics
} }
// ReadTimes returns the current accumulated read times from atomic counters.
// Always live; safe to call at any point after IntermediateRoot/Commit work.
func (s *BALStateTransition) ReadTimes() (account, storage time.Duration) {
return time.Duration(s.accountReadNS.Load()), time.Duration(s.storageReadNS.Load())
}
// WriteCounts returns the state-mutation counts tracked during the parallel // WriteCounts returns the state-mutation counts tracked during the parallel
// state-root computation. // state-root computation.
func (s *BALStateTransition) WriteCounts() StateCounts { func (s *BALStateTransition) WriteCounts() StateCounts {
@ -96,11 +104,6 @@ type BALStateTransitionMetrics struct {
SnapshotCommits time.Duration SnapshotCommits time.Duration
TrieDBCommits time.Duration TrieDBCommits time.Duration
TotalCommitTime time.Duration TotalCommitTime time.Duration
// State-root recomputation read times. Sum of CPU time across the per-
// address goroutines that call s.reader.Account/Storage during commit.
AccountReadTime time.Duration
StorageReadTime time.Duration
} }
func NewBALStateTransition(block *types.Block, prefetchReader Reader, db Database, parentRoot common.Hash) (*BALStateTransition, error) { func NewBALStateTransition(block *types.Block, prefetchReader Reader, db Database, parentRoot common.Hash) (*BALStateTransition, error) {