core/state, trie: fix trie flush order for proper pruning

This commit is contained in:
Péter Szilágyi 2022-08-23 21:17:12 +03:00
parent 81bd998353
commit 5758d1fb11
No known key found for this signature in database
GPG Key ID: E9AE538CEDF8293D
2 changed files with 56 additions and 3 deletions

View File

@ -914,3 +914,43 @@ func TestStateDBAccessList(t *testing.T) {
t.Fatalf("expected empty, got %d", got)
}
}
// Tests that account and storage tries are flushed in the correct order and that
// no data loss occurs.
func TestFlushOrderDataLoss(t *testing.T) {
// Create a state trie with many accounts and slots
var (
memdb = rawdb.NewMemoryDatabase()
statedb = NewDatabase(memdb)
state, _ = New(common.Hash{}, statedb, nil)
)
for a := byte(0); a < 10; a++ {
state.CreateAccount(common.Address{a})
for s := byte(0); s < 10; s++ {
state.SetState(common.Address{a}, common.Hash{a, s}, common.Hash{a, s})
}
}
root, err := state.Commit(false)
if err != nil {
t.Fatalf("failed to commit state trie: %v", err)
}
statedb.TrieDB().Reference(root, common.Hash{})
if err := statedb.TrieDB().Cap(1024); err != nil {
t.Fatalf("failed to cap trie dirty cache: %v", err)
}
if err := statedb.TrieDB().Commit(root, false, nil); err != nil {
t.Fatalf("failed to commit state trie: %v", err)
}
// Reopen the state trie from flushed disk and verify it
state, err = New(root, NewDatabase(memdb), nil)
if err != nil {
t.Fatalf("failed to reopen state trie: %v", err)
}
for a := byte(0); a < 10; a++ {
for s := byte(0); s < 10; s++ {
if have := state.GetState(common.Address{a}, common.Hash{a, s}); have != (common.Hash{a, s}) {
t.Errorf("account %d: slot %d: state mismatch: have %x, want %x", a, s, have, common.Hash{a, s})
}
}
}
}

View File

@ -776,9 +776,22 @@ func (db *Database) Update(nodes *MergedNodeSet) error {
// Insert dirty nodes into the database. In the same tree, it must be
// ensured that children are inserted first, then parent so that children
// can be linked with their parent correctly. The order of writing between
// different tries(account trie, storage tries) is not required.
for owner, subset := range nodes.sets {
// can be linked with their parent correctly.
//
// Note, the storage tries must be flushed before the account trie to
// retain the invariant that children go into the dirty cache first.
var order []common.Hash
for owner := range nodes.sets {
if owner == (common.Hash{}) {
continue
}
order = append(order, owner)
}
if _, ok := nodes.sets[common.Hash{}]; ok {
order = append(order, common.Hash{})
}
for _, owner := range order {
subset := nodes.sets[owner]
for _, path := range subset.paths {
n, ok := subset.nodes[path]
if !ok {