mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-23 13:16:42 +00:00
trie: add sub-trie iterator with prefix and range iteration support
This commit is contained in:
parent
f125dbd9c7
commit
2db2a18e83
3 changed files with 59 additions and 58 deletions
|
|
@ -150,11 +150,11 @@ type nodeIterator struct {
|
||||||
// Fields for subtree iteration (original byte keys)
|
// Fields for subtree iteration (original byte keys)
|
||||||
startKey []byte // Start key for subtree iteration (nil for full trie)
|
startKey []byte // Start key for subtree iteration (nil for full trie)
|
||||||
stopKey []byte // Stop key for subtree iteration (nil for full trie)
|
stopKey []byte // Stop key for subtree iteration (nil for full trie)
|
||||||
|
|
||||||
// Precomputed nibble paths for efficient comparison
|
// Precomputed nibble paths for efficient comparison
|
||||||
startPath []byte // Precomputed hex path for startKey (without terminator)
|
startPath []byte // Precomputed hex path for startKey (without terminator)
|
||||||
stopPath []byte // Precomputed hex path for stopKey (without terminator)
|
stopPath []byte // Precomputed hex path for stopKey (without terminator)
|
||||||
|
|
||||||
// Iteration mode
|
// Iteration mode
|
||||||
prefixMode bool // True if this is prefix iteration (use HasPrefix check)
|
prefixMode bool // True if this is prefix iteration (use HasPrefix check)
|
||||||
}
|
}
|
||||||
|
|
@ -309,30 +309,30 @@ func (it *nodeIterator) Next(descend bool) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we're still within the subtree boundaries using precomputed paths
|
// Check if we're still within the subtree boundaries using precomputed paths
|
||||||
if it.startPath != nil && len(path) > 0 {
|
if it.startPath != nil && len(path) > 0 {
|
||||||
if it.prefixMode {
|
if it.prefixMode {
|
||||||
// For prefix iteration, use HasPrefix to ensure we stay within the prefix
|
// For prefix iteration, use HasPrefix to ensure we stay within the prefix
|
||||||
if !bytes.HasPrefix(path, it.startPath) {
|
if !bytes.HasPrefix(path, it.startPath) {
|
||||||
it.err = errIteratorEnd
|
it.err = errIteratorEnd
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// For range iteration, ensure we don't return nodes before the lower bound.
|
// For range iteration, ensure we don't return nodes before the lower bound.
|
||||||
// Advance the iterator until we reach a node at or after startPath.
|
// Advance the iterator until we reach a node at or after startPath.
|
||||||
for bytes.Compare(path, it.startPath) < 0 {
|
for bytes.Compare(path, it.startPath) < 0 {
|
||||||
// Progress the iterator by pushing the current candidate, then peeking again.
|
// Progress the iterator by pushing the current candidate, then peeking again.
|
||||||
it.push(state, parentIndex, path)
|
it.push(state, parentIndex, path)
|
||||||
state, parentIndex, path, err = it.peek(descend)
|
state, parentIndex, path, err = it.peek(descend)
|
||||||
it.err = err
|
it.err = err
|
||||||
if it.err != nil {
|
if it.err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if len(path) == 0 {
|
if len(path) == 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if it.stopPath != nil && len(path) > 0 {
|
if it.stopPath != nil && len(path) > 0 {
|
||||||
if bytes.Compare(path, it.stopPath) >= 0 {
|
if bytes.Compare(path, it.stopPath) >= 0 {
|
||||||
it.err = errIteratorEnd
|
it.err = errIteratorEnd
|
||||||
|
|
@ -883,13 +883,13 @@ func (it *unionIterator) Error() error {
|
||||||
|
|
||||||
// NewSubtreeIterator creates an iterator that only traverses nodes within a subtree
|
// NewSubtreeIterator creates an iterator that only traverses nodes within a subtree
|
||||||
// defined by the given startKey and stopKey. This supports general range iteration
|
// defined by the given startKey and stopKey. This supports general range iteration
|
||||||
// where startKey is inclusive and stopKey is exclusive.
|
// where startKey is inclusive and stopKey is exclusive.
|
||||||
//
|
//
|
||||||
// The iterator will only visit nodes whose keys k satisfy: startKey <= k < stopKey,
|
// The iterator will only visit nodes whose keys k satisfy: startKey <= k < stopKey,
|
||||||
// where comparisons are performed in lexicographic order of byte keys (internally
|
// where comparisons are performed in lexicographic order of byte keys (internally
|
||||||
// implemented via hex-nibble path comparisons for efficiency).
|
// implemented via hex-nibble path comparisons for efficiency).
|
||||||
//
|
//
|
||||||
// If startKey is nil, iteration starts from the beginning. If stopKey is nil,
|
// If startKey is nil, iteration starts from the beginning. If stopKey is nil,
|
||||||
// iteration continues to the end of the trie. For prefix iteration, use the
|
// iteration continues to the end of the trie. For prefix iteration, use the
|
||||||
// Trie.NodeIteratorWithPrefix method which handles prefix semantics correctly.
|
// Trie.NodeIteratorWithPrefix method which handles prefix semantics correctly.
|
||||||
func NewSubtreeIterator(trie *Trie, startKey, stopKey []byte) NodeIterator {
|
func NewSubtreeIterator(trie *Trie, startKey, stopKey []byte) NodeIterator {
|
||||||
|
|
@ -912,7 +912,7 @@ func nextKey(prefix []byte) []byte {
|
||||||
// Make a copy to avoid modifying the original
|
// Make a copy to avoid modifying the original
|
||||||
next := make([]byte, len(prefix))
|
next := make([]byte, len(prefix))
|
||||||
copy(next, prefix)
|
copy(next, prefix)
|
||||||
|
|
||||||
// Increment the last byte that isn't 0xff
|
// Increment the last byte that isn't 0xff
|
||||||
for i := len(next) - 1; i >= 0; i-- {
|
for i := len(next) - 1; i >= 0; i-- {
|
||||||
if next[i] < 0xff {
|
if next[i] < 0xff {
|
||||||
|
|
@ -942,7 +942,7 @@ func newSubtreeIterator(trie *Trie, startKey, stopKey []byte, prefixMode bool) N
|
||||||
stopPath = stopPath[:len(stopPath)-1]
|
stopPath = stopPath[:len(stopPath)-1]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if trie.Hash() == types.EmptyRootHash {
|
if trie.Hash() == types.EmptyRootHash {
|
||||||
return &nodeIterator{
|
return &nodeIterator{
|
||||||
trie: trie,
|
trie: trie,
|
||||||
|
|
|
||||||
|
|
@ -786,13 +786,13 @@ func TestPrefixIteratorEdgeCases(t *testing.T) {
|
||||||
// Create a trie with test data
|
// Create a trie with test data
|
||||||
trie := NewEmpty(newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme))
|
trie := NewEmpty(newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme))
|
||||||
testData := map[string]string{
|
testData := map[string]string{
|
||||||
"abc": "value1",
|
"abc": "value1",
|
||||||
"abcd": "value2",
|
"abcd": "value2",
|
||||||
"abce": "value3",
|
"abce": "value3",
|
||||||
"abd": "value4",
|
"abd": "value4",
|
||||||
"dog": "value5",
|
"dog": "value5",
|
||||||
"dog\xff": "value6", // Test with 0xff byte
|
"dog\xff": "value6", // Test with 0xff byte
|
||||||
"dog\xff\xff": "value7", // Multiple 0xff bytes
|
"dog\xff\xff": "value7", // Multiple 0xff bytes
|
||||||
}
|
}
|
||||||
for key, value := range testData {
|
for key, value := range testData {
|
||||||
trie.Update([]byte(key), []byte(value))
|
trie.Update([]byte(key), []byte(value))
|
||||||
|
|
@ -863,7 +863,7 @@ func TestPrefixIteratorEdgeCases(t *testing.T) {
|
||||||
allFF := []byte{0xff, 0xff}
|
allFF := []byte{0xff, 0xff}
|
||||||
trie.Update(allFF, []byte("all_ff_value"))
|
trie.Update(allFF, []byte("all_ff_value"))
|
||||||
trie.Update(append(allFF, 0x00), []byte("all_ff_plus"))
|
trie.Update(append(allFF, 0x00), []byte("all_ff_plus"))
|
||||||
|
|
||||||
iter, err := trie.NodeIteratorWithPrefix(allFF)
|
iter, err := trie.NodeIteratorWithPrefix(allFF)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create iterator: %v", err)
|
t.Fatalf("Failed to create iterator: %v", err)
|
||||||
|
|
@ -905,13 +905,13 @@ func TestGeneralRangeIteration(t *testing.T) {
|
||||||
// Create a trie with test data
|
// Create a trie with test data
|
||||||
trie := NewEmpty(newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme))
|
trie := NewEmpty(newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme))
|
||||||
testData := map[string]string{
|
testData := map[string]string{
|
||||||
"apple": "fruit1",
|
"apple": "fruit1",
|
||||||
"apricot": "fruit2",
|
"apricot": "fruit2",
|
||||||
"banana": "fruit3",
|
"banana": "fruit3",
|
||||||
"cherry": "fruit4",
|
"cherry": "fruit4",
|
||||||
"date": "fruit5",
|
"date": "fruit5",
|
||||||
"fig": "fruit6",
|
"fig": "fruit6",
|
||||||
"grape": "fruit7",
|
"grape": "fruit7",
|
||||||
}
|
}
|
||||||
for key, value := range testData {
|
for key, value := range testData {
|
||||||
trie.Update([]byte(key), []byte(value))
|
trie.Update([]byte(key), []byte(value))
|
||||||
|
|
@ -977,12 +977,12 @@ func TestPrefixIteratorWithDescend(t *testing.T) {
|
||||||
// Create a trie with nested structure
|
// Create a trie with nested structure
|
||||||
trie := NewEmpty(newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme))
|
trie := NewEmpty(newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme))
|
||||||
testData := map[string]string{
|
testData := map[string]string{
|
||||||
"a": "value_a",
|
"a": "value_a",
|
||||||
"a/b": "value_ab",
|
"a/b": "value_ab",
|
||||||
"a/b/c": "value_abc",
|
"a/b/c": "value_abc",
|
||||||
"a/b/d": "value_abd",
|
"a/b/d": "value_abd",
|
||||||
"a/e": "value_ae",
|
"a/e": "value_ae",
|
||||||
"b": "value_b",
|
"b": "value_b",
|
||||||
}
|
}
|
||||||
for key, value := range testData {
|
for key, value := range testData {
|
||||||
trie.Update([]byte(key), []byte(value))
|
trie.Update([]byte(key), []byte(value))
|
||||||
|
|
@ -994,17 +994,17 @@ func TestPrefixIteratorWithDescend(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create iterator: %v", err)
|
t.Fatalf("Failed to create iterator: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count nodes at each level
|
// Count nodes at each level
|
||||||
nodesVisited := 0
|
nodesVisited := 0
|
||||||
leafsFound := make(map[string]bool)
|
leafsFound := make(map[string]bool)
|
||||||
|
|
||||||
// First call with descend=true to enter the "a" subtree
|
// First call with descend=true to enter the "a" subtree
|
||||||
if !iter.Next(true) {
|
if !iter.Next(true) {
|
||||||
t.Fatal("Expected to find at least one node")
|
t.Fatal("Expected to find at least one node")
|
||||||
}
|
}
|
||||||
nodesVisited++
|
nodesVisited++
|
||||||
|
|
||||||
// Continue iteration, sometimes with descend=false
|
// Continue iteration, sometimes with descend=false
|
||||||
descendPattern := []bool{false, true, false, true, true}
|
descendPattern := []bool{false, true, false, true, true}
|
||||||
for i := 0; iter.Next(descendPattern[i%len(descendPattern)]); i++ {
|
for i := 0; iter.Next(descendPattern[i%len(descendPattern)]); i++ {
|
||||||
|
|
@ -1013,14 +1013,15 @@ func TestPrefixIteratorWithDescend(t *testing.T) {
|
||||||
leafsFound[string(iter.LeafKey())] = true
|
leafsFound[string(iter.LeafKey())] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We should still respect the prefix boundary even when skipping
|
// We should still respect the prefix boundary even when skipping
|
||||||
|
prefix := []byte("a")
|
||||||
for key := range leafsFound {
|
for key := range leafsFound {
|
||||||
if !bytes.HasPrefix([]byte(key), []byte("a")) {
|
if !bytes.HasPrefix([]byte(key), prefix) {
|
||||||
t.Errorf("Found key outside prefix when using descend=false: %s", key)
|
t.Errorf("Found key outside prefix when using descend=false: %s", key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Should not have found "b" even if we skip some subtrees
|
// Should not have found "b" even if we skip some subtrees
|
||||||
if leafsFound["b"] {
|
if leafsFound["b"] {
|
||||||
t.Error("Iterator leaked outside prefix boundary with descend=false")
|
t.Error("Iterator leaked outside prefix boundary with descend=false")
|
||||||
|
|
|
||||||
|
|
@ -136,10 +136,10 @@ func (t *Trie) NodeIterator(start []byte) (NodeIterator, error) {
|
||||||
|
|
||||||
// NodeIteratorWithPrefix returns an iterator that returns nodes of the trie whose leaf keys
|
// NodeIteratorWithPrefix returns an iterator that returns nodes of the trie whose leaf keys
|
||||||
// start with the given prefix. Iteration includes all keys k where prefix <= k < nextKey(prefix),
|
// start with the given prefix. Iteration includes all keys k where prefix <= k < nextKey(prefix),
|
||||||
// effectively returning only keys that have the prefix. The iteration stops once it would
|
// effectively returning only keys that have the prefix. The iteration stops once it would
|
||||||
// encounter a key that doesn't start with the prefix.
|
// encounter a key that doesn't start with the prefix.
|
||||||
//
|
//
|
||||||
// For example, with prefix "dog", the iterator will return "dog", "dogcat", "dogfish" but
|
// For example, with prefix "dog", the iterator will return "dog", "dogcat", "dogfish" but
|
||||||
// not "dot" or "fog". An empty prefix iterates the entire trie.
|
// not "dot" or "fog". An empty prefix iterates the entire trie.
|
||||||
func (t *Trie) NodeIteratorWithPrefix(prefix []byte) (NodeIterator, error) {
|
func (t *Trie) NodeIteratorWithPrefix(prefix []byte) (NodeIterator, error) {
|
||||||
// Short circuit if the trie is already committed and not usable.
|
// Short circuit if the trie is already committed and not usable.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue