upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBitflash <admin@bitflash.network>2026-08-18 10:28:04 -0300
committerBitflash <admin@bitflash.network>2026-08-18 10:28:04 -0300
commit243b635267b6d34ef3b4b50ed559f743db161ad7 (patch)
tree914f2851ce35919230c51ca083d3a964d6722408 /src
parenta6eb8dab830be3135c054ae3ae442886f1867b4e (diff)
parent00c7aaed74e57da18598faa6751b7720598de3bd (diff)
Merge pull request 'wallet: run the node on a SQLite backend (opt-in)' (#59) from wallet-sqlite-activate into main
Diffstat (limited to 'src')
-rw-r--r--src/db.cpp107
-rw-r--r--src/db.h1
-rw-r--r--src/main_gui.cpp81
-rw-r--r--src/selftest.cpp25
4 files changed, 210 insertions, 4 deletions
diff --git a/src/db.cpp b/src/db.cpp
index 7cfb7c5..771a4ba 100644
--- a/src/db.cpp
+++ b/src/db.cpp
@@ -124,6 +124,13 @@ void WalletSQLiteRuntimeClose()
124{ 124{
125 if (pWalletSQLiteRuntime) 125 if (pWalletSQLiteRuntime)
126 { 126 {
127 // Fold the write-ahead log into the main file on the way out. WAL +
128 // synchronous=FULL already makes every committed write durable across a
129 // kill, so this is housekeeping, not the thing that protects the data:
130 // if it fails, or the node is killed before it runs, the wallet is
131 // still whole. Best effort, never throws.
132 string strCheckpointError;
133 pWalletSQLiteRuntime->Checkpoint(strCheckpointError);
127 delete pWalletSQLiteRuntime; 134 delete pWalletSQLiteRuntime;
128 pWalletSQLiteRuntime = NULL; 135 pWalletSQLiteRuntime = NULL;
129 } 136 }
@@ -1426,6 +1433,106 @@ bool LoadWalletFromSQLite(const string& strPath)
1426 return FinishLoadedWallet(visitor.vchDefaultKey, false); 1433 return FinishLoadedWallet(visitor.vchDefaultKey, false);
1427} 1434}
1428 1435
1436// Open a SQLite wallet read-write and keep it open as the active runtime
1437// backend, then load its records into memory the same way LoadWalletFromSQLite
1438// does. Unlike the read-only staging path, this leaves pWalletSQLiteRuntime set
1439// so every later CWalletDB read/write/erase routes to this file for the life of
1440// the process. The Berkeley DB wallet.dat is never opened.
1441bool LoadWalletFromSQLiteRuntime(const string& strPath)
1442{
1443 strWalletLoadError.clear();
1444
1445 if (!FileExists(strPath.c_str()))
1446 {
1447 strWalletLoadError = strprintf("SQLite wallet does not exist: %s",
1448 strPath.c_str());
1449 printf("LoadWallet: %s\n", strWalletLoadError.c_str());
1450 return false;
1451 }
1452
1453 string strError;
1454 if (!WalletSQLiteRuntimeOpen(strPath, strError))
1455 {
1456 strWalletLoadError = strError;
1457 printf("LoadWallet: cannot open SQLite wallet backend: %s\n",
1458 strError.c_str());
1459 return false;
1460 }
1461
1462 CSQLiteWalletRuntimeLoadVisitor visitor;
1463 try
1464 {
1465 CRITICAL_BLOCK(cs_mapKeys)
1466 CRITICAL_BLOCK(cs_mapWallet)
1467 {
1468 if (!pWalletSQLiteRuntime->ScanRecords(visitor, strError))
1469 {
1470 WalletSQLiteRuntimeClose();
1471 if (strWalletLoadError.empty())
1472 strWalletLoadError = strError;
1473 printf("LoadWallet: SQLite wallet backend could not be read. "
1474 "Failed while handling a '%s' record: %s\n",
1475 visitor.strLastType.c_str(), strError.c_str());
1476 return false;
1477 }
1478 }
1479 }
1480 catch (const std::exception& e)
1481 {
1482 WalletSQLiteRuntimeClose();
1483 strWalletLoadError = strprintf(
1484 "SQLite wallet backend could not be read while handling a '%s' record: %s",
1485 visitor.strLastType.c_str(), e.what());
1486 printf("LoadWallet: %s\n", strWalletLoadError.c_str());
1487 return false;
1488 }
1489 catch (...)
1490 {
1491 WalletSQLiteRuntimeClose();
1492 strWalletLoadError = strprintf(
1493 "SQLite wallet backend could not be read while handling a '%s' record",
1494 visitor.strLastType.c_str());
1495 printf("LoadWallet: %s\n", strWalletLoadError.c_str());
1496 return false;
1497 }
1498
1499 // Refuse an empty or non-wallet SQLite file up front, with a message that
1500 // names the likely cause. FinishLoadedWallet below already rejects a
1501 // missing default key (it is the fMayCreateDefaultKey=false path), but its
1502 // wording -- "no usable default public key" -- reads as corruption when the
1503 // real story is usually "this file was never an exported wallet". A blank
1504 // wallet.sqlite left by a typo or a half-run export should say so.
1505 if (visitor.vchDefaultKey.empty())
1506 {
1507 WalletSQLiteRuntimeClose();
1508 strWalletLoadError =
1509 "SQLite wallet has no default key record; it may be empty or not a "
1510 "wallet export. Create it with -walletsqliteexport.";
1511 printf("LoadWallet: %s\n", strWalletLoadError.c_str());
1512 return false;
1513 }
1514
1515 if (!ReconcileLoadedWalletMetadata(visitor.fHaveStoredMineMode,
1516 visitor.fHaveStoredHDCoinType))
1517 {
1518 WalletSQLiteRuntimeClose();
1519 if (strWalletLoadError.empty())
1520 strWalletLoadError = "SQLite wallet metadata is not supported by this build";
1521 return false;
1522 }
1523
1524 // Never fabricate a default key: an export always carries one, and a file
1525 // that does not is refused with the wallet left open to no writes rather
1526 // than quietly minted into a new identity.
1527 if (!FinishLoadedWallet(visitor.vchDefaultKey, false))
1528 {
1529 WalletSQLiteRuntimeClose();
1530 return false;
1531 }
1532
1533 return true;
1534}
1535
1429 1536
1430// Write a copy of wallet.dat that can be opened anywhere. 1537// Write a copy of wallet.dat that can be opened anywhere.
1431// 1538//
diff --git a/src/db.h b/src/db.h
index 8fa4ea8..fee5a82 100644
--- a/src/db.h
+++ b/src/db.h
@@ -624,6 +624,7 @@ bool ScanWalletRecords(CWalletRecordVisitor& visitor, string& strErrorRet);
624 624
625bool LoadWallet(); 625bool LoadWallet();
626bool LoadWalletFromSQLite(const string& strPath); 626bool LoadWalletFromSQLite(const string& strPath);
627bool LoadWalletFromSQLiteRuntime(const string& strPath);
627 628
628// Writes a wallet.dat that opens on its own, anywhere -- see the definition in 629// Writes a wallet.dat that opens on its own, anywhere -- see the definition in
629// db.cpp for why a plain file copy does not. Still a point-in-time snapshot: 630// db.cpp for why a plain file copy does not. Still a point-in-time snapshot:
diff --git a/src/main_gui.cpp b/src/main_gui.cpp
index ee875c7..4be4995 100644
--- a/src/main_gui.cpp
+++ b/src/main_gui.cpp
@@ -190,6 +190,8 @@ static void PrintUsage()
190 printf(" /walletsqliteloadcheck=FILE\n"); 190 printf(" /walletsqliteloadcheck=FILE\n");
191 printf(" (parse a SQLite export like the wallet loader)\n"); 191 printf(" (parse a SQLite export like the wallet loader)\n");
192 printf(" /walletsqlite=FILE (stage-load a SQLite wallet export, then exit)\n"); 192 printf(" /walletsqlite=FILE (stage-load a SQLite wallet export, then exit)\n");
193 printf(" /walletbackend=sqlite (opt-in: run the node on <datadir>/wallet.sqlite\n");
194 printf(" instead of wallet.dat; export one first)\n");
193 printf(" /rescan (walk the chain for coins this wallet owns\n"); 195 printf(" /rescan (walk the chain for coins this wallet owns\n");
194 printf(" but never recorded, then exit)\n"); 196 printf(" but never recorded, then exit)\n");
195 printf("\n"); 197 printf("\n");
@@ -618,9 +620,74 @@ int main(int argc, char* argv[])
618 printf("Loading wallet...\n"); 620 printf("Loading wallet...\n");
619 try 621 try
620 { 622 {
623 string strWalletBackend =
624 argval2(argc, argv, "/walletbackend", "-walletbackend");
621 string strWalletSQLite = 625 string strWalletSQLite =
622 argval2(argc, argv, "/walletsqlite", "-walletsqlite"); 626 argval2(argc, argv, "/walletsqlite", "-walletsqlite");
623 if (!strWalletSQLite.empty()) 627
628 // A misspelled backend must not fall through to Berkeley DB silently --
629 // someone who typed -walletbackend=sqlit meant to run on SQLite, and
630 // opening wallet.dat instead of telling them is the wrong surprise.
631 if (!strWalletBackend.empty() &&
632 strWalletBackend != "sqlite" && strWalletBackend != "bdb")
633 {
634 AttachTerminal();
635 FatalStartupError(fHeadlessStartup,
636 strprintf("Unknown wallet backend '%s'.", strWalletBackend.c_str()),
637 "Use -walletbackend=sqlite for the experimental SQLite backend, "
638 "or omit -walletbackend (or -walletbackend=bdb) to use the "
639 "default Berkeley DB wallet.dat.");
640 return 1;
641 }
642
643 if (strWalletBackend == "sqlite")
644 {
645 // Opt-in, experimental. The node runs on <datadir>/wallet.sqlite and
646 // never opens wallet.dat, but the user has to have exported one
647 // first -- there is no silent migration, and wallet.dat is left
648 // exactly where it is so a plain restart goes back to it.
649 AttachTerminal();
650 string strSQLitePath = GetAppDir() + "/wallet.sqlite";
651 if (!FileExists(strSQLitePath.c_str()))
652 {
653 string strGuide = strprintf(
654 "No SQLite wallet was found at\n"
655 " %s\n\n"
656 "The SQLite wallet backend is opt-in and needs an exported\n"
657 "wallet first. Your Berkeley DB wallet.dat is not touched by\n"
658 "any of this.\n\n"
659 " Step 1 Export your existing wallet to SQLite:\n"
660 " bitflash -walletsqliteexport=\"%s\"\n"
661 " Step 2 Start again with the SQLite backend:\n"
662 " bitflash -walletbackend=sqlite\n\n"
663 "To go back to Berkeley DB at any time, just start without\n"
664 "-walletbackend=sqlite. wallet.dat is still your wallet.",
665 strSQLitePath.c_str(), strSQLitePath.c_str());
666 FatalStartupError(fHeadlessStartup,
667 "The SQLite wallet backend needs an exported wallet first.",
668 strGuide);
669 return 1;
670 }
671
672 if (!LoadWalletFromSQLiteRuntime(strSQLitePath))
673 {
674 FatalStartupError(fHeadlessStartup,
675 "Cannot open the SQLite wallet backend.",
676 strWalletLoadError);
677 return 1;
678 }
679
680 fprintf(stderr,
681 "Wallet backend: SQLite (experimental)\n"
682 " %s\n"
683 "Your Berkeley DB wallet.dat is left untouched. Restart without\n"
684 "-walletbackend=sqlite to go back to it.\n",
685 strSQLitePath.c_str());
686 fflush(stderr);
687 // Fall through and run the node normally: every CWalletDB read,
688 // write, and erase now routes to this SQLite file.
689 }
690 else if (!strWalletSQLite.empty())
624 { 691 {
625 AttachTerminal(); 692 AttachTerminal();
626 if (!LoadWalletFromSQLite(strWalletSQLite)) 693 if (!LoadWalletFromSQLite(strWalletSQLite))
@@ -638,8 +705,7 @@ int main(int argc, char* argv[])
638 DBFlush(true); 705 DBFlush(true);
639 return 0; 706 return 0;
640 } 707 }
641 708 else if (!LoadWallet())
642 if (!LoadWallet())
643 { 709 {
644 // LoadWallet() explains itself into strWalletLoadError when it 710 // LoadWallet() explains itself into strWalletLoadError when it
645 // knows why -- an unsupported wallet format, for one, which is the 711 // knows why -- an unsupported wallet format, for one, which is the
@@ -650,7 +716,9 @@ int main(int argc, char* argv[])
650 } 716 }
651 717
652 // After the block index, so there is a chain to compare the wallet 718 // After the block index, so there is a chain to compare the wallet
653 // against, and before anything reports a balance. 719 // against, and before anything reports a balance. Works on either
720 // backend: RescanSpentFlags writes corrected flags through CWalletDB,
721 // which routes to SQLite when that backend is active.
654 RescanSpentFlags(); 722 RescanSpentFlags();
655 } 723 }
656 catch (const std::exception& e) 724 catch (const std::exception& e)
@@ -894,6 +962,10 @@ int main(int argc, char* argv[])
894 // called here, which is why a wallet.dat copied elsewhere would not 962 // called here, which is why a wallet.dat copied elsewhere would not
895 // open -- issue #40. Not optional. 963 // open -- issue #40. Not optional.
896 DBFlush(true); 964 DBFlush(true);
965 // Checkpoint and close the SQLite wallet backend if one is active. A
966 // no-op on the Berkeley DB path. Safe to kill before this runs: WAL +
967 // synchronous=FULL already made every committed write durable.
968 WalletSQLiteRuntimeClose();
897#ifdef _WIN32 969#ifdef _WIN32
898 // wallet.dat is safely flushed and closed now; let a pending console 970 // wallet.dat is safely flushed and closed now; let a pending console
899 // control handler (CLOSE/LOGOFF/SHUTDOWN) return so the OS can finish. 971 // control handler (CLOSE/LOGOFF/SHUTDOWN) return so the OS can finish.
@@ -910,6 +982,7 @@ int main(int argc, char* argv[])
910 StopNode(); 982 StopNode();
911 BtfStopManagedTor(); 983 BtfStopManagedTor();
912 DBFlush(true); 984 DBFlush(true);
985 WalletSQLiteRuntimeClose();
913 return ret; 986 return ret;
914#endif 987#endif
915} 988}
diff --git a/src/selftest.cpp b/src/selftest.cpp
index 9fa0938..098eed6 100644
--- a/src/selftest.cpp
+++ b/src/selftest.cpp
@@ -1230,6 +1230,31 @@ static int RunWalletSQLiteSelfTest()
1230 1230
1231 nFail += Check(!FileExists((strRuntimeDir + "/wallet.dat").c_str()), 1231 nFail += Check(!FileExists((strRuntimeDir + "/wallet.dat").c_str()),
1232 "active SQLite runtime does not create wallet.dat") ? 0 : 1; 1232 "active SQLite runtime does not create wallet.dat") ? 0 : 1;
1233
1234 // Durability: close the backend (checkpoints the WAL), reopen
1235 // the same file, and confirm what CWalletDB wrote is still
1236 // there. This is the write-survives-a-restart property the real
1237 // -walletbackend=sqlite node depends on.
1238 WalletSQLiteRuntimeClose();
1239 bool fReopened =
1240 WalletSQLiteRuntimeOpen(strRuntimePath, strError);
1241 nFail += Check(fReopened,
1242 "SQLite runtime wallet reopens after close") ? 0 : 1;
1243 if (fReopened)
1244 {
1245 int64 nPersisted = 0;
1246 CPrivKey vchPersistedPrivKey;
1247 vector<unsigned char> vchPersistedDefaultKey;
1248 nFail += Check(CWalletDB("r").ReadSetting("runtime-sqlite-setting",
1249 nPersisted) &&
1250 nPersisted == 909090 &&
1251 CWalletDB("r").ReadDefaultKey(vchPersistedDefaultKey) &&
1252 vchPersistedDefaultKey == vchRuntimePubKey &&
1253 CWalletDB("r").ReadKey(vchRuntimePubKey,
1254 vchPersistedPrivKey) &&
1255 vchPersistedPrivKey == vchRuntimePrivKey,
1256 "SQLite runtime writes persist across a close and reopen") ? 0 : 1;
1257 }
1233 } 1258 }
1234 1259
1235 WalletSQLiteRuntimeClose(); 1260 WalletSQLiteRuntimeClose();