From 823b582fc41fe6f8a330e93c6804be3a07c07ff9 Mon Sep 17 00:00:00 2001 From: CPerezz Date: Fri, 1 May 2026 02:07:23 +0200 Subject: [PATCH] core: derive BAL block account/storage read counts from access list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per-tx + pre-tx + post-tx StateDBs each have independent stateObjects caches, so summing their AccountLoaded/StorageLoaded counts over-counts addresses/slots touched by multiple phase StateDBs (compared to non-BAL single-StateDB semantics where the cache deduplicates). Override the read counts at the BAL stats wiring site using two new helpers on bal.BlockAccessList. The BAL is the canonical block-level deduplicated access record, so this restores cross-client comparable "unique accounts/slots touched" semantics. CodeLoaded/CodeLoadBytes still sum per-tx — the BAL doesn't track code- fetch events distinctly. Slight over-count remains there, documented. --- core/blockchain.go | 15 ++++++++++----- core/types/bal/bal_encoding.go | 17 +++++++++++++++++ 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 9912f4cb9c..8791a0b43b 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -652,12 +652,17 @@ func (bc *BlockChain) processBlockWithAccessList(parentRoot common.Hash, block * writeTime := time.Since(writeStart) var stats ExecuteStats - // Counts: aggregated from per-tx workers + pre-tx + post-tx state in the - // parallel processor (read counters), plus BAL state-root computation - // (account/code/storage write counters via stateTransition.WriteCounts). + // Counts: write counts come from the BAL state transition; read counts + // for accounts/storage come from the BAL access list itself (deduplicated). + // CodeLoaded/CodeLoadBytes still sum per-tx worker contributions because + // the BAL doesn't track code-fetch events distinctly — accept slight + // over-counting there. stats.StateCounts = res.Counts - balWrites := stateTransition.WriteCounts() - stats.StateCounts.Add(balWrites) + stats.StateCounts.Add(stateTransition.WriteCounts()) + if al := block.AccessList(); al != nil { + stats.StateCounts.AccountLoaded = al.UniqueAccountCount() + stats.StateCounts.StorageLoaded = al.UniqueStorageSlotCount() + } // Time durations under parallel execution use wall-clock semantics. // Per-tx duration sums (CPU-time) are intentionally not plumbed: they diff --git a/core/types/bal/bal_encoding.go b/core/types/bal/bal_encoding.go index fc1d36007e..0fddd5c32c 100644 --- a/core/types/bal/bal_encoding.go +++ b/core/types/bal/bal_encoding.go @@ -44,6 +44,23 @@ import ( // BlockAccessList is the encoding format of AccessListBuilder. type BlockAccessList []AccountAccess +// UniqueAccountCount returns the number of distinct account addresses in +// the block access list. +func (e BlockAccessList) UniqueAccountCount() int { + return len(e) +} + +// UniqueStorageSlotCount returns the total number of distinct (address, slot) +// pairs accessed across all accounts. Reads and writes are disjoint per +// account by spec validation, so we can sum them directly. +func (e BlockAccessList) UniqueStorageSlotCount() int { + var n int + for i := range e { + n += len(e[i].StorageReads) + len(e[i].StorageChanges) + } + return n +} + func (e BlockAccessList) EncodeRLP(_w io.Writer) error { w := rlp.NewEncoderBuffer(_w) l := w.List()