9389a883f1
CSyncManager extracts the headers-first IBD planner from main.cpp into its own translation unit. main.cpp loses ~570 lines of file-scope state and helper functions; the headers handler, block-delivery latency tracking, stall-recovery, and per-peer Tick cadence now route through g_syncManager. MaybeMigrateLevelDbToRocksDb() is now reachable via -migratechaindb / -migratechaindbforce in init.cpp. Reads from <datadir>/txleveldb and writes byte-for-byte identical records into <datadir>/rocksdb via a new CRocksTxDB::WriteRawRecordForMigration() shim over WriteRaw. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
199 lines
6.2 KiB
C++
199 lines
6.2 KiB
C++
// Copyright (c) 2026 The Triangles developers.
|
|
// Distributed under the MIT/X11 software license, see the accompanying
|
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
#include "chaindb_migrate.h"
|
|
|
|
#include "txdb-leveldb.h"
|
|
#include "txdb-rocksdb.h"
|
|
#include "util.h"
|
|
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <memory>
|
|
|
|
namespace fs = std::filesystem;
|
|
|
|
namespace {
|
|
|
|
struct ChainDbStats
|
|
{
|
|
int64_t nRecords = 0;
|
|
int64_t nUtxos = 0;
|
|
int64_t nUtxoValue = 0;
|
|
uint256 hashBestChain = 0;
|
|
int nDbFormat = 0;
|
|
};
|
|
|
|
bool CollectStats(CTxDBBase& db, ChainDbStats& stats, std::string& strError)
|
|
{
|
|
stats = ChainDbStats();
|
|
|
|
auto it = db.NewIterator();
|
|
for (it->Seek(std::string()); it->Valid(); it->Next())
|
|
stats.nRecords++;
|
|
|
|
int nUtxos = 0;
|
|
stats.nUtxoValue = db.SumUtxoValues(nUtxos);
|
|
stats.nUtxos = nUtxos;
|
|
db.ReadHashBestChain(stats.hashBestChain);
|
|
db.ReadDbFormat(stats.nDbFormat);
|
|
|
|
if (stats.nRecords <= 0) {
|
|
strError = "source chain database contains no records";
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool StatsMatch(const ChainDbStats& src, const ChainDbStats& dst, std::string& strError)
|
|
{
|
|
if (src.nRecords != dst.nRecords) {
|
|
strError = strprintf("record count mismatch after migration: source=%lld rocksdb=%lld",
|
|
(long long)src.nRecords, (long long)dst.nRecords);
|
|
return false;
|
|
}
|
|
if (src.nUtxos != dst.nUtxos || src.nUtxoValue != dst.nUtxoValue) {
|
|
strError = strprintf("UTXO mismatch after migration: source=(%lld,%lld) rocksdb=(%lld,%lld)",
|
|
(long long)src.nUtxos, (long long)src.nUtxoValue,
|
|
(long long)dst.nUtxos, (long long)dst.nUtxoValue);
|
|
return false;
|
|
}
|
|
if (src.hashBestChain != dst.hashBestChain) {
|
|
strError = strprintf("best-chain hash mismatch after migration: source=%s rocksdb=%s",
|
|
src.hashBestChain.ToString().c_str(),
|
|
dst.hashBestChain.ToString().c_str());
|
|
return false;
|
|
}
|
|
if (src.nDbFormat != dst.nDbFormat) {
|
|
strError = strprintf("dbformat mismatch after migration: source=%d rocksdb=%d",
|
|
src.nDbFormat, dst.nDbFormat);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
bool MaybeMigrateLevelDbToRocksDb(bool fForce, std::string& strError)
|
|
{
|
|
strError.clear();
|
|
|
|
const fs::path dataDir = GetDataDir();
|
|
const fs::path levelPath = dataDir / "txleveldb";
|
|
const fs::path rocksPath = dataDir / "rocksdb";
|
|
const fs::path markerPath = rocksPath / "MIGRATION_INCOMPLETE";
|
|
|
|
if (!fs::exists(levelPath))
|
|
return true;
|
|
|
|
if (fs::exists(rocksPath)) {
|
|
if (fs::exists(markerPath)) {
|
|
printf("ChainDB migration: removing incomplete previous RocksDB migration\n");
|
|
fs::remove_all(rocksPath);
|
|
}
|
|
else if (!fForce)
|
|
return true;
|
|
else {
|
|
printf("ChainDB migration: removing existing RocksDB directory due to -migratechaindbforce\n");
|
|
fs::remove_all(rocksPath);
|
|
}
|
|
}
|
|
|
|
printf("ChainDB migration: copying LevelDB chain state to RocksDB...\n");
|
|
printf("ChainDB migration: source=%s destination=%s\n",
|
|
levelPath.string().c_str(), rocksPath.string().c_str());
|
|
|
|
try {
|
|
fs::create_directories(rocksPath);
|
|
{
|
|
std::ofstream marker(markerPath);
|
|
marker << "RocksDB migration in progress. Safe to delete this directory and retry.\n";
|
|
}
|
|
|
|
CTxDB source("r");
|
|
CRocksTxDB destination("c+");
|
|
|
|
ChainDbStats srcStats;
|
|
if (!CollectStats(source, srcStats, strError)) {
|
|
source.Close();
|
|
destination.Close();
|
|
return false;
|
|
}
|
|
|
|
if (!destination.TxnBegin()) {
|
|
strError = "failed to begin RocksDB migration batch";
|
|
source.Close();
|
|
destination.Close();
|
|
return false;
|
|
}
|
|
|
|
int64_t nCopied = 0;
|
|
auto it = source.NewIterator();
|
|
for (it->Seek(std::string()); it->Valid(); it->Next())
|
|
{
|
|
if (!destination.WriteRawRecordForMigration(it->KeyStr(), it->ValueStr())) {
|
|
destination.TxnAbort();
|
|
strError = "failed to write migrated record to RocksDB";
|
|
source.Close();
|
|
destination.Close();
|
|
return false;
|
|
}
|
|
|
|
if (++nCopied % 100000 == 0)
|
|
{
|
|
if (!destination.TxnCommit()) {
|
|
strError = "failed to commit RocksDB migration batch";
|
|
source.Close();
|
|
destination.Close();
|
|
return false;
|
|
}
|
|
printf("ChainDB migration: copied %lld / %lld records\n",
|
|
(long long)nCopied, (long long)srcStats.nRecords);
|
|
if (!destination.TxnBegin()) {
|
|
strError = "failed to begin RocksDB migration batch";
|
|
source.Close();
|
|
destination.Close();
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!destination.TxnCommit()) {
|
|
strError = "failed to commit final RocksDB migration batch";
|
|
source.Close();
|
|
destination.Close();
|
|
return false;
|
|
}
|
|
|
|
ChainDbStats dstStats;
|
|
if (!CollectStats(destination, dstStats, strError)) {
|
|
source.Close();
|
|
destination.Close();
|
|
return false;
|
|
}
|
|
if (!StatsMatch(srcStats, dstStats, strError)) {
|
|
source.Close();
|
|
destination.Close();
|
|
return false;
|
|
}
|
|
|
|
printf("ChainDB migration: verified %lld records, %lld UTXOs, best=%s\n",
|
|
(long long)dstStats.nRecords,
|
|
(long long)dstStats.nUtxos,
|
|
dstStats.hashBestChain.ToString().substr(0,20).c_str());
|
|
|
|
source.Close();
|
|
destination.Close();
|
|
fs::remove(markerPath);
|
|
}
|
|
catch (std::exception& e) {
|
|
strError = e.what();
|
|
return false;
|
|
}
|
|
|
|
printf("ChainDB migration: complete. Legacy LevelDB was left untouched at %s\n",
|
|
levelPath.string().c_str());
|
|
return true;
|
|
}
|