Port UtxoSnapshot::DumpSnapshot to CTxDBBase iterator
DumpSnapshot reached into the LevelDB backend's internal handle via `extern leveldb::DB *txdb`, which silently broke under -chaindb=rocksdb. Switched to the backend-agnostic CTxDBBase::NewIterator() interface; the function now works against either backend. LoadSnapshot is more involved (writes directly into a fresh txleveldb/ directory) and is bundled with the eventual LevelDB retirement. Added an IsRocksDbChainBackend() helper and an explicit guard at LoadSnapshot's entry: refuse to load with a clear error message rather than silently creating a leveldb tree alongside an active rocksdb chain. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -62,3 +62,8 @@ std::unique_ptr<CTxDBBase> MakeChainDB(const char* pszMode)
|
|||||||
// Unreachable — ResolveChainDbKind throws on bad input.
|
// Unreachable — ResolveChainDbKind throws on bad input.
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsRocksDbChainBackend()
|
||||||
|
{
|
||||||
|
return ResolveChainDbKind() == ChainDbKind::RocksDB;
|
||||||
|
}
|
||||||
|
|||||||
@@ -27,4 +27,9 @@
|
|||||||
// 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
|
||||||
|
// (e.g. UtxoSnapshot::LoadSnapshot) that haven't yet been ported off direct
|
||||||
|
// LevelDB calls — they error out cleanly instead of corrupting state.
|
||||||
|
bool IsRocksDbChainBackend();
|
||||||
|
|
||||||
#endif // TRIANGLES_TXDB_H
|
#endif // TRIANGLES_TXDB_H
|
||||||
|
|||||||
+27
-16
@@ -24,9 +24,6 @@
|
|||||||
|
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
// Global LevelDB pointer (defined in txdb-leveldb.cpp)
|
|
||||||
extern leveldb::DB *txdb;
|
|
||||||
|
|
||||||
namespace UtxoSnapshot {
|
namespace UtxoSnapshot {
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -60,12 +57,14 @@ bool DumpSnapshot(const fs::path& destPath,
|
|||||||
std::reverse(vHeaders.begin(), vHeaders.end());
|
std::reverse(vHeaders.begin(), vHeaders.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Open the chain DB once and reuse for both the UTXO count and the
|
||||||
|
// iteration below. Backend-agnostic via the CTxDBBase abstraction.
|
||||||
|
auto txdbHolder = MakeChainDB("r");
|
||||||
|
CTxDBBase& txdbRead = *txdbHolder;
|
||||||
|
|
||||||
// Count UTXOs first
|
// Count UTXOs first
|
||||||
int nUtxoCount = 0;
|
int nUtxoCount = 0;
|
||||||
{
|
txdbRead.SumUtxoValues(nUtxoCount);
|
||||||
auto txdbRead_holder = MakeChainDB("r"); CTxDBBase& txdbRead = *txdbRead_holder;
|
|
||||||
txdbRead.SumUtxoValues(nUtxoCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nUtxoCount == 0) {
|
if (nUtxoCount == 0) {
|
||||||
strError = "No UTXOs found in database";
|
strError = "No UTXOs found in database";
|
||||||
@@ -124,31 +123,31 @@ bool DumpSnapshot(const fs::path& destPath,
|
|||||||
SHA256_Update(&sha256, strEntry.data(), entrySize);
|
SHA256_Update(&sha256, strEntry.data(), entrySize);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write UTXO section using LevelDB iterator (same pattern as SumUtxoValues)
|
// Write UTXO section using the backend-agnostic iterator (same pattern as
|
||||||
|
// SumUtxoValues). Iteration runs outside any active batch — the contract
|
||||||
|
// documented on CTxDBIteratorBase guarantees a stable view of committed state.
|
||||||
{
|
{
|
||||||
|
|
||||||
CDataStream ssKeyPrefix(SER_DISK, CLIENT_VERSION);
|
CDataStream ssKeyPrefix(SER_DISK, CLIENT_VERSION);
|
||||||
ssKeyPrefix << std::make_pair(std::string("u"), std::make_pair(uint256(0), (unsigned int)0));
|
ssKeyPrefix << std::make_pair(std::string("u"), std::make_pair(uint256(0), (unsigned int)0));
|
||||||
std::string strPrefixBegin = ssKeyPrefix.str();
|
std::string strPrefixBegin = ssKeyPrefix.str();
|
||||||
|
|
||||||
leveldb::Iterator* it = txdb->NewIterator(leveldb::ReadOptions());
|
auto it = txdbRead.NewIterator();
|
||||||
unsigned int nWritten = 0;
|
unsigned int nWritten = 0;
|
||||||
for (it->Seek(strPrefixBegin); it->Valid(); it->Next()) {
|
for (it->Seek(strPrefixBegin); it->Valid(); it->Next()) {
|
||||||
// Check key prefix is still "u"
|
std::string strKey = it->KeyStr();
|
||||||
CDataStream ssKey(it->key().data(), it->key().data() + it->key().size(), SER_DISK, CLIENT_VERSION);
|
CDataStream ssKey(strKey.data(), strKey.data() + strKey.size(), SER_DISK, CLIENT_VERSION);
|
||||||
std::string strKeyType;
|
std::string strKeyType;
|
||||||
ssKey >> strKeyType;
|
ssKey >> strKeyType;
|
||||||
if (strKeyType != "u")
|
if (strKeyType != "u")
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Extract outpoint from key
|
|
||||||
uint256 txhash;
|
uint256 txhash;
|
||||||
unsigned int nIndex;
|
unsigned int nIndex;
|
||||||
ssKey >> txhash;
|
ssKey >> txhash;
|
||||||
ssKey >> nIndex;
|
ssKey >> nIndex;
|
||||||
|
|
||||||
// Extract UTXO entry from value
|
std::string strRawValue = it->ValueStr();
|
||||||
CDataStream ssValue(it->value().data(), it->value().data() + it->value().size(), SER_DISK, CLIENT_VERSION);
|
CDataStream ssValue(strRawValue.data(), strRawValue.data() + strRawValue.size(), SER_DISK, CLIENT_VERSION);
|
||||||
CUtxoEntry entry;
|
CUtxoEntry entry;
|
||||||
ssValue >> entry;
|
ssValue >> entry;
|
||||||
|
|
||||||
@@ -170,7 +169,6 @@ bool DumpSnapshot(const fs::path& destPath,
|
|||||||
if (nWritten % 10000 == 0)
|
if (nWritten % 10000 == 0)
|
||||||
printf("UtxoSnapshot: wrote %d / %d UTXOs\n", nWritten, nUtxoCount);
|
printf("UtxoSnapshot: wrote %d / %d UTXOs\n", nWritten, nUtxoCount);
|
||||||
}
|
}
|
||||||
delete it;
|
|
||||||
|
|
||||||
// Update actual count (in case it changed during iteration)
|
// Update actual count (in case it changed during iteration)
|
||||||
if (nWritten != numUtxos) {
|
if (nWritten != numUtxos) {
|
||||||
@@ -205,6 +203,19 @@ bool LoadSnapshot(const fs::path& snapshotPath,
|
|||||||
const fs::path& dataDir,
|
const fs::path& dataDir,
|
||||||
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();
|
||||||
|
|||||||
Reference in New Issue
Block a user