Skip to content

Commit e6938d0

Browse files
committed
core,trie: rework the witness stats
1 parent 6267bb5 commit e6938d0

File tree

12 files changed

+169
-149
lines changed

12 files changed

+169
-149
lines changed

core/blockchain.go

Lines changed: 11 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -112,13 +112,6 @@ var (
112112
errChainStopped = errors.New("blockchain is stopped")
113113
errInvalidOldChain = errors.New("invalid old chain")
114114
errInvalidNewChain = errors.New("invalid new chain")
115-
116-
accountAccessDepthAvg = metrics.NewRegisteredGauge("trie/access/account/avg", nil)
117-
accountAccessDepthMin = metrics.NewRegisteredGauge("trie/access/account/min", nil)
118-
accountAccessDepthMax = metrics.NewRegisteredGauge("trie/access/account/max", nil)
119-
storageAccessDepthAvg = metrics.NewRegisteredGauge("trie/access/storage/avg", nil)
120-
storageAccessDepthMin = metrics.NewRegisteredGauge("trie/access/storage/min", nil)
121-
storageAccessDepthMax = metrics.NewRegisteredGauge("trie/access/storage/max", nil)
122115
)
123116

124117
var (
@@ -2018,7 +2011,10 @@ func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, s
20182011
// If we are past Byzantium, enable prefetching to pull in trie node paths
20192012
// while processing transactions. Before Byzantium the prefetcher is mostly
20202013
// useless due to the intermediate root hashing after each transaction.
2021-
var witness *stateless.Witness
2014+
var (
2015+
witness *stateless.Witness
2016+
witnessStats *stateless.WitnessStats
2017+
)
20222018
if bc.chainConfig.IsByzantium(block.Number()) {
20232019
// Generate witnesses either if we're self-testing, or if it's the
20242020
// only block being inserted. A bit crude, but witnesses are huge,
@@ -2028,8 +2024,11 @@ func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, s
20282024
if err != nil {
20292025
return nil, err
20302026
}
2027+
if bc.cfg.VmConfig.EnableWitnessStats {
2028+
witnessStats = stateless.NewWitnessStats()
2029+
}
20312030
}
2032-
statedb.StartPrefetcher("chain", witness)
2031+
statedb.StartPrefetcher("chain", witness, witnessStats)
20332032
defer statedb.StopPrefetcher()
20342033
}
20352034

