eth/downloader: capture deferred head during skeleton sync restart

On L2 chains with fast block times (1-2s), the skeleton sync can enter a death spiral when a missed p2p gossip block creates a chain gap. The gap triggers a sync restart, but filler.suspend() blocks while the backfiller imports queued blocks. During this window, all incoming head events are dropped, causing 2-3+ blocks to be lost at fast block rates. The restart then uses a stale head, the next block creates another gap, and the cycle repeats indefinitely. This problem was introduced by https://github.com/ethereum/go-ethereum/pull/27397 .

Fix this by remembering the latest forced head event received during the suspend window instead of just dropping it, and using it as the restart target. This ensures the restart head is current, so the next arriving block extends the chain without a gap, breaking the cascade.

L1 Ethereum is currently unaffected since suspend() completes well before the next block arrives due to the 12s block time.
This commit is contained in:
Karl Bartel 2026-03-11 12:26:51 +01:00
parent 88f8549d37
commit 913720b201
No known key found for this signature in database

View file

@ -228,6 +228,8 @@ type skeleton struct {
terminate chan chan error // Termination channel to abort sync
terminated chan struct{} // Channel to signal that the syncer is dead
deferredHead *types.Header // Latest forced head received during sync restart
// Callback hooks used during testing
syncStarting func() // callback triggered after a sync cycle is inited but before started
}
@ -309,6 +311,10 @@ func (s *skeleton) startup() {
// way that requires resyncing it. Restart sync with the new
// head to force a cleanup.
head = newhead
if s.deferredHead != nil {
head = s.deferredHead
s.deferredHead = nil
}
case err == errSyncTrimmed:
// The skeleton chain is not linked with the local chain anymore,
@ -441,6 +447,9 @@ func (s *skeleton) sync(head *types.Header) (*types.Header, error) {
case <-done:
return
case event := <-s.headEvents:
if event.force {
s.deferredHead = event.header
}
event.errc <- errors.New("beacon syncer reorging")
}
}