mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-05-23 08:19:27 +00:00
triedb/pathdb: add AdoptSyncedState for snap/2 completion path (#34874)
This PR adds `AdoptSyncedState()` alongside `Enable()`. It does the same pathdb bookkeeping (now factored into a shared `resetForReactivation()` helper), but skips the regeneration. The wiring/calling code lands in #34626 --------- Co-authored-by: Gary Rong <garyrong0905@gmail.com>
This commit is contained in:
parent
b92c86deb7
commit
06c30cc7e1
3 changed files with 150 additions and 19 deletions
|
|
@ -327,6 +327,16 @@ func (db *Database) Enable(root common.Hash) error {
|
||||||
return pdb.Enable(root)
|
return pdb.Enable(root)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AdoptSyncedState activates the database after a snap/2 sync and adopts the
|
||||||
|
// flat state populated during sync as-is, skipping regeneration.
|
||||||
|
func (db *Database) AdoptSyncedState(root common.Hash) error {
|
||||||
|
pdb, ok := db.backend.(*pathdb.Database)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("not supported")
|
||||||
|
}
|
||||||
|
return pdb.AdoptSyncedState(root)
|
||||||
|
}
|
||||||
|
|
||||||
// Journal commits an entire diff hierarchy to disk into a single journal entry.
|
// Journal commits an entire diff hierarchy to disk into a single journal entry.
|
||||||
// This is meant to be used during shutdown to persist the snapshot without
|
// This is meant to be used during shutdown to persist the snapshot without
|
||||||
// flattening everything down (bad for reorgs). It's only supported by path-based
|
// flattening everything down (bad for reorgs). It's only supported by path-based
|
||||||
|
|
|
||||||
|
|
@ -365,16 +365,9 @@ func (db *Database) Disable() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable activates database and resets the state tree with the provided persistent
|
// resetForReactivation performs the pathdb-side bookkeeping shared by both
|
||||||
// state root once the state sync is finished.
|
// Enable and AdoptSyncedState.
|
||||||
func (db *Database) Enable(root common.Hash) error {
|
func (db *Database) resetForReactivation(root common.Hash) error {
|
||||||
db.lock.Lock()
|
|
||||||
defer db.lock.Unlock()
|
|
||||||
|
|
||||||
// Short circuit if the database is in read only mode.
|
|
||||||
if db.readOnly {
|
|
||||||
return errDatabaseReadOnly
|
|
||||||
}
|
|
||||||
// Ensure the provided state root matches the stored one.
|
// Ensure the provided state root matches the stored one.
|
||||||
stored, err := db.hasher(rawdb.ReadAccountTrieNode(db.diskdb, nil))
|
stored, err := db.hasher(rawdb.ReadAccountTrieNode(db.diskdb, nil))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -383,27 +376,40 @@ func (db *Database) Enable(root common.Hash) error {
|
||||||
if stored != root {
|
if stored != root {
|
||||||
return fmt.Errorf("state root mismatch: stored %x, synced %x", stored, root)
|
return fmt.Errorf("state root mismatch: stored %x, synced %x", stored, root)
|
||||||
}
|
}
|
||||||
// Drop the stale state journal in persistent database and
|
// Drop the stale state journal marker and reset the persistent state id
|
||||||
// reset the persistent state id back to zero.
|
// back to zero.
|
||||||
batch := db.diskdb.NewBatch()
|
batch := db.diskdb.NewBatch()
|
||||||
rawdb.DeleteSnapshotRoot(batch)
|
rawdb.DeleteSnapshotRoot(batch)
|
||||||
rawdb.WritePersistentStateID(batch, 0)
|
rawdb.WritePersistentStateID(batch, 0)
|
||||||
if err := batch.Write(); err != nil {
|
if err := batch.Write(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Clean up all state histories in freezer. Theoretically
|
// Clean up all state histories in the freezer. Theoretically all root->id
|
||||||
// all root->id mappings should be removed as well. Since
|
// mappings should be removed as well; since those can be huge, leave them
|
||||||
// mappings can be huge and might take a while to clear
|
// on disk and let them be overwritten.
|
||||||
// them, just leave them in disk and wait for overwriting.
|
|
||||||
purgeHistory(db.stateFreezer, db.diskdb, typeStateHistory)
|
purgeHistory(db.stateFreezer, db.diskdb, typeStateHistory)
|
||||||
purgeHistory(db.trienodeFreezer, db.diskdb, typeTrienodeHistory)
|
purgeHistory(db.trienodeFreezer, db.diskdb, typeTrienodeHistory)
|
||||||
|
|
||||||
// Re-enable the database as the final step.
|
// Re-enable the database as the final bookkeeping step.
|
||||||
db.waitSync = false
|
db.waitSync = false
|
||||||
rawdb.WriteSnapSyncStatusFlag(db.diskdb, rawdb.StateSyncFinished)
|
rawdb.WriteSnapSyncStatusFlag(db.diskdb, rawdb.StateSyncFinished)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Re-construct a new disk layer backed by persistent state
|
// Enable activates the database after a snap/1 sync and schedules background
|
||||||
// and schedule the state snapshot generation if it's permitted.
|
// regeneration of the snapshot from the trie.
|
||||||
|
func (db *Database) Enable(root common.Hash) error {
|
||||||
|
db.lock.Lock()
|
||||||
|
defer db.lock.Unlock()
|
||||||
|
|
||||||
|
if db.readOnly {
|
||||||
|
return errDatabaseReadOnly
|
||||||
|
}
|
||||||
|
if err := db.resetForReactivation(root); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Re-construct a new disk layer backed by persistent state and schedule
|
||||||
|
// the state snapshot generation if it's permitted.
|
||||||
db.tree.init(generateSnapshot(db, root, db.isUBT || db.config.SnapshotNoBuild))
|
db.tree.init(generateSnapshot(db, root, db.isUBT || db.config.SnapshotNoBuild))
|
||||||
|
|
||||||
// After snap sync, the state of the database may have changed completely.
|
// After snap sync, the state of the database may have changed completely.
|
||||||
|
|
@ -416,6 +422,43 @@ func (db *Database) Enable(root common.Hash) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AdoptSyncedState reactivates the database after a snap/2 sync. The syncer
|
||||||
|
// already wrote a consistent flat state, so we take it as-is instead of
|
||||||
|
// rebuilding it from the trie. The new disk layer has no generator attached,
|
||||||
|
// and a "done" marker is written so future boots know the snapshot is
|
||||||
|
// already complete.
|
||||||
|
func (db *Database) AdoptSyncedState(root common.Hash) error {
|
||||||
|
db.lock.Lock()
|
||||||
|
defer db.lock.Unlock()
|
||||||
|
|
||||||
|
if db.readOnly {
|
||||||
|
return errDatabaseReadOnly
|
||||||
|
}
|
||||||
|
if err := db.resetForReactivation(root); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tell the snapshot subsystem the flat state is good by writing the new root
|
||||||
|
// and a "done" marker (nil journal) so the next boot doesn't try to rebuild it.
|
||||||
|
batch := db.diskdb.NewBatch()
|
||||||
|
rawdb.WriteSnapshotRoot(batch, root)
|
||||||
|
journalProgress(batch, nil, nil)
|
||||||
|
if err := batch.Write(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// New disk layer, no generator attached. Nothing to rebuild, and reads
|
||||||
|
// can serve the flat state right away without waiting on a generator to
|
||||||
|
// scan past every key.
|
||||||
|
dl := newDiskLayer(root, 0, db, nil, nil, newBuffer(db.config.WriteBufferSize, nil, nil, 0), nil)
|
||||||
|
db.tree.init(dl)
|
||||||
|
|
||||||
|
db.setHistoryIndexer()
|
||||||
|
|
||||||
|
log.Info("Adopted synced state", "root", root)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Recover rollbacks the database to a specified historical point.
|
// Recover rollbacks the database to a specified historical point.
|
||||||
// The state is supported as the rollback destination only if it's
|
// The state is supported as the rollback destination only if it's
|
||||||
// canonical state and the corresponding trie histories are existent.
|
// canonical state and the corresponding trie histories are existent.
|
||||||
|
|
|
||||||
|
|
@ -748,6 +748,84 @@ func TestDisable(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestAdoptSyncedState verifies that AdoptSyncedState rejects a wrong root,
|
||||||
|
// writes the on-disk markers that say the snapshot is already complete,
|
||||||
|
// leaves a single fresh disk layer with no generator attached, and clears
|
||||||
|
// out stale state histories.
|
||||||
|
func TestAdoptSyncedState(t *testing.T) {
|
||||||
|
maxDiffLayers = 4
|
||||||
|
defer func() {
|
||||||
|
maxDiffLayers = 128
|
||||||
|
}()
|
||||||
|
|
||||||
|
tester := newTester(t, &testerConfig{layers: 12})
|
||||||
|
defer tester.release()
|
||||||
|
|
||||||
|
// Push everything down to disk so the trie root is the persistent root.
|
||||||
|
if err := tester.db.Commit(tester.lastHash(), false); err != nil {
|
||||||
|
t.Fatalf("Failed to commit, err: %v", err)
|
||||||
|
}
|
||||||
|
stored := crypto.Keccak256Hash(rawdb.ReadAccountTrieNode(tester.db.diskdb, nil))
|
||||||
|
|
||||||
|
// Mimic the snap-syncing state.
|
||||||
|
if err := tester.db.Disable(); err != nil {
|
||||||
|
t.Fatalf("Failed to disable database: %v", err)
|
||||||
|
}
|
||||||
|
// Mismatched root must be rejected.
|
||||||
|
if err := tester.db.AdoptSyncedState(types.EmptyRootHash); err == nil {
|
||||||
|
t.Fatal("Mismatched root should be rejected")
|
||||||
|
}
|
||||||
|
if err := tester.db.AdoptSyncedState(stored); err != nil {
|
||||||
|
t.Fatalf("AdoptSyncedState failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// On-disk markers reflect a completed snapshot.
|
||||||
|
if got := rawdb.ReadSnapshotRoot(tester.db.diskdb); got != stored {
|
||||||
|
t.Fatalf("SnapshotRoot mismatch: got %x want %x", got, stored)
|
||||||
|
}
|
||||||
|
if blob := rawdb.ReadSnapshotGenerator(tester.db.diskdb); len(blob) == 0 {
|
||||||
|
t.Fatal("Generator journal not written")
|
||||||
|
} else {
|
||||||
|
var entry journalGenerator
|
||||||
|
if err := rlp.DecodeBytes(blob, &entry); err != nil {
|
||||||
|
t.Fatalf("Failed to decode generator journal: %v", err)
|
||||||
|
}
|
||||||
|
if !entry.Done {
|
||||||
|
t.Fatal("Generator journal should be marked Done")
|
||||||
|
}
|
||||||
|
// RLP turns a nil slice into an empty one on decode, so check length.
|
||||||
|
if len(entry.Marker) != 0 {
|
||||||
|
t.Fatalf("Generator marker should be empty, got %x", entry.Marker)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if rawdb.ReadSnapSyncStatusFlag(tester.db.diskdb) != rawdb.StateSyncFinished {
|
||||||
|
t.Fatal("Sync-status flag should be StateSyncFinished")
|
||||||
|
}
|
||||||
|
if tester.db.waitSync {
|
||||||
|
t.Fatal("waitSync should be false after adopt")
|
||||||
|
}
|
||||||
|
|
||||||
|
// State histories are purged.
|
||||||
|
if n, err := tester.db.stateFreezer.Ancients(); err != nil || n != 0 {
|
||||||
|
t.Fatalf("State histories not purged: count=%d err=%v", n, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Layer tree has a single disk layer with no generator attached.
|
||||||
|
if got := tester.db.tree.len(); got != 1 {
|
||||||
|
t.Fatalf("Expected single layer, got %d", got)
|
||||||
|
}
|
||||||
|
dl := tester.db.tree.bottom()
|
||||||
|
if dl.rootHash() != stored {
|
||||||
|
t.Fatalf("Disk layer root mismatch: got %x want %x", dl.rootHash(), stored)
|
||||||
|
}
|
||||||
|
if dl.generator != nil {
|
||||||
|
t.Fatal("Disk layer should have no generator after adopt")
|
||||||
|
}
|
||||||
|
if dl.genMarker() != nil {
|
||||||
|
t.Fatal("genMarker should be nil after adopt")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestCommit(t *testing.T) {
|
func TestCommit(t *testing.T) {
|
||||||
// Redefine the diff layer depth allowance for faster testing.
|
// Redefine the diff layer depth allowance for faster testing.
|
||||||
maxDiffLayers = 4
|
maxDiffLayers = 4
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue