mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-01-20 07:09:15 +01:00
Merge bitcoin/bitcoin#29640: Fix tiebreak when loading blocks from disk (and add tests for comparing chain ties)
0465574c12test: Fixes send_blocks_and_test docs (Sergi Delgado Segura)09c95f21e7test: Adds block tiebreak over restarts tests (Sergi Delgado Segura)18524b072eMake nSequenceId init value constants (Sergi Delgado Segura)8b91883a23Set the same best tip on restart if two candidates have the same work (Sergi Delgado Segura)5370bed21etest: add functional test for complex reorgs (Pieter Wuille)ab145cb3b4Updates CBlockIndexWorkComparator outdated comment (Sergi Delgado Segura) Pull request description: This PR grabs some interesting bits from https://github.com/bitcoin/bitcoin/pull/29284 and fixes some edge cases in how block tiebreaks are dealt with. ## Regarding #29284 The main functionality from the PR was dropped given it was not an issue anymore, however, reviewers pointed out some comments were outdated https://github.com/bitcoin/bitcoin/pull/29284#discussion_r1522023578 (which to my understanding may have led to thinking that there was still an issue) it also added test coverage for the aforementioned case which was already passing on master and is useful to keep. ## New functionality While reviewing the superseded PR, it was noticed that blocks that are loaded from disk may face a similar issue (check https://github.com/bitcoin/bitcoin/pull/29284#issuecomment-1994317785 for more context). The issue comes from how tiebreaks for equal work blocks are handled: if two blocks have the same amount of work, the one that is activatable first wins, that is, the one for which we have all its data (and all of its ancestors'). The variable that keeps track of this, within `CBlockIndex` is `nSequenceId`, which is not persisted over restarts. This means that when a node is restarted, all blocks loaded from disk are defaulted the same `nSequenceId`: 0. Now, when trying to decide what chain is best on loading blocks from disk, the previous tiebreaker rule is not decisive anymore, so the `CBlockIndexWorkComparator` has to default to its last rule: whatever block is loaded first (has a smaller memory address). This means that if multiple same work tip candidates were available before restarting the node, it could be the case that the selected chain tip after restarting does not match the one before. Therefore, the way `nSequenceId` is initialized is changed to: - 0 for blocks that belong to the previously known best chain - 1 to all other blocks loaded from disk ACKs for top commit: sipa: utACK0465574c12TheCharlatan: ACK0465574c12furszy: Tested ACK0465574c12. Tree-SHA512: 161da814da03ce10c34d27d79a315460a9c98d019b85ee35bc5daa991ed3b6a2e69a829e421fc70d093a83cf7a2e403763041e594df39ed1991445e54c16532a
This commit is contained in:
@@ -4660,7 +4660,7 @@ bool Chainstate::LoadChainTip()
|
||||
AssertLockHeld(cs_main);
|
||||
const CCoinsViewCache& coins_cache = CoinsTip();
|
||||
assert(!coins_cache.GetBestBlock().IsNull()); // Never called when the coins view is empty
|
||||
const CBlockIndex* tip = m_chain.Tip();
|
||||
CBlockIndex* tip = m_chain.Tip();
|
||||
|
||||
if (tip && tip->GetBlockHash() == coins_cache.GetBestBlock()) {
|
||||
return true;
|
||||
@@ -4672,6 +4672,20 @@ bool Chainstate::LoadChainTip()
|
||||
return false;
|
||||
}
|
||||
m_chain.SetTip(*pindex);
|
||||
tip = m_chain.Tip();
|
||||
|
||||
// Make sure our chain tip before shutting down scores better than any other candidate
|
||||
// to maintain a consistent best tip over reboots in case of a tie.
|
||||
auto target = tip;
|
||||
while (target) {
|
||||
target->nSequenceId = SEQ_ID_BEST_CHAIN_FROM_DISK;
|
||||
target = target->pprev;
|
||||
}
|
||||
|
||||
// Block index candidates are loaded before the chain tip, so we need to replace this entry
|
||||
// Otherwise the scoring will be based on the memory address location instead of the nSequenceId
|
||||
setBlockIndexCandidates.erase(tip);
|
||||
TryAddBlockIndexCandidate(tip);
|
||||
PruneBlockIndexCandidates();
|
||||
|
||||
tip = m_chain.Tip();
|
||||
@@ -5323,7 +5337,9 @@ void ChainstateManager::CheckBlockIndex() const
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!pindex->HaveNumChainTxs()) assert(pindex->nSequenceId <= 0); // nSequenceId can't be set positive for blocks that aren't linked (negative is used for preciousblock)
|
||||
// nSequenceId can't be set higher than SEQ_ID_INIT_FROM_DISK{1} for blocks that aren't linked
|
||||
// (negative is used for preciousblock, SEQ_ID_BEST_CHAIN_FROM_DISK{0} for active chain when loaded from disk)
|
||||
if (!pindex->HaveNumChainTxs()) assert(pindex->nSequenceId <= SEQ_ID_INIT_FROM_DISK);
|
||||
// VALID_TRANSACTIONS is equivalent to nTx > 0 for all nodes (whether or not pruning has occurred).
|
||||
// HAVE_DATA is only equivalent to nTx > 0 (or VALID_TRANSACTIONS) if no pruning has occurred.
|
||||
if (!m_blockman.m_have_pruned) {
|
||||
|
||||
Reference in New Issue
Block a user