From 896113e436168abe5868acf74cbfcd80d5a4083d Mon Sep 17 00:00:00 2001 From: jsvisa Date: Thu, 26 Jun 2025 23:50:57 +0800 Subject: [PATCH 1/5] eth: reset skeleton after chain rewinded Signed-off-by: jsvisa --- eth/api_backend.go | 1 + eth/downloader/downloader.go | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/eth/api_backend.go b/eth/api_backend.go index 766a99fc1e..0cf10ca000 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -63,6 +63,7 @@ func (b *EthAPIBackend) CurrentBlock() *types.Header { func (b *EthAPIBackend) SetHead(number uint64) { b.eth.handler.downloader.Cancel() + b.eth.handler.downloader.ResetSkeleton() b.eth.blockchain.SetHead(number) } diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index 020dd7314b..25989b1a50 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -662,6 +662,13 @@ func (d *Downloader) Cancel() { d.blockchain.InterruptInsert(false) } +// ResetSkeleton terminates the skeleton syncer and reinitializes it. +func (d *Downloader) ResetSkeleton() { + d.skeleton.Terminate() + rawdb.DeleteSkeletonSyncStatus(d.stateDB) + d.skeleton = newSkeleton(d.stateDB, d.peers, d.dropPeer, d.skeleton.filler) +} + // Terminate interrupts the downloader, canceling all pending operations. // The downloader cannot be reused after calling Terminate. func (d *Downloader) Terminate() { From b5f9fe087f908f4f9dd6d9f788a38d2325d33619 Mon Sep 17 00:00:00 2001 From: jsvisa Date: Fri, 27 Jun 2025 00:21:38 +0800 Subject: [PATCH 2/5] new back filler Signed-off-by: jsvisa --- eth/downloader/downloader.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index 25989b1a50..d14277b697 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -116,6 +116,7 @@ type Downloader struct { // Callbacks dropPeer peerDropFn // Drops a peer for misbehaving badBlock badBlockFn // Reports a block as rejected by the chain + success func() // Callback to signal successful sync completion // Status synchronising atomic.Bool @@ -237,6 +238,7 @@ func New(stateDb ethdb.Database, mode ethconfig.SyncMode, mux *event.TypeMux, ch chainCutoffNumber: cutoffNumber, chainCutoffHash: cutoffHash, dropPeer: dropPeer, + success: success, headerProcCh: make(chan *headerTask, 1), quitCh: make(chan struct{}), SnapSyncer: snap.NewSyncer(stateDb, chain.TrieDB().Scheme()), @@ -666,7 +668,7 @@ func (d *Downloader) Cancel() { func (d *Downloader) ResetSkeleton() { d.skeleton.Terminate() rawdb.DeleteSkeletonSyncStatus(d.stateDB) - d.skeleton = newSkeleton(d.stateDB, d.peers, d.dropPeer, d.skeleton.filler) + d.skeleton = newSkeleton(d.stateDB, d.peers, d.dropPeer, newBeaconBackfiller(d, d.success)) } // Terminate interrupts the downloader, canceling all pending operations. From b92803ebffeca52d77ebe684e8a11dd9f27f80be Mon Sep 17 00:00:00 2001 From: jsvisa Date: Sat, 16 Aug 2025 15:59:12 +0800 Subject: [PATCH 3/5] add debug log Signed-off-by: jsvisa --- eth/downloader/downloader.go | 1 + 1 file changed, 1 insertion(+) diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index d14277b697..ac0a0e27bf 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -666,6 +666,7 @@ func (d *Downloader) Cancel() { // ResetSkeleton terminates the skeleton syncer and reinitializes it. func (d *Downloader) ResetSkeleton() { + log.Debug("Resetting skeleton syncer due to chain rewind") d.skeleton.Terminate() rawdb.DeleteSkeletonSyncStatus(d.stateDB) d.skeleton = newSkeleton(d.stateDB, d.peers, d.dropPeer, newBeaconBackfiller(d, d.success)) From 0a37daf8d3f816f933b702d60ec301518c588339 Mon Sep 17 00:00:00 2001 From: jsvisa Date: Sat, 16 Aug 2025 16:00:08 +0800 Subject: [PATCH 4/5] add unit test Signed-off-by: jsvisa --- eth/downloader/downloader_test.go | 42 +++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go index 7fa2522a3d..b587d43216 100644 --- a/eth/downloader/downloader_test.go +++ b/eth/downloader/downloader_test.go @@ -734,3 +734,45 @@ func testSyncProgress(t *testing.T, protocol uint, mode SyncMode) { t.Fatalf("Failed to sync chain in three seconds") } } + +// TestSkeletonResetAfterSetHead tests that the skeleton syncer is properly reset +// when the chain is rewound using SetHead, preventing data inconsistency issues. +func TestSkeletonResetAfterSetHead(t *testing.T) { + tester := newTester(t) + defer tester.terminate() + + chain := testChainBase.shorten(800) + tester.newPeer("peer", eth.ETH68, chain.blocks[1:]) + + if _, err := tester.chain.InsertChain(chain.blocks[1:401]); err != nil { + t.Fatalf("Failed to insert chain: %v", err) + } + + // Start beacon sync to populate the skeleton + if err := tester.downloader.BeaconSync(SnapSync, chain.blocks[len(chain.blocks)-1].Header(), nil); err != nil { + t.Fatalf("Failed to start beacon sync: %v", err) + } + + // Wait for the skeleton state + time.Sleep(20 * time.Millisecond) + + // Check skeleton sync status exists in database before SetHead + if skeleton := rawdb.ReadSkeletonSyncStatus(tester.downloader.stateDB); len(skeleton) == 0 { + t.Fatal("Skeleton sync status should exist in database before SetHead") + } + + // Simulate chain rewind by calling SetHead + tester.downloader.Cancel() + tester.downloader.ResetSkeleton() + tester.chain.SetHead(200) + + // Verify skeleton sync status was cleared from database + if skeleton := rawdb.ReadSkeletonSyncStatus(tester.downloader.stateDB); len(skeleton) != 0 { + t.Fatal("Skeleton sync status should be cleared from database after SetHead") + } + + // Verify we can start a new sync after reset + if err := tester.downloader.BeaconSync(SnapSync, chain.blocks[400].Header(), nil); err != nil { + t.Fatalf("Failed to start beacon sync after reset: %v", err) + } +} From 9db49bdd7f3a9c20df521a3fd43d5fe525f8912d Mon Sep 17 00:00:00 2001 From: jsvisa Date: Fri, 12 Dec 2025 14:04:56 +0000 Subject: [PATCH 5/5] fix test Signed-off-by: jsvisa --- eth/downloader/downloader_test.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go index b587d43216..f7b6b4d1c2 100644 --- a/eth/downloader/downloader_test.go +++ b/eth/downloader/downloader_test.go @@ -738,7 +738,7 @@ func testSyncProgress(t *testing.T, protocol uint, mode SyncMode) { // TestSkeletonResetAfterSetHead tests that the skeleton syncer is properly reset // when the chain is rewound using SetHead, preventing data inconsistency issues. func TestSkeletonResetAfterSetHead(t *testing.T) { - tester := newTester(t) + tester := newTester(t, ethconfig.SnapSync) defer tester.terminate() chain := testChainBase.shorten(800) @@ -749,7 +749,8 @@ func TestSkeletonResetAfterSetHead(t *testing.T) { } // Start beacon sync to populate the skeleton - if err := tester.downloader.BeaconSync(SnapSync, chain.blocks[len(chain.blocks)-1].Header(), nil); err != nil { + header := chain.blocks[400].Header() + if err := tester.downloader.BeaconSync(header, header); err != nil { t.Fatalf("Failed to start beacon sync: %v", err) } @@ -772,7 +773,7 @@ func TestSkeletonResetAfterSetHead(t *testing.T) { } // Verify we can start a new sync after reset - if err := tester.downloader.BeaconSync(SnapSync, chain.blocks[400].Header(), nil); err != nil { + if err := tester.downloader.BeaconSync(header, header); err != nil { t.Fatalf("Failed to start beacon sync after reset: %v", err) } }