@@ -2126,25 +2125,9 @@ func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, s
21262125
if err != nil {
21272126
return nil, err
21282127
}
2129-
2130-
// If witness was generated, update metrics regarding the access paths.
2131-
if witness != nil {
2132-
a := witness.AccountDepthMetrics
2133-
if a.Count > 0 {
2134-
accountAccessDepthAvg.Update(int64(a.Sum) / int64(a.Count))
2135-
if a.Min != -1 {
2136-
accountAccessDepthMin.Update(int64(a.Min))
2137-
}
2138-
accountAccessDepthMax.Update(int64(a.Max))
2139-
}
2140-
s := witness.StateDepthMetrics
2141-
if s.Count > 0 {
2142-
storageAccessDepthAvg.Update(int64(s.Sum) / int64(s.Count))
2143-
if s.Min != -1 {
2144-
storageAccessDepthMin.Update(int64(s.Min))
2145-
}
2146-
storageAccessDepthMax.Update(int64(s.Max))
2147-
}
2128+
// Report the collected witness statistics
2129+
if witnessStats != nil {
2130+
witnessStats.ReportMetrics()
21482131
}
21492132

21502133
// Update the metrics touched during block commit

core/state/database.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,10 +124,6 @@ type Trie interface {
124124
// The returned map could be nil if the witness is empty.
125125
Witness() map[string][]byte
126126

127-
// Owner returns the owner of the trie, allowing us to distinguish between
128-
// storage and account tries.
129-
Owner() common.Hash
130-
131127
// NodeIterator returns an iterator that returns nodes of the trie. Iteration
132128
// starts at the key after the given start key. And error will be returned
133129
// if fails to create node iterator.

core/state/statedb.go

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,8 @@ type StateDB struct {
136136
journal *journal
137137

138138
// State witness if cross validation is needed
139-
witness *stateless.Witness
139+
witness *stateless.Witness
140+
witnessStats *stateless.WitnessStats
140141

141142
// Measurements gathered during execution for debugging purposes
142143
AccountReads time.Duration
@@ -191,12 +192,13 @@ func NewWithReader(root common.Hash, db Database, reader Reader) (*StateDB, erro
191192
// StartPrefetcher initializes a new trie prefetcher to pull in nodes from the
192193
// state trie concurrently while the state is mutated so that when we reach the
193194
// commit phase, most of the needed data is already hot.
194-
func (s *StateDB) StartPrefetcher(namespace string, witness *stateless.Witness) {
195+
func (s *StateDB) StartPrefetcher(namespace string, witness *stateless.Witness, witnessStats *stateless.WitnessStats) {
195196
// Terminate any previously running prefetcher
196197
s.StopPrefetcher()
197198

198199
// Enable witness collection if requested
199200
s.witness = witness
201+
s.witnessStats = witnessStats
200202

201203
// With the switch to the Proof-of-Stake consensus algorithm, block production
202204
// rewards are now handled at the consensus layer. Consequently, a block may
@@ -841,7 +843,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
841843
// If witness building is enabled and the state object has a trie,
842844
// gather the witnesses for its specific storage trie
843845
if s.witness != nil && obj.trie != nil {
844-
s.witness.AddState(obj.trie.Witness(), obj.trie.Owner())
846+
s.witness.AddState(obj.trie.Witness())
845847
}
846848
}
847849
return nil
@@ -858,9 +860,17 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
858860
continue
859861
}
860862
if trie := obj.getPrefetchedTrie(); trie != nil {
861-
s.witness.AddState(trie.Witness(), trie.Owner())
863+
witness := trie.Witness()
864+
s.witness.AddState(witness)
865+
if s.witnessStats != nil {
866+
s.witnessStats.Add(witness, obj.addrHash)
867+
}
862868
} else if obj.trie != nil {
863-
s.witness.AddState(obj.trie.Witness(), obj.trie.Owner())
869+
witness := obj.trie.Witness()
870+
s.witness.AddState(witness)
871+
if s.witnessStats != nil {
872+
s.witnessStats.Add(witness, obj.addrHash)
873+
}
864874
}
865875
}
866876
// Pull in only-read and non-destructed trie witnesses
@@ -874,9 +884,17 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
874884
continue
875885
}
876886
if trie := obj.getPrefetchedTrie(); trie != nil {
877-
s.witness.AddState(trie.Witness(), trie.Owner())
887+
witness := trie.Witness()
888+
s.witness.AddState(witness)
889+
if s.witnessStats != nil {
890+
s.witnessStats.Add(witness, obj.addrHash)
891+
}
878892
} else if obj.trie != nil {
879-
s.witness.AddState(obj.trie.Witness(), trie.Owner())
893+
witness := trie.Witness()
894+
s.witness.AddState(witness)
895+
if s.witnessStats != nil {
896+
s.witnessStats.Add(witness, obj.addrHash)
897+
}
880898
}
881899
}
882900
}
@@ -942,7 +960,11 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
942960

