diff options
Diffstat (limited to 'src/main_gui.cpp')
| -rw-r--r-- | src/main_gui.cpp | 306 |
1 files changed, 297 insertions, 9 deletions
diff --git a/src/main_gui.cpp b/src/main_gui.cpp index 211e1e2..2743429 100644 --- a/src/main_gui.cpp +++ b/src/main_gui.cpp | |||
| @@ -113,8 +113,12 @@ static void PrintUsage() | |||
| 113 | printf(" /debug\n"); | 113 | printf(" /debug\n"); |
| 114 | printf(" /gen\n"); | 114 | printf(" /gen\n"); |
| 115 | printf(" /nogui or /daemon\n"); | 115 | printf(" /nogui or /daemon\n"); |
| 116 | printf(" /selftest=wallet-keypool, wallet-hd, wallet-format, wallet-crypto, wallet-encrypt,\n"); | 116 | printf(" /selftest=wallet-keypool, wallet-hd, wallet-format, wallet-storage-sanity,\n"); |
| 117 | printf(" net-message, consensus-limits, pool-stratum, parse-money, socks5-proxy, or managed-tor\n"); | 117 | printf(" db-env-reopen, wallet-sqlite, wallet-sqlite-migration,\n"); |
| 118 | printf(" wallet-crypto, wallet-encrypt, wallet-portability,\n"); | ||
| 119 | printf(" net-message,\n"); | ||
| 120 | printf(" consensus-limits, pool-stratum,\n"); | ||
| 121 | printf(" parse-money, socks5-proxy, or managed-tor\n"); | ||
| 118 | printf("\n"); | 122 | printf("\n"); |
| 119 | printf("Mining mode:\n"); | 123 | printf("Mining mode:\n"); |
| 120 | printf(" /operator\n"); | 124 | printf(" /operator\n"); |
| @@ -175,6 +179,23 @@ static void PrintUsage() | |||
| 175 | printf(" /recoveryaudit (show how much spendable balance is\n"); | 179 | printf(" /recoveryaudit (show how much spendable balance is\n"); |
| 176 | printf(" covered by the recovery phrase;\n"); | 180 | printf(" covered by the recovery phrase;\n"); |
| 177 | printf(" exits 2 when wallet.dat is still needed)\n"); | 181 | printf(" exits 2 when wallet.dat is still needed)\n"); |
| 182 | printf(" /walletstorageaudit (count wallet.dat record types without\n"); | ||
| 183 | printf(" printing wallet values, then exit)\n"); | ||
| 184 | printf(" /walletstorageauditjson=FILE\n"); | ||
| 185 | printf(" (write the same audit as deterministic JSON)\n"); | ||
| 186 | printf(" /walletstoragecheck (fail closed on inconsistent or unsafe\n"); | ||
| 187 | printf(" wallet.dat storage state)\n"); | ||
| 188 | printf(" /walletsqliteexport=FILE (copy raw wallet.dat records into an\n"); | ||
| 189 | printf(" SQLite store, then exit)\n"); | ||
| 190 | printf(" /walletsqliteverify=FILE (compare wallet.dat with a SQLite export,\n"); | ||
| 191 | printf(" printing counts only, then exit)\n"); | ||
| 192 | printf(" /walletsqliterestore=FILE (rebuild wallet.dat from a SQLite export in\n"); | ||
| 193 | printf(" an empty data directory, then exit)\n"); | ||
| 194 | printf(" /walletsqliteloadcheck=FILE\n"); | ||
| 195 | printf(" (parse a SQLite export like the wallet loader)\n"); | ||
| 196 | printf(" /walletsqlite=FILE (stage-load a SQLite wallet export, then exit)\n"); | ||
| 197 | printf(" /walletbackend=sqlite (opt-in: run the node on <datadir>/wallet.sqlite\n"); | ||
| 198 | printf(" instead of wallet.dat; export one first)\n"); | ||
| 178 | printf(" /rescan (walk the chain for coins this wallet owns\n"); | 199 | printf(" /rescan (walk the chain for coins this wallet owns\n"); |
| 179 | printf(" but never recorded, then exit)\n"); | 200 | printf(" but never recorded, then exit)\n"); |
| 180 | printf("\n"); | 201 | printf("\n"); |
| @@ -530,10 +551,119 @@ int main(int argc, char* argv[]) | |||
| 530 | 551 | ||
| 531 | ParseStartupArguments(argc, argv); | 552 | ParseStartupArguments(argc, argv); |
| 532 | 553 | ||
| 554 | #ifdef _WIN32 | ||
| 555 | // Set by the GUI "Restart Now" button: give the previous instance a moment | ||
| 556 | // to release the network port and the block-index files before this one | ||
| 557 | // opens them, so a relaunch does not race the process it is replacing. | ||
| 558 | if (arg(argc, argv, "/restartwait") || arg(argc, argv, "-restartwait")) | ||
| 559 | Sleep(2500); | ||
| 560 | #endif | ||
| 561 | |||
| 562 | // Hidden self-test helper. It must run before LoadWallet(): several | ||
| 563 | // storage-sanity scenarios deliberately make wallet.dat unsafe to load. | ||
| 564 | string strSelfTestMutateWallet = | ||
| 565 | argval2(argc, argv, "/selftestmutatewallet", "-selftestmutatewallet"); | ||
| 566 | if (!strSelfTestMutateWallet.empty()) | ||
| 567 | return RunSelfTestMutateWallet(strSelfTestMutateWallet); | ||
| 568 | |||
| 533 | string strSelfTest = argval2(argc, argv, "/selftest", "-selftest"); | 569 | string strSelfTest = argval2(argc, argv, "/selftest", "-selftest"); |
| 534 | if (!strSelfTest.empty()) | 570 | if (!strSelfTest.empty()) |
| 535 | return RunSelfTest(strSelfTest); | 571 | return RunSelfTest(strSelfTest); |
| 536 | 572 | ||
| 573 | // Storage diagnostics must run before LoadWallet(). Their job is to | ||
| 574 | // explain wallet.dat states that the normal wallet loader may refuse. | ||
| 575 | string strWalletStorageAuditJson = | ||
| 576 | argval2(argc, argv, "/walletstorageauditjson", "-walletstorageauditjson"); | ||
| 577 | bool fDiagWantsAudit = arg(argc,argv,"/walletstorageaudit") || | ||
| 578 | arg(argc,argv,"-walletstorageaudit") || | ||
| 579 | !strWalletStorageAuditJson.empty(); | ||
| 580 | bool fDiagWantsCheck = arg(argc,argv,"/walletstoragecheck") || | ||
| 581 | arg(argc,argv,"-walletstoragecheck"); | ||
| 582 | |||
| 583 | // These diagnostics normally read wallet.dat. Under -walletbackend=sqlite | ||
| 584 | // the live wallet is wallet.sqlite, so open it read-only as the active | ||
| 585 | // store and let the same scan walk the right file. This runs before the | ||
| 586 | // normal backend resolution further down, so validate the value here too. | ||
| 587 | string strDiagBackend = argval2(argc, argv, "/walletbackend", "-walletbackend"); | ||
| 588 | if ((fDiagWantsAudit || fDiagWantsCheck) && !strDiagBackend.empty() && | ||
| 589 | strDiagBackend != "sqlite" && strDiagBackend != "bdb") | ||
| 590 | { | ||
| 591 | AttachTerminal(); | ||
| 592 | fprintf(stderr, "Unknown wallet backend '%s'. Use -walletbackend=sqlite " | ||
| 593 | "or -walletbackend=bdb.\n", strDiagBackend.c_str()); | ||
| 594 | return 1; | ||
| 595 | } | ||
| 596 | bool fDiagSQLite = (strDiagBackend == "sqlite") && | ||
| 597 | (fDiagWantsAudit || fDiagWantsCheck); | ||
| 598 | if (fDiagSQLite) | ||
| 599 | { | ||
| 600 | AttachTerminal(); | ||
| 601 | string strSQLitePath = GetAppDir() + "/wallet.sqlite"; | ||
| 602 | string strOpenErr; | ||
| 603 | if (!WalletSQLiteRuntimeOpenReadOnly(strSQLitePath, strOpenErr)) | ||
| 604 | { | ||
| 605 | fprintf(stderr, "Cannot open the SQLite wallet for diagnostics: %s\n", | ||
| 606 | strOpenErr.c_str()); | ||
| 607 | return 1; | ||
| 608 | } | ||
| 609 | } | ||
| 610 | |||
| 611 | if (fDiagWantsAudit) | ||
| 612 | { | ||
| 613 | int nRet = CmdWalletStorageAudit(strWalletStorageAuditJson); | ||
| 614 | if (fDiagSQLite) | ||
| 615 | WalletSQLiteRuntimeClose(); | ||
| 616 | else | ||
| 617 | DBFlush(true); | ||
| 618 | return nRet; | ||
| 619 | } | ||
| 620 | |||
| 621 | if (fDiagWantsCheck) | ||
| 622 | { | ||
| 623 | int nRet = CmdWalletStorageCheck(); | ||
| 624 | if (fDiagSQLite) | ||
| 625 | WalletSQLiteRuntimeClose(); | ||
| 626 | else | ||
| 627 | DBFlush(true); | ||
| 628 | return nRet; | ||
| 629 | } | ||
| 630 | |||
| 631 | string strWalletSQLiteExport = | ||
| 632 | argval2(argc, argv, "/walletsqliteexport", "-walletsqliteexport"); | ||
| 633 | if (!strWalletSQLiteExport.empty()) | ||
| 634 | { | ||
| 635 | int nRet = CmdWalletSQLiteExport(strWalletSQLiteExport); | ||
| 636 | DBFlush(true); | ||
| 637 | return nRet; | ||
| 638 | } | ||
| 639 | |||
| 640 | string strWalletSQLiteVerify = | ||
| 641 | argval2(argc, argv, "/walletsqliteverify", "-walletsqliteverify"); | ||
| 642 | if (!strWalletSQLiteVerify.empty()) | ||
| 643 | { | ||
| 644 | int nRet = CmdWalletSQLiteVerify(strWalletSQLiteVerify); | ||
| 645 | DBFlush(true); | ||
| 646 | return nRet; | ||
| 647 | } | ||
| 648 | |||
| 649 | string strWalletSQLiteRestore = | ||
| 650 | argval2(argc, argv, "/walletsqliterestore", "-walletsqliterestore"); | ||
| 651 | if (!strWalletSQLiteRestore.empty()) | ||
| 652 | { | ||
| 653 | int nRet = CmdWalletSQLiteRestore(strWalletSQLiteRestore); | ||
| 654 | DBFlush(true); | ||
| 655 | return nRet; | ||
| 656 | } | ||
| 657 | |||
| 658 | string strWalletSQLiteLoadCheck = | ||
| 659 | argval2(argc, argv, "/walletsqliteloadcheck", "-walletsqliteloadcheck"); | ||
| 660 | if (!strWalletSQLiteLoadCheck.empty()) | ||
| 661 | { | ||
| 662 | int nRet = CmdWalletSQLiteLoadCheck(strWalletSQLiteLoadCheck); | ||
| 663 | DBFlush(true); | ||
| 664 | return nRet; | ||
| 665 | } | ||
| 666 | |||
| 537 | // Berkeley DB reports failure by throwing, and CDB's constructor lets it | 667 | // Berkeley DB reports failure by throwing, and CDB's constructor lets it |
| 538 | // through. Nothing on this path caught anything, so an unreadable | 668 | // through. Nothing on this path caught anything, so an unreadable |
| 539 | // blkindex.dat or wallet.dat -- one written by another platform's Berkeley | 669 | // blkindex.dat or wallet.dat -- one written by another platform's Berkeley |
| @@ -578,18 +708,171 @@ int main(int argc, char* argv[]) | |||
| 578 | printf("Loading wallet...\n"); | 708 | printf("Loading wallet...\n"); |
| 579 | try | 709 | try |
| 580 | { | 710 | { |
| 581 | if (!LoadWallet()) | 711 | string strWalletBackend = |
| 712 | argval2(argc, argv, "/walletbackend", "-walletbackend"); | ||
| 713 | string strWalletSQLite = | ||
| 714 | argval2(argc, argv, "/walletsqlite", "-walletsqlite"); | ||
| 715 | |||
| 716 | // A misspelled backend must not fall through to Berkeley DB silently -- | ||
| 717 | // someone who typed -walletbackend=sqlit meant to run on SQLite, and | ||
| 718 | // opening wallet.dat instead of telling them is the wrong surprise. | ||
| 719 | if (!strWalletBackend.empty() && | ||
| 720 | strWalletBackend != "sqlite" && strWalletBackend != "bdb") | ||
| 582 | { | 721 | { |
| 583 | // LoadWallet() explains itself into strWalletLoadError when it | 722 | AttachTerminal(); |
| 584 | // knows why -- an unsupported wallet format, for one, which is the | 723 | FatalStartupError(fHeadlessStartup, |
| 585 | // case this whole path exists to make visible. | 724 | strprintf("Unknown wallet backend '%s'.", strWalletBackend.c_str()), |
| 586 | FatalStartupError(fHeadlessStartup, "Cannot open wallet.dat.", | 725 | "Use -walletbackend=sqlite for the SQLite backend, or " |
| 587 | strWalletLoadError); | 726 | "-walletbackend=bdb for the Berkeley DB wallet.dat."); |
| 588 | return 1; | 727 | return 1; |
| 589 | } | 728 | } |
| 590 | 729 | ||
| 730 | if (strWalletBackend == "sqlite") | ||
| 731 | { | ||
| 732 | // Explicit opt-in via the flag. The node runs on | ||
| 733 | // <datadir>/wallet.sqlite and never opens wallet.dat, but the user | ||
| 734 | // has to have exported one first -- there is no silent migration, | ||
| 735 | // and wallet.dat is left | ||
| 736 | // exactly where it is so a plain restart goes back to it. | ||
| 737 | AttachTerminal(); | ||
| 738 | string strSQLitePath = GetAppDir() + "/wallet.sqlite"; | ||
| 739 | if (!FileExists(strSQLitePath.c_str())) | ||
| 740 | { | ||
| 741 | string strGuide = strprintf( | ||
| 742 | "No SQLite wallet was found at\n" | ||
| 743 | " %s\n\n" | ||
| 744 | "The SQLite wallet backend is opt-in and needs an exported\n" | ||
| 745 | "wallet first. Your Berkeley DB wallet.dat is not touched by\n" | ||
| 746 | "any of this.\n\n" | ||
| 747 | " Step 1 Export your existing wallet to SQLite:\n" | ||
| 748 | " bitflash -walletsqliteexport=\"%s\"\n" | ||
| 749 | " Step 2 Start again with the SQLite backend:\n" | ||
| 750 | " bitflash -walletbackend=sqlite\n\n" | ||
| 751 | "To go back to Berkeley DB at any time, just start without\n" | ||
| 752 | "-walletbackend=sqlite. wallet.dat is still your wallet.", | ||
| 753 | strSQLitePath.c_str(), strSQLitePath.c_str()); | ||
| 754 | FatalStartupError(fHeadlessStartup, | ||
| 755 | "The SQLite wallet backend needs an exported wallet first.", | ||
| 756 | strGuide); | ||
| 757 | return 1; | ||
| 758 | } | ||
| 759 | |||
| 760 | if (!LoadWalletFromSQLiteRuntime(strSQLitePath)) | ||
| 761 | { | ||
| 762 | FatalStartupError(fHeadlessStartup, | ||
| 763 | "Cannot open the SQLite wallet backend.", | ||
| 764 | strWalletLoadError); | ||
| 765 | return 1; | ||
| 766 | } | ||
| 767 | |||
| 768 | fprintf(stderr, | ||
| 769 | "Wallet backend: SQLite\n" | ||
| 770 | " %s\n" | ||
| 771 | "Your Berkeley DB wallet.dat is left untouched. Restart without\n" | ||
| 772 | "-walletbackend=sqlite to go back to it.\n", | ||
| 773 | strSQLitePath.c_str()); | ||
| 774 | fflush(stderr); | ||
| 775 | // Fall through and run the node normally: every CWalletDB read, | ||
| 776 | // write, and erase now routes to this SQLite file. | ||
| 777 | } | ||
| 778 | else if (!strWalletSQLite.empty()) | ||
| 779 | { | ||
| 780 | AttachTerminal(); | ||
| 781 | if (!LoadWalletFromSQLite(strWalletSQLite)) | ||
| 782 | { | ||
| 783 | FatalStartupError(fHeadlessStartup, | ||
| 784 | "Cannot open SQLite wallet export.", | ||
| 785 | strWalletLoadError); | ||
| 786 | return 1; | ||
| 787 | } | ||
| 788 | |||
| 789 | fprintf(stderr, "SQLite wallet loaded: read-only staging backend\n"); | ||
| 790 | fprintf(stderr, "SQLite runtime writes are not enabled yet; exiting before " | ||
| 791 | "network, mining, GUI, or wallet mutation starts.\n"); | ||
| 792 | fflush(stderr); | ||
| 793 | DBFlush(true); | ||
| 794 | return 0; | ||
| 795 | } | ||
| 796 | else if (strWalletBackend == "bdb") | ||
| 797 | { | ||
| 798 | // Explicit -walletbackend=bdb overrides the marker and the default: | ||
| 799 | // force Berkeley DB (LoadWallet creates wallet.dat if none exists). | ||
| 800 | if (!LoadWallet()) | ||
| 801 | { | ||
| 802 | FatalStartupError(fHeadlessStartup, "Cannot open wallet.dat.", | ||
| 803 | strWalletLoadError); | ||
| 804 | return 1; | ||
| 805 | } | ||
| 806 | } | ||
| 807 | else | ||
| 808 | { | ||
| 809 | // No explicit -walletbackend on the command line. Resolve it from | ||
| 810 | // the persistent marker, then defaults: an existing wallet.sqlite | ||
| 811 | // stays on SQLite, an existing wallet.dat stays on Berkeley DB (the | ||
| 812 | // GUI offers to convert), and a fresh install starts on SQLite -- | ||
| 813 | // the new default. wallet.dat is never touched by any of this. | ||
| 814 | string strSQLitePath = GetAppDir() + "/wallet.sqlite"; | ||
| 815 | bool fSQLiteExists = FileExists(strSQLitePath.c_str()); | ||
| 816 | bool fWalletDatExists = FileExists((GetAppDir() + "/wallet.dat").c_str()); | ||
| 817 | string strMarker = ReadWalletBackendMarker(); | ||
| 818 | |||
| 819 | bool fUseSQLite = false; | ||
| 820 | bool fCreateSQLite = false; | ||
| 821 | if (strMarker == "sqlite") | ||
| 822 | { | ||
| 823 | if (fSQLiteExists) fUseSQLite = true; | ||
| 824 | else if (fWalletDatExists) fUseSQLite = false; // marker stale; real wallet is BDB | ||
| 825 | else { fUseSQLite = true; fCreateSQLite = true; } | ||
| 826 | } | ||
| 827 | else if (strMarker == "bdb") | ||
| 828 | { | ||
| 829 | fUseSQLite = false; | ||
| 830 | } | ||
| 831 | else // no marker recorded yet | ||
| 832 | { | ||
| 833 | if (fSQLiteExists && !fWalletDatExists) fUseSQLite = true; | ||
| 834 | else if (fWalletDatExists) fUseSQLite = false; | ||
| 835 | else { fUseSQLite = true; fCreateSQLite = true; } | ||
| 836 | } | ||
| 837 | |||
| 838 | if (fUseSQLite) | ||
| 839 | { | ||
| 840 | AttachTerminal(); | ||
| 841 | bool fOk = fCreateSQLite | ||
| 842 | ? CreateNewSQLiteWallet(strSQLitePath) | ||
| 843 | : LoadWalletFromSQLiteRuntime(strSQLitePath); | ||
| 844 | if (!fOk) | ||
| 845 | { | ||
| 846 | FatalStartupError(fHeadlessStartup, | ||
| 847 | fCreateSQLite ? "Cannot create the SQLite wallet." | ||
| 848 | : "Cannot open the SQLite wallet.", | ||
| 849 | strWalletLoadError); | ||
| 850 | return 1; | ||
| 851 | } | ||
| 852 | // Record the choice so it is stable from here on. | ||
| 853 | if (ReadWalletBackendMarker() != "sqlite") | ||
| 854 | WriteWalletBackendMarker("sqlite"); | ||
| 855 | fprintf(stderr, "Wallet backend: SQLite\n %s\n%s", | ||
| 856 | strSQLitePath.c_str(), | ||
| 857 | fCreateSQLite ? "A new wallet was created here.\n" : ""); | ||
| 858 | fflush(stderr); | ||
| 859 | // Fall through and run the node; CWalletDB routes to this file. | ||
| 860 | } | ||
| 861 | else if (!LoadWallet()) | ||
| 862 | { | ||
| 863 | // LoadWallet() explains itself into strWalletLoadError when it | ||
| 864 | // knows why -- an unsupported wallet format, for one, which is | ||
| 865 | // the case this whole path exists to make visible. | ||
| 866 | FatalStartupError(fHeadlessStartup, "Cannot open wallet.dat.", | ||
| 867 | strWalletLoadError); | ||
| 868 | return 1; | ||
| 869 | } | ||
| 870 | } | ||
| 871 | |||
| 591 | // After the block index, so there is a chain to compare the wallet | 872 | // After the block index, so there is a chain to compare the wallet |
| 592 | // against, and before anything reports a balance. | 873 | // against, and before anything reports a balance. Works on either |
| 874 | // backend: RescanSpentFlags writes corrected flags through CWalletDB, | ||
| 875 | // which routes to SQLite when that backend is active. | ||
| 593 | RescanSpentFlags(); | 876 | RescanSpentFlags(); |
| 594 | } | 877 | } |
| 595 | catch (const std::exception& e) | 878 | catch (const std::exception& e) |
| @@ -858,6 +1141,10 @@ int main(int argc, char* argv[]) | |||
| 858 | // called here, which is why a wallet.dat copied elsewhere would not | 1141 | // called here, which is why a wallet.dat copied elsewhere would not |
| 859 | // open -- issue #40. Not optional. | 1142 | // open -- issue #40. Not optional. |
| 860 | DBFlush(true); | 1143 | DBFlush(true); |
| 1144 | // Checkpoint and close the SQLite wallet backend if one is active. A | ||
| 1145 | // no-op on the Berkeley DB path. Safe to kill before this runs: WAL + | ||
| 1146 | // synchronous=FULL already made every committed write durable. | ||
| 1147 | WalletSQLiteRuntimeClose(); | ||
| 861 | #ifdef _WIN32 | 1148 | #ifdef _WIN32 |
| 862 | // wallet.dat is safely flushed and closed now; let a pending console | 1149 | // wallet.dat is safely flushed and closed now; let a pending console |
| 863 | // control handler (CLOSE/LOGOFF/SHUTDOWN) return so the OS can finish. | 1150 | // control handler (CLOSE/LOGOFF/SHUTDOWN) return so the OS can finish. |
| @@ -874,6 +1161,7 @@ int main(int argc, char* argv[]) | |||
| 874 | StopNode(); | 1161 | StopNode(); |
| 875 | BtfStopManagedTor(); | 1162 | BtfStopManagedTor(); |
| 876 | DBFlush(true); | 1163 | DBFlush(true); |
| 1164 | WalletSQLiteRuntimeClose(); | ||
| 877 | return ret; | 1165 | return ret; |
| 878 | #endif | 1166 | #endif |
| 879 | } | 1167 | } |