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]) return fmt.Errorf("non existent state [%x..]", root[:4])
} }
// The legacy snapshot tree needs to be wiped and rebuilt from the trie // The legacy snapshot tree (hash scheme only) was persistently disabled
// after a snap/1 sync. // before the sync, re-enables it explicitly.
if !isSnapV2 && bc.snaps != nil { //
bc.snaps.Rebuild(root) // 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. // If all checks out, manually set the head block.

View file

@ -29,6 +29,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
@ -721,3 +722,40 @@ func TestRecoverSnapshotFromWipingCrash(t *testing.T) {
test.teardown() 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" "fmt"
"sync" "sync"
"github.com/VictoriaMetrics/fastcache"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types" "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 { if err != nil {
log.Warn("Failed to load snapshot", "err", err) log.Warn("Failed to load snapshot", "err", err)
if !config.NoBuild { if !config.NoBuild {
snap.Rebuild(root) snap.Rebuild(root, true)
return snap, nil return snap, nil
} }
return nil, err // Bail out the error, don't rebuild automatically. 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 return base, nil
} }
// Rebuild wipes all available snapshot data from the persistent database and // Rebuild discards all caches and diff layers and re-enables the snapshot
// discard all caches and diff layers. Afterwards, it starts a new snapshot // feature. With generate set, it starts a new snapshot generator with the
// generator with the given root hash. // given root hash, wiping and regenerating the persistent flat state in the
func (t *Tree) Rebuild(root common.Hash) { // 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() t.lock.Lock()
defer t.lock.Unlock() defer t.lock.Unlock()
@ -715,6 +718,26 @@ func (t *Tree) Rebuild(root common.Hash) {
panic(fmt.Sprintf("unknown layer type: %T", layer)) 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 // 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. // generator will run a wiper first if there's not one running right now.
log.Info("Rebuilding state snapshot") log.Info("Rebuilding state snapshot")