core: derive BAL block account/storage read counts from access list

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.
This commit is contained in:
CPerezz 2026-05-01 02:07:23 +02:00
parent 4d9405a201
commit 823b582fc4
No known key found for this signature in database
GPG key ID: 62045F34B97177DD
2 changed files with 27 additions and 5 deletions

View file

@ -652,12 +652,17 @@ func (bc *BlockChain) processBlockWithAccessList(parentRoot common.Hash, block *
writeTime := time.Since(writeStart) writeTime := time.Since(writeStart)
var stats ExecuteStats var stats ExecuteStats
// Counts: aggregated from per-tx workers + pre-tx + post-tx state in the // Counts: write counts come from the BAL state transition; read counts
// parallel processor (read counters), plus BAL state-root computation // for accounts/storage come from the BAL access list itself (deduplicated).
// (account/code/storage write counters via stateTransition.WriteCounts). // 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 stats.StateCounts = res.Counts
balWrites := stateTransition.WriteCounts() stats.StateCounts.Add(stateTransition.WriteCounts())
stats.StateCounts.Add(balWrites) 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. // Time durations under parallel execution use wall-clock semantics.
// Per-tx duration sums (CPU-time) are intentionally not plumbed: they // Per-tx duration sums (CPU-time) are intentionally not plumbed: they

View file

@ -44,6 +44,23 @@ import (
// BlockAccessList is the encoding format of AccessListBuilder. // BlockAccessList is the encoding format of AccessListBuilder.
type BlockAccessList []AccountAccess 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 { func (e BlockAccessList) EncodeRLP(_w io.Writer) error {
w := rlp.NewEncoderBuffer(_w) w := rlp.NewEncoderBuffer(_w)
l := w.List() l := w.List()