Files
node/x/merkledb/view_iterator.go
zeekay 7cf87b230b node-unified is the node
The module already called itself github.com/luxfi/node while containing no node
in Go: cmd/luxd was twenty-one lines that printed the size of a few types, and
the Go column of the matrix measured a VM plugin. A repo that fetches the thing
it claims to be has not replaced it, so the Go sources move in — 1515 files,
the daemon and everything under it — and the build reaches outside this module
for nothing.

Rust and C++ sit beside it unchanged and are still measured against it by the
differential. That is the point of one repo: the reference implementation and
the two ports are the same checkout, so parity is a thing you can run rather
than a thing you assert.

Verified: builds, and the closure carries no lux-private — checked at build time
rather than believed. The daemon starts twelve chains on a local network
(P X C Q Z A B G K M F, plus D through its plugin) and reports healthy.
2026-09-07 16:15:25 -07:00

181 lines
4.5 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package merkledb
import (
"bytes"
"slices"
"github.com/luxfi/database"
)
func (v *view) NewIterator() database.Iterator {
return v.NewIteratorWithStartAndPrefix(nil, nil)
}
func (v *view) NewIteratorWithStart(start []byte) database.Iterator {
return v.NewIteratorWithStartAndPrefix(start, nil)
}
func (v *view) NewIteratorWithPrefix(prefix []byte) database.Iterator {
return v.NewIteratorWithStartAndPrefix(nil, prefix)
}
func (v *view) NewIteratorWithStartAndPrefix(start, prefix []byte) database.Iterator {
var (
changes = make([]KeyChange, 0, len(v.changes.keyChanges))
startKey = ToKey(start)
prefixKey = ToKey(prefix)
)
startKeyIndex := 0
if len(start) > 0 {
// Binary search for [startKey] index.
startKeyIndex, _ = slices.BinarySearchFunc(v.changes.sortedKeys, startKey, func(key1 Key, key2 Key) int {
return key1.Compare(key2)
})
}
for _, key := range v.changes.sortedKeys[startKeyIndex:] {
if !key.HasPrefix(prefixKey) {
if len(changes) > 0 {
// Since [sortedKeyChanges] is sorted, if the prefix isnt found anymore after we
// added at least one [KeyChange], we can stop.
break
}
continue
}
changes = append(changes, KeyChange{
Key: key.Bytes(),
Value: v.changes.keyChanges[key].after,
})
}
return &viewIterator{
view: v,
parentIter: v.parentTrie.NewIteratorWithStartAndPrefix(start, prefix),
sortedChanges: changes,
}
}
// viewIterator walks over both the in memory database and the underlying database
// at the same time.
type viewIterator struct {
view *view
parentIter database.Iterator
key, value []byte
err error
sortedChanges []KeyChange
initialized, parentIterExhausted bool
}
// Next moves the iterator to the next key/value pair. It returns whether the
// iterator is exhausted. We must pay careful attention to set the proper values
// based on if the in memory changes or the underlying db should be read next
func (it *viewIterator) Next() bool {
switch {
case it.view.isInvalid():
it.key = nil
it.value = nil
it.err = ErrInvalid
return false
case !it.initialized:
it.parentIterExhausted = !it.parentIter.Next()
it.initialized = true
}
for {
switch {
case it.parentIterExhausted && len(it.sortedChanges) == 0:
// there are no more changes or underlying key/values
it.key = nil
it.value = nil
return false
case it.parentIterExhausted:
// there are no more underlying key/values, so use the local changes
nextKeyValue := it.sortedChanges[0]
// move to next change
it.sortedChanges = it.sortedChanges[1:]
// If current change is not a deletion, return it.
// Otherwise go to next loop iteration.
if !nextKeyValue.Value.IsNothing() {
it.key = nextKeyValue.Key
it.value = nextKeyValue.Value.Value()
return true
}
case len(it.sortedChanges) == 0:
it.key = it.parentIter.Key()
it.value = it.parentIter.Value()
it.parentIterExhausted = !it.parentIter.Next()
return true
default:
memKey := it.sortedChanges[0].Key
memValue := it.sortedChanges[0].Value
parentKey := it.parentIter.Key()
switch bytes.Compare(memKey, parentKey) {
case -1:
// The current change has a smaller key than the parent key.
// Move to the next change.
it.sortedChanges = it.sortedChanges[1:]
// If current change is not a deletion, return it.
// Otherwise, go to next loop iteration.
if memValue.HasValue() {
it.key = memKey
it.value = slices.Clone(memValue.Value())
return true
}
case 1:
// The parent key is smaller, so return it and iterate the parent iterator
it.key = parentKey
it.value = it.parentIter.Value()
it.parentIterExhausted = !it.parentIter.Next()
return true
default:
// the keys are the same, so use the local change and
// iterate both the sorted changes and the parent iterator
it.sortedChanges = it.sortedChanges[1:]
it.parentIterExhausted = !it.parentIter.Next()
if memValue.HasValue() {
it.key = memKey
it.value = slices.Clone(memValue.Value())
return true
}
}
}
}
}
func (it *viewIterator) Error() error {
if it.err != nil {
return it.err
}
return it.parentIter.Error()
}
func (it *viewIterator) Key() []byte {
return it.key
}
func (it *viewIterator) Value() []byte {
return it.value
}
func (it *viewIterator) Release() {
it.key = nil
it.value = nil
it.sortedChanges = nil
it.parentIter.Release()
}