mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-23 13:16:42 +00:00
trie: refactor sub-trie iterator to extend existing nodeIterator
- Remove separate prefixIterator type per reviewer feedback - Add prefix and stop fields directly to nodeIterator - Implement proper subtree boundaries with both start and stop points - Simplify maintenance by avoiding unnecessary new types Co-authored-by: gballet <guillaume.ballet@gmail.com>
This commit is contained in:
parent
399247b4d9
commit
9a72c83e6f
3 changed files with 112 additions and 40 deletions
|
|
@ -146,6 +146,10 @@ type nodeIterator struct {
|
||||||
|
|
||||||
resolver NodeResolver // optional node resolver for avoiding disk hits
|
resolver NodeResolver // optional node resolver for avoiding disk hits
|
||||||
pool []*nodeIteratorState // local pool for iterator states
|
pool []*nodeIteratorState // local pool for iterator states
|
||||||
|
|
||||||
|
// Fields for subtree iteration
|
||||||
|
prefix []byte // Prefix for subtree iteration (nil for full trie)
|
||||||
|
stop []byte // Stop boundary for subtree iteration (nil for full trie)
|
||||||
}
|
}
|
||||||
|
|
||||||
// errIteratorEnd is stored in nodeIterator.err when iteration is done.
|
// errIteratorEnd is stored in nodeIterator.err when iteration is done.
|
||||||
|
|
@ -296,6 +300,31 @@ func (it *nodeIterator) Next(descend bool) bool {
|
||||||
if it.err != nil {
|
if it.err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if we're still within the subtree boundaries
|
||||||
|
// Note: path is already hex-encoded by the iterator
|
||||||
|
if it.prefix != nil && len(path) > 0 {
|
||||||
|
prefixHex := keybytesToHex(it.prefix)
|
||||||
|
// Remove terminator from prefix hex if present
|
||||||
|
if hasTerm(prefixHex) {
|
||||||
|
prefixHex = prefixHex[:len(prefixHex)-1]
|
||||||
|
}
|
||||||
|
if !bytes.HasPrefix(path, prefixHex) {
|
||||||
|
it.err = errIteratorEnd
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if it.stop != nil && len(path) > 0 {
|
||||||
|
stopHex := keybytesToHex(it.stop)
|
||||||
|
if hasTerm(stopHex) {
|
||||||
|
stopHex = stopHex[:len(stopHex)-1]
|
||||||
|
}
|
||||||
|
if bytes.Compare(path, stopHex) >= 0 {
|
||||||
|
it.err = errIteratorEnd
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
it.push(state, parentIndex, path)
|
it.push(state, parentIndex, path)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
@ -837,40 +866,33 @@ func (it *unionIterator) Error() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// prefixIterator is a wrapper around NodeIterator that stops iteration
|
// NewSubtreeIterator creates an iterator that only traverses nodes within a subtree
|
||||||
// when it leaves a subtree with a specific prefix.
|
// defined by the given prefix and stopping point. The prefix defines where iteration
|
||||||
type prefixIterator struct {
|
// starts, and stop defines where it ends (exclusive).
|
||||||
NodeIterator
|
func NewSubtreeIterator(trie *Trie, prefix []byte, stop []byte) NodeIterator {
|
||||||
prefix []byte
|
if trie.Hash() == types.EmptyRootHash {
|
||||||
ended bool
|
return &nodeIterator{
|
||||||
}
|
trie: trie,
|
||||||
|
err: errIteratorEnd,
|
||||||
// NewPrefixIterator creates an iterator that only traverses nodes with the given prefix.
|
prefix: prefix,
|
||||||
func NewPrefixIterator(base NodeIterator, prefix []byte) NodeIterator {
|
stop: stop,
|
||||||
return &prefixIterator{
|
}
|
||||||
NodeIterator: base,
|
}
|
||||||
prefix: prefix,
|
it := &nodeIterator{
|
||||||
}
|
trie: trie,
|
||||||
}
|
prefix: prefix,
|
||||||
|
stop: stop,
|
||||||
// Next moves the iterator to the next node within the prefix boundary.
|
}
|
||||||
// It returns false when no more nodes exist within the prefix.
|
// Seek to the starting position if prefix is provided
|
||||||
func (pi *prefixIterator) Next(descend bool) bool {
|
if prefix != nil && len(prefix) > 0 {
|
||||||
if pi.ended {
|
it.err = it.seek(prefix)
|
||||||
return false
|
} else {
|
||||||
}
|
state, err := it.init()
|
||||||
|
if err != nil {
|
||||||
if !pi.NodeIterator.Next(descend) {
|
it.err = err
|
||||||
pi.ended = true
|
} else {
|
||||||
return false
|
it.push(state, nil, nil)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Check if current path is still within the prefix boundary
|
return it
|
||||||
path := pi.Path()
|
|
||||||
if len(path) > 0 && len(pi.prefix) > 0 && !bytes.HasPrefix(path, pi.prefix) {
|
|
||||||
pi.ended = true
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -624,6 +624,58 @@ func isTrieNode(scheme string, key, val []byte) (bool, []byte, common.Hash) {
|
||||||
return true, path, hash
|
return true, path, hash
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSubtreeIterator(t *testing.T) {
|
||||||
|
diskDb := rawdb.NewMemoryDatabase()
|
||||||
|
db := newTestDatabase(diskDb, rawdb.HashScheme)
|
||||||
|
tr := NewEmpty(db)
|
||||||
|
vals := []struct{ k, v string }{
|
||||||
|
{"do", "verb"},
|
||||||
|
{"dog", "puppy"},
|
||||||
|
{"doge", "coin"},
|
||||||
|
{"horse", "stallion"},
|
||||||
|
{"house", "building"},
|
||||||
|
{"houses", "multiple"},
|
||||||
|
}
|
||||||
|
all := make(map[string]string)
|
||||||
|
for _, val := range vals {
|
||||||
|
all[val.k] = val.v
|
||||||
|
tr.MustUpdate([]byte(val.k), []byte(val.v))
|
||||||
|
}
|
||||||
|
root, nodes := tr.Commit(false)
|
||||||
|
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
|
||||||
|
|
||||||
|
// Test subtree iteration with prefix "do"
|
||||||
|
prefix := []byte("do")
|
||||||
|
stop := []byte("e")
|
||||||
|
|
||||||
|
// We need to re-open the trie from the committed state
|
||||||
|
tr, _ = New(TrieID(root), db)
|
||||||
|
it := NewSubtreeIterator(tr, prefix, stop)
|
||||||
|
|
||||||
|
found := make(map[string]string)
|
||||||
|
for it.Next(true) {
|
||||||
|
if it.Leaf() {
|
||||||
|
found[string(it.LeafKey())] = string(it.LeafBlob())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should find "do", "dog", "doge" but not "horse", "house", "houses"
|
||||||
|
expected := map[string]string{
|
||||||
|
"do": "verb",
|
||||||
|
"dog": "puppy",
|
||||||
|
"doge": "coin",
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(found) != len(expected) {
|
||||||
|
t.Errorf("wrong number of values: got %d, want %d", len(found), len(expected))
|
||||||
|
}
|
||||||
|
for k, v := range expected {
|
||||||
|
if found[k] != v {
|
||||||
|
t.Errorf("wrong value for %s: got %s, want %s", k, found[k], v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkIterator(b *testing.B) {
|
func BenchmarkIterator(b *testing.B) {
|
||||||
diskDb, srcDb, tr, _ := makeTestTrie(rawdb.HashScheme)
|
diskDb, srcDb, tr, _ := makeTestTrie(rawdb.HashScheme)
|
||||||
root := tr.Hash()
|
root := tr.Hash()
|
||||||
|
|
|
||||||
|
|
@ -141,10 +141,8 @@ func (t *Trie) NodeIteratorWithPrefix(prefix []byte) (NodeIterator, error) {
|
||||||
if t.committed {
|
if t.committed {
|
||||||
return nil, ErrCommitted
|
return nil, ErrCommitted
|
||||||
}
|
}
|
||||||
baseIterator := newNodeIterator(t, prefix)
|
// Use NewSubtreeIterator with just a prefix and no stop boundary
|
||||||
hexPrefix := keybytesToHex(prefix)
|
return NewSubtreeIterator(t, prefix, nil), nil
|
||||||
hexPrefix = hexPrefix[:len(hexPrefix)-1] // Remove terminator
|
|
||||||
return NewPrefixIterator(baseIterator, hexPrefix), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MustGet is a wrapper of Get and will omit any encountered error but just
|
// MustGet is a wrapper of Get and will omit any encountered error but just
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue