Files
bitcoin/src/node
merge-script 6b58eb6d51 Merge bitcoin/bitcoin#35070: validation: prevent FindMostWorkChain from causing UB
d6359937bf validation: check invariants when inserting into m_blocks_unlinked (stratospher)
0852925bd8 test/doc: remove misleading comment and improve tests (stratospher)
ca4a380281 test: add coverage for UB caused by FindMostWorkChain (stratospher)
c787b3b99b validation: avoid duplicates in m_blocks_unlinked (stratospher)

Pull request description:

  This is joint work with @ mzumsande.

  note: this requires a pruned node with deep reorgs to trigger. still it breaks assumptions in the codebase and is good to fix. A similar UB was fixed in https://github.com/bitcoin/bitcoin/pull/34521.

  This PR prevents duplicate insertions into `m_blocks_unlinked` in `FindMostWorkChain`. There are 3 ways to insert into `m_blocks_unlinked`:

  1. `LoadBlockIndex` - not problematic, as each block index is processed only once.
  2. `ReceivedBlockTransactions` - not problematic, as this is usually only called once per block when it is first accepted in `AcceptBlock`. in the rare case it’s triggered again after pruning, the block would have been removed from `m_blocks_unlinked` when it was initially pruned, so duplicates still can’t arise.
  3. `FindMostWorkChain` - problematic when multiple candidate tips share common chains of ancestors, traversals from each tip to the fork point may insert duplicate (`pprev`, `pindex`) entries for blocks whose parents have been pruned.

  When the missing parent is later received and `ReceivedBlockTransactions` processes `m_blocks_unlinked`, the same entry may be processed multiple times. This can result in the block being re-added to `setBlockIndexCandidates` with a modified `nSequenceId`, violating its ordering invariants and leading to undefined behavior. So avoid duplicate insertions into `m_blocks_unlinked` in `FindMostWorkChain`.

  ### how to test:

  use the updated `feature_pruning.py` which adds coverage for this scenario.
  - on master: the test (with the below diff) fails since `nSequenceId` is being modified for an entry in `setBlockIndexCandidates`
  - on this branch: the test (with the below diff) passes

  ```diff --git a/src/validation.cpp b/src/validation.cpp
  --- a/src/validation.cpp
  +++ b/src/validation.cpp
  @@ -3814,6 +3814,12 @@ void ChainstateManager::ReceivedBlockTransactions(const CBlock& block, CBlockInd
                      pindex->nHeight, pindex->m_chain_tx_count, prev_tx_sum(*pindex), CLIENT_NAME, FormatFullVersion(), CLIENT_BUGREPORT);
               }
               pindex->m_chain_tx_count = prev_tx_sum(*pindex);
  +            for (const auto& c : m_chainstates) {
  +                if (c->setBlockIndexCandidates.contains(pindex)) {
  +                    LogInfo("### pindex UB = %s", pindex);
  +                    assert(false);
  +                }
  +            }
               pindex->nSequenceId = nBlockSequenceId++;
               for (const auto& c : m_chainstates) {
                   c->TryAddBlockIndexCandidate(pindex);
  ```

ACKs for top commit:
  sedited:
    Re-ACK d6359937bf
  marcofleon:
    crACK d6359937bf
  stringintech:
    ACK d6359937bf
  mzumsande:
    Sure - Code Review ACK [d635993](d6359937bf)

Tree-SHA512: bb21adc2d92fe1865bbbcebf775a850ca3eccac6fe83d7bca10b78eee4c0abf782e44fa0ddfec9d9a70f42fa40bdc49b68a1d1b4905cdc371ba29117d3120619
2026-06-23 09:25:06 +01:00
..
2026-05-26 11:39:47 -07:00
2026-05-22 08:31:00 +02:00

src/node/

The src/node/ directory contains code that needs to access node state (state in CChain, CBlockIndex, CCoinsView, CTxMemPool, and similar classes).

Code in src/node/ is meant to be segregated from code in src/wallet/ and src/qt/, to ensure wallet and GUI code changes don't interfere with node operation, to allow wallet and GUI code to run in separate processes, and to perhaps eventually allow wallet and GUI code to be maintained in separate source repositories.

As a rule of thumb, code in one of the src/node/, src/wallet/, or src/qt/ directories should avoid calling code in the other directories directly, and only invoke it indirectly through the more limited src/interfaces/ classes.

This directory is at the moment sparsely populated. Eventually more substantial files like src/validation.cpp and src/txmempool.cpp might be moved there.