From e0c5cff4df7c571de6091ac9afb896110d615847 Mon Sep 17 00:00:00 2001 From: CPerezz Date: Sat, 18 Apr 2026 20:23:42 +0200 Subject: [PATCH] core/rawdb, eth/downloader: persist partial-sync completion across restarts d.partialSyncComplete is consulted by beaconBackfiller.resume() to skip redundant downloader cycles after the initial partial-state sync has finished. It was an in-memory atomic.Bool, so every process restart reset it to false, and the next forkchoiceUpdated from the CL would re-enter the sync loop. Persist the flag in leveldb via a new PartialSyncComplete marker: - Add ReadPartialSyncComplete / WritePartialSyncComplete / DeletePartialSyncComplete accessors in core/rawdb/accessors_chain.go backed by a single-byte value under the PartialSyncComplete key. - Write the marker in the downloader right after AdvancePartialHead succeeds (same spot we flip the in-memory flag). - Rehydrate the in-memory flag from leveldb in Downloader.New() so a freshly-started process with a completed partial-state sync keeps the resume short-circuit active from the first beacon forkchoice. Without this, the restart invariant relied on HasState(header.Root) accidentally returning false to reroute the downloader back to SnapSync; with this the resume guard is the primary protection regardless of how header-root convergence evolves. --- core/rawdb/accessors_chain.go | 26 ++++++++++++++++++++++++++ core/rawdb/schema.go | 6 ++++++ eth/downloader/downloader.go | 10 ++++++++++ 3 files changed, 42 insertions(+) diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index 0582e842c3..bf85df85e0 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -200,6 +200,32 @@ func WriteLastPivotNumber(db ethdb.KeyValueWriter, pivot uint64) { } } +// ReadPartialSyncComplete reports whether the partial-state initial sync +// completed successfully on this datadir. Returns false if the flag is +// unset or absent (fresh database, non-partial-state node, or sync in +// progress). +func ReadPartialSyncComplete(db ethdb.KeyValueReader) bool { + data, _ := db.Get(partialSyncCompleteKey) + return len(data) > 0 && data[0] == 1 +} + +// WritePartialSyncComplete marks the partial-state initial sync as finished. +// The downloader uses this on restart to skip redundant sync cycles. +func WritePartialSyncComplete(db ethdb.KeyValueWriter) { + if err := db.Put(partialSyncCompleteKey, []byte{1}); err != nil { + log.Crit("Failed to store partial-sync-complete flag", "err", err) + } +} + +// DeletePartialSyncComplete clears the partial-state sync completion flag. +// Used when the node is reset to genesis or rewound behind the pivot so a +// fresh partial sync can run. +func DeletePartialSyncComplete(db ethdb.KeyValueWriter) { + if err := db.Delete(partialSyncCompleteKey); err != nil { + log.Crit("Failed to delete partial-sync-complete flag", "err", err) + } +} + // ReadTxIndexTail retrieves the number of oldest indexed block // whose transaction indices has been indexed. func ReadTxIndexTail(db ethdb.KeyValueReader) *uint64 { diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index 7731e24d1c..2d11c72647 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -104,6 +104,12 @@ var ( // snapSyncStatusFlagKey flags that status of snap sync. snapSyncStatusFlagKey = []byte("SnapSyncStatus") + // partialSyncCompleteKey flags that the partial-state initial sync + // (snap sync + second state sync to HEAD + AdvancePartialHead) has + // finished successfully on this datadir. Consumed by the downloader + // so beaconBackfiller.resume() keeps short-circuiting across restarts. + partialSyncCompleteKey = []byte("PartialSyncComplete") + // Data item prefixes (use single byte to avoid mixing data types, avoid `i`, used for indexes). headerPrefix = []byte("h") // headerPrefix + num (uint64 big endian) + hash -> header headerTDSuffix = []byte("t") // headerPrefix + num (uint64 big endian) + hash + headerTDSuffix -> td (deprecated) diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index 5444f97f2b..ed3531845e 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -268,6 +268,13 @@ func New(stateDb ethdb.Database, mode ethconfig.SyncMode, mux *event.TypeMux, ch stateSyncStart: make(chan *stateSync), syncStartBlock: chain.CurrentSnapBlock().Number.Uint64(), } + // Rehydrate the partial-state completion flag across restarts. Without + // this, a freshly-started process would re-enter the downloader loop for + // every beacon forkchoice update, defeating beaconBackfiller.resume()'s + // short-circuit. + if partialFilter != nil && rawdb.ReadPartialSyncComplete(stateDb) { + dl.partialSyncComplete.Store(true) + } // Create the post-merge skeleton syncer and start the process dl.skeleton = newSkeleton(stateDb, dl.peers, dropPeer, newBeaconBackfiller(dl, success), chain) @@ -1027,6 +1034,9 @@ func (d *Downloader) processSnapSyncContent() error { return err } d.partialSyncComplete.Store(true) + // Persist the completion flag so a restart does not + // re-run the sync cycle on every beacon forkchoice. + rawdb.WritePartialSyncComplete(d.stateDB) log.Info("Partial state initial sync complete") } }