all: simplify and fix database iteration with prefix/start (#20808)

* core/state/snapshot: start fixing disk iterator seek

* ethdb, rawdb, leveldb, memorydb: implement iterators with prefix and start

* les, core/state/snapshot: iterator fixes

* all: remove two iterator methods

* all: rename Iteratee.NewIteratorWith -> NewIterator

* ethdb: fix review concerns
This commit is contained in:
Martin Holst Swende 2020-04-15 13:08:53 +02:00 committed by GitHub
parent 00064ddcfb
commit 6402c42b67
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 248 additions and 187 deletions

@ -301,7 +301,7 @@ func ExportPreimages(db ethdb.Database, fn string) error {
defer writer.(*gzip.Writer).Close() defer writer.(*gzip.Writer).Close()
} }
// Iterate over the preimages and export them // Iterate over the preimages and export them
it := db.NewIteratorWithPrefix([]byte("secure-key-")) it := db.NewIterator([]byte("secure-key-"), nil)
defer it.Release() defer it.Release()
for it.Next() { for it.Next() {

@ -145,3 +145,14 @@ func TrimLeftZeroes(s []byte) []byte {
} }
return s[idx:] return s[idx:]
} }
// TrimRightZeroes returns a subslice of s without trailing zeroes
func TrimRightZeroes(s []byte) []byte {
idx := len(s)
for ; idx > 0; idx-- {
if s[idx-1] != 0 {
break
}
}
return s[:idx]
}

@ -105,3 +105,22 @@ func TestNoPrefixShortHexOddLength(t *testing.T) {
t.Errorf("Expected %x got %x", expected, result) t.Errorf("Expected %x got %x", expected, result)
} }
} }
func TestTrimRightZeroes(t *testing.T) {
tests := []struct {
arr []byte
exp []byte
}{
{FromHex("0x00ffff00ff0000"), FromHex("0x00ffff00ff")},
{FromHex("0x00000000000000"), []byte{}},
{FromHex("0xff"), FromHex("0xff")},
{[]byte{}, []byte{}},
{FromHex("0x00ffffffffffff"), FromHex("0x00ffffffffffff")},
}
for i, test := range tests {
got := TrimRightZeroes(test.arr)
if !bytes.Equal(got, test.exp) {
t.Errorf("test %d, got %x exp %x", i, got, test.exp)
}
}
}

