Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core/state/snapshot: clean up dangling storages in snapshot generation #24665

Closed
wants to merge 8 commits into from
Prev Previous commit
Next Next commit
core/state/snapshot: fix and add tests
  • Loading branch information
rjl493456442 committed Apr 26, 2022
commit 18c52ce0ac39bdcfde68b213151c630d942fe7c3
31 changes: 20 additions & 11 deletions core/state/snapshot/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,15 @@ func (dl *diskLayer) generateRange(root common.Hash, prefix []byte, kind string,
logger.Debug("Regenerated state range", "root", root, "last", hexutil.Encode(last),
"count", count, "created", created, "updated", updated, "untouched", untouched, "deleted", deleted)

// Clean up the left dangling storages at the end in case the entire
// snapshot is regenerated. Note if the last is nil, it means there
// is no snapshot data at disk at all, the entire snapshot is expected
// to be re-generated already.
if last == nil && r != nil {
if err := r.cleanup(nil); err != nil {
return false, nil, err
}
}
// If there are either more trie items, or there are more snap items
// (in the next segment), then we need to keep working
return !trieMore && !result.diskMore, last, nil
Expand Down Expand Up @@ -620,18 +629,13 @@ func generateStorages(dl *diskLayer, account common.Hash, storageRoot common.Has
if err != nil {
return err // The procedure it aborted, either by external signal or internal error.
}
if origin = increaseKey(last); origin == nil {
break // special case, the last is 0xffffffff...fff
}
// Abort the procedure if the entire contract storage is generated
if exhausted {
// Last step, cleanup the storages after the last generated
// account. All the left storages should be treated as dangling.
if err := newDanglingRange(dl.diskdb, origin, nil, false).cleanup(nil); err != nil {
return err
}
break
}
if origin = increaseKey(last); origin == nil {
break // special case, the last is 0xffffffff...fff
}
}
return nil
}
Expand Down Expand Up @@ -743,13 +747,18 @@ func generateAccounts(dl *diskLayer, accMarker []byte, batch ethdb.Batch, stats
if err != nil {
return err // The procedure it aborted, either by external signal or internal error.
}
if origin = increaseKey(last); origin == nil {
break // special case, the last is 0xffffffff...fff
}
// Abort the procedure if the entire snapshot is generated
if exhausted {
// Last step, cleanup the storages after the last account.
// All the left storages should be treated as dangling.
if err := newDanglingRange(dl.diskdb, origin, nil, false).cleanup(nil); err != nil {
return err
}
break
}
if origin = increaseKey(last); origin == nil {
break // special case, the last is 0xffffffff...fff
}
accountRange = accountCheckRange
}
return nil
Expand Down
125 changes: 125 additions & 0 deletions core/state/snapshot/generate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,10 @@ func TestGenerateExistentState(t *testing.T) {

func checkSnapRoot(t *testing.T, snap *diskLayer, trieRoot common.Hash) {
t.Helper()

accIt := snap.AccountIterator(common.Hash{})
defer accIt.Release()

snapRoot, err := generateTrieRoot(nil, accIt, common.Hash{}, stackTrieGenerate,
func(db ethdb.KeyValueWriter, accountHash, codeHash common.Hash, stat *generateStats) (common.Hash, error) {
storageIt, _ := snap.StorageIterator(accountHash, common.Hash{})
Expand All @@ -168,6 +170,10 @@ func checkSnapRoot(t *testing.T, snap *diskLayer, trieRoot common.Hash) {
if snapRoot != trieRoot {
t.Fatalf("snaproot: %#x != trieroot #%x", snapRoot, trieRoot)
}
scanner := newDanglingRange(snap.diskdb, nil, nil, false)
if len(scanner.result) != 0 {
t.Fatalf("Detected dangling storages %d", len(scanner.result))
}
}

type testHelper struct {
Expand Down Expand Up @@ -831,3 +837,122 @@ func TestGenerateWithIncompleteStorage(t *testing.T) {
snap.genAbort <- stop
<-stop
}

func incKey(key []byte) []byte {
for i := len(key) - 1; i >= 0; i-- {
key[i]++
if key[i] != 0x0 {
break
}
}
return key
}

func decKey(key []byte) []byte {
for i := len(key) - 1; i >= 0; i-- {
key[i]--
if key[i] != 0xff {
break
}
}
return key
}

func populateDangling(disk ethdb.KeyValueStore) {
populate := func(accountHash common.Hash, keys []string, vals []string) {
for i, key := range keys {
rawdb.WriteStorageSnapshot(disk, accountHash, hashData([]byte(key)), []byte(vals[i]))
}
}
// Dangling storages of the "first" account
populate(common.Hash{}, []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})

// Dangling storages of the "last" account
populate(common.HexToHash("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})

// Dangling storages around the account 1
hash := decKey(hashData([]byte("acc-1")).Bytes())
populate(common.BytesToHash(hash), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
hash = incKey(hashData([]byte("acc-1")).Bytes())
populate(common.BytesToHash(hash), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})

// Dangling storages around the account 2
hash = decKey(hashData([]byte("acc-2")).Bytes())
populate(common.BytesToHash(hash), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
hash = incKey(hashData([]byte("acc-2")).Bytes())
populate(common.BytesToHash(hash), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})

// Dangling storages around the account 3
hash = decKey(hashData([]byte("acc-3")).Bytes())
populate(common.BytesToHash(hash), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
hash = incKey(hashData([]byte("acc-3")).Bytes())
populate(common.BytesToHash(hash), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})

// Dangling storages of the random account
populate(randomHash(), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
populate(randomHash(), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
populate(randomHash(), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
}

// Tests that snapshot generation with dangling storages. Dangling storage means
// the storage data is existent while the corresponding account data is missing.
//
// This test will populate some dangling storages to see if they can be cleaned up.
func TestGenerateCompleteSnapshotWithDanglingStorage(t *testing.T) {
var helper = newHelper()
stRoot := helper.makeStorageTrie([]string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})

helper.addAccount("acc-1", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})
helper.addAccount("acc-2", &Account{Balance: big.NewInt(1), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()})
helper.addAccount("acc-3", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})

helper.addSnapStorage("acc-1", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
helper.addSnapStorage("acc-3", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})

populateDangling(helper.diskdb)

root, snap := helper.Generate()
select {
case <-snap.genPending:
// Snapshot generation succeeded

case <-time.After(3 * time.Second):
t.Errorf("Snapshot generation failed")
}
checkSnapRoot(t, snap, root)

// Signal abortion to the generator and wait for it to tear down
stop := make(chan *generatorStats)
snap.genAbort <- stop
<-stop
}

// Tests that snapshot generation with dangling storages. Dangling storage means
// the storage data is existent while the corresponding account data is missing.
//
// This test will populate some dangling storages to see if they can be cleaned up.
func TestGenerateBrokenSnapshotWithDanglingStorage(t *testing.T) {
var helper = newHelper()
stRoot := helper.makeStorageTrie([]string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})

helper.addTrieAccount("acc-1", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})
helper.addTrieAccount("acc-2", &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()})
helper.addTrieAccount("acc-3", &Account{Balance: big.NewInt(3), Root: stRoot, CodeHash: emptyCode.Bytes()})

populateDangling(helper.diskdb)

root, snap := helper.Generate()
select {
case <-snap.genPending:
// Snapshot generation succeeded

case <-time.After(3 * time.Second):
t.Errorf("Snapshot generation failed")
}
checkSnapRoot(t, snap, root)

// Signal abortion to the generator and wait for it to tear down
stop := make(chan *generatorStats)
snap.genAbort <- stop
<-stop
}