validation: CheckBlockIndex() const: Assertion !c->setBlockIndexCandidates.contains(const_cast<CBlockIndex*>(pindex)) failed #34503

issue dergoegge openend this issue on February 4, 2026
  1. dergoegge commented at 12:11 pm on February 4, 2026: member

    This assertion in CheckBlockIndex may fail under certain block race and shutdown/restart conditions (see attached example log).

    The root cause appears to be the modification of the nSequenceId field of CBlockIndex pointers, after they have been added to setBlockIndexCandidates. This is undefined behavior as the CBlockIndexWorkComparator compares nSequenceIds.

    Specifically, here the tip’s (and ancestor) sequence ids are changed while all the indices are in the candidates set already.

    setBlockIndexCandidates_contains.log

    This was found with a test running on Antithesis.

  2. dergoegge commented at 12:11 pm on February 4, 2026: member
    @marcofleon is working on a fix
  3. dergoegge commented at 12:15 pm on February 4, 2026: member
    Looks like this was introduced by https://github.com/bitcoin/bitcoin/pull/29640
  4. maflcko commented at 12:18 pm on February 4, 2026: member
    I guess this is #33948, which I had difficulties reproducing. So thanks for making this reproducible!
  5. fanquake renamed this:
    validation: CheckBlockIndex() const: Assertion '!c->setBlockIndexCandidates.contains(const_cast<CBlockIndex*>(pindex))' failed
    validation: CheckBlockIndex() const: Assertion `!c->setBlockIndexCandidates.contains(const_cast<CBlockIndex*>(pindex))` failed
    on Feb 5, 2026
  6. mzumsande commented at 3:08 pm on February 5, 2026: contributor

    Could someone explain a bit more? The linked code attempts to deal with this very problem by removing/readding the tip to setBlockIndexCandidates, while ancestors of the tip shouldn’t be in setBlockIndexCandidates. Where is the error in this?

    In any case, this should be tagged/fixed for v31.

  7. fanquake added this to the milestone 31.0 on Feb 5, 2026
  8. marcofleon commented at 3:58 pm on February 5, 2026: contributor

    The issue is that erase sometimes doesn’t succeed in removing the tip. We populate setBlockIndexCandidates in LoadBlockIndex with sequence id 1 and then later change it to 0 in LoadChainTip, which ends up corrupting the tree structure of the set. The tip was initially stored based on the sequence id being 1, so calling erase after the change to 0 might just return null.

    After we re-add the tip, it’s now in setBlockIndexCandidates twice, but because there was a block race, that set ends up looking something like this:

    02026-02-05T15:42:13Z DEBUG: Before erase/re-insert, candidates size=535, tip ptr=0x600000d17d20
    12026-02-05T15:42:13Z DEBUG: erase(tip) removed 0 elements
    22026-02-05T15:42:13Z DEBUG: After prune, candidates size=4 (should be 1)
    32026-02-05T15:42:13Z DEBUG: h=505 seqId=0 ptr=0x600000d17d20 hash=583a9332474464f8
    42026-02-05T15:42:13Z DEBUG: h=505 seqId=1 ptr=0x600000d149f0 hash=0f002c5df6f09f9b
    52026-02-05T15:42:13Z DEBUG: h=505 seqId=1 ptr=0x600000d07df0 hash=5ed3fc31e5ae9f6d
    62026-02-05T15:42:13Z DEBUG: h=505 seqId=0 ptr=0x600000d17d20 hash=583a9332474464f8
    

    because the prune stops at the first instance of the tip. So now in CheckBlockIndex the assertion that a block that sorts worse than the tip is not in the set fails.

    The simple fix is just to move the erase to before changing the sequence ids, but it’s still undefined behavior to modify the sequence id while blocks are in setBlockIndexCandidates. I believe the proper fix is to move the population of that set from LoadBlockIndex to LoadChainTip after setting the sequence ids. This would also be better because the current behavior has the candidate set at some point holding the entire block tree, which isn’t really what that set is for.

    The refactor is unfortunately proving to be more difficult than expected because of the other snapshot chainstate that we maintain. I’m happy to just open the quick fix for v31.

  9. willcl-ark added the label Bug on Feb 5, 2026
  10. willcl-ark added the label Validation on Feb 5, 2026

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-02-17 06:13 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me