@ -69,7 +69,7 @@ func ReadAllHashes(db ethdb.Iteratee, number uint64) []common.Hash {
prefix := headerKeyPrefix(number) prefix := headerKeyPrefix(number)
hashes := make([]common.Hash, 0, 1) hashes := make([]common.Hash, 0, 1)
it := db.NewIteratorWithPrefix(prefix) it := db.NewIterator(prefix, nil)
defer it.Release() defer it.Release()
for it.Next() { for it.Next() {

@ -93,7 +93,7 @@ func DeleteStorageSnapshot(db ethdb.KeyValueWriter, accountHash, storageHash com
// IterateStorageSnapshots returns an iterator for walking the entire storage // IterateStorageSnapshots returns an iterator for walking the entire storage
// space of a specific account. // space of a specific account.
func IterateStorageSnapshots(db ethdb.Iteratee, accountHash common.Hash) ethdb.Iterator { func IterateStorageSnapshots(db ethdb.Iteratee, accountHash common.Hash) ethdb.Iterator {
return db.NewIteratorWithPrefix(storageSnapshotsKey(accountHash)) return db.NewIterator(storageSnapshotsKey(accountHash), nil)
} }
// ReadSnapshotJournal retrieves the serialized in-memory diff layers saved at // ReadSnapshotJournal retrieves the serialized in-memory diff layers saved at

@ -221,7 +221,7 @@ func NewLevelDBDatabaseWithFreezer(file string, cache int, handles int, freezer
// InspectDatabase traverses the entire database and checks the size // InspectDatabase traverses the entire database and checks the size
// of all different categories of data. // of all different categories of data.
func InspectDatabase(db ethdb.Database) error { func InspectDatabase(db ethdb.Database) error {
it := db.NewIterator() it := db.NewIterator(nil, nil)
defer it.Release() defer it.Release()
var ( var (

@ -103,27 +103,12 @@ func (t *table) Delete(key []byte) error {
return t.db.Delete(append([]byte(t.prefix), key...)) return t.db.Delete(append([]byte(t.prefix), key...))
} }
// NewIterator creates a binary-alphabetical iterator over the entire keyspace // NewIterator creates a binary-alphabetical iterator over a subset
// contained within the database. // of database content with a particular key prefix, starting at a particular
func (t *table) NewIterator() ethdb.Iterator { // initial key (or after, if it does not exist).
return t.NewIteratorWithPrefix(nil) func (t *table) NewIterator(prefix []byte, start []byte) ethdb.Iterator {
} innerPrefix := append([]byte(t.prefix), prefix...)
iter := t.db.NewIterator(innerPrefix, start)
// NewIteratorWithStart creates a binary-alphabetical iterator over a subset of
// database content starting at a particular initial key (or after, if it does
// not exist).
func (t *table) NewIteratorWithStart(start []byte) ethdb.Iterator {
iter := t.db.NewIteratorWithStart(append([]byte(t.prefix), start...))
return &tableIterator{
iter: iter,
prefix: t.prefix,
}
}
// NewIteratorWithPrefix creates a binary-alphabetical iterator over a subset
// of database content with a particular key prefix.
func (t *table) NewIteratorWithPrefix(prefix []byte) ethdb.Iterator {
iter := t.db.NewIteratorWithPrefix(append([]byte(t.prefix), prefix...))
return &tableIterator{ return &tableIterator{
iter: iter, iter: iter,
prefix: t.prefix, prefix: t.prefix,

@ -19,6 +19,8 @@ package rawdb
import ( import (
"bytes" "bytes"
"testing" "testing"
"github.com/ethereum/go-ethereum/ethdb"
) )
func TestTableDatabase(t *testing.T) { testTableDatabase(t, "prefix") } func TestTableDatabase(t *testing.T) { testTableDatabase(t, "prefix") }
@ -96,48 +98,31 @@ func testTableDatabase(t *testing.T, prefix string) {
} }
} }
check := func(iter ethdb.Iterator, expCount, index int) {
count := 0
for iter.Next() {
key, value := iter.Key(), iter.Value()
if !bytes.Equal(key, entries[index].key) {
t.Fatalf("Key mismatch: want=%v, got=%v", entries[index].key, key)
}
if !bytes.Equal(value, entries[index].value) {
t.Fatalf("Value mismatch: want=%v, got=%v", entries[index].value, value)
}
index += 1
count++
}
if count != expCount {
t.Fatalf("Wrong number of elems, exp %d got %d", expCount, count)
}
iter.Release()
}
// Test iterators // Test iterators
iter := db.NewIterator() check(db.NewIterator(nil, nil), 6, 0)
var index int
for iter.Next() {
key, value := iter.Key(), iter.Value()
if !bytes.Equal(key, entries[index].key) {
t.Fatalf("Key mismatch: want=%v, got=%v", entries[index].key, key)
}
if !bytes.Equal(value, entries[index].value) {
t.Fatalf("Value mismatch: want=%v, got=%v", entries[index].value, value)
}
index += 1
}
iter.Release()
// Test iterators with prefix // Test iterators with prefix
iter = db.NewIteratorWithPrefix([]byte{0xff, 0xff}) check(db.NewIterator([]byte{0xff, 0xff}, nil), 3, 3)
index = 3
for iter.Next() {
key, value := iter.Key(), iter.Value()
if !bytes.Equal(key, entries[index].key) {
t.Fatalf("Key mismatch: want=%v, got=%v", entries[index].key, key)
}
if !bytes.Equal(value, entries[index].value) {
t.Fatalf("Value mismatch: want=%v, got=%v", entries[index].value, value)
}
index += 1
}
iter.Release()
// Test iterators with start point // Test iterators with start point
iter = db.NewIteratorWithStart([]byte{0xff, 0xff, 0x02}) check(db.NewIterator(nil, []byte{0xff, 0xff, 0x02}), 2, 4)
index = 4 // Test iterators with prefix and start point
for iter.Next() { check(db.NewIterator([]byte{0xee}, nil), 0, 0)
key, value := iter.Key(), iter.Value() check(db.NewIterator(nil, []byte{0x00}), 6, 0)
if !bytes.Equal(key, entries[index].key) {
t.Fatalf("Key mismatch: want=%v, got=%v", entries[index].key, key)
}
if !bytes.Equal(value, entries[index].value) {
t.Fatalf("Value mismatch: want=%v, got=%v", entries[index].value, value)
}
index += 1
}
iter.Release()
} }

@ -51,7 +51,7 @@ func TestNodeIteratorCoverage(t *testing.T) {
t.Errorf("state entry not reported %x", hash) t.Errorf("state entry not reported %x", hash)
} }
} }
it := db.TrieDB().DiskDB().(ethdb.Database).NewIterator() it := db.TrieDB().DiskDB().(ethdb.Database).NewIterator(nil, nil)
for it.Next() { for it.Next() {
key := it.Key() key := it.Key()
if bytes.HasPrefix(key, []byte("secure-key-")) { if bytes.HasPrefix(key, []byte("secure-key-")) {

@ -18,11 +18,15 @@ package snapshot
import ( import (
"bytes" "bytes"
"io/ioutil"
"os"
"testing" "testing"
"github.com/VictoriaMetrics/fastcache" "github.com/VictoriaMetrics/fastcache"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/ethdb/leveldb"
"github.com/ethereum/go-ethereum/ethdb/memorydb" "github.com/ethereum/go-ethereum/ethdb/memorydb"
) )
@ -432,4 +436,76 @@ func TestDiskPartialMerge(t *testing.T) {
// This test case is a tiny specialized case of TestDiskPartialMerge, which tests // This test case is a tiny specialized case of TestDiskPartialMerge, which tests
// some very specific cornercases that random tests won't ever trigger. // some very specific cornercases that random tests won't ever trigger.
func TestDiskMidAccountPartialMerge(t *testing.T) { func TestDiskMidAccountPartialMerge(t *testing.T) {
// TODO(@karalabe) ?
}
// TestDiskSeek tests that seek-operations work on the disk layer
func TestDiskSeek(t *testing.T) {
// Create some accounts in the disk layer
var db ethdb.Database
if dir, err := ioutil.TempDir("", "disklayer-test"); err != nil {
t.Fatal(err)
} else {
defer os.RemoveAll(dir)
diskdb, err := leveldb.New(dir, 256, 0, "")
if err != nil {
t.Fatal(err)
}
db = rawdb.NewDatabase(diskdb)
}
// Fill even keys [0,2,4...]
for i := 0; i < 0xff; i += 2 {
acc := common.Hash{byte(i)}
rawdb.WriteAccountSnapshot(db, acc, acc[:])
}
// Add an 'higher' key, with incorrect (higher) prefix
highKey := []byte{rawdb.SnapshotAccountPrefix[0] + 1}
db.Put(highKey, []byte{0xff, 0xff})
baseRoot := randomHash()
rawdb.WriteSnapshotRoot(db, baseRoot)
snaps := &Tree{
layers: map[common.Hash]snapshot{
baseRoot: &diskLayer{
diskdb: db,
cache: fastcache.New(500 * 1024),
root: baseRoot,
},
},
}
// Test some different seek positions
type testcase struct {
pos byte
expkey byte
}
var cases = []testcase{
{0xff, 0x55}, // this should exit immediately without checking key
{0x01, 0x02},
{0xfe, 0xfe},
{0xfd, 0xfe},
{0x00, 0x00},
}
for i, tc := range cases {
it, err := snaps.AccountIterator(baseRoot, common.Hash{tc.pos})
if err != nil {
t.Fatalf("case %d, error: %v", i, err)
}
count := 0
for it.Next() {
k, v, err := it.Hash()[0], it.Account()[0], it.Error()
if err != nil {
t.Fatalf("test %d, item %d, error: %v", i, count, err)
}
// First item in iterator should have the expected key
if count == 0 && k != tc.expkey {
t.Fatalf("test %d, item %d, got %v exp %v", i, count, k, tc.expkey)
}
count++
if v != k {
t.Fatalf("test %d, item %d, value wrong, got %v exp %v", i, count, v, k)
}
}
}
} }

@ -148,10 +148,10 @@ type diskAccountIterator struct {
// AccountIterator creates an account iterator over a disk layer. // AccountIterator creates an account iterator over a disk layer.
func (dl *diskLayer) AccountIterator(seek common.Hash) AccountIterator { func (dl *diskLayer) AccountIterator(seek common.Hash) AccountIterator {
// TODO: Fix seek position, or remove seek parameter pos := common.TrimRightZeroes(seek[:])
return &diskAccountIterator{ return &diskAccountIterator{
layer: dl, layer: dl,
it: dl.diskdb.NewIteratorWithPrefix(rawdb.SnapshotAccountPrefix), it: dl.diskdb.NewIterator(rawdb.SnapshotAccountPrefix, pos),
} }
} }

@ -92,7 +92,7 @@ func wipeKeyRange(db ethdb.KeyValueStore, kind string, prefix []byte, keylen int
// Iterate over the key-range and delete all of them // Iterate over the key-range and delete all of them
start, logged := time.Now(), time.Now() start, logged := time.Now(), time.Now()
it := db.NewIteratorWithStart(prefix) it := db.NewIterator(prefix, nil)
for it.Next() { for it.Next() {
// Skip any keys with the correct prefix but wrong lenth (trie nodes) // Skip any keys with the correct prefix but wrong lenth (trie nodes)
key := it.Key() key := it.Key()
@ -113,7 +113,8 @@ func wipeKeyRange(db ethdb.KeyValueStore, kind string, prefix []byte, keylen int
return err return err
} }
batch.Reset() batch.Reset()
it = db.NewIteratorWithStart(key) seekPos := key[len(prefix):]
it = db.NewIterator(prefix, seekPos)
if time.Since(logged) > 8*time.Second { if time.Since(logged) > 8*time.Second {
log.Info("Deleting state snapshot leftovers", "kind", kind, "wiped", items, "elapsed", common.PrettyDuration(time.Since(start))) log.Info("Deleting state snapshot leftovers", "kind", kind, "wiped", items, "elapsed", common.PrettyDuration(time.Since(start)))

@ -60,7 +60,7 @@ func TestWipe(t *testing.T) {
// Sanity check that all the keys are present // Sanity check that all the keys are present
var items int var items int
it := db.NewIteratorWithPrefix(rawdb.SnapshotAccountPrefix) it := db.NewIterator(rawdb.SnapshotAccountPrefix, nil)
defer it.Release() defer it.Release()
for it.Next() { for it.Next() {
@ -69,7 +69,7 @@ func TestWipe(t *testing.T) {
items++ items++
} }
} }
it = db.NewIteratorWithPrefix(rawdb.SnapshotStoragePrefix) it = db.NewIterator(rawdb.SnapshotStoragePrefix, nil)
defer it.Release() defer it.Release()
for it.Next() { for it.Next() {
@ -88,7 +88,7 @@ func TestWipe(t *testing.T) {
<-wipeSnapshot(db, true) <-wipeSnapshot(db, true)
// Iterate over the database end ensure no snapshot information remains // Iterate over the database end ensure no snapshot information remains
it = db.NewIteratorWithPrefix(rawdb.SnapshotAccountPrefix) it = db.NewIterator(rawdb.SnapshotAccountPrefix, nil)
defer it.Release() defer it.Release()
for it.Next() { for it.Next() {
@ -97,7 +97,7 @@ func TestWipe(t *testing.T) {
t.Errorf("snapshot entry remained after wipe: %x", key) t.Errorf("snapshot entry remained after wipe: %x", key)
} }
} }
it = db.NewIteratorWithPrefix(rawdb.SnapshotStoragePrefix) it = db.NewIterator(rawdb.SnapshotStoragePrefix, nil)
defer it.Release() defer it.Release()
for it.Next() { for it.Next() {
@ -112,7 +112,7 @@ func TestWipe(t *testing.T) {
// Iterate over the database and ensure miscellaneous items are present // Iterate over the database and ensure miscellaneous items are present
items = 0 items = 0
it = db.NewIterator() it = db.NewIterator(nil, nil)
defer it.Release() defer it.Release()
for it.Next() { for it.Next() {

@ -60,7 +60,7 @@ func TestUpdateLeaks(t *testing.T) {
} }
// Ensure that no data was leaked into the database // Ensure that no data was leaked into the database
it := db.NewIterator() it := db.NewIterator(nil, nil)
for it.Next() { for it.Next() {
t.Errorf("State leaked into database: %x -> %x", it.Key(), it.Value()) t.Errorf("State leaked into database: %x -> %x", it.Key(), it.Value())
} }
@ -118,7 +118,7 @@ func TestIntermediateLeaks(t *testing.T) {
t.Errorf("can not commit trie %v to persistent database", finalRoot.Hex()) t.Errorf("can not commit trie %v to persistent database", finalRoot.Hex())
} }
it := finalDb.NewIterator() it := finalDb.NewIterator(nil, nil)
for it.Next() { for it.Next() {
key, fvalue := it.Key(), it.Value() key, fvalue := it.Key(), it.Value()
tvalue, err := transDb.Get(key) tvalue, err := transDb.Get(key)
@ -131,7 +131,7 @@ func TestIntermediateLeaks(t *testing.T) {
} }
it.Release() it.Release()
it = transDb.NewIterator() it = transDb.NewIterator(nil, nil)
for it.Next() { for it.Next() {
key, tvalue := it.Key(), it.Value() key, tvalue := it.Key(), it.Value()
fvalue, err := finalDb.Get(key) fvalue, err := finalDb.Get(key)

@ -147,7 +147,7 @@ var bloomBitsPrefix = []byte("bloomBits-")
func clearBloomBits(db ethdb.Database) { func clearBloomBits(db ethdb.Database) {
fmt.Println("Clearing bloombits data...") fmt.Println("Clearing bloombits data...")
it := db.NewIteratorWithPrefix(bloomBitsPrefix) it := db.NewIterator(bloomBitsPrefix, nil)
for it.Next() { for it.Next() {
db.Delete(it.Key()) db.Delete(it.Key())
} }

@ -317,7 +317,7 @@ func testGetNodeData(t *testing.T, protocol int) {
// Fetch for now the entire chain db // Fetch for now the entire chain db
hashes := []common.Hash{} hashes := []common.Hash{}
it := db.NewIterator() it := db.NewIterator(nil, nil)
for it.Next() { for it.Next() {
if key := it.Key(); len(key) == common.HashLength { if key := it.Key(); len(key) == common.HashLength {
hashes = append(hashes, common.BytesToHash(key)) hashes = append(hashes, common.BytesToHash(key))

@ -32,31 +32,32 @@ func TestDatabaseSuite(t *testing.T, New func() ethdb.KeyValueStore) {
tests := []struct { tests := []struct {
content map[string]string content map[string]string
prefix string prefix string
start string
order []string order []string
}{ }{
// Empty databases should be iterable // Empty databases should be iterable
{map[string]string{}, "", nil}, {map[string]string{}, "", "", nil},
{map[string]string{}, "non-existent-prefix", nil}, {map[string]string{}, "non-existent-prefix", "", nil},
// Single-item databases should be iterable // Single-item databases should be iterable
{map[string]string{"key": "val"}, "", []string{"key"}}, {map[string]string{"key": "val"}, "", "", []string{"key"}},
{map[string]string{"key": "val"}, "k", []string{"key"}}, {map[string]string{"key": "val"}, "k", "", []string{"key"}},
{map[string]string{"key": "val"}, "l", nil}, {map[string]string{"key": "val"}, "l", "", nil},
// Multi-item databases should be fully iterable // Multi-item databases should be fully iterable
{ {
map[string]string{"k1": "v1", "k5": "v5", "k2": "v2", "k4": "v4", "k3": "v3"}, map[string]string{"k1": "v1", "k5": "v5", "k2": "v2", "k4": "v4", "k3": "v3"},
"", "", "",
[]string{"k1", "k2", "k3", "k4", "k5"}, []string{"k1", "k2", "k3", "k4", "k5"},
}, },
{ {
map[string]string{"k1": "v1", "k5": "v5", "k2": "v2", "k4": "v4", "k3": "v3"}, map[string]string{"k1": "v1", "k5": "v5", "k2": "v2", "k4": "v4", "k3": "v3"},
"k", "k", "",
[]string{"k1", "k2", "k3", "k4", "k5"}, []string{"k1", "k2", "k3", "k4", "k5"},
}, },
{ {
map[string]string{"k1": "v1", "k5": "v5", "k2": "v2", "k4": "v4", "k3": "v3"}, map[string]string{"k1": "v1", "k5": "v5", "k2": "v2", "k4": "v4", "k3": "v3"},
"l", "l", "",
nil, nil,
}, },
// Multi-item databases should be prefix-iterable // Multi-item databases should be prefix-iterable
@ -65,7 +66,7 @@ func TestDatabaseSuite(t *testing.T, New func() ethdb.KeyValueStore) {
"ka1": "va1", "ka5": "va5", "ka2": "va2", "ka4": "va4", "ka3": "va3", "ka1": "va1", "ka5": "va5", "ka2": "va2", "ka4": "va4", "ka3": "va3",
"kb1": "vb1", "kb5": "vb5", "kb2": "vb2", "kb4": "vb4", "kb3": "vb3", "kb1": "vb1", "kb5": "vb5", "kb2": "vb2", "kb4": "vb4", "kb3": "vb3",
}, },
"ka", "ka", "",
[]string{"ka1", "ka2", "ka3", "ka4", "ka5"}, []string{"ka1", "ka2", "ka3", "ka4", "ka5"},
}, },
{ {
@ -73,7 +74,24 @@ func TestDatabaseSuite(t *testing.T, New func() ethdb.KeyValueStore) {
"ka1": "va1", "ka5": "va5", "ka2": "va2", "ka4": "va4", "ka3": "va3", "ka1": "va1", "ka5": "va5", "ka2": "va2", "ka4": "va4", "ka3": "va3",
"kb1": "vb1", "kb5": "vb5", "kb2": "vb2", "kb4": "vb4", "kb3": "vb3", "kb1": "vb1", "kb5": "vb5", "kb2": "vb2", "kb4": "vb4", "kb3": "vb3",
}, },
"kc", "kc", "",
nil,
},
// Multi-item databases should be prefix-iterable with start position
{
map[string]string{
"ka1": "va1", "ka5": "va5", "ka2": "va2", "ka4": "va4", "ka3": "va3",
"kb1": "vb1", "kb5": "vb5", "kb2": "vb2", "kb4": "vb4", "kb3": "vb3",
},
"ka", "3",
[]string{"ka3", "ka4", "ka5"},
},
{
map[string]string{
"ka1": "va1", "ka5": "va5", "ka2": "va2", "ka4": "va4", "ka3": "va3",
"kb1": "vb1", "kb5": "vb5", "kb2": "vb2", "kb4": "vb4", "kb3": "vb3",
},
"ka", "8",
nil, nil,
}, },
} }
@ -86,7 +104,7 @@ func TestDatabaseSuite(t *testing.T, New func() ethdb.KeyValueStore) {
} }
} }
// Iterate over the database with the given configs and verify the results // Iterate over the database with the given configs and verify the results
it, idx := db.NewIteratorWithPrefix([]byte(tt.prefix)), 0 it, idx := db.NewIterator([]byte(tt.prefix), []byte(tt.start)), 0
for it.Next() { for it.Next() {
if len(tt.order) <= idx { if len(tt.order) <= idx {
t.Errorf("test %d: prefix=%q more items than expected: checking idx=%d (key %q), expecting len=%d", i, tt.prefix, idx, it.Key(), len(tt.order)) t.Errorf("test %d: prefix=%q more items than expected: checking idx=%d (key %q), expecting len=%d", i, tt.prefix, idx, it.Key(), len(tt.order))
@ -124,62 +142,57 @@ func TestDatabaseSuite(t *testing.T, New func() ethdb.KeyValueStore) {
} }
{ {
it := db.NewIterator() it := db.NewIterator(nil, nil)
got, want := iterateKeys(it), keys got, want := iterateKeys(it), keys
if err := it.Error(); err != nil { if err := it.Error(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
it.Release()
if !reflect.DeepEqual(got, want) { if !reflect.DeepEqual(got, want) {
t.Errorf("Iterator: got: %s; want: %s", got, want) t.Errorf("Iterator: got: %s; want: %s", got, want)
} }
} }
{ {
it := db.NewIteratorWithPrefix([]byte("1")) it := db.NewIterator([]byte("1"), nil)
got, want := iterateKeys(it), []string{"1", "10", "11", "12"} got, want := iterateKeys(it), []string{"1", "10", "11", "12"}
if err := it.Error(); err != nil { if err := it.Error(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
it.Release()
if !reflect.DeepEqual(got, want) { if !reflect.DeepEqual(got, want) {
t.Errorf("IteratorWithPrefix(1): got: %s; want: %s", got, want) t.Errorf("IteratorWith(1,nil): got: %s; want: %s", got, want)
} }
} }
{ {
it := db.NewIteratorWithPrefix([]byte("5")) it := db.NewIterator([]byte("5"), nil)
got, want := iterateKeys(it), []string{} got, want := iterateKeys(it), []string{}
if err := it.Error(); err != nil { if err := it.Error(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
it.Release()
if !reflect.DeepEqual(got, want) { if !reflect.DeepEqual(got, want) {
t.Errorf("IteratorWithPrefix(1): got: %s; want: %s", got, want) t.Errorf("IteratorWith(5,nil): got: %s; want: %s", got, want)
} }
} }
{ {
it := db.NewIteratorWithStart([]byte("2")) it := db.NewIterator(nil, []byte("2"))
got, want := iterateKeys(it), []string{"2", "20", "21", "22", "3", "4", "6"} got, want := iterateKeys(it), []string{"2", "20", "21", "22", "3", "4", "6"}
if err := it.Error(); err != nil { if err := it.Error(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
it.Release()
if !reflect.DeepEqual(got, want) { if !reflect.DeepEqual(got, want) {
t.Errorf("IteratorWithStart(2): got: %s; want: %s", got, want) t.Errorf("IteratorWith(nil,2): got: %s; want: %s", got, want)
} }
} }
{ {
it := db.NewIteratorWithStart([]byte("5")) it := db.NewIterator(nil, []byte("5"))
got, want := iterateKeys(it), []string{"6"} got, want := iterateKeys(it), []string{"6"}
if err := it.Error(); err != nil { if err := it.Error(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
it.Release()
if !reflect.DeepEqual(got, want) { if !reflect.DeepEqual(got, want) {
t.Errorf("IteratorWithStart(2): got: %s; want: %s", got, want) t.Errorf("IteratorWith(nil,5): got: %s; want: %s", got, want)
} }
} }
}) })
@ -246,11 +259,10 @@ func TestDatabaseSuite(t *testing.T, New func() ethdb.KeyValueStore) {
} }
{ {
it := db.NewIterator() it := db.NewIterator(nil, nil)
if got, want := iterateKeys(it), []string{"1", "2", "3", "4"}; !reflect.DeepEqual(got, want) { if got, want := iterateKeys(it), []string{"1", "2", "3", "4"}; !reflect.DeepEqual(got, want) {
t.Errorf("got: %s; want: %s", got, want) t.Errorf("got: %s; want: %s", got, want)
} }
it.Release()
} }
b.Reset() b.Reset()
@ -267,11 +279,10 @@ func TestDatabaseSuite(t *testing.T, New func() ethdb.KeyValueStore) {
} }
{ {
it := db.NewIterator() it := db.NewIterator(nil, nil)
if got, want := iterateKeys(it), []string{"2", "3", "4", "5", "6"}; !reflect.DeepEqual(got, want) { if got, want := iterateKeys(it), []string{"2", "3", "4", "5", "6"}; !reflect.DeepEqual(got, want) {
t.Errorf("got: %s; want: %s", got, want) t.Errorf("got: %s; want: %s", got, want)
} }
it.Release()
} }
}) })
@ -296,11 +307,10 @@ func TestDatabaseSuite(t *testing.T, New func() ethdb.KeyValueStore) {
t.Fatal(err) t.Fatal(err)
} }
it := db.NewIterator() it := db.NewIterator(nil, nil)
if got := iterateKeys(it); !reflect.DeepEqual(got, want) { if got := iterateKeys(it); !reflect.DeepEqual(got, want) {
t.Errorf("got: %s; want: %s", got, want) t.Errorf("got: %s; want: %s", got, want)
} }
it.Release()
}) })
} }
@ -311,5 +321,6 @@ func iterateKeys(it ethdb.Iterator) []string {
keys = append(keys, string(it.Key())) keys = append(keys, string(it.Key()))
} }
sort.Strings(keys) sort.Strings(keys)
it.Release()
return keys return keys
} }

@ -51,16 +51,11 @@ type Iterator interface {
// Iteratee wraps the NewIterator methods of a backing data store. // Iteratee wraps the NewIterator methods of a backing data store.
type Iteratee interface { type Iteratee interface {
// NewIterator creates a binary-alphabetical iterator over the entire keyspace // NewIterator creates a binary-alphabetical iterator over a subset
// contained within the key-value database. // of database content with a particular key prefix, starting at a particular
NewIterator() Iterator // initial key (or after, if it does not exist).
//
// NewIteratorWithStart creates a binary-alphabetical iterator over a subset of // Note: This method assumes that the prefix is NOT part of the start, so there's
// database content starting at a particular initial key (or after, if it does // no need for the caller to prepend the prefix to the start
// not exist). NewIterator(prefix []byte, start []byte) Iterator
NewIteratorWithStart(start []byte) Iterator
// NewIteratorWithPrefix creates a binary-alphabetical iterator over a subset
// of database content with a particular key prefix.
NewIteratorWithPrefix(prefix []byte) Iterator
} }

@ -183,23 +183,11 @@ func (db *Database) NewBatch() ethdb.Batch {
} }
} }
// NewIterator creates a binary-alphabetical iterator over the entire keyspace // NewIterator creates a binary-alphabetical iterator over a subset
// contained within the leveldb database. // of database content with a particular key prefix, starting at a particular
func (db *Database) NewIterator() ethdb.Iterator { // initial key (or after, if it does not exist).
return db.db.NewIterator(new(util.Range), nil) func (db *Database) NewIterator(prefix []byte, start []byte) ethdb.Iterator {
} return db.db.NewIterator(bytesPrefixRange(prefix, start), nil)
// NewIteratorWithStart creates a binary-alphabetical iterator over a subset of
// database content starting at a particular initial key (or after, if it does
// not exist).
func (db *Database) NewIteratorWithStart(start []byte) ethdb.Iterator {
return db.db.NewIterator(&util.Range{Start: start}, nil)
}
// NewIteratorWithPrefix creates a binary-alphabetical iterator over a subset
// of database content with a particular key prefix.
func (db *Database) NewIteratorWithPrefix(prefix []byte) ethdb.Iterator {
return db.db.NewIterator(util.BytesPrefix(prefix), nil)
} }
// Stat returns a particular internal stat of the database. // Stat returns a particular internal stat of the database.
@ -488,3 +476,12 @@ func (r *replayer) Delete(key []byte) {
} }
r.failure = r.writer.Delete(key) r.failure = r.writer.Delete(key)
} }
// bytesPrefixRange returns key range that satisfy
// - the given prefix, and
// - the given seek position
func bytesPrefixRange(prefix, start []byte) *util.Range {
r := util.BytesPrefix(prefix)
r.Start = append(r.Start, start...)
return r
}

@ -129,55 +129,26 @@ func (db *Database) NewBatch() ethdb.Batch {
} }
} }
// NewIterator creates a binary-alphabetical iterator over the entire keyspace // NewIterator creates a binary-alphabetical iterator over a subset
// contained within the memory database. // of database content with a particular key prefix, starting at a particular
func (db *Database) NewIterator() ethdb.Iterator { // initial key (or after, if it does not exist).
return db.NewIteratorWithStart(nil) func (db *Database) NewIterator(prefix []byte, start []byte) ethdb.Iterator {
}
// NewIteratorWithStart creates a binary-alphabetical iterator over a subset of
// database content starting at a particular initial key (or after, if it does
// not exist).
func (db *Database) NewIteratorWithStart(start []byte) ethdb.Iterator {
db.lock.RLock()
defer db.lock.RUnlock()
var (
st = string(start)
keys = make([]string, 0, len(db.db))
values = make([][]byte, 0, len(db.db))
)
// Collect the keys from the memory database corresponding to the given start
for key := range db.db {
if key >= st {
keys = append(keys, key)
}
}
// Sort the items and retrieve the associated values
sort.Strings(keys)
for _, key := range keys {
values = append(values, db.db[key])
}
return &iterator{
keys: keys,
values: values,
}
}
// NewIteratorWithPrefix creates a binary-alphabetical iterator over a subset
// of database content with a particular key prefix.
func (db *Database) NewIteratorWithPrefix(prefix []byte) ethdb.Iterator {
db.lock.RLock() db.lock.RLock()
defer db.lock.RUnlock() defer db.lock.RUnlock()
var ( var (
pr = string(prefix) pr = string(prefix)
st = string(append(prefix, start...))
keys = make([]string, 0, len(db.db)) keys = make([]string, 0, len(db.db))
values = make([][]byte, 0, len(db.db)) values = make([][]byte, 0, len(db.db))
) )
// Collect the keys from the memory database corresponding to the given prefix // Collect the keys from the memory database corresponding to the given prefix
// and start
for key := range db.db { for key := range db.db {
if strings.HasPrefix(key, pr) { if !strings.HasPrefix(key, pr) {
continue
}
if key >= st {
keys = append(keys, key) keys = append(keys, key)
} }
} }

@ -691,6 +691,14 @@ func (db *nodeDB) close() {
close(db.closeCh) close(db.closeCh)
} }
func (db *nodeDB) getPrefix(neg bool) []byte {
prefix := positiveBalancePrefix
if neg {
prefix = negativeBalancePrefix
}
return append(db.verbuf[:], prefix...)
}
func (db *nodeDB) key(id []byte, neg bool) []byte { func (db *nodeDB) key(id []byte, neg bool) []byte {
prefix := positiveBalancePrefix prefix := positiveBalancePrefix
if neg { if neg {
@ -761,7 +769,8 @@ func (db *nodeDB) getPosBalanceIDs(start, stop enode.ID, maxCount int) (result [
if maxCount <= 0 { if maxCount <= 0 {
return return
} }
it := db.db.NewIteratorWithStart(db.key(start.Bytes(), false)) prefix := db.getPrefix(false)
it := db.db.NewIterator(prefix, start.Bytes())
defer it.Release() defer it.Release()
for i := len(stop[:]) - 1; i >= 0; i-- { for i := len(stop[:]) - 1; i >= 0; i-- {
stop[i]-- stop[i]--
@ -840,8 +849,9 @@ func (db *nodeDB) expireNodes() {
visited int visited int
deleted int deleted int
start = time.Now() start = time.Now()
prefix = db.getPrefix(true)
) )
iter := db.db.NewIteratorWithPrefix(append(db.verbuf[:], negativeBalancePrefix...)) iter := db.db.NewIterator(prefix, nil)
for iter.Next() { for iter.Next() {
visited += 1 visited += 1
var balance negBalance var balance negBalance

@ -120,7 +120,7 @@ func TestNodeIteratorCoverage(t *testing.T) {
} }
} }
} }
it := db.diskdb.NewIterator() it := db.diskdb.NewIterator(nil, nil)
for it.Next() { for it.Next() {
key := it.Key() key := it.Key()
if _, ok := hashes[common.BytesToHash(key)]; !ok { if _, ok := hashes[common.BytesToHash(key)]; !ok {
@ -312,7 +312,7 @@ func testIteratorContinueAfterError(t *testing.T, memonly bool) {
if memonly { if memonly {
memKeys = triedb.Nodes() memKeys = triedb.Nodes()
} else { } else {
it := diskdb.NewIterator() it := diskdb.NewIterator(nil, nil)
for it.Next() { for it.Next() {
diskKeys = append(diskKeys, it.Key()) diskKeys = append(diskKeys, it.Key())
} }

@ -106,7 +106,7 @@ func TestBadProof(t *testing.T) {
if proof == nil { if proof == nil {
t.Fatalf("prover %d: nil proof", i) t.Fatalf("prover %d: nil proof", i)
} }
it := proof.NewIterator() it := proof.NewIterator(nil, nil)
for i, d := 0, mrand.Intn(proof.Len()); i <= d; i++ { for i, d := 0, mrand.Intn(proof.Len()); i <= d; i++ {
it.Next() it.Next()
} }

@ -99,7 +99,7 @@ func (b *SyncBloom) init(database ethdb.Iteratee) {
// Note, this is fine, because everything inserted into leveldb by fast sync is // Note, this is fine, because everything inserted into leveldb by fast sync is
// also pushed into the bloom directly, so we're not missing anything when the // also pushed into the bloom directly, so we're not missing anything when the
// iterator is swapped out for a new one. // iterator is swapped out for a new one.
it := database.NewIterator() it := database.NewIterator(nil, nil)
var ( var (
start = time.Now() start = time.Now()
@ -116,7 +116,7 @@ func (b *SyncBloom) init(database ethdb.Iteratee) {
key := common.CopyBytes(it.Key()) key := common.CopyBytes(it.Key())
it.Release() it.Release()
it = database.NewIteratorWithStart(key) it = database.NewIterator(nil, key)
log.Info("Initializing fast sync bloom", "items", b.bloom.N(), "errorrate", b.errorRate(), "elapsed", common.PrettyDuration(time.Since(start))) log.Info("Initializing fast sync bloom", "items", b.bloom.N(), "errorrate", b.errorRate(), "elapsed", common.PrettyDuration(time.Since(start)))
swap = time.Now() swap = time.Now()