diff --git a/core/blockchain.go b/core/blockchain.go index 0b92a94b6c..f40bcf6819 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -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 diff --git a/core/state/database.go b/core/state/database.go index 55fb3a0d97..a66dbc73b7 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -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. diff --git a/core/state/statedb.go b/core/state/statedb.go index efb09a08a0..bdfe27f2d1 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -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 } diff --git a/core/stateless/database.go b/core/stateless/database.go index f54c123dda..b2d3efb0b1 100644 --- a/core/stateless/database.go +++ b/core/stateless/database.go @@ -63,5 +63,6 @@ func (w *Witness) MakeHashDB() ethdb.Database { rawdb.WriteLegacyTrieNode(memdb, common.BytesToHash(hash), blob) } + return memdb } diff --git a/core/stateless/witness.go b/core/stateless/witness.go index aecfad1d52..671adcd37b 100644 --- a/core/stateless/witness.go +++ b/core/stateless/witness.go @@ -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 { diff --git a/trie/secure_trie.go b/trie/secure_trie.go index 408fe64051..09e333371c 100644 --- a/trie/secure_trie.go +++ b/trie/secure_trie.go @@ -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. diff --git a/trie/tracer.go b/trie/tracer.go index 2e2d0928b5..186f0187d4 100644 --- a/trie/tracer.go +++ b/trie/tracer.go @@ -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() diff --git a/trie/trie.go b/trie/trie.go index c5b0377c12..82715fa291 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -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. diff --git a/trie/verkle.go b/trie/verkle.go index c8b9a6dd46..40bd495c39 100644 --- a/trie/verkle.go +++ b/trie/verkle.go @@ -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") +}