943961
// If witness building is enabled, gather the account trie witness
944962
if s.witness != nil {
945-
s.witness.AddState(s.trie.Witness(), s.trie.Owner())
963+
witness := s.trie.Witness()
964+
s.witness.AddState(witness)
965+
if s.witnessStats != nil {
966+
s.witnessStats.Add(witness, common.Hash{})
967+
}
946968
}
947969
return hash
948970
}
@@ -1295,17 +1317,6 @@ func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool, noStorag
12951317
if err != nil {
12961318
return nil, err
12971319
}
1298-
1299-
// This iterates through nodeset and tracks path depths for tracking
1300-
// state and account trie modifications.
1301-
if s.witness != nil && ret != nil && !ret.empty() {
1302-
for owner, set := range ret.nodes.Sets {
1303-
for path := range set.Origins {
1304-
s.witness.AddStateModify(len(path), owner)
1305-
}
1306-
}
1307-
}
1308-
13091320
// Commit dirty contract code if any exists
13101321
if db := s.db.TrieDB().Disk(); db != nil && len(ret.codes) > 0 {
13111322
batch := db.NewBatch()

core/stateless/stats.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// Copyright 2025 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package stateless
18+
19+
import (
20+
"maps"
21+
22+
"github.com/ethereum/go-ethereum/common"
23+
"github.com/ethereum/go-ethereum/metrics"
24+
)
25+
26+
var (
27+
accountTrieDepthAvg = metrics.NewRegisteredGauge("witness/trie/account/depth/avg", nil)
28+
accountTrieDepthMin = metrics.NewRegisteredGauge("witness/trie/account/depth/min", nil)
29+
accountTrieDepthMax = metrics.NewRegisteredGauge("witness/trie/account/depth/max", nil)
30+
31+
storageTrieDepthAvg = metrics.NewRegisteredGauge("witness/trie/storage/depth/avg", nil)
32+
storageTrieDepthMin = metrics.NewRegisteredGauge("witness/trie/storage/depth/min", nil)
33+
storageTrieDepthMax = metrics.NewRegisteredGauge("witness/trie/storage/depth/max", nil)
34+
)
35+
36+
// depthStats tracks min/avg/max statistics for trie access depths.
37+
type depthStats struct {
38+
totalDepth int64
39+
samples int64
40+
minDepth int64
41+
maxDepth int64
42+
}
43+
44+
// newDepthStats creates a new depthStats with default values.
45+
func newDepthStats() *depthStats {
46+
return &depthStats{minDepth: -1}
47+
}
48+
49+
// Add records a new depth sample.
50+
func (d *depthStats) Add(n int64) {
51+
if n < 0 {
52+
return
53+
}
54+
d.totalDepth += n
55+
d.samples++
56+
57+
if d.minDepth == -1 || n < d.minDepth {
58+
d.minDepth = n
59+
}
60+
if n > d.maxDepth {
61+
d.maxDepth = n
62+
}
63+
}
64+
65+
// report uploads the collected statistics into the provided gauges.
66+
func (d *depthStats) report(maxGauge, minGauge, avgGauge *metrics.Gauge) {
67+
if d.samples == 0 {
68+
return
69+
}
70+
maxGauge.Update(d.maxDepth)
71+
minGauge.Update(d.minDepth)
72+
avgGauge.Update(d.totalDepth / d.samples)
73+
}
74+
75+
// WitnessStats aggregates statistics for account and storage trie accesses.
76+
type WitnessStats struct {
77+
accountTrie *depthStats
78+
storageTrie *depthStats
79+
}
80+
81+
// NewWitnessStats creates a new WitnessStats collector.
82+
func NewWitnessStats() *WitnessStats {
83+
return &WitnessStats{
84+
accountTrie: newDepthStats(),
85+
storageTrie: newDepthStats(),
86+
}
87+
}
88+
89+
// Add records trie access depths from the given node paths.
90+
// If `owner` is the zero hash, accesses are attributed to the account trie;
91+
// otherwise, they are attributed to the storage trie of that account.
92+
func (s *WitnessStats) Add(nodes map[string][]byte, owner common.Hash) {
93+
if owner == (common.Hash{}) {
94+
for path := range maps.Keys(nodes) {
95+
s.accountTrie.Add(int64(len(path)))
96+
}
97+
} else {
98+
for path := range maps.Keys(nodes) {
99+
s.storageTrie.Add(int64(len(path)))
100+
}
101+
}
102+
}
103+
104+
// ReportMetrics reports the collected statistics to the global metrics registry.
105+
func (s *WitnessStats) ReportMetrics() {
106+
s.accountTrie.report(accountTrieDepthMax, accountTrieDepthMin, accountTrieDepthAvg)
107+
s.storageTrie.report(storageTrieDepthMax, storageTrieDepthMin, storageTrieDepthAvg)
108+
}

0 commit comments

Comments
 (0)