Skip to content

Backport stall/reorg fixes onto v0.6.x - #468

Open
rearden-grok[bot] wants to merge 31 commits into
v0.6.xfrom
backport/0.6.x-stalls
Open

Backport stall/reorg fixes onto v0.6.x#468
rearden-grok[bot] wants to merge 31 commits into
v0.6.xfrom
backport/0.6.x-stalls

Conversation

@rearden-grok

@rearden-grok rearden-grok Bot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Backport onto v0.6.x (schema 20, still 0.6.0) of the tip-follow stall and reorg fixes that landed on master after 0.6.0. This is not a 0.6.1 cut.

Left on master (not needed for these incidents, or 0.6.0-incompatible):

  • Generic try_reconstruct<T: Borrow<Transaction>> and the pre-ground 48-bit wtxid collision journey (a8c5dcee). Unit pins unique_shortid_wrong_body / apply_blocktxn_wrong_body / mutated_compact_child_of_held_sibling_is_not_block_failed are here.
  • Restore post-v0.6.0 coverage and IBD recover resume #441 extras (1p1c, maxfeerate, Class A 3-stem pwrite observer).
  • Schema 21 / spent.idx drop / X-01 confirm_stats.

0.6.x adapters: Query::open_or_create instead of rbitcoin_query::testutil; SCHEMA.md keeps schema-20 Mode 10 linked-walk wording.

Test plan

  • cargo test -p rbitcoin-store --lib extent_span_over_cap_still_unlinks
  • cargo test -p rbitcoin-query --lib disconnect_tip_unlinks_megakey / uring_recover
  • cargo test -p rbitcoin-consensus --lib accept_rejects_same_block_coinbase / already_at_height_retries_post_commit
  • compact merkle + mutated_compact_child_of_held_sibling_is_not_block_failed
  • IBD confirm recover / post_lookup_reject_rewinds_taken_hi
  • cargo test -p rbitcoin-query --lib resume_subtree_score_prev_fk_cycle_terminates
  • required GitHub Actions green (fmt, deny, clippy, ast-grep, test, windows, macos, multinode, coverage)
  • CodeQL Analyze (rust) / Analyze (actions) after operator pushes the workflow commit

rearden-grok Bot and others added 30 commits September 12, 2026 13:01
Inner strings like "invariant: io_uring undrained" lack "corrupt
record", so from_err_str treated them as Cascade. Recover/halt need
EngineFault. Pin each session-fault string on StoreError and the
confirm reject map.

Co-authored-by: Cursor <cursoragent@cursor.com>
A 5s spin used to return with leftover SQEs so callers dropped
in-flight buffers. Every UringSession now uses DrainBudget: progress
resets the zero-CQE clock, slow drains log, and zero completions for
RBITCOIN_URING_DRAIN_HARD_SECS (default 120) abort. Tests hit HardCap
as undrained instead of abort().

Co-authored-by: Cursor <cursoragent@cursor.com>
Class C leftover above tip is the same repair as open. Credit is a
tip gap so a recovered batch of 144 does not mint a new recover.
Exhausted does not repair. Drops only this thread's TLS ring.

Co-authored-by: Cursor <cursoragent@cursor.com>
Write requeues the same BQ heights after a credited recover; lookup
does the same instead of warning every 20s. Credit exhausted or a
drain stall aborts and names RBITCOIN_IO=pread. Failed head drain
re-notes the taken pending list. ibd: perf gains uring_recover,
slow_drain, and lookup_faults.

Co-authored-by: Cursor <cursoragent@cursor.com>
Operators saw 19h of undrained warnings under idle IO. Document the
120s hard-cap abort, 1000-height recover, Class C repair, and that
there is no mid-run libc fallback.

Co-authored-by: Cursor <cursoragent@cursor.com>
Darwin SessionBackend is Pool-only, so `_` in drain_all_pool was
unreachable under -D warnings and failed macos CI.

Co-authored-by: Cursor <cursoragent@cursor.com>
Keep IoCtx public (0.6.0 surface). The 0.6.99 pick wanted pub(crate)
and dropped with_thread_local from the same use.

Co-authored-by: Cursor <cursoragent@cursor.com>
Assumed 100ms windows let EINTR spin hit the hard cap. note() now
takes a real dt. submit_and_wait_one shares that budget. Session Drop
waits then poisons without abort(). Session-fault match is any
Corrupt io_uring message except Linux-only/unavailable. drop_thread_local
is gone: poisoned TLS already reopens.

Co-authored-by: Cursor <cursoragent@cursor.com>
Lookup must not clear strong bits while write sets them before the
fence. In-process recover no longer runs Class C repair. Two threads
cannot both pass the tip-gap check.

Co-authored-by: Cursor <cursoragent@cursor.com>
ConsensusError::is_uring_session_fault covers leftover cqe and submit
failed. Head insert returns the taken batch on Err so write does not
clone the drain list on the success path.

