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:
Diffstat (limited to 'src')
-rw-r--r--src/db.cpp91
-rw-r--r--src/db.h1
-rw-r--r--src/main_gui.cpp66
-rw-r--r--src/selftest.cpp25
4 files changed, 179 insertions, 4 deletions
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()
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,90 @@ 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 if (!ReconcileLoadedWalletMetadata(visitor.fHaveStoredMineMode,
1500 visitor.fHaveStoredHDCoinType))
1501 {
1502 WalletSQLiteRuntimeClose();
1503 if (strWalletLoadError.empty())
1504 strWalletLoadError = "SQLite wallet metadata is not supported by this build";
1505 return false;
1506 }
1507
1508 // Never fabricate a default key: an export always carries one, and a file
1509 // that does not is refused with the wallet left open to no writes rather
1510 // than quietly minted into a new identity.
1511 if (!FinishLoadedWallet(visitor.vchDefaultKey, false))
1512 {
1513 WalletSQLiteRuntimeClose();
1514 return false;
1515 }
1516
1517 return true;
1518}
1519
1429 1520
1430// Write a copy of wallet.dat that can be opened anywhere. 1521// Write a copy of wallet.dat that can be opened anywhere.
1431// 1522//
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..4d77533 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,59 @@ 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 if (strWalletBackend == "sqlite")
629 {
630 // Opt-in, experimental. The node runs on <datadir>/wallet.sqlite and
631 // never opens wallet.dat, but the user has to have exported one
632 // first -- there is no silent migration, and wallet.dat is left
633 // exactly where it is so a plain restart goes back to it.
634 AttachTerminal();
635 string strSQLitePath = GetAppDir() + "/wallet.sqlite";
636 if (!FileExists(strSQLitePath.c_str()))
637 {
638 string strGuide = strprintf(
639 "No SQLite wallet was found at\n"
640 " %s\n\n"
641 "The SQLite wallet backend is opt-in and needs an exported\n"
642 "wallet first. Your Berkeley DB wallet.dat is not touched by\n"
643 "any of this.\n\n"
644 " Step 1 Export your existing wallet to SQLite:\n"
645 " bitflash -walletsqliteexport=\"%s\"\n"
646 " Step 2 Start again with the SQLite backend:\n"
647 " bitflash -walletbackend=sqlite\n\n"
648 "To go back to Berkeley DB at any time, just start without\n"
649 "-walletbackend=sqlite. wallet.dat is still your wallet.",
650 strSQLitePath.c_str(), strSQLitePath.c_str());
651 FatalStartupError(fHeadlessStartup,
652 "The SQLite wallet backend needs an exported wallet first.",
653 strGuide);
654 return 1;
655 }
656
657 if (!LoadWalletFromSQLiteRuntime(strSQLitePath))
658 {
659 FatalStartupError(fHeadlessStartup,
660 "Cannot open the SQLite wallet backend.",
661 strWalletLoadError);
662 return 1;
663 }
664
665 fprintf(stderr,
666 "Wallet backend: SQLite (experimental)\n"
667 " %s\n"
668 "Your Berkeley DB wallet.dat is left untouched. Restart without\n"
669 "-walletbackend=sqlite to go back to it.\n",
670 strSQLitePath.c_str());
671 fflush(stderr);
672 // Fall through and run the node normally: every CWalletDB read,
673 // write, and erase now routes to this SQLite file.
674 }
675 else if (!strWalletSQLite.empty())
624 { 676 {
625 AttachTerminal(); 677 AttachTerminal();
626 if (!LoadWalletFromSQLite(strWalletSQLite)) 678 if (!LoadWalletFromSQLite(strWalletSQLite))
@@ -638,8 +690,7 @@ int main(int argc, char* argv[])
638 DBFlush(true); 690 DBFlush(true);
639 return 0; 691 return 0;
640 } 692 }
641 693 else if (!LoadWallet())
642 if (!LoadWallet())
643 { 694 {
644 // LoadWallet() explains itself into strWalletLoadError when it 695 // LoadWallet() explains itself into strWalletLoadError when it
645 // knows why -- an unsupported wallet format, for one, which is the 696 // knows why -- an unsupported wallet format, for one, which is the
@@ -650,7 +701,9 @@ int main(int argc, char* argv[])
650 } 701 }
651 702
652 // After the block index, so there is a chain to compare the wallet 703 // After the block index, so there is a chain to compare the wallet
653 // against, and before anything reports a balance. 704 // against, and before anything reports a balance. Works on either
705 // backend: RescanSpentFlags writes corrected flags through CWalletDB,
706 // which routes to SQLite when that backend is active.
654 RescanSpentFlags(); 707 RescanSpentFlags();
655 } 708 }
656 catch (const std::exception& e) 709 catch (const std::exception& e)
@@ -894,6 +947,10 @@ int main(int argc, char* argv[])
894 // called here, which is why a wallet.dat copied elsewhere would not 947 // called here, which is why a wallet.dat copied elsewhere would not
895 // open -- issue #40. Not optional. 948 // open -- issue #40. Not optional.
896 DBFlush(true); 949 DBFlush(true);
950 // Checkpoint and close the SQLite wallet backend if one is active. A
951 // no-op on the Berkeley DB path. Safe to kill before this runs: WAL +
952 // synchronous=FULL already made every committed write durable.
953 WalletSQLiteRuntimeClose();
897#ifdef _WIN32 954#ifdef _WIN32
898 // wallet.dat is safely flushed and closed now; let a pending console 955 // wallet.dat is safely flushed and closed now; let a pending console
899 // control handler (CLOSE/LOGOFF/SHUTDOWN) return so the OS can finish. 956 // control handler (CLOSE/LOGOFF/SHUTDOWN) return so the OS can finish.
@@ -910,6 +967,7 @@ int main(int argc, char* argv[])
910 StopNode(); 967 StopNode();
911 BtfStopManagedTor(); 968 BtfStopManagedTor();
912 DBFlush(true); 969 DBFlush(true);
970 WalletSQLiteRuntimeClose();
913 return ret; 971 return ret;
914#endif 972#endif
915} 973}
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();