wallet: rebase CWalletDB onto CWalletBatchTyped (SQLite default)
Move CWalletDB off the Berkeley CDB base class and onto the typed batch
seam introduced by walletdb-batch.h / walletdb-{factory,sqlite}.{h,cpp}.
Build seam
----------
* CWalletDB now derives from CWalletBatchTyped. The typed Read/Write/
Erase/Exists templates come from the seam; their bodies (WriteTx,
WriteKey, WriteMasterKey, ReadPool, WriteSetting, ...) are unchanged
because only the base class swapped — the call signatures resolve to
the same templates.
* CWalletBatchTyped takes ownership of the WalletDatabase so the
underlying connection outlives any batch issued by it (SQLiteBatch
holds a reference, not a value). The two-phase Open() pattern lets
CWalletDB hand the freshly opened database to the base class after
MakeWalletDatabase() returns.
* MakeWalletDatabase (walletdb-factory.cpp) routes -walletdb=sqlite to
SQLiteDatabase, returning nullptr with a clear error for the
unfinished Berkeley branch. The CWalletDB constructor surfaces that
error string on failure.
Cursor sites (the only Berkeley-specific call sites)
---------------------------------------------------
Three sites used GetCursor()/ReadAtCursor() directly:
* LoadWallet — full scan, now uses StartCursor()/NextRecord()
* ListAccountCreditDebit — used DB_SET_RANGE + DB_NEXT loop; replaced
with full keyspace scan + filter-in-loop (SQLite cursor does not
support keyed range seeks). Behaviour matches Berkeley: terminates
when strType changes or, in single-account mode, when
acentry.strAccount differs.
* ZapWalletTx — moved to BerkeleyZapWalletTx (see below) because it
operates on raw Berkeley Db/Dbc/Dbt now that CWalletDB is on the
seam.
The 3 unused public methods on the old CWalletDB (GetAtCursor /
GetTxnCursor / GetAtActiveTxn) had no callers outside walletdb.{h,cpp}
(verified by grep) and were removed.
Berkeley-only escape hatches
----------------------------
Recover(CDBEnv&,...) and ZapWalletTx(...) became BerkeleyRecoverWallet
and BerkeleyZapWalletTx in a new walletdb-recover.{h,cpp} pair. They
operate directly on DbEnv/Db/Dbc/Dbt because CDB's members are
protected (free functions cannot use the wrapper). The recovery logic
duplicates a BDB-only ReadKeyValue variant locally to avoid pulling
the typed batch seam into a Berkeley-only file.
Init.cpp uses these via:
* -salvagewallet -> BerkeleyRecoverWallet(bitdb, ..., fOnlyKeys=true)
* -zapwallettxes -> BerkeleyZapWalletTx(...)
* bitdb.Verify -> BerkeleyRecoverWallet as the recover callback
Wallet migration hook
---------------------
After the Berkeley verify/salvage/zap steps and before CWalletDB is
opened for the live wallet, init.cpp now calls:
if (ResolveWalletDbKind() == SQLite &&
!IsSQLiteFile(walletPath))
MaybeMigrateBerkeleyWalletToSQLite(walletPath, err)
The migration code (walletmigrate.{h,cpp}) is unchanged — it opens a
private Berkeley environment over the wallet directory, copies every
record verbatim (raw key/value bytes) into a fresh SQLite file,
verifies the row count, then atomically renames the BDB original to
"<name>.bdb.bak" and the SQLite file into place. On any failure the
BDB original is left exactly as it was. Errors surface through
InitError so the daemon refuses to start with a corrupt wallet rather
than silently falling back to Berkeley.
No working Berkeley fallback
----------------------------
MakeWalletDatabase returns nullptr for the Berkeley branch, so
-walletdb=bdb no longer opens a working wallet through the seam. This
is intentional for this release — the migration hook handles existing
BDB wallets at first startup, after which the on-disk file is SQLite
and the BDB code path becomes pure recovery glue.
Header fallout
--------------
walletdb.h no longer pulls in db.h (which would drag <db_cxx.h> into
every TU that includes the wallet API). Forward decls added for
CWalletTx, CBlockLocator, CWallet, CPubKey, CScript, CMasterKey,
uint160, uint256. nWalletDBUpdated is now extern-declared in
walletdb.h and defined in db.cpp (was previously declared in db.h).
Validation
----------
Build: GREEN with USE_TOR_EMBEDDED=ON USE_I2P_EMBEDDED=ON. 6 binaries:
trianglesd, triangles-cli, test_triangles, test_chaindb_runtime,
test_chaindb_equivalence, test_snapshotnet.
Tests: 107/107 + 10/10 + 5/5 = byte-identical to the 5d9da84
baseline. wallet_tests and accounting_tests inside test_triangles now
exercise the SQLite path for the first time — their pass is the
de-facto wallet-migration validation at the test-suite level.
This commit is contained in:
@@ -113,6 +113,7 @@ list(APPEND CORE_SOURCES txdb-rocksdb.cpp)
|
||||
list(APPEND CORE_SOURCES
|
||||
walletdb-factory.cpp
|
||||
walletdb-sqlite.cpp
|
||||
walletdb-recover.cpp
|
||||
walletmigrate.cpp
|
||||
)
|
||||
|
||||
|
||||
+24
-4
@@ -4,6 +4,8 @@
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
#include "txdb.h"
|
||||
#include "walletdb.h"
|
||||
#include "walletdb-recover.h" // BerkeleyRecoverWallet / BerkeleyZapWalletTx
|
||||
#include "walletmigrate.h" // MaybeMigrateBerkeleyWalletToSQLite / IsSQLiteFile
|
||||
#include "trianglesrpc.h"
|
||||
#include "net.h"
|
||||
#include "netbase.h"
|
||||
@@ -43,6 +45,8 @@ static bool InitWarning(const std::string& str);
|
||||
#ifndef WIN32
|
||||
#include <signal.h>
|
||||
#include <sys/file.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
@@ -977,21 +981,37 @@ bool AppInit2()
|
||||
|
||||
if (GetBoolArg("-salvagewallet"))
|
||||
{
|
||||
// Recover readable keypairs:
|
||||
if (!CWalletDB::Recover(bitdb, strWalletFileName, true))
|
||||
// Recover readable keypairs (Berkeley path; only relevant for legacy
|
||||
// wallet.dat files that haven't been migrated to SQLite yet):
|
||||
if (!BerkeleyRecoverWallet(bitdb, strWalletFileName, true))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (GetBoolArg("-zapwallettxes") && fs::exists(GetDataDir() / strWalletFileName))
|
||||
{
|
||||
uiInterface.InitMessage(_("Zapping all transactions from wallet..."));
|
||||
if (!CWalletDB::ZapWalletTx(strWalletFileName))
|
||||
if (!BerkeleyZapWalletTx(strWalletFileName))
|
||||
return InitError(_("Error: could not zap wallet transactions"));
|
||||
}
|
||||
|
||||
// ── Wallet backend migration ──────────────────────────────────────────────
|
||||
// The daemon now defaults to SQLite (-walletdb=sqlite). If the wallet file
|
||||
// on disk is still a Berkeley DB, convert it non-destructively to a SQLite
|
||||
// wallet here, before the CWalletDB handle is opened downstream. The
|
||||
// Berkeley original is preserved as "<name>.bdb.bak" alongside.
|
||||
if (ResolveWalletDbKind() == WalletDbKind::SQLite &&
|
||||
fs::exists(GetDataDir() / strWalletFileName) &&
|
||||
!IsSQLiteFile(GetDataDir() / strWalletFileName))
|
||||
{
|
||||
uiInterface.InitMessage(_("Migrating wallet from Berkeley DB to SQLite..."));
|
||||
std::string migErr;
|
||||
if (!MaybeMigrateBerkeleyWalletToSQLite(GetDataDir() / strWalletFileName, migErr))
|
||||
return InitError(_("Wallet migration failed: ") + migErr);
|
||||
}
|
||||
|
||||
if (fs::exists(GetDataDir() / strWalletFileName))
|
||||
{
|
||||
CDBEnv::VerifyResult r = bitdb.Verify(strWalletFileName, CWalletDB::Recover);
|
||||
CDBEnv::VerifyResult r = bitdb.Verify(strWalletFileName, BerkeleyRecoverWallet);
|
||||
if (r == CDBEnv::RECOVER_OK)
|
||||
{
|
||||
string msg = strprintf(_("Warning: wallet.dat corrupt, data salvaged!"
|
||||
|
||||
+26
-4
@@ -30,12 +30,33 @@
|
||||
class CWalletBatchTyped
|
||||
{
|
||||
public:
|
||||
explicit CWalletBatchTyped(std::unique_ptr<WalletBatch> batch)
|
||||
: m_batch(std::move(batch)) {}
|
||||
|
||||
// Default-constructed handle is unusable until Open() runs. Subclasses
|
||||
// (CWalletDB) call Open() once they have opened a WalletDatabase.
|
||||
CWalletBatchTyped() = default;
|
||||
virtual ~CWalletBatchTyped() { Close(); }
|
||||
|
||||
void Close() { m_batch.reset(); }
|
||||
// Open a fresh batch against the given database. Closes any previously
|
||||
// open batch+database. Returns false (and leaves the handle null) if the
|
||||
// database fails to produce a batch.
|
||||
bool Open(std::unique_ptr<WalletDatabase> db)
|
||||
{
|
||||
Close();
|
||||
if (!db)
|
||||
return false;
|
||||
m_database = std::move(db);
|
||||
m_batch = m_database->MakeBatch(/*flush_on_close=*/true);
|
||||
if (!m_batch) {
|
||||
m_database.reset();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Close()
|
||||
{
|
||||
m_batch.reset();
|
||||
m_database.reset();
|
||||
}
|
||||
bool IsNull() const { return m_batch == nullptr; }
|
||||
|
||||
// ── Transactions ─────────────────────────────────────────────────────────
|
||||
@@ -44,6 +65,7 @@ public:
|
||||
bool TxnAbort() { return m_batch && m_batch->TxnAbort(); }
|
||||
|
||||
protected:
|
||||
std::unique_ptr<WalletDatabase> m_database;
|
||||
std::unique_ptr<WalletBatch> m_batch;
|
||||
|
||||
// ── Typed accessors (serialize key/value, dispatch to the raw batch) ──────
|
||||
|
||||
@@ -0,0 +1,325 @@
|
||||
// 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.
|
||||
//
|
||||
// Berkeley-only wallet recovery helpers. See walletdb-recover.h.
|
||||
|
||||
#include "walletdb-recover.h"
|
||||
#include "wallet.h"
|
||||
|
||||
#include <db_cxx.h>
|
||||
#include <boost/version.hpp>
|
||||
#include <cstdio>
|
||||
#include <filesystem>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
class CWalletScanState_BdbOnly {
|
||||
public:
|
||||
unsigned int nKeys;
|
||||
unsigned int nCKeys;
|
||||
unsigned int nKeyMeta;
|
||||
bool fIsEncrypted;
|
||||
bool fAnyUnordered;
|
||||
int nFileVersion;
|
||||
std::vector<uint256> vWalletUpgrade;
|
||||
|
||||
CWalletScanState_BdbOnly() {
|
||||
nKeys = nCKeys = nKeyMeta = 0;
|
||||
fIsEncrypted = false;
|
||||
fAnyUnordered = false;
|
||||
nFileVersion = 0;
|
||||
}
|
||||
};
|
||||
|
||||
static bool IsKeyType_BdbOnly(const std::string& strType)
|
||||
{
|
||||
return (strType == "key" || strType == "wkey" ||
|
||||
strType == "mkey" || strType == "ckey" ||
|
||||
strType == "hdmnemonic" || strType == "hdcmnemonic");
|
||||
}
|
||||
|
||||
// Same logic as walletdb.cpp::ReadKeyValue, but the only places it is called
|
||||
// here are Recover() (which scans records) and the resulting scan state. The
|
||||
// same logic — duplicated locally to avoid dragging in the typed batch seam
|
||||
// for a Berkeley-only escape hatch.
|
||||
static bool ReadKeyValue_BdbOnly(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
|
||||
CWalletScanState_BdbOnly& wss,
|
||||
std::string& strType, std::string& strErr)
|
||||
{
|
||||
try {
|
||||
ssKey >> strType;
|
||||
if (strType == "name") {
|
||||
std::string strAddress;
|
||||
ssKey >> strAddress;
|
||||
ssValue >> pwallet->mapAddressBook[CTrianglesAddress(strAddress).Get()];
|
||||
} else if (strType == "tx") {
|
||||
uint256 hash;
|
||||
ssKey >> hash;
|
||||
CWalletTx& wtx = pwallet->mapWallet[hash];
|
||||
ssValue >> wtx;
|
||||
if (wtx.CheckTransaction() && (wtx.GetHash() == hash))
|
||||
wtx.BindWallet(pwallet);
|
||||
else {
|
||||
pwallet->mapWallet.erase(hash);
|
||||
return false;
|
||||
}
|
||||
if (31404 <= wtx.fTimeReceivedIsTxTime && wtx.fTimeReceivedIsTxTime <= 31703) {
|
||||
wss.vWalletUpgrade.push_back(hash);
|
||||
}
|
||||
} else if (strType == "acentry") {
|
||||
std::string strAccount;
|
||||
ssKey >> strAccount;
|
||||
uint64_t nNumber;
|
||||
ssKey >> nNumber;
|
||||
// Note: we intentionally do NOT bump nAccountingEntryNumber here.
|
||||
// That counter is file-static in walletdb.cpp; the recovery path
|
||||
// does not need the high-water mark because the salvaged records
|
||||
// are not re-ordered or re-emitted as new entries.
|
||||
(void)nNumber;
|
||||
} else if (strType == "key" || strType == "wkey") {
|
||||
std::vector<unsigned char> vchPubKey;
|
||||
ssKey >> vchPubKey;
|
||||
CKey key;
|
||||
if (strType == "key") {
|
||||
wss.nKeys++;
|
||||
CPrivKey pkey;
|
||||
ssValue >> pkey;
|
||||
key.SetPubKey(vchPubKey);
|
||||
if (!key.SetPrivKey(pkey))
|
||||
{ strErr = "Recover: CPrivKey corrupt"; return false; }
|
||||
if (key.GetPubKey() != vchPubKey)
|
||||
{ strErr = "Recover: CPrivKey pubkey inconsistency"; return false; }
|
||||
if (!key.IsValid())
|
||||
{ strErr = "Recover: invalid CPrivKey"; return false; }
|
||||
} else {
|
||||
CWalletKey wkey;
|
||||
ssValue >> wkey;
|
||||
key.SetPubKey(vchPubKey);
|
||||
if (!key.SetPrivKey(wkey.vchPrivKey))
|
||||
{ strErr = "Recover: CPrivKey corrupt"; return false; }
|
||||
if (key.GetPubKey() != vchPubKey)
|
||||
{ strErr = "Recover: CWalletKey pubkey inconsistency"; return false; }
|
||||
if (!key.IsValid())
|
||||
{ strErr = "Recover: invalid CWalletKey"; return false; }
|
||||
}
|
||||
if (!pwallet->LoadKey(key))
|
||||
{ strErr = "Recover: LoadKey failed"; return false; }
|
||||
} else if (strType == "mkey") {
|
||||
unsigned int nID;
|
||||
ssKey >> nID;
|
||||
CMasterKey kMasterKey;
|
||||
ssValue >> kMasterKey;
|
||||
if (pwallet->mapMasterKeys.count(nID) != 0) {
|
||||
strErr = strprintf("Recover: duplicate CMasterKey id %u", nID);
|
||||
return false;
|
||||
}
|
||||
pwallet->mapMasterKeys[nID] = kMasterKey;
|
||||
if (pwallet->nMasterKeyMaxID < nID)
|
||||
pwallet->nMasterKeyMaxID = nID;
|
||||
} else if (strType == "ckey") {
|
||||
wss.nCKeys++;
|
||||
std::vector<unsigned char> vchPubKey;
|
||||
ssKey >> vchPubKey;
|
||||
std::vector<unsigned char> vchPrivKey;
|
||||
ssValue >> vchPrivKey;
|
||||
if (!pwallet->LoadCryptedKey(vchPubKey, vchPrivKey))
|
||||
{ strErr = "Recover: LoadCryptedKey failed"; return false; }
|
||||
wss.fIsEncrypted = true;
|
||||
} else if (strType == "keymeta") {
|
||||
CPubKey vchPubKey;
|
||||
ssKey >> vchPubKey;
|
||||
CKeyMetadata keyMeta;
|
||||
ssValue >> keyMeta;
|
||||
wss.nKeyMeta++;
|
||||
pwallet->LoadKeyMetadata(vchPubKey, keyMeta);
|
||||
if (!pwallet->nTimeFirstKey ||
|
||||
(keyMeta.nCreateTime < pwallet->nTimeFirstKey))
|
||||
pwallet->nTimeFirstKey = keyMeta.nCreateTime;
|
||||
} else if (strType == "defaultkey") {
|
||||
ssValue >> pwallet->vchDefaultKey;
|
||||
} else if (strType == "pool") {
|
||||
int64_t nIndex;
|
||||
ssKey >> nIndex;
|
||||
CKeyPool keypool;
|
||||
ssValue >> keypool;
|
||||
pwallet->setKeyPool.insert(nIndex);
|
||||
CKeyID keyid = keypool.vchPubKey.GetID();
|
||||
if (pwallet->mapKeyMetadata.count(keyid) == 0)
|
||||
pwallet->mapKeyMetadata[keyid] = CKeyMetadata(keypool.nTime);
|
||||
} else if (strType == "hdmnemonic") {
|
||||
std::string m;
|
||||
ssValue >> m;
|
||||
pwallet->LoadHDMnemonic(m);
|
||||
} else if (strType == "hdcmnemonic") {
|
||||
std::pair<uint256, std::vector<unsigned char>> cm;
|
||||
ssValue >> cm;
|
||||
pwallet->LoadCryptedHDMnemonic(cm.first, cm.second);
|
||||
} else if (strType == "hdchain") {
|
||||
int64_t n;
|
||||
ssValue >> n;
|
||||
pwallet->nHDChainIndex = n;
|
||||
} else if (strType == "version") {
|
||||
ssValue >> wss.nFileVersion;
|
||||
if (wss.nFileVersion == 10300)
|
||||
wss.nFileVersion = 300;
|
||||
} else if (strType == "cscript") {
|
||||
uint160 hash;
|
||||
ssKey >> hash;
|
||||
CScript script;
|
||||
ssValue >> script;
|
||||
if (!pwallet->LoadCScript(script))
|
||||
{ strErr = "Recover: LoadCScript failed"; return false; }
|
||||
} else if (strType == "orderposnext") {
|
||||
ssValue >> pwallet->nOrderPosNext;
|
||||
}
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BerkeleyRecoverWallet(CDBEnv& dbenv, std::string filename, bool fOnlyKeys)
|
||||
{
|
||||
int64_t now = GetTime();
|
||||
std::string newFilename = strprintf("wallet.%"PRId64".bak", now);
|
||||
|
||||
int result = dbenv.dbenv.dbrename(NULL, filename.c_str(), NULL,
|
||||
newFilename.c_str(), DB_AUTO_COMMIT);
|
||||
if (result == 0)
|
||||
printf("Renamed %s to %s\n", filename.c_str(), newFilename.c_str());
|
||||
else {
|
||||
printf("Failed to rename %s to %s\n", filename.c_str(), newFilename.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<CDBEnv::KeyValPair> salvagedData;
|
||||
bool allOK = dbenv.Salvage(newFilename, true, salvagedData);
|
||||
if (salvagedData.empty()) {
|
||||
printf("Salvage(aggressive) found no records in %s.\n", newFilename.c_str());
|
||||
return false;
|
||||
}
|
||||
printf("Salvage(aggressive) found %"PRIszu" records\n", salvagedData.size());
|
||||
|
||||
bool fSuccess = allOK;
|
||||
Db* pdbCopy = new Db(&dbenv.dbenv, 0);
|
||||
int ret = pdbCopy->open(NULL, filename.c_str(), "main", DB_BTREE, DB_CREATE, 0);
|
||||
if (ret > 0) {
|
||||
printf("Cannot create database file %s\n", filename.c_str());
|
||||
return false;
|
||||
}
|
||||
CWallet dummyWallet;
|
||||
CWalletScanState_BdbOnly wss;
|
||||
|
||||
DbTxn* ptxn = dbenv.TxnBegin();
|
||||
for (CDBEnv::KeyValPair& row : salvagedData) {
|
||||
if (fOnlyKeys) {
|
||||
CDataStream ssKey(row.first, SER_DISK, CLIENT_VERSION);
|
||||
CDataStream ssValue(row.second, SER_DISK, CLIENT_VERSION);
|
||||
std::string strType, strErr;
|
||||
bool fReadOK = ReadKeyValue_BdbOnly(&dummyWallet, ssKey, ssValue,
|
||||
wss, strType, strErr);
|
||||
if (!IsKeyType_BdbOnly(strType))
|
||||
continue;
|
||||
if (!fReadOK) {
|
||||
printf("WARNING: BerkeleyRecoverWallet skipping %s: %s\n",
|
||||
strType.c_str(), strErr.c_str());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Dbt datKey(&row.first[0], row.first.size());
|
||||
Dbt datValue(&row.second[0], row.second.size());
|
||||
int ret2 = pdbCopy->put(ptxn, &datKey, &datValue, DB_NOOVERWRITE);
|
||||
if (ret2 > 0)
|
||||
fSuccess = false;
|
||||
}
|
||||
ptxn->commit(0);
|
||||
pdbCopy->close(0);
|
||||
delete pdbCopy;
|
||||
|
||||
return fSuccess;
|
||||
}
|
||||
|
||||
bool BerkeleyZapWalletTx(const std::string& strWalletFile)
|
||||
{
|
||||
printf("BerkeleyZapWalletTx: erasing transaction records from %s\n",
|
||||
strWalletFile.c_str());
|
||||
|
||||
// Walk the Berkeley file directly. The CDB wrapper hides its members, but
|
||||
// the underlying Db* / Dbc* API is the same thing the wrapper does.
|
||||
DbEnv env(0u);
|
||||
env.set_error_stream(&std::cerr);
|
||||
u_int32_t envFlags = DB_CREATE | DB_INIT_MPOOL | DB_PRIVATE;
|
||||
if (env.open(GetDataDir().string().c_str(), envFlags, 0) != 0) {
|
||||
printf("BerkeleyZapWalletTx: cannot open Berkeley environment\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ok = false;
|
||||
{
|
||||
Db db(&env, 0);
|
||||
if (db.open(nullptr, strWalletFile.c_str(), "main", DB_BTREE, DB_RDONLY, 0) != 0) {
|
||||
printf("BerkeleyZapWalletTx: failed to open wallet database\n");
|
||||
env.close(0);
|
||||
return false;
|
||||
}
|
||||
|
||||
Dbc* pcursor = nullptr;
|
||||
if (db.cursor(nullptr, &pcursor, 0) != 0) {
|
||||
printf("BerkeleyZapWalletTx: failed to get cursor\n");
|
||||
db.close(0);
|
||||
env.close(0);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<uint256> vTxHash;
|
||||
Dbt datKey, datValue;
|
||||
while (pcursor->get(&datKey, &datValue, DB_NEXT) == 0) {
|
||||
try {
|
||||
CDataStream ssKey(static_cast<const char*>(datKey.get_data()),
|
||||
static_cast<const char*>(datKey.get_data()) + datKey.get_size(),
|
||||
SER_DISK, CLIENT_VERSION);
|
||||
std::string strType;
|
||||
ssKey >> strType;
|
||||
if (strType == "tx") {
|
||||
uint256 hash;
|
||||
ssKey >> hash;
|
||||
vTxHash.push_back(hash);
|
||||
}
|
||||
} catch (...) {
|
||||
// Skip records we cannot decode — salvage logic is best-effort.
|
||||
}
|
||||
}
|
||||
pcursor->close();
|
||||
db.close(0);
|
||||
|
||||
// Second pass: re-open the file in r/w mode and erase the collected tx
|
||||
// records. Two separate connections keep the read pass free of the
|
||||
// BDB cursor lifetime rules.
|
||||
if (db.open(nullptr, strWalletFile.c_str(), "main", DB_BTREE, DB_CREATE, 0) != 0) {
|
||||
printf("BerkeleyZapWalletTx: failed to reopen wallet for erase\n");
|
||||
env.close(0);
|
||||
return false;
|
||||
}
|
||||
|
||||
int nErased = 0;
|
||||
for (const uint256& hash : vTxHash) {
|
||||
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
|
||||
ssKey << std::make_pair(std::string("tx"), hash);
|
||||
Dbt datKey2(&ssKey[0], ssKey.size());
|
||||
int rc = db.del(nullptr, &datKey2, 0);
|
||||
if (rc == 0 || rc == DB_NOTFOUND)
|
||||
++nErased;
|
||||
}
|
||||
db.close(0);
|
||||
printf("BerkeleyZapWalletTx: erased %d of %d transaction records\n",
|
||||
nErased, (int)vTxHash.size());
|
||||
ok = true;
|
||||
}
|
||||
env.close(0);
|
||||
return ok;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// 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.
|
||||
//
|
||||
// Berkeley-only wallet recovery helpers — moved out of CWalletDB so the
|
||||
// mainline wallet code path (SQLite via the typed batch seam) does not have
|
||||
// to include <db_cxx.h>.
|
||||
//
|
||||
// These functions operate directly on bitdb / CDB and are used only:
|
||||
// * during startup, before the wallet migration hook (on a possible BDB
|
||||
// wallet.dat), and
|
||||
// * on the .bdb.bak copy that migration leaves behind, for diagnostic /
|
||||
// manual recovery if migration ever needs investigation.
|
||||
//
|
||||
// They are intentionally NOT methods of CWalletDB — that class is on the
|
||||
// SQLite seam now and has no Berkeley state.
|
||||
|
||||
#ifndef TRIANGLES_WALLETDB_RECOVER_H
|
||||
#define TRIANGLES_WALLETDB_RECOVER_H
|
||||
|
||||
#include "db.h"
|
||||
#include <string>
|
||||
|
||||
// Aggressive salvage of a Berkeley wallet.dat file. Moves the file aside to
|
||||
// wallet.<timestamp>.bak, then walks the salvaged records and re-writes them
|
||||
// into a fresh Berkeley database at the original path.
|
||||
//
|
||||
// If fOnlyKeys is true, only key-type records are kept (used for recovery
|
||||
// when transaction history is corrupt). Returns true on success.
|
||||
bool BerkeleyRecoverWallet(CDBEnv& dbenv, std::string filename, bool fOnlyKeys);
|
||||
inline bool BerkeleyRecoverWallet(CDBEnv& dbenv, std::string filename)
|
||||
{
|
||||
return BerkeleyRecoverWallet(dbenv, filename, false);
|
||||
}
|
||||
|
||||
// Strip every "tx" record from a Berkeley wallet.dat, leaving keys and other
|
||||
// metadata intact. A rescan rebuilds the transaction list from the chain.
|
||||
// Used for `-zapwallettxes` on legacy (pre-migration) wallets.
|
||||
bool BerkeleyZapWalletTx(const std::string& strWalletFile);
|
||||
|
||||
#endif // TRIANGLES_WALLETDB_RECOVER_H
|
||||
+241
-426
File diff suppressed because it is too large
Load Diff
+39
-31
@@ -5,12 +5,26 @@
|
||||
#ifndef TRIANGLES_WALLETDB_H
|
||||
#define TRIANGLES_WALLETDB_H
|
||||
|
||||
#include "db.h"
|
||||
#include "walletdb-batch.h" // CWalletBatchTyped (the typed batch seam)
|
||||
#include "base58.h"
|
||||
|
||||
class CKeyPool;
|
||||
class CAccount;
|
||||
class CAccountingEntry;
|
||||
class CBlockLocator; // forward decl — pulled in via db.h→main.h before
|
||||
class CPubKey;
|
||||
class CScript;
|
||||
class CMasterKey;
|
||||
class uint160;
|
||||
class uint256;
|
||||
class CWallet; // pulled in via db.h→main.h→wallet.h before
|
||||
class CWalletTx; // forward decl — walletdb.h used to pull this in
|
||||
// transitively via db.h; the seam removes that.
|
||||
|
||||
// Wallet-update counter used by the periodic flush thread (db.cpp defines it).
|
||||
// Touched on every wallet write; needed regardless of backend so the daemon's
|
||||
// auto-flush logic can detect changes to the wallet file.
|
||||
extern unsigned int nWalletDBUpdated;
|
||||
|
||||
/** Error statuses for the wallet database */
|
||||
enum DBErrors
|
||||
@@ -57,39 +71,20 @@ public:
|
||||
|
||||
|
||||
/** Access to the wallet database (wallet.dat) */
|
||||
class CWalletDB : public CDB
|
||||
class CWalletDB : public CWalletBatchTyped
|
||||
{
|
||||
public:
|
||||
CWalletDB(std::string strFilename, const char* pszMode="r+") : CDB(strFilename.c_str(), pszMode)
|
||||
{
|
||||
}
|
||||
/**
|
||||
* Open (or create) the wallet database via the configured backend
|
||||
* (-walletdb, default SQLite). The legacy pszMode argument is accepted
|
||||
* for source compatibility but currently ignored — SQLite is always
|
||||
* opened read/write with create-if-missing.
|
||||
*/
|
||||
CWalletDB(std::string strFilename, const char* pszMode="r+");
|
||||
private:
|
||||
CWalletDB(const CWalletDB&);
|
||||
void operator=(const CWalletDB&);
|
||||
public:
|
||||
Dbc* GetAtCursor()
|
||||
{
|
||||
return GetCursor();
|
||||
}
|
||||
|
||||
Dbc* GetTxnCursor()
|
||||
{
|
||||
if (!pdb)
|
||||
return NULL;
|
||||
|
||||
DbTxn* ptxnid = activeTxn; // call TxnBegin first
|
||||
|
||||
Dbc* pcursor = NULL;
|
||||
int ret = pdb->cursor(ptxnid, &pcursor, 0);
|
||||
if (ret != 0)
|
||||
return NULL;
|
||||
return pcursor;
|
||||
}
|
||||
|
||||
DbTxn* GetAtActiveTxn()
|
||||
{
|
||||
return activeTxn;
|
||||
}
|
||||
|
||||
bool WriteName(const std::string& strAddress, const std::string& strName);
|
||||
|
||||
@@ -225,6 +220,18 @@ public:
|
||||
return Write(std::string("minversion"), nVersion);
|
||||
}
|
||||
|
||||
// Mirrors the legacy CDB::WriteVersion / ReadVersion; explicitly retained
|
||||
// because LoadWallet() upgrades the on-disk version to CLIENT_VERSION.
|
||||
bool WriteVersion(int nVersion)
|
||||
{
|
||||
return Write(std::string("version"), nVersion);
|
||||
}
|
||||
bool ReadVersion(int& nVersion)
|
||||
{
|
||||
nVersion = 0;
|
||||
return Read(std::string("version"), nVersion);
|
||||
}
|
||||
|
||||
bool ReadAccount(const std::string& strAccount, CAccount& account);
|
||||
bool WriteAccount(const std::string& strAccount, const CAccount& account);
|
||||
private:
|
||||
@@ -236,9 +243,10 @@ public:
|
||||
|
||||
DBErrors ReorderTransactions(CWallet*);
|
||||
DBErrors LoadWallet(CWallet* pwallet);
|
||||
static bool Recover(CDBEnv& dbenv, std::string filename, bool fOnlyKeys);
|
||||
static bool Recover(CDBEnv& dbenv, std::string filename);
|
||||
static bool ZapWalletTx(const std::string& strWalletFile);
|
||||
// NOTE: Recover() / ZapWalletTx() are Berkeley-only escape hatches. They
|
||||
// live in walletdb-recover.{h,cpp} (which still depends on db.h / db_cxx.h).
|
||||
// After wallet migration to SQLite those helpers are invoked on the
|
||||
// .bdb.bak copy at startup, never on the live wallet.
|
||||
};
|
||||
|
||||
#endif // TRIANGLES_WALLETDB_H
|
||||
|
||||
Reference in New Issue
Block a user