core: re-enable the legacy snapshot after sync (#35163)
Some checks are pending
/ Linux Build (push) Waiting to run
/ Linux Build (arm) (push) Waiting to run
/ Keeper Build (push) Waiting to run
/ Windows Build (push) Waiting to run
/ Docker Image (push) Waiting to run

This commit is contained in:
rjl493456442 2026-06-16 11:09:32 +08:00 committed by GitHub
parent 6ed112aee0
commit 7d74166d3d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 73 additions and 9 deletions

View file

@ -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.

View file

@ -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()
}
}

View file

@ -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")