From 76579e3059bfe7be4da652d1259858329e28fc42 Mon Sep 17 00:00:00 2001 From: Sami Ahmed Date: Sun, 26 Apr 2026 17:43:17 -0700 Subject: [PATCH] Port UtxoSnapshot::DumpSnapshot to CTxDBBase iterator DumpSnapshot reached into the LevelDB backend's internal handle via `extern leveldb::DB *txdb`, which silently broke under -chaindb=rocksdb. Switched to the backend-agnostic CTxDBBase::NewIterator() interface; the function now works against either backend. LoadSnapshot is more involved (writes directly into a fresh txleveldb/ directory) and is bundled with the eventual LevelDB retirement. Added an IsRocksDbChainBackend() helper and an explicit guard at LoadSnapshot's entry: refuse to load with a clear error message rather than silently creating a leveldb tree alongside an active rocksdb chain. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/txdb-factory.cpp | 5 +++++ src/txdb.h | 5 +++++ src/utxosnapshot.cpp | 43 +++++++++++++++++++++++++++---------------- 3 files changed, 37 insertions(+), 16 deletions(-) diff --git a/src/txdb-factory.cpp b/src/txdb-factory.cpp index 60cc5a7..15550f0 100644 --- a/src/txdb-factory.cpp +++ b/src/txdb-factory.cpp @@ -62,3 +62,8 @@ std::unique_ptr MakeChainDB(const char* pszMode) // Unreachable — ResolveChainDbKind throws on bad input. return nullptr; } + +bool IsRocksDbChainBackend() +{ + return ResolveChainDbKind() == ChainDbKind::RocksDB; +} diff --git a/src/txdb.h b/src/txdb.h index b2ceeb3..70d294e 100644 --- a/src/txdb.h +++ b/src/txdb.h @@ -27,4 +27,9 @@ // CTxDB constructor convention. std::unique_ptr MakeChainDB(const char* pszMode = "r+"); +// True when the configured chain-DB backend is RocksDB. Used by code paths +// (e.g. UtxoSnapshot::LoadSnapshot) that haven't yet been ported off direct +// LevelDB calls — they error out cleanly instead of corrupting state. +bool IsRocksDbChainBackend(); + #endif // TRIANGLES_TXDB_H diff --git a/src/utxosnapshot.cpp b/src/utxosnapshot.cpp index e0f6b92..efe04a0 100644 --- a/src/utxosnapshot.cpp +++ b/src/utxosnapshot.cpp @@ -24,9 +24,6 @@ namespace fs = std::filesystem; -// Global LevelDB pointer (defined in txdb-leveldb.cpp) -extern leveldb::DB *txdb; - namespace UtxoSnapshot { // --------------------------------------------------------------------------- @@ -60,12 +57,14 @@ bool DumpSnapshot(const fs::path& destPath, std::reverse(vHeaders.begin(), vHeaders.end()); } + // Open the chain DB once and reuse for both the UTXO count and the + // iteration below. Backend-agnostic via the CTxDBBase abstraction. + auto txdbHolder = MakeChainDB("r"); + CTxDBBase& txdbRead = *txdbHolder; + // Count UTXOs first int nUtxoCount = 0; - { - auto txdbRead_holder = MakeChainDB("r"); CTxDBBase& txdbRead = *txdbRead_holder; - txdbRead.SumUtxoValues(nUtxoCount); - } + txdbRead.SumUtxoValues(nUtxoCount); if (nUtxoCount == 0) { strError = "No UTXOs found in database"; @@ -124,31 +123,31 @@ bool DumpSnapshot(const fs::path& destPath, SHA256_Update(&sha256, strEntry.data(), entrySize); } - // Write UTXO section using LevelDB iterator (same pattern as SumUtxoValues) + // Write UTXO section using the backend-agnostic iterator (same pattern as + // SumUtxoValues). Iteration runs outside any active batch — the contract + // documented on CTxDBIteratorBase guarantees a stable view of committed state. { - CDataStream ssKeyPrefix(SER_DISK, CLIENT_VERSION); ssKeyPrefix << std::make_pair(std::string("u"), std::make_pair(uint256(0), (unsigned int)0)); std::string strPrefixBegin = ssKeyPrefix.str(); - leveldb::Iterator* it = txdb->NewIterator(leveldb::ReadOptions()); + auto it = txdbRead.NewIterator(); unsigned int nWritten = 0; for (it->Seek(strPrefixBegin); it->Valid(); it->Next()) { - // Check key prefix is still "u" - CDataStream ssKey(it->key().data(), it->key().data() + it->key().size(), SER_DISK, CLIENT_VERSION); + std::string strKey = it->KeyStr(); + CDataStream ssKey(strKey.data(), strKey.data() + strKey.size(), SER_DISK, CLIENT_VERSION); std::string strKeyType; ssKey >> strKeyType; if (strKeyType != "u") break; - // Extract outpoint from key uint256 txhash; unsigned int nIndex; ssKey >> txhash; ssKey >> nIndex; - // Extract UTXO entry from value - CDataStream ssValue(it->value().data(), it->value().data() + it->value().size(), SER_DISK, CLIENT_VERSION); + std::string strRawValue = it->ValueStr(); + CDataStream ssValue(strRawValue.data(), strRawValue.data() + strRawValue.size(), SER_DISK, CLIENT_VERSION); CUtxoEntry entry; ssValue >> entry; @@ -170,7 +169,6 @@ bool DumpSnapshot(const fs::path& destPath, if (nWritten % 10000 == 0) printf("UtxoSnapshot: wrote %d / %d UTXOs\n", nWritten, nUtxoCount); } - delete it; // Update actual count (in case it changed during iteration) if (nWritten != numUtxos) { @@ -205,6 +203,19 @@ bool LoadSnapshot(const fs::path& snapshotPath, const fs::path& dataDir, std::string& strError) { + // The loader writes the snapshot directly into a fresh txleveldb/ + // directory using the LevelDB API. Porting it to the CTxDBBase abstraction + // requires a "wipe + create-fresh + write-batch" path that doesn't exist + // on the base class yet — that work is bundled with the Phase-4 LevelDB + // retirement. Until then, refuse to load under rocksdb instead of silently + // creating a leveldb tree alongside an active rocksdb chain. + if (IsRocksDbChainBackend()) { + strError = "UTXO snapshot loading is not yet supported under " + "-chaindb=rocksdb. Run with -chaindb=leveldb to load this " + "snapshot, or sync from genesis."; + return false; + } + FILE* file = fopen(snapshotPath.string().c_str(), "rb"); if (!file) { strError = "Cannot open snapshot file: " + snapshotPath.string();