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:
-235
@@ -271,233 +271,6 @@ bool CCoinsViewDB::GetStats(CCoinsStats &stats) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Helper: count entries in the coins DB whose key prefix matches `prefix`.
|
||||
// LevelDB returns keys in sorted order, so iteration is deterministic across nodes.
|
||||
static uint64_t CountByPrefix(CDBWrapper &db, char prefix)
|
||||
{
|
||||
boost::scoped_ptr<CDBIterator> pcursor(db.NewIterator());
|
||||
uint64_t n = 0;
|
||||
for (pcursor->Seek(prefix); pcursor->Valid(); pcursor->Next()) {
|
||||
boost::this_thread::interruption_point();
|
||||
std::pair<char, uint256> key;
|
||||
if (pcursor->GetKey(key) && key.first == prefix) n++;
|
||||
else break;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
bool CCoinsViewDB::DumpSnapshot(const std::string &path, CUTXOSnapshotHeader &header, uint256 &hashRet, std::string &strError) const
|
||||
{
|
||||
CDBWrapper *pdb = const_cast<CDBWrapper*>(&db);
|
||||
|
||||
// Counting pass (caller holds cs_main and has flushed, so the set is stable).
|
||||
header.nCoins = CountByPrefix(*pdb, DB_COINS);
|
||||
header.nSaplingAnchors = CountByPrefix(*pdb, DB_SAPLING_ANCHOR);
|
||||
header.nSaplingNullifiers = CountByPrefix(*pdb, DB_SAPLING_NULLIFIER);
|
||||
|
||||
FILE *f = fopen(path.c_str(), "wb");
|
||||
if (f == nullptr) { strError = "cannot open snapshot file for writing: " + path; return false; }
|
||||
CAutoFile fileout(f, SER_DISK, CLIENT_VERSION);
|
||||
|
||||
// The content hash is computed over the same logical object stream the loader will
|
||||
// reconstruct, so producer and consumer agree regardless of on-disk encoding.
|
||||
CHashWriter hasher(SER_GETHASH, PROTOCOL_VERSION);
|
||||
|
||||
fileout << header;
|
||||
hasher << header;
|
||||
|
||||
// Coins ('c')
|
||||
{
|
||||
boost::scoped_ptr<CDBIterator> pcursor(pdb->NewIterator());
|
||||
uint64_t n = 0;
|
||||
for (pcursor->Seek(DB_COINS); pcursor->Valid(); pcursor->Next()) {
|
||||
boost::this_thread::interruption_point();
|
||||
std::pair<char, uint256> key;
|
||||
CCoins coins;
|
||||
if (pcursor->GetKey(key) && key.first == DB_COINS) {
|
||||
if (!pcursor->GetValue(coins)) { strError = "failed reading coins record"; return false; }
|
||||
fileout << key.second; hasher << key.second;
|
||||
fileout << coins; hasher << coins;
|
||||
n++;
|
||||
} else break;
|
||||
}
|
||||
if (n != header.nCoins) { strError = "coin count changed during dump"; return false; }
|
||||
}
|
||||
|
||||
// Sapling anchors ('Z') — the commitment trees referenced by spends above H.
|
||||
{
|
||||
boost::scoped_ptr<CDBIterator> pcursor(pdb->NewIterator());
|
||||
uint64_t n = 0;
|
||||
for (pcursor->Seek(DB_SAPLING_ANCHOR); pcursor->Valid(); pcursor->Next()) {
|
||||
boost::this_thread::interruption_point();
|
||||
std::pair<char, uint256> key;
|
||||
SaplingMerkleTree tree;
|
||||
if (pcursor->GetKey(key) && key.first == DB_SAPLING_ANCHOR) {
|
||||
if (!pcursor->GetValue(tree)) { strError = "failed reading sapling anchor"; return false; }
|
||||
fileout << key.second; hasher << key.second;
|
||||
fileout << tree; hasher << tree;
|
||||
n++;
|
||||
} else break;
|
||||
}
|
||||
if (n != header.nSaplingAnchors) { strError = "sapling anchor count changed during dump"; return false; }
|
||||
}
|
||||
|
||||
// Sapling nullifiers ('S') — spent markers; value is always true, so only the key matters.
|
||||
{
|
||||
boost::scoped_ptr<CDBIterator> pcursor(pdb->NewIterator());
|
||||
uint64_t n = 0;
|
||||
for (pcursor->Seek(DB_SAPLING_NULLIFIER); pcursor->Valid(); pcursor->Next()) {
|
||||
boost::this_thread::interruption_point();
|
||||
std::pair<char, uint256> key;
|
||||
if (pcursor->GetKey(key) && key.first == DB_SAPLING_NULLIFIER) {
|
||||
fileout << key.second; hasher << key.second;
|
||||
n++;
|
||||
} else break;
|
||||
}
|
||||
if (n != header.nSaplingNullifiers) { strError = "sapling nullifier count changed during dump"; return false; }
|
||||
}
|
||||
|
||||
hashRet = hasher.GetHash();
|
||||
fileout << hashRet; // trailing content hash (not fed into the hasher)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CCoinsViewDB::LoadSnapshot(const std::string &path, const uint256 &expectedHash, bool fRequireExpected,
|
||||
CUTXOSnapshotHeader &headerRet, uint256 &hashRet, std::string &strError)
|
||||
{
|
||||
uint32_t netmagic = 0;
|
||||
memcpy(&netmagic, Params().MessageStart(), 4);
|
||||
|
||||
// ---- Pass 1: read + verify integrity (and the trusted hash) WITHOUT writing to the DB ----
|
||||
CUTXOSnapshotHeader header;
|
||||
uint256 computed;
|
||||
{
|
||||
FILE *f = fopen(path.c_str(), "rb");
|
||||
if (f == nullptr) { strError = "cannot open snapshot file: " + path; return false; }
|
||||
CAutoFile filein(f, SER_DISK, CLIENT_VERSION);
|
||||
CHashWriter hasher(SER_GETHASH, PROTOCOL_VERSION);
|
||||
try {
|
||||
filein >> header; hasher << header;
|
||||
if (header.nMagic != UTXO_SNAPSHOT_MAGIC) { strError = "not a DragonX UTXO snapshot (bad magic)"; return false; }
|
||||
if (header.nVersion != UTXO_SNAPSHOT_VERSION) { strError = "unsupported snapshot version"; return false; }
|
||||
if (header.nNetworkMagic != netmagic) { strError = "snapshot is for a different network"; return false; }
|
||||
|
||||
for (uint64_t i = 0; i < header.nCoins; i++) {
|
||||
boost::this_thread::interruption_point();
|
||||
uint256 txid; CCoins coins;
|
||||
filein >> txid; filein >> coins;
|
||||
hasher << txid; hasher << coins;
|
||||
}
|
||||
for (uint64_t i = 0; i < header.nSaplingAnchors; i++) {
|
||||
boost::this_thread::interruption_point();
|
||||
uint256 root; SaplingMerkleTree tree;
|
||||
filein >> root; filein >> tree;
|
||||
hasher << root; hasher << tree;
|
||||
}
|
||||
for (uint64_t i = 0; i < header.nSaplingNullifiers; i++) {
|
||||
boost::this_thread::interruption_point();
|
||||
uint256 nf;
|
||||
filein >> nf;
|
||||
hasher << nf;
|
||||
}
|
||||
uint256 stored;
|
||||
filein >> stored;
|
||||
computed = hasher.GetHash();
|
||||
if (computed != stored) { strError = "snapshot content hash mismatch (corrupt or truncated)"; return false; }
|
||||
} catch (const std::exception &e) {
|
||||
strError = std::string("error reading snapshot: ") + e.what();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (fRequireExpected && computed != expectedHash) {
|
||||
strError = "snapshot hash does not match the trusted value hardcoded for this network";
|
||||
return false;
|
||||
}
|
||||
hashRet = computed;
|
||||
headerRet = header;
|
||||
|
||||
// ---- Pass 2: apply to the (empty) chainstate DB in bounded batches ----
|
||||
const size_t CHUNK = 100000;
|
||||
CCoinsMap mapCoins;
|
||||
CAnchorsSproutMap mapSproutAnchors; // unused on this chain, always empty
|
||||
CAnchorsSaplingMap mapSaplingAnchors;
|
||||
CNullifiersMap mapSproutNullifiers; // unused, always empty
|
||||
CNullifiersMap mapSaplingNullifiers;
|
||||
{
|
||||
FILE *f = fopen(path.c_str(), "rb");
|
||||
if (f == nullptr) { strError = "cannot reopen snapshot file: " + path; return false; }
|
||||
CAutoFile filein(f, SER_DISK, CLIENT_VERSION);
|
||||
try {
|
||||
CUTXOSnapshotHeader hdr2;
|
||||
filein >> hdr2; // header already validated in pass 1
|
||||
|
||||
for (uint64_t i = 0; i < header.nCoins; i++) {
|
||||
boost::this_thread::interruption_point();
|
||||
uint256 txid; CCoins coins;
|
||||
filein >> txid; filein >> coins;
|
||||
CCoinsCacheEntry &e = mapCoins[txid];
|
||||
e.coins = coins;
|
||||
e.flags = CCoinsCacheEntry::DIRTY;
|
||||
if (mapCoins.size() >= CHUNK) {
|
||||
if (!BatchWrite(mapCoins, uint256(), uint256(), uint256(), mapSproutAnchors, mapSaplingAnchors, mapSproutNullifiers, mapSaplingNullifiers))
|
||||
{ strError = "batch write failed (coins)"; return false; }
|
||||
mapCoins.clear();
|
||||
}
|
||||
}
|
||||
if (!mapCoins.empty()) {
|
||||
if (!BatchWrite(mapCoins, uint256(), uint256(), uint256(), mapSproutAnchors, mapSaplingAnchors, mapSproutNullifiers, mapSaplingNullifiers))
|
||||
{ strError = "batch write failed (coins remainder)"; return false; }
|
||||
mapCoins.clear();
|
||||
}
|
||||
|
||||
for (uint64_t i = 0; i < header.nSaplingAnchors; i++) {
|
||||
boost::this_thread::interruption_point();
|
||||
uint256 root; SaplingMerkleTree tree;
|
||||
filein >> root; filein >> tree;
|
||||
CAnchorsSaplingCacheEntry &e = mapSaplingAnchors[root];
|
||||
e.entered = true;
|
||||
e.tree = tree;
|
||||
e.flags = CAnchorsSaplingCacheEntry::DIRTY;
|
||||
if (mapSaplingAnchors.size() >= CHUNK) {
|
||||
if (!BatchWrite(mapCoins, uint256(), uint256(), uint256(), mapSproutAnchors, mapSaplingAnchors, mapSproutNullifiers, mapSaplingNullifiers))
|
||||
{ strError = "batch write failed (anchors)"; return false; }
|
||||
mapSaplingAnchors.clear();
|
||||
}
|
||||
}
|
||||
if (!mapSaplingAnchors.empty()) {
|
||||
if (!BatchWrite(mapCoins, uint256(), uint256(), uint256(), mapSproutAnchors, mapSaplingAnchors, mapSproutNullifiers, mapSaplingNullifiers))
|
||||
{ strError = "batch write failed (anchors remainder)"; return false; }
|
||||
mapSaplingAnchors.clear();
|
||||
}
|
||||
|
||||
for (uint64_t i = 0; i < header.nSaplingNullifiers; i++) {
|
||||
boost::this_thread::interruption_point();
|
||||
uint256 nf;
|
||||
filein >> nf;
|
||||
CNullifiersCacheEntry &e = mapSaplingNullifiers[nf];
|
||||
e.entered = true;
|
||||
e.flags = CNullifiersCacheEntry::DIRTY;
|
||||
if (mapSaplingNullifiers.size() >= CHUNK) {
|
||||
if (!BatchWrite(mapCoins, uint256(), uint256(), uint256(), mapSproutAnchors, mapSaplingAnchors, mapSproutNullifiers, mapSaplingNullifiers))
|
||||
{ strError = "batch write failed (nullifiers)"; return false; }
|
||||
mapSaplingNullifiers.clear();
|
||||
}
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
strError = std::string("error applying snapshot: ") + e.what();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Final write: flush any remaining nullifiers AND set the best-block / best-sapling-anchor
|
||||
// pointers, so GetBestBlock()==H and GetBestAnchor(SAPLING) resolve after load.
|
||||
if (!BatchWrite(mapCoins, header.baseBlockHash, uint256(), header.bestSaplingAnchor,
|
||||
mapSproutAnchors, mapSaplingAnchors, mapSproutNullifiers, mapSaplingNullifiers))
|
||||
{ strError = "final batch write failed"; return false; }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBlockTreeDB::WriteBatchSync(const std::vector<std::pair<int, const CBlockFileInfo*> >& fileInfo, int nLastFile, const std::vector<CBlockIndex*>& blockinfo) {
|
||||
CDBBatch batch(*this);
|
||||
if (fDebug)
|
||||
@@ -884,14 +657,6 @@ bool CBlockTreeDB::ReadFlag(const std::string &name, bool &fValue) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBlockTreeDB::WriteAssumeutxoHeight(int nHeight) {
|
||||
return Write(std::make_pair(DB_FLAG, std::string("assumeutxoheight")), nHeight);
|
||||
}
|
||||
|
||||
bool CBlockTreeDB::ReadAssumeutxoHeight(int &nHeight) const {
|
||||
return Read(std::make_pair(DB_FLAG, std::string("assumeutxoheight")), nHeight);
|
||||
}
|
||||
|
||||
void hush_index2pubkey33(uint8_t *pubkey33,CBlockIndex *pindex,int32_t height);
|
||||
|
||||
bool CBlockTreeDB::blockOnchainActive(const uint256 &hash) {
|
||||
|
||||
Reference in New Issue
Block a user