From 16e98f5d93af7b64ad8fc2c56d725b867c45f1b8 Mon Sep 17 00:00:00 2001 From: CPerezz Date: Thu, 30 Apr 2026 23:14:35 +0200 Subject: [PATCH] core: refresh BAL Metrics() snapshot after writeBlockWithState The first Metrics() call inside calcAndVerifyRoot snapshots accountReadNS and storageReadNS into the cached AccountReadTime/StorageReadTime fields. But commitAccount (called from writeBlockWithState's CommitWithUpdate path) increments storageReadNS *after* that snapshot, so reading m.StorageReadTime later would silently drop those reads. Re-call Metrics() before reading the read-time fields so the cache reflects the post-commit atomics. Other metric fields (AccountUpdate, AccountCommits, etc.) are written directly to s.metrics elsewhere and remain untouched by Metrics(). Found via the metric-correctness audit. --- core/blockchain.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 58e8be7362..22d36e76c4 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -677,11 +677,12 @@ func (bc *BlockChain) processBlockWithAccessList(parentRoot common.Hash, block * stats.DatabaseCommit = m.TrieDBCommits stats.Prefetch = m.StatePrefetch } - // Read durations: sum across all three sources (per-tx execution, BAL - // state-transition recomputation, prefetcher async fetches). This is - // sum-of-CPU-time across parallel workers, not wall-clock — it can - // exceed TotalTime, which is the intended interpretation under parallel - // execution: "total CPU-time spent reading state across the block". + // 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. var prefetchAccountReads, prefetchStorageReads time.Duration if pr, ok := prefetchReader.(interface { PrefetchReadTimes() (time.Duration, time.Duration)