mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-08 07:58:40 +00:00
core/state, core: introduce state.StateCounts snapshot type
Adds the StateCounts type that the BAL slow-block work depends on: - core/state/state_counts.go: 10-field plain-int snapshot type with Add merge primitive; isolates the live atomic mutation surface from the value-typed aggregation pipeline. - core/state/statedb.go: SnapshotCounts() method that converts the StateDB's atomic counters to a plain StateCounts at the boundary. - core/blockchain_stats.go: ExecuteStats embeds state.StateCounts; adds ExecWall/PostProcess/Prefetch BAL extension fields, the slowBlockBAL JSON struct + BAL field on slowBlockLog, and extracts buildSlowBlockLog as a pure helper for direct testing. Without this commit the bal-devnet-3 branch as committed in subsequent commits would not build for a fresh clone (state.StateCounts undefined).
This commit is contained in:
parent
ae69e96efd
commit
812fa198c3
3 changed files with 147 additions and 25 deletions
|
|
@ -38,16 +38,10 @@ type ExecuteStats struct {
|
|||
StorageCommits time.Duration // Time spent on the storage trie commit
|
||||
CodeReads time.Duration // Time spent on the contract code read
|
||||
|
||||
AccountLoaded int // Number of accounts loaded
|
||||
AccountUpdated int // Number of accounts updated
|
||||
AccountDeleted int // Number of accounts deleted
|
||||
StorageLoaded int // Number of storage slots loaded
|
||||
StorageUpdated int // Number of storage slots updated
|
||||
StorageDeleted int // Number of storage slots deleted
|
||||
CodeLoaded int // Number of contract code loaded
|
||||
CodeLoadBytes int // Number of bytes read from contract code
|
||||
CodeUpdated int // Number of contract code written (CREATE/CREATE2 + EIP-7702)
|
||||
CodeUpdateBytes int // Total bytes of code written
|
||||
// Embedded state-mutation counts. Field promotion preserves access as
|
||||
// s.AccountLoaded etc. Note StorageUpdated/StorageDeleted are int64 here
|
||||
// (snapshot from atomic.Int64 on StateDB).
|
||||
state.StateCounts
|
||||
|
||||
Execution time.Duration // Time spent on the EVM execution
|
||||
Validation time.Duration // Time spent on the block validation
|
||||
|
|
@ -59,6 +53,13 @@ type ExecuteStats struct {
|
|||
TotalTime time.Duration // The total time spent on block execution
|
||||
MgasPerSecond float64 // The million gas processed per second
|
||||
|
||||
// BAL extension durations — set by processBlockWithAccessList for blocks
|
||||
// processed via the parallel BAL path. Surfaced in the slow-block log's
|
||||
// optional `bal` block.
|
||||
ExecWall time.Duration // Wall-clock parallel transaction execution
|
||||
PostProcess time.Duration // Post-tx finalization (system contracts, requests)
|
||||
Prefetch time.Duration // BAL state prefetching
|
||||
|
||||
// Cache hit rates
|
||||
StateReadCacheStats state.ReaderStats
|
||||
StatePrefetchCacheStats state.ReaderStats
|
||||
|
|
@ -120,6 +121,10 @@ type slowBlockLog struct {
|
|||
StateReads slowBlockReads `json:"state_reads"`
|
||||
StateWrites slowBlockWrites `json:"state_writes"`
|
||||
Cache slowBlockCache `json:"cache"`
|
||||
// BAL is the parallel-execution extension. Present iff the block was
|
||||
// processed via the BAL parallel path. Cross-client consumers can use its
|
||||
// presence to distinguish parallel-executed blocks from sequential ones.
|
||||
BAL *slowBlockBAL `json:"bal,omitempty"`
|
||||
}
|
||||
|
||||
type slowBlockInfo struct {
|
||||
|
|
@ -180,24 +185,33 @@ type slowBlockCodeCacheEntry struct {
|
|||
MissBytes int64 `json:"miss_bytes"`
|
||||
}
|
||||
|
||||
// slowBlockBAL is the parallel-execution extension surfaced under the
|
||||
// optional "bal" field of slowBlockLog. It carries timings that are
|
||||
// well-defined under parallel execution but don't fit the sequential schema.
|
||||
type slowBlockBAL struct {
|
||||
ExecWallMs float64 `json:"exec_wall_ms"`
|
||||
PostProcessMs float64 `json:"post_process_ms"`
|
||||
PrefetchMs float64 `json:"prefetch_ms"`
|
||||
StatePrefetchMs float64 `json:"state_prefetch_ms"`
|
||||
AccountUpdateMs float64 `json:"account_update_ms"`
|
||||
StateUpdateMs float64 `json:"state_update_ms"`
|
||||
StateHashMs float64 `json:"state_hash_ms"`
|
||||
AccountCommitMs float64 `json:"account_commit_ms"`
|
||||
StorageCommitMs float64 `json:"storage_commit_ms"`
|
||||
TrieDBCommitMs float64 `json:"triedb_commit_ms"`
|
||||
SnapshotCommitMs float64 `json:"snapshot_commit_ms"`
|
||||
}
|
||||
|
||||
// durationToMs converts a time.Duration to milliseconds as a float64
|
||||
// with sub-millisecond precision for accurate cross-client metrics.
|
||||
func durationToMs(d time.Duration) float64 {
|
||||
return float64(d.Nanoseconds()) / 1e6
|
||||
}
|
||||
|
||||
// logSlow prints the detailed execution statistics in JSON format if the block
|
||||
// is regarded as slow. The JSON format is designed for cross-client compatibility
|
||||
// with other Ethereum execution clients.
|
||||
func (s *ExecuteStats) logSlow(block *types.Block, slowBlockThreshold time.Duration) {
|
||||
// Negative threshold means disabled (default when flag not set)
|
||||
if slowBlockThreshold < 0 {
|
||||
return
|
||||
}
|
||||
// Threshold of 0 logs all blocks; positive threshold filters
|
||||
if slowBlockThreshold > 0 && s.TotalTime < slowBlockThreshold {
|
||||
return
|
||||
}
|
||||
// buildSlowBlockLog constructs the slow-block log JSON struct from execution
|
||||
// statistics. Pure function — no side effects, no logging — to make the JSON
|
||||
// shape directly testable.
|
||||
func buildSlowBlockLog(s *ExecuteStats, block *types.Block) slowBlockLog {
|
||||
logEntry := slowBlockLog{
|
||||
Level: "warn",
|
||||
Msg: "Slow block",
|
||||
|
|
@ -226,8 +240,8 @@ func (s *ExecuteStats) logSlow(block *types.Block, slowBlockThreshold time.Durat
|
|||
StateWrites: slowBlockWrites{
|
||||
Accounts: s.AccountUpdated,
|
||||
AccountsDeleted: s.AccountDeleted,
|
||||
StorageSlots: s.StorageUpdated,
|
||||
StorageSlotsDeleted: s.StorageDeleted,
|
||||
StorageSlots: int(s.StorageUpdated),
|
||||
StorageSlotsDeleted: int(s.StorageDeleted),
|
||||
Code: s.CodeUpdated,
|
||||
CodeBytes: s.CodeUpdateBytes,
|
||||
},
|
||||
|
|
@ -251,7 +265,38 @@ func (s *ExecuteStats) logSlow(block *types.Block, slowBlockThreshold time.Durat
|
|||
},
|
||||
},
|
||||
}
|
||||
jsonBytes, err := json.Marshal(logEntry)
|
||||
// Populate the parallel-execution extension only for BAL-processed blocks.
|
||||
if m := s.balTransitionStats; m != nil {
|
||||
logEntry.BAL = &slowBlockBAL{
|
||||
ExecWallMs: durationToMs(s.ExecWall),
|
||||
PostProcessMs: durationToMs(s.PostProcess),
|
||||
PrefetchMs: durationToMs(s.Prefetch),
|
||||
StatePrefetchMs: durationToMs(m.StatePrefetch),
|
||||
AccountUpdateMs: durationToMs(m.AccountUpdate),
|
||||
StateUpdateMs: durationToMs(m.StateUpdate),
|
||||
StateHashMs: durationToMs(m.StateHash),
|
||||
AccountCommitMs: durationToMs(m.AccountCommits),
|
||||
StorageCommitMs: durationToMs(m.StorageCommits),
|
||||
TrieDBCommitMs: durationToMs(m.TrieDBCommits),
|
||||
SnapshotCommitMs: durationToMs(m.SnapshotCommits),
|
||||
}
|
||||
}
|
||||
return logEntry
|
||||
}
|
||||
|
||||
// logSlow prints the detailed execution statistics in JSON format if the block
|
||||
// is regarded as slow. The JSON format is designed for cross-client compatibility
|
||||
// with other Ethereum execution clients.
|
||||
func (s *ExecuteStats) logSlow(block *types.Block, slowBlockThreshold time.Duration) {
|
||||
// Negative threshold means disabled (default when flag not set)
|
||||
if slowBlockThreshold < 0 {
|
||||
return
|
||||
}
|
||||
// Threshold of 0 logs all blocks; positive threshold filters
|
||||
if slowBlockThreshold > 0 && s.TotalTime < slowBlockThreshold {
|
||||
return
|
||||
}
|
||||
jsonBytes, err := json.Marshal(buildSlowBlockLog(s, block))
|
||||
if err != nil {
|
||||
log.Error("Failed to marshal slow block log", "error", err)
|
||||
return
|
||||
|
|
|
|||
59
core/state/state_counts.go
Normal file
59
core/state/state_counts.go
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
// Copyright 2026 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package state
|
||||
|
||||
// StateCounts holds count-only statistics gathered during a block's state
|
||||
// transition. It is the snapshot/aggregation type: all fields are plain ints,
|
||||
// safe to copy and pass by value through channels and struct fields.
|
||||
//
|
||||
// StateDB still uses atomic counters internally (for concurrent worker
|
||||
// updates); the conversion to plain ints happens at the snapshot boundary
|
||||
// in (*StateDB).SnapshotCounts. This separation keeps the live atomics
|
||||
// scoped to the mutation surface and lets the rest of the pipeline use
|
||||
// vet-clean value semantics.
|
||||
//
|
||||
// Only counts live here — time.Duration fields (AccountReads, StorageReads,
|
||||
// etc.) stay on StateDB directly, since their parallel-execution semantics
|
||||
// don't fit the simple Add merge pattern.
|
||||
type StateCounts struct {
|
||||
AccountLoaded int // accounts retrieved from the database during the state transition
|
||||
AccountUpdated int // accounts updated during the state transition
|
||||
AccountDeleted int // accounts deleted during the state transition
|
||||
StorageLoaded int // storage slots retrieved from the database during the state transition
|
||||
StorageUpdated int64 // storage slots updated (snapshotted from atomic on StateDB)
|
||||
StorageDeleted int64 // storage slots deleted (snapshotted from atomic on StateDB)
|
||||
CodeLoaded int // contract code reads
|
||||
CodeLoadBytes int // total bytes of resolved code
|
||||
CodeUpdated int // code writes (CREATE/CREATE2/EIP-7702)
|
||||
CodeUpdateBytes int // total bytes of persisted code written
|
||||
}
|
||||
|
||||
// Add merges other into c. Plain integer addition — no atomics here, since
|
||||
// StateCounts is the snapshot type. Callers must ensure other is no longer
|
||||
// being mutated when Add is invoked.
|
||||
func (c *StateCounts) Add(other *StateCounts) {
|
||||
c.AccountLoaded += other.AccountLoaded
|
||||
c.AccountUpdated += other.AccountUpdated
|
||||
c.AccountDeleted += other.AccountDeleted
|
||||
c.StorageLoaded += other.StorageLoaded
|
||||
c.StorageUpdated += other.StorageUpdated
|
||||
c.StorageDeleted += other.StorageDeleted
|
||||
c.CodeLoaded += other.CodeLoaded
|
||||
c.CodeLoadBytes += other.CodeLoadBytes
|
||||
c.CodeUpdated += other.CodeUpdated
|
||||
c.CodeUpdateBytes += other.CodeUpdateBytes
|
||||
}
|
||||
|
|
@ -223,6 +223,24 @@ func (s *StateDB) WithReader(reader Reader) *StateDB {
|
|||
return cpy
|
||||
}
|
||||
|
||||
// SnapshotCounts returns a value-copy of the state-mutation counters as a
|
||||
// plain-int StateCounts. Atomic fields are read via Load(); the result is
|
||||
// safe to copy, pass through channels, and aggregate via StateCounts.Add.
|
||||
func (s *StateDB) SnapshotCounts() StateCounts {
|
||||
return StateCounts{
|
||||
AccountLoaded: s.AccountLoaded,
|
||||
AccountUpdated: s.AccountUpdated,
|
||||
AccountDeleted: s.AccountDeleted,
|
||||
StorageLoaded: s.StorageLoaded,
|
||||
StorageUpdated: s.StorageUpdated.Load(),
|
||||
StorageDeleted: s.StorageDeleted.Load(),
|
||||
CodeLoaded: s.CodeLoaded,
|
||||
CodeLoadBytes: s.CodeLoadBytes,
|
||||
CodeUpdated: s.CodeUpdated,
|
||||
CodeUpdateBytes: s.CodeUpdateBytes,
|
||||
}
|
||||
}
|
||||
|
||||
// StartPrefetcher initializes a new trie prefetcher to pull in nodes from the
|
||||
// state trie concurrently while the state is mutated so that when we reach the
|
||||
// commit phase, most of the needed data is already hot.
|
||||
|
|
|
|||
Loading…
Reference in a new issue