From 546d2b457e85be19d63b0a2493256c3ce5d47f56 Mon Sep 17 00:00:00 2001 From: CPerezz Date: Fri, 1 May 2026 00:12:54 +0200 Subject: [PATCH] core: split BAL read-time access from cached metrics struct MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- core/blockchain.go | 13 +++---------- core/state/bal_state_transition.go | 17 ++++++++++------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 59f5716638..17deeac42b 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -677,10 +677,6 @@ func (bc *BlockChain) processBlockWithAccessList(parentRoot common.Hash, block * stats.DatabaseCommit = m.TrieDBCommits 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 // prefetcher async fetches. Sum-of-CPU-time, not wall-clock. if w, ok := prefetchReader.(interface{ WaitPrefetch() }); ok { @@ -692,13 +688,10 @@ func (bc *BlockChain) processBlockWithAccessList(parentRoot common.Hash, block * }); ok { prefetchAccountReads, prefetchStorageReads = pr.PrefetchReadTimes() } - stats.AccountReads = res.PerTxAccountReads + prefetchAccountReads - stats.StorageReads = res.PerTxStorageReads + prefetchStorageReads + balAccountReads, balStorageReads := stateTransition.ReadTimes() + stats.AccountReads = res.PerTxAccountReads + prefetchAccountReads + balAccountReads + stats.StorageReads = res.PerTxStorageReads + prefetchStorageReads + balStorageReads 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). if r, ok := prefetchReader.(state.ReaderStater); ok { diff --git a/core/state/bal_state_transition.go b/core/state/bal_state_transition.go index 3bffe934a5..ab7bcf37f8 100644 --- a/core/state/bal_state_transition.go +++ b/core/state/bal_state_transition.go @@ -64,12 +64,20 @@ type BALStateTransition struct { 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 { - s.metrics.AccountReadTime = time.Duration(s.accountReadNS.Load()) - s.metrics.StorageReadTime = time.Duration(s.storageReadNS.Load()) 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 // state-root computation. func (s *BALStateTransition) WriteCounts() StateCounts { @@ -96,11 +104,6 @@ type BALStateTransitionMetrics struct { SnapshotCommits time.Duration TrieDBCommits 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) {