Remove assumeutxo / UTXO-snapshot feature

Removes the dumptxoutset RPC, -loadutxosnapshot / -loadutxosnapshotunsafe,
the CCoinsViewDB Dump/LoadSnapshot machinery + CUTXOSnapshotHeader, the
AssumeutxoData chainparams anchor, the LoadSnapshotChainstate activation +
reorg-below-H guard, the persisted assumeutxo-height flag, and the gtest.

Rationale: it duplicated the existing bootstrap (same skip-the-genesis-grind
fast-sync, no speed advantage), its only real edge was a trust model we don't
need for this chain, and it was inert anyway (no published snapshot hash in
chainparams). The -loadutxosnapshot load path adopted an external UTXO set and
bypassed genesis validation, so removing it also drops that attack surface.
Builds clean (no dangling references); the kept IBD speedups (RandomX
pre-verify, adaptive dbcache, tlsmanager) are untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-30 16:27:34 -05:00
parent 1f2b109d95
commit 84aefb5475
9 changed files with 2 additions and 703 deletions

View File

@@ -85,7 +85,6 @@ void hush_pricesupdate(int32_t height,CBlock *pblock);
BlockMap mapBlockIndex;
CChain chainActive;
CBlockIndex *pindexBestHeader = NULL;
int nAssumeutxoSnapshotHeight = -1; // height H of a loaded UTXO snapshot; reorgs below H are refused (-1 = none)
static int64_t nTimeBestReceived = 0;
CWaitableCriticalSection csBestBlock;
CConditionVariable cvBlockChange;
@@ -4201,45 +4200,6 @@ static void PruneBlockIndexCandidates() {
assert(!setBlockIndexCandidates.empty());
}
// Activate a trusted UTXO snapshot (assumeutxo-style) as the chain tip WITHOUT replaying blocks
// 0..H. The chainstate has already been populated by CCoinsViewDB::LoadSnapshot(); here we mark the
// snapshot's base block (height H) as fully validated and set it as the active tip. Blocks above H
// then connect normally with full PoW + script + Sapling-proof validation. Requires that the block
// HEADERS for height H are already present in mapBlockIndex (from prior header sync or bootstrap).
// NOTE: below-H blocks have no body/undo data, so reorgs below H are impossible (see Stage D guard).
bool LoadSnapshotChainstate(const CUTXOSnapshotHeader& header, std::string& strError)
{
LOCK(cs_main);
BlockMap::iterator it = mapBlockIndex.find(header.baseBlockHash);
if (it == mapBlockIndex.end() || it->second == NULL) {
strError = "block header for the snapshot height is not present; sync headers (or use the bootstrap) before loading a UTXO snapshot";
return false;
}
CBlockIndex* pindexH = it->second;
if (pindexH->GetHeight() != header.nHeight) {
strError = "snapshot base block height does not match its header index";
return false;
}
// Only nChainTx is consensus-relevant for tip selection; nTx must merely be non-zero so the
// (nChainTx != 0) candidate-eligibility checks hold. Ancestors legitimately have nTx==0 here
// because we never received their bodies — this is the assumeutxo trust assumption.
if (pindexH->nTx == 0)
pindexH->nTx = (header.nChainTx > 0 ? (unsigned int)header.nChainTx : 1);
pindexH->nChainTx = (unsigned int)header.nChainTx;
if (header.fHasChainSaplingValue)
pindexH->nChainSaplingValue = header.nChainSaplingValue;
pindexH->RaiseValidity(BLOCK_VALID_SCRIPTS);
nAssumeutxoSnapshotHeight = pindexH->GetHeight(); // arm the reorg-below-H guard (Stage D)
setBlockIndexCandidates.insert(pindexH);
chainActive.SetTip(pindexH);
if (pindexBestHeader == NULL || pindexBestHeader->GetHeight() < pindexH->GetHeight())
pindexBestHeader = pindexH;
PruneBlockIndexCandidates();
return true;
}
/**
* Try to make some progress towards making pindexMostWork the active block.
* pblock is either NULL or a pointer to a CBlock corresponding to pindexMostWork.
@@ -4288,15 +4248,6 @@ static bool ActivateBestChainStep(bool fSkipdpow, CValidationState &state, CBloc
return state.DoS(100, error("ActivateBestChainStep(): pindexOldTip->GetHeight().%d > notarizedht %d && pindexFork->GetHeight().%d is < notarizedht %d, so ignore it",(int32_t)pindexOldTip->GetHeight(),notarizedht,(int32_t)pindexFork->GetHeight(),notarizedht),
REJECT_INVALID, "past-notarized-height");
}
// Refuse reorgs whose fork point is below a loaded UTXO snapshot height (Stage D): the node has
// no block/undo data for 0..H, so disconnecting below H is impossible. Belt-and-suspenders on top
// of checkpoint fork-rejection (H sits at/below the last hardcoded checkpoint).
if ( nAssumeutxoSnapshotHeight >= 0 && pindexFork != 0 && pindexFork->GetHeight() < nAssumeutxoSnapshotHeight )
{
return state.DoS(100, error("ActivateBestChainStep(): reorg fork height %d is below the loaded UTXO snapshot height %d; refusing",
(int32_t)pindexFork->GetHeight(), nAssumeutxoSnapshotHeight),
REJECT_INVALID, "below-assumeutxo-snapshot");
}
// - On ChainDB initialization, pindexOldTip will be null, so there are no removable blocks.
// - If pindexMostWork is in a chain that doesn't have the same genesis block as our chain,
@@ -6101,15 +6052,6 @@ bool static LoadBlockIndexDB()
pblocktree->ReadReindexing(fReindexing);
fReindex |= fReindexing;
// Restore the loaded-UTXO-snapshot height so the reorg-below-H guard survives restarts.
{
int snapHeight = -1;
if (pblocktree->ReadAssumeutxoHeight(snapHeight) && snapHeight >= 0) {
nAssumeutxoSnapshotHeight = snapHeight;
LogPrintf("%s: loaded-from-UTXO-snapshot height is %d; reorgs below it are refused\n", __func__, snapHeight);
}
}
// Check whether we have a transaction index
pblocktree->ReadFlag("txindex", fTxIndex);
LogPrintf("%s: transaction index %s\n", __func__, fTxIndex ? "enabled" : "disabled");