From 2a2b9d74f8fb6b5d86d750e6e5d18c942738f95c Mon Sep 17 00:00:00 2001 From: bitflash Date: Mon, 17 Aug 2026 17:45:01 -0300 Subject: wallet: run the node on a SQLite backend (opt-in) Adds -walletbackend=sqlite: an opt-in flag that runs the node on /wallet.sqlite instead of wallet.dat. It requires an export to exist first (no silent migration) and prints a step-by-step guide when one does not; wallet.dat is never opened or touched, so a plain restart goes back to Berkeley DB. - LoadWalletFromSQLiteRuntime opens the export read-write, keeps the handle open as the active backend, loads records into memory through the same visitor/reconcile/finish path as the read-only staging load, and never fabricates a default key. - Shutdown checkpoints and closes the SQLite backend; a kill before that loses nothing because WAL + synchronous=FULL already made every committed write durable. - Self-test proves runtime writes persist across a close and reopen. BDB stays the default: nothing activates the SQLite runtime unless the flag is given. One-shot audit/recovery commands still use Berkeley DB. --- src/db.cpp | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) (limited to 'src/db.cpp') diff --git a/src/db.cpp b/src/db.cpp index 7cfb7c5..c4ea81e 100644 --- a/src/db.cpp +++ b/src/db.cpp @@ -124,6 +124,13 @@ void WalletSQLiteRuntimeClose() { if (pWalletSQLiteRuntime) { + // Fold the write-ahead log into the main file on the way out. WAL + + // synchronous=FULL already makes every committed write durable across a + // kill, so this is housekeeping, not the thing that protects the data: + // if it fails, or the node is killed before it runs, the wallet is + // still whole. Best effort, never throws. + string strCheckpointError; + pWalletSQLiteRuntime->Checkpoint(strCheckpointError); delete pWalletSQLiteRuntime; pWalletSQLiteRuntime = NULL; } @@ -1426,6 +1433,90 @@ bool LoadWalletFromSQLite(const string& strPath) return FinishLoadedWallet(visitor.vchDefaultKey, false); } +// Open a SQLite wallet read-write and keep it open as the active runtime +// backend, then load its records into memory the same way LoadWalletFromSQLite +// does. Unlike the read-only staging path, this leaves pWalletSQLiteRuntime set +// so every later CWalletDB read/write/erase routes to this file for the life of +// the process. The Berkeley DB wallet.dat is never opened. +bool LoadWalletFromSQLiteRuntime(const string& strPath) +{ + strWalletLoadError.clear(); + + if (!FileExists(strPath.c_str())) + { + strWalletLoadError = strprintf("SQLite wallet does not exist: %s", + strPath.c_str()); + printf("LoadWallet: %s\n", strWalletLoadError.c_str()); + return false; + } + + string strError; + if (!WalletSQLiteRuntimeOpen(strPath, strError)) + { + strWalletLoadError = strError; + printf("LoadWallet: cannot open SQLite wallet backend: %s\n", + strError.c_str()); + return false; + } + + CSQLiteWalletRuntimeLoadVisitor visitor; + try + { + CRITICAL_BLOCK(cs_mapKeys) + CRITICAL_BLOCK(cs_mapWallet) + { + if (!pWalletSQLiteRuntime->ScanRecords(visitor, strError)) + { + WalletSQLiteRuntimeClose(); + if (strWalletLoadError.empty()) + strWalletLoadError = strError; + printf("LoadWallet: SQLite wallet backend could not be read. " + "Failed while handling a '%s' record: %s\n", + visitor.strLastType.c_str(), strError.c_str()); + return false; + } + } + } + catch (const std::exception& e) + { + WalletSQLiteRuntimeClose(); + strWalletLoadError = strprintf( + "SQLite wallet backend could not be read while handling a '%s' record: %s", + visitor.strLastType.c_str(), e.what()); + printf("LoadWallet: %s\n", strWalletLoadError.c_str()); + return false; + } + catch (...) + { + WalletSQLiteRuntimeClose(); + strWalletLoadError = strprintf( + "SQLite wallet backend could not be read while handling a '%s' record", + visitor.strLastType.c_str()); + printf("LoadWallet: %s\n", strWalletLoadError.c_str()); + return false; + } + + if (!ReconcileLoadedWalletMetadata(visitor.fHaveStoredMineMode, + visitor.fHaveStoredHDCoinType)) + { + WalletSQLiteRuntimeClose(); + if (strWalletLoadError.empty()) + strWalletLoadError = "SQLite wallet metadata is not supported by this build"; + return false; + } + + // Never fabricate a default key: an export always carries one, and a file + // that does not is refused with the wallet left open to no writes rather + // than quietly minted into a new identity. + if (!FinishLoadedWallet(visitor.vchDefaultKey, false)) + { + WalletSQLiteRuntimeClose(); + return false; + } + + return true; +} + // Write a copy of wallet.dat that can be opened anywhere. // -- cgit v1.2.3