Merge bitcoin/bitcoin#34521: validation: fix UB in LoadChainTip

20ae9b98ea Extend functional test for setBlockIndexCandidates UB (marcofleon)
854a6d5a9a validation: fix UB in LoadChainTip (marcofleon)
9249e6089e validation: remove LoadChainTip call from ActivateSnapshot (marcofleon)

Pull request description:

  Addresses https://github.com/bitcoin/bitcoin/issues/34503. See this issue for more details as well.

  Fixes a bug where, under certain conditions, `setBlockIndexCandidates` had blocks in it that were worse than the tip. The block index candidate set uses `nSequenceId` as a sort key, so modifying this field while blocks are in the set results in undefined behavior. This PR populates `setBlockIndexCandidates` after the `nSequenceId` modifications, avoiding the UB.

ACKs for top commit:
  achow101:
    ACK 20ae9b98ea
  sedited:
    Re-ACK 20ae9b98ea
  sipa:
    Code review ACK 20ae9b98ea

Tree-SHA512: 121c170bb70fb6365089d578db63c811e7926e129d7206e569947f7a1f6c5ddc8d5f4937b80f1ba1b7d7daa42789b143ca5b56f154b7ab968a1cd55f925f378d
This commit is contained in:
Ava Chow
2026-03-06 08:22:42 -08:00
6 changed files with 76 additions and 43 deletions

View File

@@ -126,6 +126,13 @@ static ChainstateLoadResult CompleteChainstateInitialization(
}
}
// Populate setBlockIndexCandidates in a separate loop, after all LoadChainTip()
// calls have finished modifying nSequenceId. Because nSequenceId is used in the
// set's comparator, changing it while blocks are in the set would be UB.
for (const auto& chainstate : chainman.m_chainstates) {
chainstate->PopulateBlockIndexCandidates();
}
const auto& chainstates{chainman.m_chainstates};
if (std::any_of(chainstates.begin(), chainstates.end(),
[](const auto& cs) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return cs->NeedsRedownload(); })) {