#36149 validation: Leave pruned blocks in `m_blocks_unlinked`, fix another `nSequenceId` issue
| Reviewability | Reviews | Agreement | Size | ||||
|---|---|---|---|---|---|---|---|
| #36149 validation: Leave pruned blocks in `m_blocks_unlinked`, fix another `nSequenceId` issue | Ready | 1 | Strong | 30 + 39 tests | |||
|
|
|
|
| |||
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:
| Reviewer | Type | Harm, request, or question | Status | Must be | Author replied | Quote |
|---|---|---|---|---|---|---|
| jeanpablojp (contributor) | question, interface | Whether the getchaintips help description of valid-headers would be better phrased as 'received at some point' rather than 'are available' | open | answered | no | 2026-09-08: 'Would "received at some point" be closer than "are available" there?' |
| marcofleon (member) | suggestion, correctness | Add a 2-block fork to test submitting a missing parent of a pruned child before restarting | open | answered | no | 2026-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
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