Skip to content

sync: clamp suggested scan ranges to the current chain tip in initialize#640

Open
pacu wants to merge 1 commit into
mainfrom
fix-636-sync-busy-loop
Open

sync: clamp suggested scan ranges to the current chain tip in initialize#640
pacu wants to merge 1 commit into
mainfrom
fix-636-sync-busy-loop

Conversation

@pacu

@pacu pacu commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

Fixes #636.

Summary

initialize's catch-up loop busy-looped at full speed — pinning a core and writing ~40 log lines/second indefinitely — whenever suggest_scan_ranges returned a range starting above the current chain view's tip. scan_blocks has nothing to scan for such a range and returns Ok without breaking, and the Ok(_) arm re-ran the loop instantly with no state change, so nothing inside the loop could ever resolve the condition (as reported: Scanning blocks Historic(3413618..3414133) against Latest block height is 3413617, repeating indefinitely).

The wallet database can legitimately be ahead of the chain view: it records the highest tip it has ever observed, while Zebra's read-only secondary serves finalized on-disk state that can lag (or effectively regress across a restart).

Fix

Clamp every suggested range to the current tip when selecting the next range to scan (select_initial_scan_range, extracted for testability):

  • a range straddling the tip scans its available prefix — real progress each iteration;
  • a range entirely above the tip is dropped, and when nothing scannable remains the existing no-range arm exits initialize to steady_state, which waits for chain-tip changes properly instead of polling.

This also covers the silent variant of the same spin where tree_state_as_of(start - 1) returns None (range starting more than one block above the tip), which previously re-looped without even logging.

Verification

  • 7 new unit tests on select_initial_scan_range, including the reported repro (range 3413618..3414133 against tip 3413617 → dropped), straddle clamping, Verify-vs-Historic boundary handling, and above-tip ranges not masking later scannable candidates. cargo test -p zallet-core: 201 passed.
  • End-to-end regression: wallet_z_shieldcoinbase.py (fresh-wallet initialize + 100-block catch-up scan) passes against a release build of this branch on the zebrad+zainod regtest stack.

🤖 Generated with Claude Code

https://claude.ai/code/session_017WB1TU6tyLmMa1n4SQ9XgK

The wallet database can record a higher chain tip than the current chain
view serves (e.g. Zebra's read-only secondary exposing finalized state
that lags what the wallet observed before a restart). In that state
suggest_scan_ranges returns ranges starting above the tip;
steps::scan_blocks has nothing to scan for them and returns Ok without
progress, so the initialize catch-up loop re-ran instantly with no state
change - pinning a core and logging tens of lines per second until the
chain view happened to advance past the range.

Clamp every suggested range to the current tip when selecting the next
range to scan: a partially-available range scans its available prefix
(real progress), and a range entirely above the tip is dropped so the
existing no-range arm exits initialize to steady_state, which waits for
chain tip changes properly. Selection logic is extracted into
select_initial_scan_range and unit-tested, including the reported repro
(range 3413618..3414133 against tip 3413617).

Fixes #636.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017WB1TU6tyLmMa1n4SQ9XgK
@pacu
pacu marked this pull request as ready for review July 17, 2026 12:54
@pacu
pacu requested a review from nullcopy July 17, 2026 12:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

sync::initialize busy-loops with no backoff when suggested scan range exceeds current chain tip

1 participant