Skip to content

Commit cdf86ae

Browse files
dboehm-avalabstonyqzhangDan LaineDarioush Jalali
authored
Merkle db iterator (ava-labs#1533)
Signed-off-by: David Boehm <91908103+dboehm-avalabs@users.noreply.github.com> Co-authored-by: Qian (Tony) Zhang <tonyqzhang@yahoo.com> Co-authored-by: Dan Laine <daniel.laine@avalabs.org> Co-authored-by: Darioush Jalali <darioush.jalali@avalabs.org>
1 parent 925230d commit cdf86ae

File tree

6 files changed

+455
-227
lines changed

6 files changed

+455
-227
lines changed

x/merkledb/db.go

Lines changed: 1 addition & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1162,7 +1162,7 @@ func (db *merkleDB) getHistoricalViewForRange(
11621162
return newTrieViewWithChanges(db, db, changeHistory, len(changeHistory.nodes))
11631163
}
11641164

1165-
// Returns all of the keys in range [start, end] that aren't in [keySet].
1165+
// Returns all keys in range [start, end] that aren't in [keySet].
11661166
// If [start] is nil, then the range has no lower bound.
11671167
// If [end] is nil, then the range has no upper bound.
11681168
func (db *merkleDB) getKeysNotInSet(start, end []byte, keySet set.Set[string]) ([][]byte, error) {
@@ -1242,55 +1242,6 @@ func (db *merkleDB) getNode(key path) (*node, error) {
12421242
return node, err
12431243
}
12441244

1245-
// If [lock], grabs [db.lock]'s read lock.
1246-
// Otherwise assumes [db.lock] is already read locked.
1247-
func (db *merkleDB) getKeyValues(
1248-
start []byte,
1249-
end []byte,
1250-
maxLength int,
1251-
keysToIgnore set.Set[string],
1252-
lock bool,
1253-
) ([]KeyValue, error) {
1254-
if lock {
1255-
db.lock.RLock()
1256-
defer db.lock.RUnlock()
1257-
}
1258-
1259-
if db.closed {
1260-
return nil, database.ErrClosed
1261-
}
1262-
1263-
if maxLength <= 0 {
1264-
return nil, fmt.Errorf("%w but was %d", ErrInvalidMaxLength, maxLength)
1265-
}
1266-
1267-
it := db.NewIteratorWithStart(start)
1268-
defer it.Release()
1269-
1270-
remainingLength := maxLength
1271-
result := make([]KeyValue, 0, maxLength)
1272-
// Keep adding key/value pairs until one of the following:
1273-
// * We hit a key that is lexicographically larger than the end key.
1274-
// * [maxLength] elements are in [result].
1275-
// * There are no more values to add.
1276-
for remainingLength > 0 && it.Next() {
1277-
key := it.Key()
1278-
if len(end) != 0 && bytes.Compare(it.Key(), end) > 0 {
1279-
break
1280-
}
1281-
if keysToIgnore.Contains(string(key)) {
1282-
continue
1283-
}
1284-
result = append(result, KeyValue{
1285-
Key: key,
1286-
Value: it.Value(),
1287-
})
1288-
remainingLength--
1289-
}
1290-
1291-
return result, it.Error()
1292-
}
1293-
12941245
// Returns a new view atop [db] with the changes in [ops] applied to it.
12951246
// Assumes [db.commitLock] is read locked.
12961247
func (db *merkleDB) prepareBatchView(ops []database.BatchOp) (*trieView, error) {

x/merkledb/mock_db.go

Lines changed: 0 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

x/merkledb/trie.go

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import (
77
"context"
88
"errors"
99

10+
"github.com/ava-labs/avalanchego/database"
1011
"github.com/ava-labs/avalanchego/ids"
11-
"github.com/ava-labs/avalanchego/utils/set"
1212
)
1313

1414
var errNoNewRoot = errors.New("there was no updated root in change list")
@@ -46,13 +46,7 @@ type ReadOnlyTrie interface {
4646
// GetRangeProof generates a proof of up to maxLength smallest key/values with keys between start and end
4747
GetRangeProof(ctx context.Context, start, end []byte, maxLength int) (*RangeProof, error)
4848

49-
getKeyValues(
50-
start []byte,
51-
end []byte,
52-
maxLength int,
53-
keysToIgnore set.Set[string],
54-
lock bool,
55-
) ([]KeyValue, error)
49+
database.Iteratee
5650
}
5751

5852
type Trie interface {

x/merkledb/trieview.go

Lines changed: 16 additions & 153 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@ import (
2020

2121
"github.com/ava-labs/avalanchego/database"
2222
"github.com/ava-labs/avalanchego/ids"
23-
"github.com/ava-labs/avalanchego/utils/set"
2423
)
2524

26-
const defaultPreallocationSize = 100
25+
const (
26+
initKeyValuesSize = 256
27+
defaultPreallocationSize = 100
28+
)
2729

2830
var (
2931
_ TrieView = (*trieView)(nil)
@@ -418,25 +420,20 @@ func (t *trieView) GetRangeProof(
418420
t.lock.RLock()
419421
}
420422

421-
var (
422-
result RangeProof
423-
err error
424-
)
423+
var result RangeProof
425424

426-
result.KeyValues, err = t.getKeyValues(
427-
start,
428-
end,
429-
maxLength,
430-
set.Set[string]{},
431-
false, /*lock*/
432-
)
433-
if err != nil {
434-
return nil, err
425+
result.KeyValues = make([]KeyValue, 0, initKeyValuesSize)
426+
it := t.NewIteratorWithStart(start)
427+
for it.Next() && len(result.KeyValues) < maxLength && (len(end) == 0 || bytes.Compare(it.Key(), end) <= 0) {
428+
// clone the value to prevent editing of the values stored within the trie
429+
result.KeyValues = append(result.KeyValues, KeyValue{
430+
Key: it.Key(),
431+
Value: slices.Clone(it.Value()),
432+
})
435433
}
436-
437-
// copy values, so edits won't affect the underlying arrays
438-
for i, kv := range result.KeyValues {
439-
result.KeyValues[i] = KeyValue{Key: kv.Key, Value: slices.Clone(kv.Value)}
434+
it.Release()
435+
if err := it.Error(); err != nil {
436+
return nil, err
440437
}
441438

442439
// This proof may not contain all key-value pairs in [start, end] due to size limitations.
@@ -719,140 +716,6 @@ func (t *trieView) getMerkleRoot(ctx context.Context) (ids.ID, error) {
719716
return t.root.id, nil
720717
}
721718

722-
// Returns up to [maxLength] key/values from keys in closed range [start, end].
723-
// Acts similarly to the merge step of a merge sort to combine state from the view
724-
// with state from the parent trie.
725-
// If [lock], grabs [t.lock]'s read lock.
726-
// Otherwise assumes [t.lock]'s read lock is held.
727-
func (t *trieView) getKeyValues(
728-
start []byte,
729-
end []byte,
730-
maxLength int,
731-
keysToIgnore set.Set[string],
732-
lock bool,
733-
) ([]KeyValue, error) {
734-
if lock {
735-
t.lock.RLock()
736-
defer t.lock.RUnlock()
737-
}
738-
739-
if maxLength <= 0 {
740-
return nil, fmt.Errorf("%w but was %d", ErrInvalidMaxLength, maxLength)
741-
}
742-
743-
if t.isInvalid() {
744-
return nil, ErrInvalid
745-
}
746-
747-
// collect all values that have changed or been deleted
748-
changes := make([]KeyValue, 0, len(t.changes.values))
749-
for key, change := range t.changes.values {
750-
if change.after.IsNothing() {
751-
// This was deleted
752-
keysToIgnore.Add(string(key.Serialize().Value))
753-
} else {
754-
changes = append(changes, KeyValue{
755-
Key: key.Serialize().Value,
756-
Value: change.after.value,
757-
})
758-
}
759-
}
760-
// sort [changes] so they can be merged with the parent trie's state
761-
slices.SortFunc(changes, func(a, b KeyValue) bool {
762-
return bytes.Compare(a.Key, b.Key) == -1
763-
})
764-
765-
baseKeyValues, err := t.getParentTrie().getKeyValues(
766-
start,
767-
end,
768-
maxLength,
769-
keysToIgnore,
770-
true, /*lock*/
771-
)
772-
if err != nil {
773-
return nil, err
774-
}
775-
776-
var (
777-
// True if there are more key/value pairs from [baseKeyValues] to add to result
778-
baseKeyValuesFinished = false
779-
// True if there are more key/value pairs from [changes] to add to result
780-
changesFinished = false
781-
// The index of the next key/value pair to add from [baseKeyValues].
782-
baseKeyValuesIndex = 0
783-
// The index of the next key/value pair to add from [changes].
784-
changesIndex = 0
785-
remainingLength = maxLength
786-
hasUpperBound = len(end) > 0
787-
result = make([]KeyValue, 0, len(baseKeyValues))
788-
)
789-
790-
// keep adding key/value pairs until one of the following:
791-
// * a key that is lexicographically larger than the end key is hit
792-
// * the maxLength is hit
793-
// * no more values are available to add
794-
for remainingLength > 0 {
795-
// the baseKeyValues iterator is finished when we have run out of keys or hit a key greater than the end key
796-
baseKeyValuesFinished = baseKeyValuesFinished ||
797-
(baseKeyValuesIndex >= len(baseKeyValues) || (hasUpperBound && bytes.Compare(baseKeyValues[baseKeyValuesIndex].Key, end) == 1))
798-
799-
// the changes iterator is finished when we have run out of keys or hit a key greater than the end key
800-
changesFinished = changesFinished ||
801-
(changesIndex >= len(changes) || (hasUpperBound && bytes.Compare(changes[changesIndex].Key, end) == 1))
802-
803-
// if both the base state and changes are finished, return the result of the merge
804-
if baseKeyValuesFinished && changesFinished {
805-
return result, nil
806-
}
807-
808-
// one or both iterators still have values, so one will be added to the result
809-
remainingLength--
810-
811-
// both still have key/values available, so add the smallest key
812-
if !changesFinished && !baseKeyValuesFinished {
813-
currentChangeState := changes[changesIndex]
814-
currentKeyValues := baseKeyValues[baseKeyValuesIndex]
815-
816-
switch bytes.Compare(currentChangeState.Key, currentKeyValues.Key) {
817-
case -1:
818-
result = append(result, currentChangeState)
819-
changesIndex++
820-
case 0:
821-
// the keys are the same, so override the base value with the changed value
822-
result = append(result, currentChangeState)
823-
changesIndex++
824-
baseKeyValuesIndex++
825-
case 1:
826-
result = append(result, currentKeyValues)
827-
baseKeyValuesIndex++
828-
}
829-
continue
830-
}
831-
832-
// the base state is not finished, but the changes is finished.
833-
// add the next base state value.
834-
if !baseKeyValuesFinished {
835-
currentBaseState := baseKeyValues[baseKeyValuesIndex]
836-
result = append(result, currentBaseState)
837-
baseKeyValuesIndex++
838-
continue
839-
}
840-
841-
// the base state is finished, but the changes is not finished.
842-
// add the next changes value.
843-
currentChangeState := changes[changesIndex]
844-
result = append(result, currentChangeState)
845-
changesIndex++
846-
}
847-
848-
// ensure no ancestor changes occurred during execution
849-
if t.isInvalid() {
850-
return nil, ErrInvalid
851-
}
852-
853-
return result, nil
854-
}
855-
856719
func (t *trieView) GetValues(_ context.Context, keys [][]byte) ([][]byte, []error) {
857720
t.lock.RLock()
858721
defer t.lock.RUnlock()

0 commit comments

Comments
 (0)