Port LoadSnapshot + reindex/bootstrap-guard to chain DB abstraction
LoadSnapshot previously opened LevelDB directly at <datadir>/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) <noreply@anthropic.com>
This commit is contained in:
+15
-13
@@ -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 success = false;
|
||||||
bool triedUtxoSnapshot = false;
|
bool triedUtxoSnapshot = false;
|
||||||
if (needsBootstrap && !fs::exists(dataPath / "txleveldb")) {
|
if (needsBootstrap && !fs::exists(GetChainDataDir())) {
|
||||||
uiInterface.InitMessage(_("Downloading UTXO snapshot..."));
|
uiInterface.InitMessage(_("Downloading UTXO snapshot..."));
|
||||||
printf("Bootstrap: trying UTXO snapshot from %s (fast path)...\n", host.c_str());
|
printf("Bootstrap: trying UTXO snapshot from %s (fast path)...\n", host.c_str());
|
||||||
|
|
||||||
@@ -1002,13 +1003,14 @@ bool AppInit2()
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// ********************************************************* Step 6c: manual UTXO snapshot loading
|
// ********************************************************* 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 dataPath = GetDataDir();
|
||||||
fs::path snapshotFile = dataPath / "utxo-snapshot.bin";
|
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");
|
printf("Found utxo-snapshot.bin — loading UTXO snapshot...\n");
|
||||||
uiInterface.InitMessage(_("Loading UTXO snapshot..."));
|
uiInterface.InitMessage(_("Loading UTXO snapshot..."));
|
||||||
|
|
||||||
@@ -1040,16 +1042,16 @@ bool AppInit2()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle -reindex: delete the LevelDB block index so it gets rebuilt
|
// Handle -reindex: delete the chain DB so it gets rebuilt from the raw
|
||||||
// from the raw blk*.dat files via FastImportBlockFile().
|
// blk*.dat files via FastImportBlockFile(). This recalculates money
|
||||||
// This recalculates money supply, tx index, and UTXO set from scratch.
|
// 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))
|
if (GetBoolArg("-reindex", false))
|
||||||
{
|
{
|
||||||
printf("Reindex requested: removing block index database...\n");
|
printf("Reindex requested: removing chain database...\n");
|
||||||
uiInterface.InitMessage(_("Removing block index for reindex..."));
|
uiInterface.InitMessage(_("Removing chain database for reindex..."));
|
||||||
fs::path txleveldbPath = GetDataDir() / "txleveldb";
|
WipeChainDataDir();
|
||||||
if (fs::exists(txleveldbPath))
|
|
||||||
fs::remove_all(txleveldbPath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uiInterface.InitMessage(_("Loading block index..."));
|
uiInterface.InitMessage(_("Loading block index..."));
|
||||||
|
|||||||
@@ -5,9 +5,12 @@
|
|||||||
#include "txdb.h"
|
#include "txdb.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// Pick the backend once per process. -chaindb is a startup flag; switching at
|
// Pick the backend once per process. -chaindb is a startup flag; switching at
|
||||||
@@ -52,3 +55,19 @@ bool IsRocksDbChainBackend()
|
|||||||
{
|
{
|
||||||
return ResolveChainDbKind() == ChainDbKind::RocksDB;
|
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);
|
||||||
|
}
|
||||||
|
|||||||
+11
-3
@@ -11,6 +11,7 @@
|
|||||||
#include "txdb-leveldb.h"
|
#include "txdb-leveldb.h"
|
||||||
#include "txdb-rocksdb.h"
|
#include "txdb-rocksdb.h"
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
// Factory: returns a chain-database handle whose concrete backend is chosen
|
// Factory: returns a chain-database handle whose concrete backend is chosen
|
||||||
@@ -24,9 +25,16 @@
|
|||||||
// CTxDB constructor convention.
|
// CTxDB constructor convention.
|
||||||
std::unique_ptr<CTxDBBase> MakeChainDB(const char* pszMode = "r+");
|
std::unique_ptr<CTxDBBase> MakeChainDB(const char* pszMode = "r+");
|
||||||
|
|
||||||
// True when the configured chain-DB backend is RocksDB. Used by code paths
|
// True when the configured chain-DB backend is RocksDB.
|
||||||
// (e.g. UtxoSnapshot::LoadSnapshot) that haven't yet been ported off direct
|
|
||||||
// LevelDB calls — they error out cleanly instead of corrupting state.
|
|
||||||
bool IsRocksDbChainBackend();
|
bool IsRocksDbChainBackend();
|
||||||
|
|
||||||
|
// On-disk directory of the chain DB for the configured backend, e.g.
|
||||||
|
// <datadir>/txleveldb (LevelDB) or <datadir>/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
|
#endif // TRIANGLES_TXDB_H
|
||||||
|
|||||||
+50
-95
@@ -11,11 +11,6 @@
|
|||||||
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
|
||||||
#include <leveldb/db.h>
|
|
||||||
#include <leveldb/write_batch.h>
|
|
||||||
#include <leveldb/cache.h>
|
|
||||||
#include <leveldb/filter_policy.h>
|
|
||||||
|
|
||||||
#include <openssl/sha.h>
|
#include <openssl/sha.h>
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -200,22 +195,9 @@ bool DumpSnapshot(const fs::path& destPath,
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
bool LoadSnapshot(const fs::path& snapshotPath,
|
bool LoadSnapshot(const fs::path& snapshotPath,
|
||||||
const fs::path& dataDir,
|
const fs::path& /*dataDir — unused; resolved per-backend via GetChainDataDir()*/,
|
||||||
std::string& strError)
|
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");
|
FILE* file = fopen(snapshotPath.string().c_str(), "rb");
|
||||||
if (!file) {
|
if (!file) {
|
||||||
strError = "Cannot open snapshot file: " + snapshotPath.string();
|
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",
|
printf("UtxoSnapshot: loading snapshot at height %d (%d headers, %d UTXOs)\n",
|
||||||
height, numHeaders, numUtxos);
|
height, numHeaders, numUtxos);
|
||||||
|
|
||||||
// Create fresh LevelDB directory
|
// Wipe the chain DB directory for the configured backend, then open fresh
|
||||||
fs::path txleveldbPath = dataDir / "txleveldb";
|
// via the factory. Must run before any other code touches the chain DB
|
||||||
if (fs::exists(txleveldbPath))
|
// (the global handle is opened lazily on first MakeChainDB call).
|
||||||
fs::remove_all(txleveldbPath);
|
WipeChainDataDir();
|
||||||
fs::create_directories(txleveldbPath);
|
|
||||||
|
|
||||||
// Open LevelDB directly (not via CTxDB - it's not initialized yet)
|
auto txdbHolder = MakeChainDB("c+");
|
||||||
leveldb::Options options;
|
if (!txdbHolder) {
|
||||||
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()) {
|
|
||||||
fclose(file);
|
fclose(file);
|
||||||
delete options.filter_policy;
|
strError = "Failed to open fresh chain DB";
|
||||||
delete options.block_cache;
|
|
||||||
strError = "Cannot create LevelDB: " + status.ToString();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
CTxDBBase& txdb = *txdbHolder;
|
||||||
|
|
||||||
SHA256_CTX sha256;
|
SHA256_CTX sha256;
|
||||||
SHA256_Init(&sha256);
|
SHA256_Init(&sha256);
|
||||||
|
|
||||||
leveldb::WriteBatch batch;
|
|
||||||
bool success = true;
|
bool success = true;
|
||||||
unsigned int nBatchSize = 0;
|
unsigned int nBatchSize = 0;
|
||||||
|
|
||||||
|
if (!txdb.TxnBegin()) {
|
||||||
|
fclose(file);
|
||||||
|
strError = "Failed to begin chain DB transaction";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
auto flushBatch = [&]() -> bool {
|
auto flushBatch = [&]() -> bool {
|
||||||
if (nBatchSize == 0)
|
if (nBatchSize == 0)
|
||||||
return true;
|
return true;
|
||||||
leveldb::Status s = pdb->Write(leveldb::WriteOptions(), &batch);
|
if (!txdb.TxnCommit()) {
|
||||||
if (!s.ok()) {
|
strError = "Chain DB batch commit failed";
|
||||||
strError = "LevelDB write failed: " + s.ToString();
|
return false;
|
||||||
|
}
|
||||||
|
if (!txdb.TxnBegin()) {
|
||||||
|
strError = "Chain DB batch restart failed";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
batch.Clear();
|
|
||||||
nBatchSize = 0;
|
nBatchSize = 0;
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
@@ -355,14 +332,11 @@ bool LoadSnapshot(const fs::path& snapshotPath,
|
|||||||
ssEntry >> entryHash;
|
ssEntry >> entryHash;
|
||||||
ssEntry >> diskindex;
|
ssEntry >> diskindex;
|
||||||
|
|
||||||
// Write to LevelDB as "blockindex" key
|
if (!txdb.WriteBlockIndex(diskindex)) {
|
||||||
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
|
success = false;
|
||||||
ssKey << std::make_pair(std::string("blockindex"), entryHash);
|
strError = "WriteBlockIndex failed at header " + std::to_string(i);
|
||||||
|
break;
|
||||||
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
|
}
|
||||||
ssValue << diskindex;
|
|
||||||
|
|
||||||
batch.Put(ssKey.str(), ssValue.str());
|
|
||||||
nBatchSize++;
|
nBatchSize++;
|
||||||
|
|
||||||
if (nBatchSize >= 1000) {
|
if (nBatchSize >= 1000) {
|
||||||
@@ -404,14 +378,11 @@ bool LoadSnapshot(const fs::path& snapshotPath,
|
|||||||
ssRecord >> nIndex;
|
ssRecord >> nIndex;
|
||||||
ssRecord >> entry;
|
ssRecord >> entry;
|
||||||
|
|
||||||
// Write to LevelDB with "u" prefix key
|
if (!txdb.WriteUtxo(txhash, nIndex, entry)) {
|
||||||
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
|
success = false;
|
||||||
ssKey << std::make_pair(std::string("u"), std::make_pair(txhash, nIndex));
|
strError = "WriteUtxo failed at index " + std::to_string(i);
|
||||||
|
break;
|
||||||
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
|
}
|
||||||
ssValue << entry;
|
|
||||||
|
|
||||||
batch.Put(ssKey.str(), ssValue.str());
|
|
||||||
nBatchSize++;
|
nBatchSize++;
|
||||||
|
|
||||||
if (nBatchSize >= 50000) {
|
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) {
|
if (success) {
|
||||||
leveldb::WriteBatch metaBatch;
|
if (!txdb.TxnBegin()) {
|
||||||
|
|
||||||
// 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()) {
|
|
||||||
success = false;
|
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);
|
fclose(file);
|
||||||
|
|
||||||
if (!success) {
|
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());
|
printf("UtxoSnapshot: load failed: %s\n", strError.c_str());
|
||||||
if (fs::exists(txleveldbPath))
|
WipeChainDataDir();
|
||||||
fs::remove_all(txleveldbPath);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user