diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/db.cpp | 107 | ||||
| -rw-r--r-- | src/db.h | 1 | ||||
| -rw-r--r-- | src/main_gui.cpp | 81 | ||||
| -rw-r--r-- | src/selftest.cpp | 25 |
4 files changed, 210 insertions, 4 deletions
| @@ -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. | ||
| 1441 | bool 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 | // |
| @@ -624,6 +624,7 @@ bool ScanWalletRecords(CWalletRecordVisitor& visitor, string& strErrorRet); | |||
| 624 | 624 | ||
| 625 | bool LoadWallet(); | 625 | bool LoadWallet(); |
| 626 | bool LoadWalletFromSQLite(const string& strPath); | 626 | bool LoadWalletFromSQLite(const string& strPath); |
| 627 | bool 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(); |