Skip to content

ibd: fetch archived bodies above the tip after a crash - #1068

Merged
reardencode merged 6 commits into
reardencode:masterfrom
smolcars:ibd/restart-class-a-above-tip
Oct 18, 2026
Merged

reardencode merged 6 commits into
reardencode:masterfrom
smolcars:ibd/restart-class-a-above-tip

Conversation

@niteshbalusu11

@niteshbalusu11 niteshbalusu11 commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #1067. The first three commits are #1067. Please merge #1067 first.

Summary

A crash between the Class A write and the tip advance leaves bodies above the tip. I hit this with kill -9 during a regtest IBD.

On restart:

  • the resume seed marks those bodies archived,
  • densify skips archived hashes (need_hash_at: "tip-hole cover re-gets tip batch"),
  • the tip-hole cover only ran when the body queue was a quarter full (race_tip).

So when the queue stays thin, nobody asks for tip+1 and IBD sits at the old tip.

 assign_work_ordered
   tip_holes = contiguous_tip_holes(...)
   if race_tip
     cover_tip_batch_holes(...)
+  else
+    cover_tip_holes(archived tip holes, one peer)

Evidence

kill -9 three times during a 1,500-block regtest IBD whose blocks spend same-block outputs, then restart:

Seed master with #1067 and #1068
1 stuck at 1128 (hole=, 0.0/s) 1500
3 halted at 254 (lookup stage miss) 1500
4 halted at 241 (lookup stage miss) 1500
5 stuck at 1378 (hole=, 0.0/s) 1500
6 stuck at 556 (hole=, 0.0/s) 1500

"stuck" is this PR. "halted" is #1067.

New beat in end_of_ibd_follow. A fresh store runs catch-up IBD only. Then two new blocks are written as Class A above its tip. The second block spends a create of the first and one of its own. Then it restarts against the miner.

On mainnet the race starts once 1,000 bodies or 256 MiB are queued, so a deep IBD recovers late. A crash near the end of IBD can stay stuck.

The cover asks one peer per hole. If that peer stalls, the quiet-peer disconnect or the normal race takes over.

Merge Danger

Door: two-way

Blast Radius: small. Only archived tip holes when there is no race. One peer, at most 32 hashes.

🤖 Generated with Claude Code

@rearden-grok rearden-grok Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

A crash after the Class A append and before confirmed[] advances leaves header_txs bodies above the connected tip. Restart seeds those hashes as archived, densify's need_hash_at skips them, and the tip-hole race only runs once the body queue is quarter full. This PR asks one peer for each archived hash in the contiguous tip hole (at most 32) when that race is off. That getdata is the right unlock for a thin queue. On this base the bodies then confirm as plan=None with an IBD skeleton, and a same-batch spend still dies in load. The new journey never builds that spend, so it stays green while an ordinary block halts.

Issue counts by severity

  • bugs: 1
  • suggestions: 2
  • nits: 0

Comment thread crates/rbitcoin-net/src/ibd/assign.rs
Comment thread crates/rbitcoin-test/tests/end_of_ibd.rs Outdated
Comment thread docs/crash-recovery.md
@niteshbalusu11
niteshbalusu11 force-pushed the ibd/restart-class-a-above-tip branch from 6e3d736 to e54f288 Compare October 18, 2026 13:18
@niteshbalusu11

niteshbalusu11 commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor Author

@rearden-grok please re-review the latest commits (rebased on #1067, then 2bc94cf).

niteshbalusu11 and others added 6 commits October 18, 2026 14:57
A batch with an invalid block is retried from its archived bodies. With
the IBD skeleton, the stamp gave no range to a parent created in that
batch. Load failed with "lookup stage miss" twice and IBD halted below
the valid blocks. Fill ranges for the batch's own creates on that retry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review nit: the test should fail if a batching change stops putting both
blocks in one wave.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review: a wave longer than one load batch notes chunk 1's archived creates
in flight with no outs. A spend of one in chunk 2 was not in that chunk's
metas, so it still had no range. Fill every parent with no body on the
archived retry: this batch's own creates and an earlier chunk's. Skeleton
parents keep their body and in-flight pins are still skipped.

Test: 145 archived blocks, the last spends a create from the first and
loads in the second chunk. The stamp.rs module note and the invariants row
name the exception.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A crash between the Class A write and the tip advance leaves bodies above
the tip. On restart the resume seed marks them archived, densify skips
archived hashes, and the tip-hole cover only ran once the queue was a
quarter full. With a short chain left, IBD sat at the old tip. Cover
archived tip holes with one peer when there is no race.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review: crash-recovery.md said Class A never leads tip. A crash between
the Class A write and the tip advance leaves bodies above it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The end-of-IBD journey now plants two Class A blocks above a
catch-up-only tip. The second spends a create of the first and one
of its own. The restart must fetch both and connect them.

Docs: Class A never leads tip on a clean run; point the crash
exception at crash-recovery.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@niteshbalusu11
niteshbalusu11 force-pushed the ibd/restart-class-a-above-tip branch from e54f288 to 2bc94cf Compare October 18, 2026 19:04
@niteshbalusu11

Copy link
Copy Markdown
Contributor Author

@rearden-grok I rebased this on the updated #1067. All your review threads are answered and resolved. Please re-review the latest commit (2bc94cf).

@rearden-grok rearden-grok Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

When the body queue is not in the tip-hole race, densify still skips a Class A seed (need_hash_at returns on is_known_archived, and claim_ready does not treat Class A alone as in hand). The new arm sends those archived tip holes through cover_tip_holes, one peer each, which is the re-get the resume seed already expected: known Class A is not a download skip, because the RAM body queue is empty after a restart and confirm needs peer wire. A best-chain hole that is not archived still waits for densify. Out-of-order connect is still the confirm path's problem, and the same-batch spend above the tip is locked by crash_with_class_a_above_tip together with #1067.

Prior notes are addressed: the branch carries the #1067 fill, the journey spends the archived parent rather than a confirmed coinbase, and architecture, concurrency, and crash-recovery say Class A leads the tip only inside that crash window.

The assign comment is the counterintuitive why (densify will not fetch the seed, and only this cover runs when the race is off).

Issue counts by severity

  • bugs: 0
  • suggestions: 0
  • nits: 0

Rebase is done. This head is ahead of master, mergeable, and its first three commits are #1067's tip. Merge #1067 first.

@reardencode
reardencode merged commit 0a1d040 into reardencode:master Oct 18, 2026
18 checks passed
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.

2 participants