index: Check BIP30 blocks when rewinding Coinstatsindex
What changed, and why it matters
This commit adds a safety check inside Bitcoin Core's CoinStatsIndex when it has to 'rewind' or undo a block during a deep blockchain reorganization. Specifically, it skips undoing the coinbase transactions of two very old special blocks (heights 91842 and 91880) whose outputs are permanently unspendable under a rule called BIP30. The change is mostly for internal consistency and documentation, because a reorganization deep enough to affect those blocks is practically impossible on today's network.
No urgent action required. Treat as a minor correctness/consistency improvement. Node operators do not need to upgrade solely for this change. Reviewers may verify that the IsBIP30Unspendable helper is used consistently across CustomAppend and RevertBlock.
Security signals we found
Defensive consistency fix in index rewind logic
BIP30 unspendable block handling
Deep reorg edge case only; commit message downplays practical likelihood
Evidence from the diff
In CoinStatsIndex::RevertBlock(), the patch adds a continue when the transaction being reverted is a coinbase and the block is one of the two BIP30-unspendable blocks (91842/91880). Those blocks had duplicate coinbase transaction hashes whose outputs were later made unspendable by BIP30; they were never added to the UTXO set and therefore should not be subtracted on rewind. Without this check, a deep reorg that removed those blocks could try to erase UTXO entries that do not exist, leading to an inconsistency between RevertBlock and CustomAppend. The commit message explicitly calls the scenario ‘practically irrelevant’ due to the depth required.
Changed components
src/index/coinstatsindex.cppCoinStatsIndex::RevertBlockInspect captured patch +4 / −0
diff --git a/src/index/coinstatsindex.cpp b/src/index/coinstatsindex.cpp
index e4daf9ef..24b1837b 100644
--- a/src/index/coinstatsindex.cpp
+++ b/src/index/coinstatsindex.cpp
@@ -426,6 +426,10 @@ bool CoinStatsIndex::RevertBlock(const interfaces::BlockInfo& block)
const auto& tx{block.data->vtx.at(i)};
const bool is_coinbase{tx->IsCoinBase()};
+ if (is_coinbase && IsBIP30Unspendable(block.hash, block.height)) {
+ continue;
+ }
+
for (uint32_t j = 0; j < tx->vout.size(); ++j) {
const CTxOut& out{tx->vout[j]};
const COutPoint outpoint{tx->GetHash(), j};
Why this scored 18/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.