|
|
|
@ -70,31 +70,53 @@ func makeTestTrie(scheme string) (ethdb.Database, *Database, *StateTrie, map[str |
|
|
|
|
|
|
|
|
|
// checkTrieContents cross references a reconstructed trie with an expected data
|
|
|
|
|
// content map.
|
|
|
|
|
func checkTrieContents(t *testing.T, db ethdb.Database, scheme string, root []byte, content map[string][]byte) { |
|
|
|
|
func checkTrieContents(t *testing.T, db ethdb.Database, scheme string, root []byte, content map[string][]byte, rawTrie bool) { |
|
|
|
|
// Check root availability and trie contents
|
|
|
|
|
ndb := newTestDatabase(db, scheme) |
|
|
|
|
trie, err := NewStateTrie(TrieID(common.BytesToHash(root)), ndb) |
|
|
|
|
if err != nil { |
|
|
|
|
t.Fatalf("failed to create trie at %x: %v", root, err) |
|
|
|
|
} |
|
|
|
|
if err := checkTrieConsistency(db, scheme, common.BytesToHash(root)); err != nil { |
|
|
|
|
if err := checkTrieConsistency(db, scheme, common.BytesToHash(root), rawTrie); err != nil { |
|
|
|
|
t.Fatalf("inconsistent trie at %x: %v", root, err) |
|
|
|
|
} |
|
|
|
|
type reader interface { |
|
|
|
|
MustGet(key []byte) []byte |
|
|
|
|
} |
|
|
|
|
var r reader |
|
|
|
|
if rawTrie { |
|
|
|
|
trie, err := New(TrieID(common.BytesToHash(root)), ndb) |
|
|
|
|
if err != nil { |
|
|
|
|
t.Fatalf("failed to create trie at %x: %v", root, err) |
|
|
|
|
} |
|
|
|
|
r = trie |
|
|
|
|
} else { |
|
|
|
|
trie, err := NewStateTrie(TrieID(common.BytesToHash(root)), ndb) |
|
|
|
|
if err != nil { |
|
|
|
|
t.Fatalf("failed to create trie at %x: %v", root, err) |
|
|
|
|
} |
|
|
|
|
r = trie |
|
|
|
|
} |
|
|
|
|
for key, val := range content { |
|
|
|
|
if have := trie.MustGet([]byte(key)); !bytes.Equal(have, val) { |
|
|
|
|
if have := r.MustGet([]byte(key)); !bytes.Equal(have, val) { |
|
|
|
|
t.Errorf("entry %x: content mismatch: have %x, want %x", key, have, val) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// checkTrieConsistency checks that all nodes in a trie are indeed present.
|
|
|
|
|
func checkTrieConsistency(db ethdb.Database, scheme string, root common.Hash) error { |
|
|
|
|
func checkTrieConsistency(db ethdb.Database, scheme string, root common.Hash, rawTrie bool) error { |
|
|
|
|
ndb := newTestDatabase(db, scheme) |
|
|
|
|
trie, err := NewStateTrie(TrieID(root), ndb) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil // Consider a non existent state consistent
|
|
|
|
|
var it NodeIterator |
|
|
|
|
if rawTrie { |
|
|
|
|
trie, err := New(TrieID(root), ndb) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil // Consider a non existent state consistent
|
|
|
|
|
} |
|
|
|
|
it = trie.MustNodeIterator(nil) |
|
|
|
|
} else { |
|
|
|
|
trie, err := NewStateTrie(TrieID(root), ndb) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil // Consider a non existent state consistent
|
|
|
|
|
} |
|
|
|
|
it = trie.MustNodeIterator(nil) |
|
|
|
|
} |
|
|
|
|
it := trie.MustNodeIterator(nil) |
|
|
|
|
for it.Next(true) { |
|
|
|
|
} |
|
|
|
|
return it.Error() |
|
|
|
@ -205,7 +227,7 @@ func testIterativeSync(t *testing.T, count int, bypath bool, scheme string) { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
// Cross check that the two tries are in sync
|
|
|
|
|
checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), srcData) |
|
|
|
|
checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), srcData, false) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Tests that the trie scheduler can correctly reconstruct the state even if only
|
|
|
|
@ -271,7 +293,7 @@ func testIterativeDelayedSync(t *testing.T, scheme string) { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
// Cross check that the two tries are in sync
|
|
|
|
|
checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), srcData) |
|
|
|
|
checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), srcData, false) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Tests that given a root hash, a trie can sync iteratively on a single thread,
|
|
|
|
@ -341,7 +363,7 @@ func testIterativeRandomSync(t *testing.T, count int, scheme string) { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
// Cross check that the two tries are in sync
|
|
|
|
|
checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), srcData) |
|
|
|
|
checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), srcData, false) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Tests that the trie scheduler can correctly reconstruct the state even if only
|
|
|
|
@ -413,7 +435,7 @@ func testIterativeRandomDelayedSync(t *testing.T, scheme string) { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
// Cross check that the two tries are in sync
|
|
|
|
|
checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), srcData) |
|
|
|
|
checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), srcData, false) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Tests that a trie sync will not request nodes multiple times, even if they
|
|
|
|
@ -484,7 +506,7 @@ func testDuplicateAvoidanceSync(t *testing.T, scheme string) { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
// Cross check that the two tries are in sync
|
|
|
|
|
checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), srcData) |
|
|
|
|
checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), srcData, false) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Tests that at any point in time during a sync, only complete sub-tries are in
|
|
|
|
@ -569,7 +591,7 @@ func testIncompleteSync(t *testing.T, scheme string) { |
|
|
|
|
nodeHash := addedHashes[i] |
|
|
|
|
value := rawdb.ReadTrieNode(diskdb, owner, inner, nodeHash, scheme) |
|
|
|
|
rawdb.DeleteTrieNode(diskdb, owner, inner, nodeHash, scheme) |
|
|
|
|
if err := checkTrieConsistency(diskdb, srcDb.Scheme(), root); err == nil { |
|
|
|
|
if err := checkTrieConsistency(diskdb, srcDb.Scheme(), root, false); err == nil { |
|
|
|
|
t.Fatalf("trie inconsistency not caught, missing: %x", path) |
|
|
|
|
} |
|
|
|
|
rawdb.WriteTrieNode(diskdb, owner, inner, nodeHash, value, scheme) |
|
|
|
@ -643,7 +665,7 @@ func testSyncOrdering(t *testing.T, scheme string) { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
// Cross check that the two tries are in sync
|
|
|
|
|
checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), srcData) |
|
|
|
|
checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), srcData, false) |
|
|
|
|
|
|
|
|
|
// Check that the trie nodes have been requested path-ordered
|
|
|
|
|
for i := 0; i < len(reqs)-1; i++ { |
|
|
|
@ -664,7 +686,7 @@ func syncWith(t *testing.T, root common.Hash, db ethdb.Database, srcDb *Database |
|
|
|
|
|
|
|
|
|
// The code requests are ignored here since there is no code
|
|
|
|
|
// at the testing trie.
|
|
|
|
|
paths, nodes, _ := sched.Missing(1) |
|
|
|
|
paths, nodes, _ := sched.Missing(0) |
|
|
|
|
var elements []trieElement |
|
|
|
|
for i := 0; i < len(paths); i++ { |
|
|
|
|
elements = append(elements, trieElement{ |
|
|
|
@ -698,7 +720,7 @@ func syncWith(t *testing.T, root common.Hash, db ethdb.Database, srcDb *Database |
|
|
|
|
} |
|
|
|
|
batch.Write() |
|
|
|
|
|
|
|
|
|
paths, nodes, _ = sched.Missing(1) |
|
|
|
|
paths, nodes, _ = sched.Missing(0) |
|
|
|
|
elements = elements[:0] |
|
|
|
|
for i := 0; i < len(paths); i++ { |
|
|
|
|
elements = append(elements, trieElement{ |
|
|
|
@ -724,7 +746,7 @@ func testSyncMovingTarget(t *testing.T, scheme string) { |
|
|
|
|
// Create a destination trie and sync with the scheduler
|
|
|
|
|
diskdb := rawdb.NewMemoryDatabase() |
|
|
|
|
syncWith(t, srcTrie.Hash(), diskdb, srcDb) |
|
|
|
|
checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), srcData) |
|
|
|
|
checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), srcData, false) |
|
|
|
|
|
|
|
|
|
// Push more modifications into the src trie, to see if dest trie can still
|
|
|
|
|
// sync with it(overwrite stale states)
|
|
|
|
@ -748,7 +770,7 @@ func testSyncMovingTarget(t *testing.T, scheme string) { |
|
|
|
|
srcTrie, _ = NewStateTrie(TrieID(root), srcDb) |
|
|
|
|
|
|
|
|
|
syncWith(t, srcTrie.Hash(), diskdb, srcDb) |
|
|
|
|
checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), diff) |
|
|
|
|
checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), diff, false) |
|
|
|
|
|
|
|
|
|
// Revert added modifications from the src trie, to see if dest trie can still
|
|
|
|
|
// sync with it(overwrite reverted states)
|
|
|
|
@ -772,5 +794,98 @@ func testSyncMovingTarget(t *testing.T, scheme string) { |
|
|
|
|
srcTrie, _ = NewStateTrie(TrieID(root), srcDb) |
|
|
|
|
|
|
|
|
|
syncWith(t, srcTrie.Hash(), diskdb, srcDb) |
|
|
|
|
checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), reverted) |
|
|
|
|
checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), reverted, false) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Tests if state syncer can correctly catch up the pivot move.
|
|
|
|
|
func TestPivotMove(t *testing.T) { |
|
|
|
|
testPivotMove(t, rawdb.HashScheme, true) |
|
|
|
|
testPivotMove(t, rawdb.HashScheme, false) |
|
|
|
|
testPivotMove(t, rawdb.PathScheme, true) |
|
|
|
|
testPivotMove(t, rawdb.PathScheme, false) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func testPivotMove(t *testing.T, scheme string, tiny bool) { |
|
|
|
|
var ( |
|
|
|
|
srcDisk = rawdb.NewMemoryDatabase() |
|
|
|
|
srcTrieDB = newTestDatabase(srcDisk, scheme) |
|
|
|
|
srcTrie, _ = New(TrieID(types.EmptyRootHash), srcTrieDB) |
|
|
|
|
|
|
|
|
|
deleteFn = func(key []byte, tr *Trie, states map[string][]byte) { |
|
|
|
|
tr.Delete(key) |
|
|
|
|
delete(states, string(key)) |
|
|
|
|
} |
|
|
|
|
writeFn = func(key []byte, val []byte, tr *Trie, states map[string][]byte) { |
|
|
|
|
if val == nil { |
|
|
|
|
if tiny { |
|
|
|
|
val = randBytes(4) |
|
|
|
|
} else { |
|
|
|
|
val = randBytes(32) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
tr.Update(key, val) |
|
|
|
|
states[string(key)] = common.CopyBytes(val) |
|
|
|
|
} |
|
|
|
|
copyStates = func(states map[string][]byte) map[string][]byte { |
|
|
|
|
cpy := make(map[string][]byte) |
|
|
|
|
for k, v := range states { |
|
|
|
|
cpy[k] = v |
|
|
|
|
} |
|
|
|
|
return cpy |
|
|
|
|
} |
|
|
|
|
) |
|
|
|
|
stateA := make(map[string][]byte) |
|
|
|
|
writeFn([]byte{0x01, 0x23}, nil, srcTrie, stateA) |
|
|
|
|
writeFn([]byte{0x01, 0x24}, nil, srcTrie, stateA) |
|
|
|
|
writeFn([]byte{0x12, 0x33}, nil, srcTrie, stateA) |
|
|
|
|
writeFn([]byte{0x12, 0x34}, nil, srcTrie, stateA) |
|
|
|
|
writeFn([]byte{0x02, 0x34}, nil, srcTrie, stateA) |
|
|
|
|
writeFn([]byte{0x13, 0x44}, nil, srcTrie, stateA) |
|
|
|
|
|
|
|
|
|
rootA, nodesA, _ := srcTrie.Commit(false) |
|
|
|
|
if err := srcTrieDB.Update(rootA, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodesA), nil); err != nil { |
|
|
|
|
panic(err) |
|
|
|
|
} |
|
|
|
|
if err := srcTrieDB.Commit(rootA, false); err != nil { |
|
|
|
|
panic(err) |
|
|
|
|
} |
|
|
|
|
// Create a destination trie and sync with the scheduler
|
|
|
|
|
destDisk := rawdb.NewMemoryDatabase() |
|
|
|
|
syncWith(t, rootA, destDisk, srcTrieDB) |
|
|
|
|
checkTrieContents(t, destDisk, scheme, srcTrie.Hash().Bytes(), stateA, true) |
|
|
|
|
|
|
|
|
|
// Delete element to collapse trie
|
|
|
|
|
stateB := copyStates(stateA) |
|
|
|
|
srcTrie, _ = New(TrieID(rootA), srcTrieDB) |
|
|
|
|
deleteFn([]byte{0x02, 0x34}, srcTrie, stateB) |
|
|
|
|
deleteFn([]byte{0x13, 0x44}, srcTrie, stateB) |
|
|
|
|
writeFn([]byte{0x01, 0x24}, nil, srcTrie, stateB) |
|
|
|
|
|
|
|
|
|
rootB, nodesB, _ := srcTrie.Commit(false) |
|
|
|
|
if err := srcTrieDB.Update(rootB, rootA, 0, trienode.NewWithNodeSet(nodesB), nil); err != nil { |
|
|
|
|
panic(err) |
|
|
|
|
} |
|
|
|
|
if err := srcTrieDB.Commit(rootB, false); err != nil { |
|
|
|
|
panic(err) |
|
|
|
|
} |
|
|
|
|
syncWith(t, rootB, destDisk, srcTrieDB) |
|
|
|
|
checkTrieContents(t, destDisk, scheme, srcTrie.Hash().Bytes(), stateB, true) |
|
|
|
|
|
|
|
|
|
// Add elements to expand trie
|
|
|
|
|
stateC := copyStates(stateB) |
|
|
|
|
srcTrie, _ = New(TrieID(rootB), srcTrieDB) |
|
|
|
|
|
|
|
|
|
writeFn([]byte{0x01, 0x24}, stateA[string([]byte{0x01, 0x24})], srcTrie, stateC) |
|
|
|
|
writeFn([]byte{0x02, 0x34}, nil, srcTrie, stateC) |
|
|
|
|
writeFn([]byte{0x13, 0x44}, nil, srcTrie, stateC) |
|
|
|
|
|
|
|
|
|
rootC, nodesC, _ := srcTrie.Commit(false) |
|
|
|
|
if err := srcTrieDB.Update(rootC, rootB, 0, trienode.NewWithNodeSet(nodesC), nil); err != nil { |
|
|
|
|
panic(err) |
|
|
|
|
} |
|
|
|
|
if err := srcTrieDB.Commit(rootC, false); err != nil { |
|
|
|
|
panic(err) |
|
|
|
|
} |
|
|
|
|
syncWith(t, rootC, destDisk, srcTrieDB) |
|
|
|
|
checkTrieContents(t, destDisk, scheme, srcTrie.Hash().Bytes(), stateC, true) |
|
|
|
|
} |
|
|
|
|