add metrics for depth of state trie access

This commit is contained in:
shantichanal 2025-08-15 15:39:09 +02:00 committed by Gary Rong
parent 765c380b6c
commit 80bc2f3ac1
9 changed files with 75 additions and 12 deletions

View file

@ -112,6 +112,9 @@ var (
errChainStopped = errors.New("blockchain is stopped")
errInvalidOldChain = errors.New("invalid old chain")
errInvalidNewChain = errors.New("invalid new chain")
avgAccessDepthInBlock = metrics.NewRegisteredGauge("trie/access/depth/avg", nil)
minAccessDepthInBlock = metrics.NewRegisteredGauge("trie/access/depth/min", nil)
)
var (
@ -2083,6 +2086,7 @@ func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, s
return nil, fmt.Errorf("stateless self-validation receipt root mismatch (cross: %x local: %x)", crossReceiptRoot, block.ReceiptHash())
}
}
xvtime := time.Since(xvstart)
proctime := time.Since(startTime) // processing + validation + cross validation
@ -2118,6 +2122,24 @@ func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, s
if err != nil {
return nil, err
}
// If witness was generated, update metrics regarding the access paths.
if witness != nil {
paths := witness.Paths
totaldepth, pathnum, mindepth := 0, 0, -1
if len(paths) > 0 {
for path, _ := range paths {
if len(path) < mindepth || mindepth < 0 {
mindepth = len(path)
}
totaldepth += len(path)
pathnum++
}
avgAccessDepthInBlock.Update(int64(totaldepth) / int64(pathnum))
minAccessDepthInBlock.Update(int64(mindepth))
}
}
// Update the metrics touched during block commit
accountCommitTimer.Update(statedb.AccountCommits) // Account commits are complete, we can mark them
storageCommitTimer.Update(statedb.StorageCommits) // Storage commits are complete, we can mark them

View file

@ -132,6 +132,10 @@ type Trie interface {
// The returned map could be nil if the witness is empty.
Witness() map[string]struct{}
// WitnessPaths returns a set of paths for all trie nodes. For future reference,
// witness can be deprecated and used as a replacement to witness.
WitnessPaths() map[string]struct{}
// NodeIterator returns an iterator that returns nodes of the trie. Iteration
// starts at the key after the given start key. And error will be returned
// if fails to create node iterator.

View file

@ -841,7 +841,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
// If witness building is enabled and the state object has a trie,
// gather the witnesses for its specific storage trie
if s.witness != nil && obj.trie != nil {
s.witness.AddState(obj.trie.Witness())
s.witness.AddState(obj.trie.Witness(), obj.trie.WitnessPaths())
}
}
return nil
@ -858,9 +858,9 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
continue
}
if trie := obj.getPrefetchedTrie(); trie != nil {
s.witness.AddState(trie.Witness())
s.witness.AddState(trie.Witness(), trie.WitnessPaths())
} else if obj.trie != nil {
s.witness.AddState(obj.trie.Witness())
s.witness.AddState(obj.trie.Witness(), obj.trie.WitnessPaths())
}
}
// Pull in only-read and non-destructed trie witnesses
@ -874,9 +874,9 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
continue
}
if trie := obj.getPrefetchedTrie(); trie != nil {
s.witness.AddState(trie.Witness())
s.witness.AddState(trie.Witness(), trie.WitnessPaths())
} else if obj.trie != nil {
s.witness.AddState(obj.trie.Witness())
s.witness.AddState(obj.trie.Witness(), obj.trie.WitnessPaths())
}
}
}
@ -942,7 +942,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
// If witness building is enabled, gather the account trie witness
if s.witness != nil {
s.witness.AddState(s.trie.Witness())
s.witness.AddState(s.trie.Witness(), nil)
}
return hash
}

View file

@ -63,5 +63,6 @@ func (w *Witness) MakeHashDB() ethdb.Database {
rawdb.WriteLegacyTrieNode(memdb, common.BytesToHash(hash), blob)
}
return memdb
}

View file

