diff --git a/core/blockchain.go b/core/blockchain.go index acf2da1921..ecb33ce0b8 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1194,10 +1194,13 @@ func (bc *BlockChain) SnapSyncComplete(hash common.Hash, isSnapV2 bool) error { return fmt.Errorf("non existent state [%x..]", root[:4]) } - // The legacy snapshot tree needs to be wiped and rebuilt from the trie - // after a snap/1 sync. - if !isSnapV2 && bc.snaps != nil { - bc.snaps.Rebuild(root) + // The legacy snapshot tree (hash scheme only) was persistently disabled + // before the sync, re-enables it explicitly. + // + // For snap/2 the downloaded flat state is already complete and root-verified, + // so the background generation is unnecessary. + if bc.snaps != nil { + bc.snaps.Rebuild(root, !isSnapV2) } // If all checks out, manually set the head block. diff --git a/core/blockchain_snapshot_test.go b/core/blockchain_snapshot_test.go index ae9398b97d..cd95f19697 100644 --- a/core/blockchain_snapshot_test.go +++ b/core/blockchain_snapshot_test.go @@ -29,6 +29,7 @@ import ( "testing" "time" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core/rawdb" @@ -721,3 +722,40 @@ func TestRecoverSnapshotFromWipingCrash(t *testing.T) { test.teardown() } } + +// TestSnapSyncCompleteRebuildsSnapshot verifies that completing a snap sync +// re-enables the legacy snapshot tree on the hash scheme for both syncer +// versions: SnapSyncStart persistently disables the tree, and only the +// rebuild on completion clears the marker again. +func TestSnapSyncCompleteRebuildsSnapshot(t *testing.T) { + for _, isSnapV2 := range []bool{false, true} { + _, _, chain, err := newCanonical(ethash.NewFaker(), 8, true, rawdb.HashScheme) + if err != nil { + t.Fatalf("failed to create chain: %v", err) + } + if err := chain.SnapSyncStart(); err != nil { + t.Fatalf("failed to start snap sync: %v", err) + } + if !rawdb.ReadSnapshotDisabled(chain.db) { + t.Fatal("snapshot should be disabled during snap sync") + } + head := chain.CurrentBlock() + if err := chain.SnapSyncComplete(head.Hash(), isSnapV2); err != nil { + t.Fatalf("failed to complete snap sync (v2=%v): %v", isSnapV2, err) + } + if rawdb.ReadSnapshotDisabled(chain.db) { + t.Fatalf("snapshot should be re-enabled after snap sync completion (v2=%v)", isSnapV2) + } + // snap/2 adopts the flat state without regeneration, so the snapshot + // must be immediately usable; snap/1 schedules a background rebuild + // instead (which may or may not have finished, no assertion there). + if isSnapV2 { + it, err := chain.snaps.AccountIterator(head.Root, common.Hash{}) + if err != nil { + t.Fatalf("adopted snapshot not immediately usable: %v", err) + } + it.Release() + } + chain.Stop() + } +} diff --git a/core/state/snapshot/snapshot.go b/core/state/snapshot/snapshot.go index cd0a55fee6..d3a5e9aa21 100644 --- a/core/state/snapshot/snapshot.go +++ b/core/state/snapshot/snapshot.go @@ -23,6 +23,7 @@ import ( "fmt" "sync" + "github.com/VictoriaMetrics/fastcache" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" @@ -213,7 +214,7 @@ func New(config Config, diskdb ethdb.KeyValueStore, triedb *triedb.Database, roo if err != nil { log.Warn("Failed to load snapshot", "err", err) if !config.NoBuild { - snap.Rebuild(root) + snap.Rebuild(root, true) return snap, nil } return nil, err // Bail out the error, don't rebuild automatically. @@ -683,10 +684,12 @@ func (t *Tree) Journal(root common.Hash) (common.Hash, error) { return base, nil } -// Rebuild wipes all available snapshot data from the persistent database and -// discard all caches and diff layers. Afterwards, it starts a new snapshot -// generator with the given root hash. -func (t *Tree) Rebuild(root common.Hash) { +// Rebuild discards all caches and diff layers and re-enables the snapshot +// feature. With generate set, it starts a new snapshot generator with the +// given root hash, wiping and regenerating the persistent flat state in the +// background. Without it, the on-disk flat state is adopted as the fully +// generated disk layer directly. +func (t *Tree) Rebuild(root common.Hash, generate bool) { t.lock.Lock() defer t.lock.Unlock() @@ -715,6 +718,26 @@ func (t *Tree) Rebuild(root common.Hash) { panic(fmt.Sprintf("unknown layer type: %T", layer)) } } + // Adopt the existing flat state as the generated disk layer if + // regeneration was not requested. + if !generate { + batch := t.diskdb.NewBatch() + rawdb.WriteSnapshotRoot(batch, root) + journalProgress(batch, nil, nil) + if err := batch.Write(); err != nil { + log.Crit("Failed to write snapshot completion marker", "err", err) + } + log.Info("Adopted state snapshot", "root", root) + t.layers = map[common.Hash]snapshot{ + root: &diskLayer{ + diskdb: t.diskdb, + triedb: t.triedb, + cache: fastcache.New(t.config.CacheSize * 1024 * 1024), + root: root, + }, + } + return + } // Start generating a new snapshot from scratch on a background thread. The // generator will run a wiper first if there's not one running right now. log.Info("Rebuilding state snapshot")