diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index 39639c8..267d62b 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -20,7 +20,7 @@ jobs: sudo apt-get update sudo apt-get install -y build-essential cmake ninja-build \ libboost-all-dev libssl-dev libdb++-dev libleveldb-dev \ - libevent-dev libminiupnpc-dev zlib1g-dev + librocksdb-dev libevent-dev libminiupnpc-dev zlib1g-dev - name: Configure run: | @@ -60,7 +60,7 @@ jobs: sudo apt-get update sudo apt-get install -y build-essential cmake ninja-build \ libboost-all-dev libssl-dev libdb++-dev libleveldb-dev \ - libevent-dev libminiupnpc-dev zlib1g-dev + librocksdb-dev libevent-dev libminiupnpc-dev zlib1g-dev - name: Configure with sanitizers run: | @@ -105,6 +105,7 @@ jobs: mingw-w64-x86_64-libevent mingw-w64-x86_64-miniupnpc mingw-w64-x86_64-zlib + mingw-w64-x86_64-rocksdb - name: Set VERSION run: | @@ -253,6 +254,7 @@ jobs: mingw-w64-x86_64-libevent mingw-w64-x86_64-miniupnpc mingw-w64-x86_64-zlib + mingw-w64-x86_64-rocksdb - name: Configure run: | @@ -318,7 +320,7 @@ jobs: sudo apt-get install -y build-essential cmake ninja-build \ qtbase5-dev qttools5-dev-tools \ libboost-all-dev libssl-dev libdb++-dev \ - libleveldb-dev libevent-dev libminiupnpc-dev zlib1g-dev + libleveldb-dev librocksdb-dev libevent-dev libminiupnpc-dev zlib1g-dev - name: Configure run: | @@ -434,7 +436,7 @@ jobs: sudo apt-get update sudo apt-get install -y build-essential cmake ninja-build \ libboost-all-dev libssl-dev libdb++-dev libleveldb-dev \ - libevent-dev libminiupnpc-dev zlib1g-dev + librocksdb-dev libevent-dev libminiupnpc-dev zlib1g-dev - name: Configure run: | @@ -561,7 +563,7 @@ jobs: - name: Install dependencies run: | - brew install cmake ninja qt@5 openssl@3 boost berkeley-db@5 leveldb libevent miniupnpc + brew install cmake ninja qt@5 openssl@3 boost berkeley-db@5 leveldb rocksdb libevent miniupnpc - name: Configure run: | diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 9646b8f..c93487e 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -55,7 +55,7 @@ jobs: sudo apt-get update sudo apt-get install -y build-essential cmake ninja-build clang-tidy-15 \ libboost-all-dev libssl-dev libdb++-dev libleveldb-dev \ - libevent-dev libminiupnpc-dev zlib1g-dev + librocksdb-dev libevent-dev libminiupnpc-dev zlib1g-dev sudo ln -sf /usr/bin/clang-tidy-15 /usr/local/bin/clang-tidy - name: Configure (export compile_commands.json) diff --git a/CMakeLists.txt b/CMakeLists.txt index fbd3e77..58bfa36 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,7 +52,6 @@ option(USE_QRCODE "Enable QR code generation via libqrencode" OFF option(USE_DBUS "Enable D-Bus notifications (Linux only)" ON) option(USE_ZMQ "Enable ZMQ publisher support" OFF) option(USE_TOR_EMBEDDED "Enable embedded Tor library linking" OFF) -option(BUILD_ROCKSDB "Build with RocksDB chain database backend" OFF) option(USE_O3 "Use -O3 optimization instead of -O2" OFF) option(ENABLE_PIE "Build position-independent executables" OFF) option(ENABLE_STATIC "Prefer static linking (Linux release builds)" OFF) @@ -96,16 +95,13 @@ if(USE_ZMQ) pkg_check_modules(ZMQ REQUIRED IMPORTED_TARGET libzmq) endif() -if(BUILD_ROCKSDB) - # RocksDB ships a CMake config package on most distros (rocksdbConfig.cmake). - # On MSYS2/Homebrew/vcpkg the imported target is RocksDB::rocksdb. - find_package(RocksDB CONFIG) - if(NOT RocksDB_FOUND) - # Fall back to pkg-config for systems without the CMake config (older - # Linux distros). Builds an IMPORTED target named PkgConfig::RocksDB. - find_package(PkgConfig REQUIRED) - pkg_check_modules(RocksDB REQUIRED IMPORTED_TARGET rocksdb) - endif() +# RocksDB is now a hard dependency: backs both the chain database and the +# secure-messaging store (smessage). On MSYS2/Homebrew/vcpkg the imported +# target is RocksDB::rocksdb; on older Linux distros we fall back to pkg-config. +find_package(RocksDB CONFIG) +if(NOT RocksDB_FOUND) + find_package(PkgConfig REQUIRED) + pkg_check_modules(RocksDB REQUIRED IMPORTED_TARGET rocksdb) endif() if(BUILD_QT) @@ -143,7 +139,6 @@ message(STATUS " QR code: ${USE_QRCODE}") message(STATUS " D-Bus: ${USE_DBUS}") message(STATUS " ZMQ: ${USE_ZMQ}") message(STATUS " Embedded Tor: ${USE_TOR_EMBEDDED}") -message(STATUS " RocksDB backend: ${BUILD_ROCKSDB}") message(STATUS " Static linking: ${ENABLE_STATIC}") message(STATUS " ccache: ${CCACHE_PROGRAM}") message(STATUS " Unity build: ${ENABLE_UNITY_BUILD}") diff --git a/contrib/bench/README.md b/contrib/bench/README.md index 1c0e3d0..52b2b0a 100644 --- a/contrib/bench/README.md +++ b/contrib/bench/README.md @@ -5,13 +5,13 @@ Measures `FastImportBlockFile()` speed under each chain-DB backend ## Prerequisites -- A `trianglesd` binary built with both backends: +- A `trianglesd` binary (RocksDB is now a hard build dep, both backends are + always available): ``` cmake -B build -G Ninja \ -DCMAKE_BUILD_TYPE=Release \ -DBUILD_QT=OFF \ - -DBUILD_DAEMON=ON \ - -DBUILD_ROCKSDB=ON + -DBUILD_DAEMON=ON cmake --build build ``` - An `blk0001.dat` file (old-style block stream). If you have a synced diff --git a/contrib/bench/bench-chaindb.sh b/contrib/bench/bench-chaindb.sh index bb33e8a..84ef18d 100644 --- a/contrib/bench/bench-chaindb.sh +++ b/contrib/bench/bench-chaindb.sh @@ -17,7 +17,7 @@ # [--rpc-port=BASE] default 19112; each run uses BASE+offset # # Notes: -# - The binary must be built with BUILD_ROCKSDB=ON when --backends includes rocksdb. +# - RocksDB is a hard build dep, so any current trianglesd has both backends. # - This script does not assume Tor is configured. It launches with -nolisten # and -connect=0 to keep the run network-isolated. # - Wall time comes from the daemon's own perf log line: diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 501ff86..b0e6c85 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -97,10 +97,9 @@ elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "arm|ARM") list(APPEND CORE_SOURCES scrypt-arm.S) endif() -# Optional: RocksDB chain database backend -if(BUILD_ROCKSDB) - list(APPEND CORE_SOURCES txdb-rocksdb.cpp) -endif() +# RocksDB chain database backend (always built; see top-level CMakeLists.txt +# for the rationale — RocksDB also backs the smessage store). +list(APPEND CORE_SOURCES txdb-rocksdb.cpp) add_library(triangles_common OBJECT ${CORE_SOURCES}) @@ -148,14 +147,11 @@ if(USE_ZMQ) target_link_libraries(triangles_common PUBLIC PkgConfig::ZMQ) endif() -# Optional: RocksDB -if(BUILD_ROCKSDB) - target_compile_definitions(triangles_common PUBLIC BUILD_ROCKSDB) - if(TARGET RocksDB::rocksdb) - target_link_libraries(triangles_common PUBLIC RocksDB::rocksdb) - elseif(TARGET PkgConfig::RocksDB) - target_link_libraries(triangles_common PUBLIC PkgConfig::RocksDB) - endif() +# RocksDB (mandatory) +if(TARGET RocksDB::rocksdb) + target_link_libraries(triangles_common PUBLIC RocksDB::rocksdb) +elseif(TARGET PkgConfig::RocksDB) + target_link_libraries(triangles_common PUBLIC PkgConfig::RocksDB) endif() # Optional: Embedded Tor diff --git a/src/qt/messagemodel.cpp b/src/qt/messagemodel.cpp index c5aca57..e25c878 100644 --- a/src/qt/messagemodel.cpp +++ b/src/qt/messagemodel.cpp @@ -93,7 +93,7 @@ public: QDateTime received_datetime; std::string sPrefix("im"); - leveldb::Iterator* it = dbSmsg.pdb->NewIterator(leveldb::ReadOptions()); + rocksdb::Iterator* it = dbSmsg.pdb->NewIterator(rocksdb::ReadOptions()); while (dbSmsg.NextSmesg(it, sPrefix, chKey, smsgStored)) { uint32_t nPayload = smsgStored.vchMessage.size() - SMSG_HDR_LEN; @@ -121,7 +121,7 @@ public: delete it; sPrefix = "sm"; - it = dbSmsg.pdb->NewIterator(leveldb::ReadOptions()); + it = dbSmsg.pdb->NewIterator(rocksdb::ReadOptions()); while (dbSmsg.NextSmesg(it, sPrefix, chKey, smsgStored)) { uint32_t nPayload = smsgStored.vchMessage.size() - SMSG_HDR_LEN; diff --git a/src/rpcsmessage.cpp b/src/rpcsmessage.cpp index 4c8e45d..68834d2 100644 --- a/src/rpcsmessage.cpp +++ b/src/rpcsmessage.cpp @@ -609,7 +609,7 @@ Value smsginbox(const Array& params, bool fHelp) { dbInbox.TxnBegin(); - leveldb::Iterator* it = dbInbox.pdb->NewIterator(leveldb::ReadOptions()); + rocksdb::Iterator* it = dbInbox.pdb->NewIterator(rocksdb::ReadOptions()); while (dbInbox.NextSmesgKey(it, sPrefix, chKey)) { dbInbox.EraseSmesg(chKey); @@ -631,7 +631,7 @@ Value smsginbox(const Array& params, bool fHelp) dbInbox.TxnBegin(); - leveldb::Iterator* it = dbInbox.pdb->NewIterator(leveldb::ReadOptions()); + rocksdb::Iterator* it = dbInbox.pdb->NewIterator(rocksdb::ReadOptions()); while (dbInbox.NextSmesg(it, sPrefix, chKey, smsgStored)) { if (fCheckReadStatus @@ -719,7 +719,7 @@ Value smsgoutbox(const Array& params, bool fHelp) { dbOutbox.TxnBegin(); - leveldb::Iterator* it = dbOutbox.pdb->NewIterator(leveldb::ReadOptions()); + rocksdb::Iterator* it = dbOutbox.pdb->NewIterator(rocksdb::ReadOptions()); while (dbOutbox.NextSmesgKey(it, sPrefix, chKey)) { dbOutbox.EraseSmesg(chKey); @@ -736,7 +736,7 @@ Value smsgoutbox(const Array& params, bool fHelp) { SecMsgStored smsgStored; MessageData msg; - leveldb::Iterator* it = dbOutbox.pdb->NewIterator(leveldb::ReadOptions()); + rocksdb::Iterator* it = dbOutbox.pdb->NewIterator(rocksdb::ReadOptions()); while (dbOutbox.NextSmesg(it, sPrefix, chKey, smsgStored)) { uint32_t nPayload = smsgStored.vchMessage.size() - SMSG_HDR_LEN; @@ -932,7 +932,7 @@ Value smsgbroadcast(const Array& params, bool fHelp) // Iterate all "pk" entries std::string sPrefix("pk"); - leveldb::Iterator* it = dbPub.pdb->NewIterator(leveldb::ReadOptions()); + rocksdb::Iterator* it = dbPub.pdb->NewIterator(rocksdb::ReadOptions()); for (it->Seek(sPrefix); it->Valid(); it->Next()) { std::string key = it->key().ToString(); diff --git a/src/smessage.cpp b/src/smessage.cpp index a713fc5..f6bad5b 100644 --- a/src/smessage.cpp +++ b/src/smessage.cpp @@ -94,7 +94,7 @@ uint32_t nPeerIdCounter = 1; CCriticalSection cs_smsg; CCriticalSection cs_smsgDB; -leveldb::DB *smsgDB = NULL; +rocksdb::DB *smsgDB = NULL; namespace fs = std::filesystem; @@ -401,9 +401,9 @@ bool SecMsgDB::Open(const char* pszMode) return false; }; - leveldb::Options options; + rocksdb::Options options; options.create_if_missing = fCreate; - leveldb::Status s = leveldb::DB::Open(options, fullpath.string(), &smsgDB); + rocksdb::Status s = rocksdb::DB::Open(options, fullpath.string(), &smsgDB); if (!s.ok()) { @@ -417,17 +417,17 @@ bool SecMsgDB::Open(const char* pszMode) }; -class SecMsgBatchScanner : public leveldb::WriteBatch::Handler +class SecMsgBatchScanner : public rocksdb::WriteBatch::Handler { public: std::string needle; bool* deleted; std::string* foundValue; bool foundEntry; - + SecMsgBatchScanner() : foundEntry(false) {} - - virtual void Put(const leveldb::Slice& key, const leveldb::Slice& value) + + virtual void Put(const rocksdb::Slice& key, const rocksdb::Slice& value) override { if (key.ToString() == needle) { @@ -436,8 +436,8 @@ public: *foundValue = value.ToString(); }; }; - - virtual void Delete(const leveldb::Slice& key) + + virtual void Delete(const rocksdb::Slice& key) override { if (key.ToString() == needle) { @@ -462,7 +462,7 @@ bool SecMsgDB::ScanBatch(const CDataStream& key, std::string* value, bool* delet scanner.needle = key.str(); scanner.deleted = deleted; scanner.foundValue = value; - leveldb::Status s = activeBatch->Iterate(&scanner); + rocksdb::Status s = activeBatch->Iterate(&scanner); if (!s.ok()) { printf("SecMsgDB ScanBatch error: %s\n", s.ToString().c_str()); @@ -476,7 +476,7 @@ bool SecMsgDB::TxnBegin() { if (activeBatch) return true; - activeBatch = new leveldb::WriteBatch(); + activeBatch = new rocksdb::WriteBatch(); return true; }; @@ -484,10 +484,10 @@ bool SecMsgDB::TxnCommit() { if (!activeBatch) return false; - - leveldb::WriteOptions writeOptions; + + rocksdb::WriteOptions writeOptions; writeOptions.sync = true; - leveldb::Status status = pdb->Write(writeOptions, activeBatch); + rocksdb::Status status = pdb->Write(writeOptions, activeBatch); delete activeBatch; activeBatch = NULL; @@ -531,12 +531,12 @@ bool SecMsgDB::ReadPK(CKeyID& addr, CPubKey& pubkey) if (readFromDb) { - leveldb::Status s = pdb->Get(leveldb::ReadOptions(), ssKey.str(), &strValue); + rocksdb::Status s = pdb->Get(rocksdb::ReadOptions(), ssKey.str(), &strValue); if (!s.ok()) { if (s.IsNotFound()) return false; - printf("LevelDB read failure: %s\n", s.ToString().c_str()); + printf("RocksDB read failure: %s\n", s.ToString().c_str()); return false; }; }; @@ -572,9 +572,9 @@ bool SecMsgDB::WritePK(CKeyID& addr, CPubKey& pubkey) return true; }; - leveldb::WriteOptions writeOptions; + rocksdb::WriteOptions writeOptions; writeOptions.sync = true; - leveldb::Status s = pdb->Put(writeOptions, ssKey.str(), ssValue.str()); + rocksdb::Status s = pdb->Put(writeOptions, ssKey.str(), ssValue.str()); if (!s.ok()) { printf("SecMsgDB write failure: %s\n", s.ToString().c_str()); @@ -605,12 +605,12 @@ bool SecMsgDB::ExistsPK(CKeyID& addr) }; }; - leveldb::Status s = pdb->Get(leveldb::ReadOptions(), ssKey.str(), &unused); + rocksdb::Status s = pdb->Get(rocksdb::ReadOptions(), ssKey.str(), &unused); return s.IsNotFound() == false; }; -bool SecMsgDB::NextSmesg(leveldb::Iterator* it, std::string& prefix, unsigned char* chKey, SecMsgStored& smsgStored) +bool SecMsgDB::NextSmesg(rocksdb::Iterator* it, std::string& prefix, unsigned char* chKey, SecMsgStored& smsgStored) { if (!pdb) return false; @@ -638,7 +638,7 @@ bool SecMsgDB::NextSmesg(leveldb::Iterator* it, std::string& prefix, unsigned ch return true; }; -bool SecMsgDB::NextSmesgKey(leveldb::Iterator* it, std::string& prefix, unsigned char* chKey) +bool SecMsgDB::NextSmesgKey(rocksdb::Iterator* it, std::string& prefix, unsigned char* chKey) { if (!pdb) return false; @@ -679,12 +679,12 @@ bool SecMsgDB::ReadSmesg(unsigned char* chKey, SecMsgStored& smsgStored) if (readFromDb) { - leveldb::Status s = pdb->Get(leveldb::ReadOptions(), ssKey.str(), &strValue); + rocksdb::Status s = pdb->Get(rocksdb::ReadOptions(), ssKey.str(), &strValue); if (!s.ok()) { if (s.IsNotFound()) return false; - printf("LevelDB read failure: %s\n", s.ToString().c_str()); + printf("RocksDB read failure: %s\n", s.ToString().c_str()); return false; }; }; @@ -716,9 +716,9 @@ bool SecMsgDB::WriteSmesg(unsigned char* chKey, SecMsgStored& smsgStored) return true; }; - leveldb::WriteOptions writeOptions; + rocksdb::WriteOptions writeOptions; writeOptions.sync = true; - leveldb::Status s = pdb->Put(writeOptions, ssKey.str(), ssValue.str()); + rocksdb::Status s = pdb->Put(writeOptions, ssKey.str(), ssValue.str()); if (!s.ok()) { printf("SecMsgDB write failed: %s\n", s.ToString().c_str()); @@ -746,7 +746,7 @@ bool SecMsgDB::ExistsSmesg(unsigned char* chKey) }; }; - leveldb::Status s = pdb->Get(leveldb::ReadOptions(), ssKey.str(), &unused); + rocksdb::Status s = pdb->Get(rocksdb::ReadOptions(), ssKey.str(), &unused); return s.IsNotFound() == false; return true; }; @@ -762,9 +762,9 @@ bool SecMsgDB::EraseSmesg(unsigned char* chKey) return true; }; - leveldb::WriteOptions writeOptions; + rocksdb::WriteOptions writeOptions; writeOptions.sync = true; - leveldb::Status s = pdb->Delete(writeOptions, ssKey.str()); + rocksdb::Status s = pdb->Delete(writeOptions, ssKey.str()); if (s.ok() || s.IsNotFound()) return true; @@ -881,7 +881,7 @@ void ThreadSecureMsgPow(void* parg) // -- sleep at end, then fSecMsgEnabled is tested on wake SecMsgDB dbOutbox; - leveldb::Iterator* it; + rocksdb::Iterator* it; { LOCK(cs_smsgDB); @@ -889,7 +889,7 @@ void ThreadSecureMsgPow(void* parg) continue; // -- fifo (smallest key first) - it = dbOutbox.pdb->NewIterator(leveldb::ReadOptions()); + it = dbOutbox.pdb->NewIterator(rocksdb::ReadOptions()); } // -- break up lock, SecureMsgSetHash will take long diff --git a/src/smessage.h b/src/smessage.h index bdac1f4..2244da7 100644 --- a/src/smessage.h +++ b/src/smessage.h @@ -4,8 +4,8 @@ #ifndef SEC_MESSAGE_H #define SEC_MESSAGE_H -#include -#include +#include +#include #include "net.h" #include "db.h" @@ -314,15 +314,15 @@ public: bool WritePK(CKeyID& addr, CPubKey& pubkey); bool ExistsPK(CKeyID& addr); - bool NextSmesg(leveldb::Iterator* it, std::string& prefix, unsigned char* vchKey, SecMsgStored& smsgStored); - bool NextSmesgKey(leveldb::Iterator* it, std::string& prefix, unsigned char* vchKey); + bool NextSmesg(rocksdb::Iterator* it, std::string& prefix, unsigned char* vchKey, SecMsgStored& smsgStored); + bool NextSmesgKey(rocksdb::Iterator* it, std::string& prefix, unsigned char* vchKey); bool ReadSmesg(unsigned char* chKey, SecMsgStored& smsgStored); bool WriteSmesg(unsigned char* chKey, SecMsgStored& smsgStored); bool ExistsSmesg(unsigned char* chKey); bool EraseSmesg(unsigned char* chKey); - leveldb::DB *pdb; // points to the global instance - leveldb::WriteBatch *activeBatch; + rocksdb::DB *pdb; // points to the global instance + rocksdb::WriteBatch *activeBatch; }; diff --git a/src/txdb-factory.cpp b/src/txdb-factory.cpp index 15550f0..7379941 100644 --- a/src/txdb-factory.cpp +++ b/src/txdb-factory.cpp @@ -24,25 +24,12 @@ ChainDbKind ResolveChainDbKind() if (s == "leveldb") return ChainDbKind::LevelDB; - - if (s == "rocksdb") { -#ifdef BUILD_ROCKSDB + if (s == "rocksdb") return ChainDbKind::RocksDB; -#else - throw std::runtime_error( - "-chaindb=rocksdb requested but this binary was built without " - "BUILD_ROCKSDB. Rebuild with -DBUILD_ROCKSDB=ON, or use " - "-chaindb=leveldb."); -#endif - } throw std::runtime_error( "-chaindb=" + s + " is not a recognized backend. " - "Valid values: leveldb" -#ifdef BUILD_ROCKSDB - ", rocksdb" -#endif - "."); + "Valid values: leveldb, rocksdb."); }(); return kKind; } @@ -54,10 +41,8 @@ std::unique_ptr MakeChainDB(const char* pszMode) switch (ResolveChainDbKind()) { case ChainDbKind::LevelDB: return std::unique_ptr(new CTxDB(pszMode)); -#ifdef BUILD_ROCKSDB case ChainDbKind::RocksDB: return std::unique_ptr(new CRocksTxDB(pszMode)); -#endif } // Unreachable — ResolveChainDbKind throws on bad input. return nullptr; diff --git a/src/txdb-rocksdb.cpp b/src/txdb-rocksdb.cpp index cb93b02..57f15ee 100644 --- a/src/txdb-rocksdb.cpp +++ b/src/txdb-rocksdb.cpp @@ -2,8 +2,6 @@ // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#ifdef BUILD_ROCKSDB - #include "txdb-rocksdb.h" #include @@ -698,5 +696,3 @@ bool CRocksTxDB::LoadBlockIndex() return true; } - -#endif // BUILD_ROCKSDB diff --git a/src/txdb-rocksdb.h b/src/txdb-rocksdb.h index 90ae572..1167f96 100644 --- a/src/txdb-rocksdb.h +++ b/src/txdb-rocksdb.h @@ -5,8 +5,6 @@ #ifndef TRIANGLES_TXDB_ROCKSDB_H #define TRIANGLES_TXDB_ROCKSDB_H -#ifdef BUILD_ROCKSDB - #include "txdb-base.h" #include @@ -52,6 +50,4 @@ private: bool ScanBatch(const std::string& key, std::string* value, bool* deleted) const; }; -#endif // BUILD_ROCKSDB - #endif // TRIANGLES_TXDB_ROCKSDB_H diff --git a/src/txdb.h b/src/txdb.h index 70d294e..4739200 100644 --- a/src/txdb.h +++ b/src/txdb.h @@ -9,18 +9,15 @@ #include "txdb-base.h" #include "txdb-leveldb.h" - -#ifdef BUILD_ROCKSDB #include "txdb-rocksdb.h" -#endif #include // Factory: returns a chain-database handle whose concrete backend is chosen // by the -chaindb command-line argument: // -// -chaindb=leveldb (default) -// -chaindb=rocksdb (only when built with -DBUILD_ROCKSDB=ON) +// -chaindb=leveldb (default — pending Phase-4 retirement) +// -chaindb=rocksdb // // Callers receive a CTxDBBase*, so the rest of the codebase stays // backend-agnostic. Mode strings ("r", "r+", "cr+") match the pre-existing