@ -41,6 +41,7 @@ type Witness struct {
Headers []*types.Header // Past headers in reverse order (0=parent, 1=parent's-parent, etc). First *must* be set.
Codes map[string]struct{} // Set of bytecodes ran or accessed
State map[string]struct{} // Set of MPT state trie nodes (account and storage together)
Paths map[string]struct{} // Set of MPT trie paths (i.e. all accessed nodes, not just the ones in state)
chain HeaderReader // Chain reader to convert block hash ops to header proofs
lock sync.Mutex // Lock to allow concurrent state insertions
@ -58,13 +59,15 @@ func NewWitness(context *types.Header, chain HeaderReader) (*Witness, error) {
}
headers = append(headers, parent)
}
// Create the wtness with a reconstructed gutted out block
// Create the witness with a reconstructed gutted out block
return &Witness{
context: context,
Headers: headers,
Codes: make(map[string]struct{}),
State: make(map[string]struct{}),
chain: chain,
Paths: make(map[string]struct{}),
chain: chain,
}, nil
}
@ -88,7 +91,7 @@ func (w *Witness) AddCode(code []byte) {
}
// AddState inserts a batch of MPT trie nodes into the witness.
func (w *Witness) AddState(nodes map[string]struct{}) {
func (w *Witness) AddState(nodes map[string]struct{}, paths map[string]struct{}) {
if len(nodes) == 0 {
return
}
@ -96,6 +99,9 @@ func (w *Witness) AddState(nodes map[string]struct{}) {
defer w.lock.Unlock()
maps.Copy(w.State, nodes)
if paths != nil {
maps.Copy(w.Paths, paths)
}
}
// Copy deep-copies the witness object. Witness.Block isn't deep-copied as it
@ -105,6 +111,7 @@ func (w *Witness) Copy() *Witness {
Headers: slices.Clone(w.Headers),
Codes: maps.Clone(w.Codes),
State: maps.Clone(w.State),
Paths: maps.Clone(w.Paths),
chain: w.chain,
}
if w.context != nil {

View file

@ -277,6 +277,11 @@ func (t *StateTrie) Witness() map[string]struct{} {
return t.trie.Witness()
}
// Witness returns a set containing all trie nodes that have been accessed.
func (t *StateTrie) WitnessPaths() map[string]struct{} {
return t.trie.WitnessPaths()
}
// Commit collects all dirty nodes in the trie and replaces them with the
// corresponding node hash. All collected nodes (including dirty leaves if
// collectLeaf is true) will be encapsulated into a nodeset for return.

View file

@ -154,6 +154,10 @@ func (t *prevalueTracer) values() [][]byte {
return slices.Collect(maps.Values(t.data))
}
func (t *prevalueTracer) keys() []string {
return slices.Collect(maps.Keys(t.data))
}
// reset resets the cached content in the prevalueTracer.
func (t *prevalueTracer) reset() {
t.lock.Lock()

View file

@ -762,11 +762,26 @@ func (t *Trie) Witness() map[string]struct{} {
if len(values) == 0 {
return nil
}
witness := make(map[string]struct{}, len(values))
witnessStates := make(map[string]struct{}, len(values))
for _, val := range values {
witness[string(val)] = struct{}{}
witnessStates[string(val)] = struct{}{}
}
return witness
return witnessStates
}
func (t *Trie) WitnessPaths() map[string]struct{} {
// Return the paths of all nodes that have been accessed.
// The paths are the keys of the prevalue tracer.
keys := t.prevalueTracer.keys()
if len(keys) == 0 {
return nil
}
witnessPaths := make(map[string]struct{}, len(keys))
for _, key := range keys {
witnessPaths[string(key)] = struct{}{}
}
return witnessPaths
}
// Reset drops the referenced root node and cleans all internal state.

View file

@ -455,3 +455,8 @@ func (t *VerkleTrie) nodeResolver(path []byte) ([]byte, error) {
func (t *VerkleTrie) Witness() map[string]struct{} {
panic("not implemented")
}
// Witness returns a set containing all trie nodes that have been accessed.
func (t *VerkleTrie) WitnessPaths() map[string]struct{} {
panic("not implemented")
}