#36149 validation: Leave pruned blocks in `m_blocks_unlinked`, fix another `nSequenceId` issue

ReviewabilityReviewsAgreementSize
#36149 mzumsande validation: Leave pruned blocks in `m_blocks_unlinked`, fix another `nSequenceId` issueReady1Strong30 + 39 tests
  • Prevent assertion crashes and node inconsistency when pruned blocks receive late parents or are redownloaded
  • Avoid block candidate lookup failures caused by corrupting sort order during redownloads
  • Ready to review
  • Author is exploring an alternative rework in draft, but no blockers are noted
  • Strong support with substantive fuzz testing and review (jeanpablojp, marcofleon)
  • Verified the fuzz target catches candidate set ordering failures without the fix (jeanpablojp)
  • Tested that the fix resolves potential undefined behavior in candidate lookups (marcofleon)
  • Minor suggestions on RPC help text and test coverage remain open (jeanpablojp, marcofleon)
  • 30 lines added or modified outside tests
  • 39 lines added or modified in tests
  • 37 lines removed in total
  • 5 files, 4 commits
Validation#4 of 53
P1 · bug fix
  • P1 because it resolves block index crashes and internal data structure corruption
  • Fixes assertion failures in block index checks when handling pruned blocks
  • Prevents lookup failures in candidate block sets and ensures accurate chain transaction counts
Reviewability: ReadyStalePaused
Agreement: StrongPositivePositive w/ caveatsNeutralMildDisputedBlockedCrickets
Reviews: current code-review ACKs, then (+stale ACKs) and -NACKs; greener = more ACKs.
Size: lines added or modified outside tests, then in tests; greener = smaller.

Goal

Keep pruned blocks in `m_blocks_unlinked` and re-add them during block index loading even when their block data is missing. Before updating a block's `nSequenceId` upon re-receiving transactions, erase it from `setBlockIndexCandidates` to prevent multiset order corruption. Update `CheckBlockIndex` assertions and enable out-of-chain block pruning in the `block_index_tree` fuzz target.

Problem: When a node prunes a block whose parent's transactions have not yet been received, removing the block from `m_blocks_unlinked` prevents `m_chain_tx_count` from ever being computed if that missing parent arrives later (e.g., via `getblockfrompeer`). This puts the node into an inconsistent state, returns incorrect RPC data, and triggers assertion failures in `CheckBlockIndex`. Furthermore, re-downloading a pruned block mutates its `nSequenceId` while it resides in `setBlockIndexCandidates`, corrupting the set's sort order.

Evidence of importance:

  • Closes issue #31512 ('validation: CheckBlockIndex can fail in combination with pruning') and issue #36021 ('validation: Assertion failed: ((pindexFirstNeverProcessed == nullptr || pindex == snap_base) == pindex->HaveNumChainTxs())')
  • Author notes the sequence ID corruption: 'nSequenceId is one of the sort keys of setBlockIndexCandidates, so changing it for a block that currently is in one of these sets corrupts their ordering, after which lookups for other entries can fail.'
  • Author notes the fuzzer impact: 'This was one of two reasons, the fuzz target block_index_tree couldn't cover pruning of blocks not in the main chain.'

Reviewability

Ready

Nothing known that would invalidate a review now.

Waiting on (from the thread read):

  • author: rework PR and address comments after putting into draft 2026-09-09
  • author: reworking approach and addressing comments while PR is in draft 2026-09-09

Agreement

Strong

substantive support, no open objection (jeanpablojp, marcofleon)

Thread notes: Added review suggestions/questions from jeanpablojp and marcofleon; PR is currently in draft awaiting author rework. / Added review suggestions from jeanpablojp and marcofleon; author put PR into draft to explore an alternative conditional removal approach.

Objections, suggestions, and questions:

ReviewerTypeHarm, request, or questionStatusMust beAuthor repliedQuote
jeanpablojp (contributor)question, interfaceWhether the getchaintips help description of valid-headers would be better phrased as 'received at some point' rather than 'are available'openansweredno2026-09-08: 'Would "received at some point" be closer than "are available" there?'
marcofleon (member)suggestion, correctnessAdd a 2-block fork to test submitting a missing parent of a pruned child before restartingopenansweredno2026-09-09: 'Could add a separate 2-block fork to cover the no restart case?'

Support:

  • l0rinc (member) (Concept ACK, 2026-09-02): thanks for fixing it [not substantive]
  • jeanpablojp (contributor) (Concept ACK, 2026-09-08) [tested: block_index_tree]: confirmed that without the candidate erase, block_index_tree target fails on existing qa-assets corpus input
  • marcofleon (member) (ACK, 2026-09-09) [both: 7c7ccc9cc0939e00f060b49ec476a761f2c1c85e, 80477ba97820485c8371f71d0b46b7bb23325cf2, block_index_tree, setBlockIndexCandidates]: fix looks correct, fuzz testing passed, and fixes potential UB in setBlockIndexCandidates caught by fuzzing

Participants:

  • l0rinc (member, support, 1 statements 2026-09-02 to 2026-09-02): Concept ACK
  • jeanpablojp (contributor, support, 3 statements 2026-09-08 to 2026-09-08): Concept ACK after testing fuzz target; asked about getchaintips help wording
  • marcofleon (member, support, 2 statements 2026-09-09 to 2026-09-09): ACK with fuzz testing; suggested adding a test case for unlinked blocks without restart

Files

56 lines under test/bench/ci.

  • test/functional/feature_prune_stale_fork.py +37/-3
  • src/node/blockstorage.cpp +6/-16
  • src/validation.cpp +14/-3
  • src/test/fuzz/block_index_tree.cpp +2/-14
  • src/node/blockstorage.h +10/-1

Card

The short text the ranking pass compares across PRs.

This PR retains pruned blocks in `m_blocks_unlinked` across disk flushes and restarts, and removes candidates from `setBlockIndexCandidates` before modifying `nSequenceId` when block data is re-received. When parent blocks arrived out-of-order after a child block had been pruned, `m_chain_tx_count` failed to update, leading to internal state inconsistency and `CheckBlockIndex` assertion crashes (#31512, #36021). Mutating `nSequenceId` in-place also corrupted candidate set ordering. These fixes resolve the assertion failures and allow the `block_index_tree` fuzzer to prune out-of-chain blocks without crashing.

Data

dossier JSON · extract JSON · model ledger, generated 2026-09-26T01:23, confidence high, input hash 4ea98d098bbacf26
thread read through 2026-09-09 (8 statements), head 40a31d97ab
labels: Validation · draft

Categories

Validation

P1 · bug fix

Fixes assertion crashes and data structure corruption in the block index. As noted in the description, mutating `nSequenceId` while in `setBlockIndexCandidates` corrupts set ordering and causes lookups to fail, while unlinking pruned blocks causes assertion failures in `CheckBlockIndex` and node inconsistency (closing issues #31512 and #36021).

Membership: Validation label and changes to src/validation.cpp and src/node/blockstorage.cpp affecting block index management, unlinked block tracking, and block index candidate sets.

Factors: security/stability 2, bug 2, performance 0, user value 1, leverage 1