Merge origin/master (Krystie's RocksDB+IBD integration + snapshot wiring)

Reconciles two parallel implementations of multi-backend chain DB:
local kept its MakeChainDB factory + std::filesystem + unconditional
RocksDB + abstracted utxosnapshot, since those are downstream of the
boost-cleanup, smessage-RocksDB-port, and CTxDBBase abstraction work.

Preserved from origin (Krystie's branch):
- Block 2,203,594 checkpoint and matching mapSnapshotHashes entry
  for P2P snapshot verification (src/checkpoints.cpp)
- Headers-first IBD stall-recovery path: during IBD, replace the
  legacy PushGetBlocks fallback with RequestHeaderSyncRefillAllPeers
  + QueueHeaderSyncBlocksParallel so a stall on a weak peer set
  doesn't park at a low common ancestor (src/main.cpp SendMessages)

Discarded from origin:
- src/txdb.cpp (CActiveTxDB wrapper) — superseded by txdb-factory.cpp
- BUILD_ROCKSDB-gated paths and inline LevelDB+RocksDB code in
  utxosnapshot.cpp — already factored out behind CTxDBBase
- Public ReadRawBytes/WriteRawBytes/... wrappers added to
  CTxDBBase for CActiveTxDB; no remaining callers
- GetActiveChainDbDirName / UseRocksDbBackend in bootstrap.cpp;
  switched to GetChainDataDir()

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-29 11:04:00 -07:00
3 changed files with 59 additions and 25 deletions
+10 -8
View File
@@ -3,6 +3,7 @@
#include "bootstrap.h"
#include "utxosnapshot.h"
#include "txdb.h"
#include <filesystem>
#include <fstream>
@@ -718,16 +719,16 @@ bool DownloadBootstrap(const std::string& host,
return false;
}
// Check if the archive included a trusted pre-built index (txleveldb/)
// with a valid snapshot.manifest. If verified, keep it to skip the
// Check if the archive included a trusted pre-built index for the active
// backend with a valid snapshot.manifest. If verified, keep it to skip the
// multi-hour FastImportBlockFile() rebuild.
fs::path txleveldb = dataDir / "txleveldb";
fs::path chainDbPath = GetChainDataDir();
fs::path database = dataDir / "database";
fs::path manifestPath = dataDir / "snapshot.manifest";
bool keepIndex = false;
if (fs::exists(manifestPath) && fs::exists(txleveldb)) {
if (fs::exists(manifestPath) && fs::exists(chainDbPath)) {
SnapshotManifest manifest;
std::string manifestError;
@@ -754,9 +755,10 @@ bool DownloadBootstrap(const std::string& host,
if (!keepIndex) {
// No valid manifest or verification failed - delete the index.
// FastImportBlockFile() will rebuild from blk0001.dat on next startup.
printf("Bootstrap: removing extracted txleveldb/ (will rebuild index from blk0001.dat)\n");
if (fs::exists(txleveldb))
fs::remove_all(txleveldb);
printf("Bootstrap: removing extracted %s/ (will rebuild index from blk0001.dat)\n",
GetChainDataDir().filename().string().c_str());
if (fs::exists(chainDbPath))
fs::remove_all(chainDbPath);
}
// Always remove BDB database/ dir (wallet environment from another machine)
@@ -791,7 +793,7 @@ bool DownloadUtxoSnapshot(const std::string& host,
printf("Bootstrap: UTXO snapshot downloaded, loading into database...\n");
// Load the snapshot into a fresh txleveldb
// Load the snapshot into a fresh active chain DB
if (!UtxoSnapshot::LoadSnapshot(tmpPath, dataDir, strError)) {
fs::remove(tmpPath);
return false;
+2 -1
View File
@@ -38,6 +38,7 @@ namespace Checkpoints
{2200000, uint256("0x0a8d0442f031f1258120f713f34e45f4f9a625fb753558e27b89b32ad5a9a740")},
{2205000, uint256("0xf7aa893ec012181e321783d6a5487addf6997377908faa3e760c9054a4217d29")},
{2206000, uint256("0x780ae878f8b10b6cbd51ceb2c0799c90d3551fa916be62974375071b9e36581c")},
{2203594, uint256("0x5e016ae5d1f163c6679292b717a3db467a39d24b0a315182f4783caa79c722d8")},
{2207000, uint256("0x8836d67b0f08036c4a7c26ff0a29d4461a52b6d8f552165ad9c1abec2f3cadfd")},
};
@@ -50,7 +51,7 @@ namespace Checkpoints
// here. The corresponding (height, blockhash) must already exist in
// mapCheckpoints / mapCheckpointsTestnet.
static std::map<int, uint256> mapSnapshotHashes = {
// {2186940, uint256("0x...sha256-of-utxo-snapshot.bin...")},
{2203594, uint256("0x49b35dd01659975c4a31954f37174c6e2e8878dd0723ab306ccecd991c80f79a")},
};
static std::map<int, uint256> mapSnapshotHashesTestnet = {
+47 -16
View File
@@ -5994,24 +5994,55 @@ bool SendMessages(CNode* pto, bool fSendTrickle)
(int)pto->mapAskFor.size());
nLastStallLog = GetTime();
}
// Use the walk-forward progress point if available, to avoid
// restarting from pindexBest (which hits the CBlockLocator
// exponential gap and starts the walk-forward from scratch).
pto->pindexLastGetBlocksBegin = NULL;
if (nHighestInvWalk > nBestHeight && hashHighestInvWalk != 0 &&
mapBlockIndex.count(hashHighestInvWalk))
// During IBD, avoid falling back to legacy getblocks recovery
// anchored at pindexBest or a stale inv walk point. That path can
// repeatedly resolve the locator to the same low common ancestor
// on a weak peer set, which looks like a sync "freeze" near an
// early height even though the real bug is the recovery loop.
// Keep stall recovery header-driven instead so the planner tip
// advances from the newest known header state.
if (IsInitialBlockDownload())
{
pto->PushGetBlocks(mapBlockIndex[hashHighestInvWalk], uint256(0));
printf("SYNC-DIAG: stall re-request from walk=%d (not best=%d)\n",
nHighestInvWalk, nBestHeight);
} else {
pto->PushGetBlocks(pindexBest, uint256(0));
pto->pindexLastGetHeadersBegin = NULL;
uint256 hashLocatorTip = hashBestHeaderSync;
if (hashLocatorTip == 0 && nHighestInvWalk > nBestHeight &&
hashHighestInvWalk != 0 && mapBlockIndex.count(hashHighestInvWalk))
{
hashLocatorTip = hashHighestInvWalk;
}
unsigned int nRefilled = RequestHeaderSyncRefillAllPeers(
hashLocatorTip,
0,
"stall-recovery");
unsigned int nQueued = QueueHeaderSyncBlocksParallel(HEADER_DOWNLOAD_WINDOW);
printf("SYNC-DIAG: stall recovery used headers-first path (locator=%s, refillPeers=%u, queued=%u)\n",
hashLocatorTip.ToString().substr(0,20).c_str(),
nRefilled,
nQueued);
}
else
{
// Outside IBD, preserve the older walk-forward getblocks
// behavior since we're no longer building out a header planner.
pto->pindexLastGetBlocksBegin = NULL;
if (nHighestInvWalk > nBestHeight && hashHighestInvWalk != 0 &&
mapBlockIndex.count(hashHighestInvWalk))
{
pto->PushGetBlocks(mapBlockIndex[hashHighestInvWalk], uint256(0));
printf("SYNC-DIAG: stall re-request from walk=%d (not best=%d)\n",
nHighestInvWalk, nBestHeight);
}
else
{
pto->PushGetBlocks(pindexBest, uint256(0));
}
pto->pindexLastGetHeadersBegin = NULL;
pto->PushGetHeaders(pindexBest, uint256(0));
}
// Also send getheaders during stall to restart the header planner.
// Without this, a drained header cache stays empty because only
// getblocks is sent on stall, which can't refill mapHeaderSync.
pto->pindexLastGetHeadersBegin = NULL;
pto->PushGetHeaders(pindexBest, uint256(0));
nLastBlockReceived = GetTime();
}
}