From ccfada5ca9d5288c33799b269fc0d2ebfc4d7166 Mon Sep 17 00:00:00 2001 From: Sami Ahmed Date: Sun, 26 Apr 2026 18:19:46 -0700 Subject: [PATCH] Port LoadSnapshot + reindex/bootstrap-guard to chain DB abstraction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LoadSnapshot previously opened LevelDB directly at /txleveldb to write the snapshot in. Refactored to use the CTxDBBase abstraction: - WipeChainDataDir() removes the configured backend's chain DB dir - MakeChainDB("c+") opens fresh via the factory - High-level methods (WriteBlockIndex, WriteUtxo, WriteHashBestChain, WriteVersion, WriteDbFormat) replace manual key/value construction - TxnBegin/Commit cycles every 1000 headers / 50000 UTXOs preserve the prior batching cadence The IsRocksDbChainBackend() guard added in 76579e3 is dropped — snapshot loading now works on either backend. Two adjacent paths in init.cpp also hardcoded "txleveldb": the snapshot auto-load guard (Step 6c) and the -reindex datadir wipe. Both updated to GetChainDataDir() / WipeChainDataDir() so they pick the right directory for the configured backend. Helpers added to txdb.h / txdb-factory.cpp: - GetChainDataDir(): on-disk path of the configured backend's chain DB - WipeChainDataDir(): rm -rf the same path Bootstrap archive paths (bootstrap.cpp lines 721+) intentionally still reference txleveldb specifically — the prebuilt-index distribution remains LevelDB-format until that pipeline is ported separately. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/init.cpp | 28 +++++---- src/txdb-factory.cpp | 19 ++++++ src/txdb.h | 14 ++++- src/utxosnapshot.cpp | 145 +++++++++++++++---------------------------- 4 files changed, 95 insertions(+), 111 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index c670f25..a61b40c 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -962,10 +962,11 @@ bool AppInit2() } }; - // Try UTXO snapshot first (fast: ~2-10 MB download) + // Try UTXO snapshot first (fast: ~2-10 MB download). Only attempted + // if the configured backend's chain DB doesn't already exist. bool success = false; bool triedUtxoSnapshot = false; - if (needsBootstrap && !fs::exists(dataPath / "txleveldb")) { + if (needsBootstrap && !fs::exists(GetChainDataDir())) { uiInterface.InitMessage(_("Downloading UTXO snapshot...")); printf("Bootstrap: trying UTXO snapshot from %s (fast path)...\n", host.c_str()); @@ -1002,13 +1003,14 @@ bool AppInit2() #endif // ********************************************************* Step 6c: manual UTXO snapshot loading - // If utxo-snapshot.bin exists in data dir and no txleveldb, load it. + // If utxo-snapshot.bin exists in data dir and the chain DB hasn't been + // initialized for the configured backend, load it. { fs::path dataPath = GetDataDir(); fs::path snapshotFile = dataPath / "utxo-snapshot.bin"; - fs::path txleveldbDir = dataPath / "txleveldb"; + fs::path chainDbDir = GetChainDataDir(); - if (fs::exists(snapshotFile) && !fs::exists(txleveldbDir)) { + if (fs::exists(snapshotFile) && !fs::exists(chainDbDir)) { printf("Found utxo-snapshot.bin — loading UTXO snapshot...\n"); uiInterface.InitMessage(_("Loading UTXO snapshot...")); @@ -1040,16 +1042,16 @@ bool AppInit2() return false; } - // Handle -reindex: delete the LevelDB block index so it gets rebuilt - // from the raw blk*.dat files via FastImportBlockFile(). - // This recalculates money supply, tx index, and UTXO set from scratch. + // Handle -reindex: delete the chain DB so it gets rebuilt from the raw + // blk*.dat files via FastImportBlockFile(). This recalculates money + // supply, tx index, and UTXO set from scratch. Backend-agnostic via + // WipeChainDataDir(), which resolves the directory per the configured + // -chaindb backend. if (GetBoolArg("-reindex", false)) { - printf("Reindex requested: removing block index database...\n"); - uiInterface.InitMessage(_("Removing block index for reindex...")); - fs::path txleveldbPath = GetDataDir() / "txleveldb"; - if (fs::exists(txleveldbPath)) - fs::remove_all(txleveldbPath); + printf("Reindex requested: removing chain database...\n"); + uiInterface.InitMessage(_("Removing chain database for reindex...")); + WipeChainDataDir(); } uiInterface.InitMessage(_("Loading block index...")); diff --git a/src/txdb-factory.cpp b/src/txdb-factory.cpp index 7379941..95957ee 100644 --- a/src/txdb-factory.cpp +++ b/src/txdb-factory.cpp @@ -5,9 +5,12 @@ #include "txdb.h" #include "util.h" +#include #include #include +namespace fs = std::filesystem; + namespace { // Pick the backend once per process. -chaindb is a startup flag; switching at @@ -52,3 +55,19 @@ bool IsRocksDbChainBackend() { return ResolveChainDbKind() == ChainDbKind::RocksDB; } + +std::filesystem::path GetChainDataDir() +{ + switch (ResolveChainDbKind()) { + case ChainDbKind::LevelDB: return GetDataDir() / "txleveldb"; + case ChainDbKind::RocksDB: return GetDataDir() / "rocksdb"; + } + return GetDataDir() / "txleveldb"; // unreachable +} + +void WipeChainDataDir() +{ + fs::path p = GetChainDataDir(); + if (fs::exists(p)) + fs::remove_all(p); +} diff --git a/src/txdb.h b/src/txdb.h index 4739200..0328c42 100644 --- a/src/txdb.h +++ b/src/txdb.h @@ -11,6 +11,7 @@ #include "txdb-leveldb.h" #include "txdb-rocksdb.h" +#include #include // Factory: returns a chain-database handle whose concrete backend is chosen @@ -24,9 +25,16 @@ // 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. +// True when the configured chain-DB backend is RocksDB. bool IsRocksDbChainBackend(); +// On-disk directory of the chain DB for the configured backend, e.g. +// /txleveldb (LevelDB) or /rocksdb (RocksDB). +std::filesystem::path GetChainDataDir(); + +// Remove the chain DB directory for the configured backend. Callers that +// need a fresh DB (-reindex, snapshot load) must invoke this BEFORE +// MakeChainDB() opens the global handle for the first time. +void WipeChainDataDir(); + #endif // TRIANGLES_TXDB_H diff --git a/src/utxosnapshot.cpp b/src/utxosnapshot.cpp index efe04a0..f86a518 100644 --- a/src/utxosnapshot.cpp +++ b/src/utxosnapshot.cpp @@ -11,11 +11,6 @@ #include -#include -#include -#include -#include - #include #include @@ -200,22 +195,9 @@ bool DumpSnapshot(const fs::path& destPath, // --------------------------------------------------------------------------- bool LoadSnapshot(const fs::path& snapshotPath, - const fs::path& dataDir, + const fs::path& /*dataDir — unused; resolved per-backend via GetChainDataDir()*/, 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(); @@ -281,47 +263,42 @@ bool LoadSnapshot(const fs::path& snapshotPath, printf("UtxoSnapshot: loading snapshot at height %d (%d headers, %d UTXOs)\n", height, numHeaders, numUtxos); - // Create fresh LevelDB directory - fs::path txleveldbPath = dataDir / "txleveldb"; - if (fs::exists(txleveldbPath)) - fs::remove_all(txleveldbPath); - fs::create_directories(txleveldbPath); + // Wipe the chain DB directory for the configured backend, then open fresh + // via the factory. Must run before any other code touches the chain DB + // (the global handle is opened lazily on first MakeChainDB call). + WipeChainDataDir(); - // Open LevelDB directly (not via CTxDB - it's not initialized yet) - leveldb::Options options; - int nCacheSizeMB = GetArg("-dbcache", 2048); - options.block_cache = leveldb::NewLRUCache(nCacheSizeMB * 1048576); - options.filter_policy = leveldb::NewBloomFilterPolicy(10); - options.write_buffer_size = 64 * 1048576; - options.max_open_files = 1000; - options.create_if_missing = true; - - leveldb::DB* pdb = NULL; - leveldb::Status status = leveldb::DB::Open(options, txleveldbPath.string(), &pdb); - if (!status.ok()) { + auto txdbHolder = MakeChainDB("c+"); + if (!txdbHolder) { fclose(file); - delete options.filter_policy; - delete options.block_cache; - strError = "Cannot create LevelDB: " + status.ToString(); + strError = "Failed to open fresh chain DB"; return false; } + CTxDBBase& txdb = *txdbHolder; SHA256_CTX sha256; SHA256_Init(&sha256); - leveldb::WriteBatch batch; bool success = true; unsigned int nBatchSize = 0; + if (!txdb.TxnBegin()) { + fclose(file); + strError = "Failed to begin chain DB transaction"; + return false; + } + auto flushBatch = [&]() -> bool { if (nBatchSize == 0) return true; - leveldb::Status s = pdb->Write(leveldb::WriteOptions(), &batch); - if (!s.ok()) { - strError = "LevelDB write failed: " + s.ToString(); + if (!txdb.TxnCommit()) { + strError = "Chain DB batch commit failed"; + return false; + } + if (!txdb.TxnBegin()) { + strError = "Chain DB batch restart failed"; return false; } - batch.Clear(); nBatchSize = 0; return true; }; @@ -355,14 +332,11 @@ bool LoadSnapshot(const fs::path& snapshotPath, ssEntry >> entryHash; ssEntry >> diskindex; - // Write to LevelDB as "blockindex" key - CDataStream ssKey(SER_DISK, CLIENT_VERSION); - ssKey << std::make_pair(std::string("blockindex"), entryHash); - - CDataStream ssValue(SER_DISK, CLIENT_VERSION); - ssValue << diskindex; - - batch.Put(ssKey.str(), ssValue.str()); + if (!txdb.WriteBlockIndex(diskindex)) { + success = false; + strError = "WriteBlockIndex failed at header " + std::to_string(i); + break; + } nBatchSize++; if (nBatchSize >= 1000) { @@ -404,14 +378,11 @@ bool LoadSnapshot(const fs::path& snapshotPath, ssRecord >> nIndex; ssRecord >> entry; - // Write to LevelDB with "u" prefix key - CDataStream ssKey(SER_DISK, CLIENT_VERSION); - ssKey << std::make_pair(std::string("u"), std::make_pair(txhash, nIndex)); - - CDataStream ssValue(SER_DISK, CLIENT_VERSION); - ssValue << entry; - - batch.Put(ssKey.str(), ssValue.str()); + if (!txdb.WriteUtxo(txhash, nIndex, entry)) { + success = false; + strError = "WriteUtxo failed at index " + std::to_string(i); + break; + } nBatchSize++; if (nBatchSize >= 50000) { @@ -441,50 +412,34 @@ bool LoadSnapshot(const fs::path& snapshotPath, } } - // Write metadata + // Write metadata via the abstraction's named operations. These produce + // bit-identical key bytes across backends, so the new DB is in the same + // canonical state as it would be after a normal IBD. if (success) { - leveldb::WriteBatch metaBatch; - - // hashBestChain - CDataStream ssKey1(SER_DISK, CLIENT_VERSION); - ssKey1 << std::string("hashBestChain"); - CDataStream ssVal1(SER_DISK, CLIENT_VERSION); - ssVal1 << blockHash; - metaBatch.Put(ssKey1.str(), ssVal1.str()); - - // dbformat = 3 - CDataStream ssKey2(SER_DISK, CLIENT_VERSION); - ssKey2 << std::string("dbformat"); - CDataStream ssVal2(SER_DISK, CLIENT_VERSION); - ssVal2 << (int)3; - metaBatch.Put(ssKey2.str(), ssVal2.str()); - - // version - CDataStream ssKey3(SER_DISK, CLIENT_VERSION); - ssKey3 << std::string("version"); - CDataStream ssVal3(SER_DISK, CLIENT_VERSION); - ssVal3 << DATABASE_VERSION; - metaBatch.Put(ssKey3.str(), ssVal3.str()); - - leveldb::Status s = pdb->Write(leveldb::WriteOptions(), &metaBatch); - if (!s.ok()) { + if (!txdb.TxnBegin()) { success = false; - strError = "Failed to write metadata: " + s.ToString(); + strError = "Failed to begin metadata transaction"; + } + } + if (success) { + if (!txdb.WriteHashBestChain(blockHash) || + !txdb.WriteDbFormat(3) || + !txdb.WriteVersion(DATABASE_VERSION) || + !txdb.TxnCommit()) + { + success = false; + strError = "Failed to write snapshot metadata"; } } - - // Clean up LevelDB - delete pdb; - delete options.filter_policy; - delete options.block_cache; fclose(file); if (!success) { - // Remove corrupted/incomplete database + // Roll back any pending batch and remove the partial DB so the next + // startup begins from a clean slate. + txdb.TxnAbort(); printf("UtxoSnapshot: load failed: %s\n", strError.c_str()); - if (fs::exists(txleveldbPath)) - fs::remove_all(txleveldbPath); + WipeChainDataDir(); return false; }