Co-authored-by: Cursor <cursoragent@cursor.com>
One helper returns Option recover. Write classify takes that Option.
Lookup EngineFault skips emit when ready has no hash. Load stamp/pin
and scripts requeue on credited recover. Tip connect retries instead of
note_invalid_block.

Co-authored-by: Cursor <cursoragent@cursor.com>
Open still repairs leftover strong. In-process recover does not.
Lookup 8 consecutive Io dequeues BQ; session-fault recover does not.

Co-authored-by: Cursor <cursoragent@cursor.com>
WaitOneWindow::Enter is only built from io_uring enter errors. IOCP
wait_one_cqe had no callers after the timeout drain loop. Both failed
-D warnings on macos/windows CI.

Co-authored-by: Cursor <cursoragent@cursor.com>
Write, load, scripts, lookup, and tip-connect each matched Recovered vs
Exhausted. Credit/abort lives on Query; IBD requeue sites share one helper.
Lookup's 8-strike halt no longer carries recover arms.

Co-authored-by: Cursor <cursoragent@cursor.com>
Explicit drain_all stays fail-closed. A hard cap while a drain guard
is live poisons and returns Corrupt in tests; it does not abort.
…rain

Class C can commit, then post_commit or tx.head drain session-fault.
height_of_hash matching is not write-finished; post_commit is idempotent.
Write session-fault requeues even when has_block so annotate+drain
can finish. Load recover clears speculative create FKs inline;
sync_body_hwm max would not roll next_tx_start down.
The guard drops before SQE buffers. Fail-open on hard cap frees those
buffers while the kernel still owns the SQEs. Session Drop stays
fail-open; it does not own buffers.
Keep 0.6.0 with_thread_local / IoCtx public. Changelog drain stall
is credit-only (Class C leftover waits for open repair).

Co-authored-by: Cursor <cursoragent@cursor.com>
Session-fault after Class C is connected: take recover credit and call
finish_post_commit_hashes in place. Requeue is stale vs tip and dropped
by lookup. Annotate only hashes whose height_of_hash matches; multi-list
repeat of the same spend_fk is Skip.
Core treats nHeight < coinbaseHeight + 100 including the creating
block. Structural spentness skipped NULL creates, so a later tx
in the same block could spend the coinbase.

Co-authored-by: Cursor <cursoragent@cursor.com>
claim_ready treated H ≤ taken_hi as in-hand. Only BadPrev rewound
it, so merkle/cascade/engine-fault left densify with hole=0 and
no getdata. Cancelled still leaves the high-water.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
NULL create_fk already means same-block on the existing spends walk.
Notate this block's coinbase txid once and reject there, instead of a
second spends.iter() pass.

Co-authored-by: Cursor <cursoragent@cursor.com>
Assemble already maps this block's txids and refuses child-before-parent
when pj >= ti. Parent index 0 is the coinbase, so maturity is the same
lookup — no second spends walk and no extra coinbase txid hash.

Co-authored-by: Cursor <cursoragent@cursor.com>
A reconstructed compact on a held equal-work sibling can fail
bad-txnmrklroot after accept_branch wraps Mutated as ConnectFailed.
That marked the header hash invalid, so the later honest getdata and
every peer advertising the winning chain were banned (mainnet 966501).
Drop the mutated body and keep the hash acceptable; true consensus
rejects still mark BLOCK_FAILED.

Co-authored-by: Cursor <cursoragent@cursor.com>
BIP152 allows a unique short-id or blocktxn fill to assemble the wrong
txs under the compact header. We treated a full slot fill as a block and
offered it to accept_branch (reorg then bad-txnmrklroot). FinishBlock
before Ok so the peer path getdata instead.

Co-authored-by: Cursor <cursoragent@cursor.com>
DisconnectTip collects every create list to rewrite it. IBD megakeys
can stamp extent_n past the 64MiB bulk-pread cap; treating that as
Corrupt blocked equal-work sibling rewind. Walk the linked chain
instead, and pin SH+tweaks disconnect together.

Co-authored-by: Cursor <cursoragent@cursor.com>
v0.6.0 has no rbitcoin_query::testutil. Backported tests open a tiny-head
Query (or ChainHub) with Query::open_or_create so they compile on this line.

Co-authored-by: Cursor <cursoragent@cursor.com>
create_tiny and WriteIoBackend annotate args are master-only. The extent
unlink pin uses ScriptHashTable::create; the 441 annotate pin uses
SpendAnnBackend::Pwrite.

Co-authored-by: Cursor <cursoragent@cursor.com>
Backport of master #469. False parent edges can cycle the header child
map; iterative DFS re-pushed gray nodes forever so IBD sat on
"resume seed walk start". Skip on-stack nodes and log graph n= before
rewind (so SH unlink can run).

Co-authored-by: Cursor <cursoragent@cursor.com>
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.

0 participants