diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/db.cpp | 91 | ||||
| -rw-r--r-- | src/db.h | 5 | ||||
| -rw-r--r-- | src/gui.cpp | 90 | ||||
| -rw-r--r-- | src/main_gui.cpp | 80 | ||||
| -rw-r--r-- | src/makefile.mingw | 2 | ||||
| -rw-r--r-- | src/selftest.cpp | 241 | ||||
| -rw-r--r-- | src/walletcmd.cpp | 119 | ||||
| -rw-r--r-- | src/walletcmd.h | 9 |
8 files changed, 590 insertions, 47 deletions
| @@ -101,10 +101,13 @@ bool WalletSQLiteRuntimeActive() | |||
| 101 | return pWalletSQLiteRuntime != NULL; | 101 | return pWalletSQLiteRuntime != NULL; |
| 102 | } | 102 | } |
| 103 | 103 | ||
| 104 | bool WalletSQLiteRuntimeOpen(const string& strPath, string& strErrorRet) | 104 | bool WalletSQLiteRuntimeOpen(const string& strPath, string& strErrorRet, bool fMustExist) |
| 105 | { | 105 | { |
| 106 | strErrorRet.clear(); | 106 | strErrorRet.clear(); |
| 107 | if (!FileExists(strPath.c_str())) | 107 | // Callers that load an existing wallet want the clear "does not exist" |
| 108 | // message rather than silently creating a blank one; the create path passes | ||
| 109 | // fMustExist=false so Open() (SQLITE_OPEN_CREATE) can make a fresh file. | ||
| 110 | if (fMustExist && !FileExists(strPath.c_str())) | ||
| 108 | { | 111 | { |
| 109 | strErrorRet = strprintf("SQLite wallet does not exist: %s", | 112 | strErrorRet = strprintf("SQLite wallet does not exist: %s", |
| 110 | strPath.c_str()); | 113 | strPath.c_str()); |
| @@ -230,6 +233,47 @@ bool WalletSQLiteRuntimeCheckpoint(string& strErrorRet) | |||
| 230 | return pWalletSQLiteRuntime->Checkpoint(strErrorRet); | 233 | return pWalletSQLiteRuntime->Checkpoint(strErrorRet); |
| 231 | } | 234 | } |
| 232 | 235 | ||
| 236 | // The persistent record of which backend this datadir uses, so the choice | ||
| 237 | // survives a restart without a command-line flag. A plain text file holding | ||
| 238 | // "sqlite" or "bdb" -- visible and hand-editable on purpose, no hidden state. | ||
| 239 | static string WalletBackendMarkerPath() | ||
| 240 | { | ||
| 241 | return GetAppDir() + "/wallet-backend"; | ||
| 242 | } | ||
| 243 | |||
| 244 | string ReadWalletBackendMarker() | ||
| 245 | { | ||
| 246 | FILE* pf = fopen(WalletBackendMarkerPath().c_str(), "rb"); | ||
| 247 | if (!pf) | ||
| 248 | return ""; | ||
| 249 | char buf[16] = {0}; | ||
| 250 | size_t n = fread(buf, 1, sizeof(buf) - 1, pf); | ||
| 251 | fclose(pf); | ||
| 252 | string s(buf, n); | ||
| 253 | while (!s.empty() && isspace((unsigned char)s[s.size() - 1])) | ||
| 254 | s.erase(s.size() - 1); | ||
| 255 | while (!s.empty() && isspace((unsigned char)s[0])) | ||
| 256 | s.erase(0, 1); | ||
| 257 | if (s == "sqlite" || s == "bdb") | ||
| 258 | return s; | ||
| 259 | return ""; | ||
| 260 | } | ||
| 261 | |||
| 262 | bool WriteWalletBackendMarker(const string& strBackend) | ||
| 263 | { | ||
| 264 | if (strBackend != "sqlite" && strBackend != "bdb") | ||
| 265 | return false; | ||
| 266 | FILE* pf = fopen(WalletBackendMarkerPath().c_str(), "wb"); | ||
| 267 | if (!pf) | ||
| 268 | return false; | ||
| 269 | bool fOk = fwrite(strBackend.c_str(), 1, strBackend.size(), pf) == strBackend.size(); | ||
| 270 | if (fputc('\n', pf) == EOF) | ||
| 271 | fOk = false; | ||
| 272 | if (fclose(pf) != 0) | ||
| 273 | fOk = false; | ||
| 274 | return fOk; | ||
| 275 | } | ||
| 276 | |||
| 233 | 277 | ||
| 234 | CDB::CDB(const char* pszFile, const char* pszMode, bool fTxn) : pdb(NULL) | 278 | CDB::CDB(const char* pszFile, const char* pszMode, bool fTxn) : pdb(NULL) |
| 235 | { | 279 | { |
| @@ -1609,6 +1653,49 @@ bool LoadWalletFromSQLiteRuntime(const string& strPath) | |||
| 1609 | return true; | 1653 | return true; |
| 1610 | } | 1654 | } |
| 1611 | 1655 | ||
| 1656 | // Create a brand-new wallet directly in the SQLite backend. For a fresh install | ||
| 1657 | // where SQLite is the default there is no wallet.dat to export from, so open a | ||
| 1658 | // new wallet.sqlite (Open() creates the file and its schema) and let | ||
| 1659 | // FinishLoadedWallet mint the first default key straight into it -- the same | ||
| 1660 | // call the Berkeley DB create path uses, so a new wallet is identical either | ||
| 1661 | // way. Refuses to touch an existing file, and cleans up on failure so a broken | ||
| 1662 | // half-initialised wallet.sqlite is never left for the next start to load. | ||
| 1663 | bool CreateNewSQLiteWallet(const string& strPath) | ||
| 1664 | { | ||
| 1665 | strWalletLoadError.clear(); | ||
| 1666 | |||
| 1667 | if (FileExists(strPath.c_str())) | ||
| 1668 | { | ||
| 1669 | strWalletLoadError = strprintf("SQLite wallet already exists: %s", | ||
| 1670 | strPath.c_str()); | ||
| 1671 | printf("CreateNewSQLiteWallet: %s\n", strWalletLoadError.c_str()); | ||
| 1672 | return false; | ||
| 1673 | } | ||
| 1674 | |||
| 1675 | string strError; | ||
| 1676 | if (!WalletSQLiteRuntimeOpen(strPath, strError, false)) | ||
| 1677 | { | ||
| 1678 | strWalletLoadError = strError; | ||
| 1679 | printf("CreateNewSQLiteWallet: cannot create SQLite wallet: %s\n", | ||
| 1680 | strError.c_str()); | ||
| 1681 | return false; | ||
| 1682 | } | ||
| 1683 | |||
| 1684 | vector<unsigned char> vchNoDefaultKey; | ||
| 1685 | if (!FinishLoadedWallet(vchNoDefaultKey, true)) | ||
| 1686 | { | ||
| 1687 | WalletSQLiteRuntimeClose(); | ||
| 1688 | remove(strPath.c_str()); | ||
| 1689 | remove((strPath + "-wal").c_str()); | ||
| 1690 | remove((strPath + "-shm").c_str()); | ||
| 1691 | if (strWalletLoadError.empty()) | ||
| 1692 | strWalletLoadError = "could not initialise the new SQLite wallet"; | ||
| 1693 | return false; | ||
| 1694 | } | ||
| 1695 | |||
| 1696 | return true; | ||
| 1697 | } | ||
| 1698 | |||
| 1612 | 1699 | ||
| 1613 | // Write a copy of wallet.dat that can be opened anywhere. | 1700 | // Write a copy of wallet.dat that can be opened anywhere. |
| 1614 | // | 1701 | // |
| @@ -357,7 +357,7 @@ private: | |||
| 357 | // peer discovery is entirely Nostr/.btf-based now. | 357 | // peer discovery is entirely Nostr/.btf-based now. |
| 358 | 358 | ||
| 359 | bool WalletSQLiteRuntimeActive(); | 359 | bool WalletSQLiteRuntimeActive(); |
| 360 | bool WalletSQLiteRuntimeOpen(const string& strPath, string& strErrorRet); | 360 | bool WalletSQLiteRuntimeOpen(const string& strPath, string& strErrorRet, bool fMustExist=true); |
| 361 | void WalletSQLiteRuntimeClose(); | 361 | void WalletSQLiteRuntimeClose(); |
| 362 | bool WalletSQLiteRuntimeReadRecord(const vector<unsigned char>& vchKey, | 362 | bool WalletSQLiteRuntimeReadRecord(const vector<unsigned char>& vchKey, |
| 363 | vector<unsigned char>& vchValueRet); | 363 | vector<unsigned char>& vchValueRet); |
| @@ -370,6 +370,9 @@ bool WalletSQLiteRuntimeCommitTxn(); | |||
| 370 | void WalletSQLiteRuntimeRollbackTxn(); | 370 | void WalletSQLiteRuntimeRollbackTxn(); |
| 371 | bool WalletSQLiteRuntimeOpenReadOnly(const string& strPath, string& strErrorRet); | 371 | bool WalletSQLiteRuntimeOpenReadOnly(const string& strPath, string& strErrorRet); |
| 372 | bool WalletSQLiteRuntimeCheckpoint(string& strErrorRet); | 372 | bool WalletSQLiteRuntimeCheckpoint(string& strErrorRet); |
| 373 | string ReadWalletBackendMarker(); | ||
| 374 | bool WriteWalletBackendMarker(const string& strBackend); | ||
| 375 | bool CreateNewSQLiteWallet(const string& strPath); | ||
| 373 | 376 | ||
| 374 | 377 | ||
| 375 | 378 | ||
diff --git a/src/gui.cpp b/src/gui.cpp index 8dcb854..89a7ab6 100644 --- a/src/gui.cpp +++ b/src/gui.cpp | |||
| @@ -1391,6 +1391,84 @@ static void DrawAboutDialog() | |||
| 1391 | ImGui::End(); | 1391 | ImGui::End(); |
| 1392 | } | 1392 | } |
| 1393 | 1393 | ||
| 1394 | // First-run offer to move a Berkeley DB wallet to the SQLite backend. Only the | ||
| 1395 | // button actions touch anything: "Convert" exports wallet.dat to wallet.sqlite | ||
| 1396 | // and records the backend marker (wallet.dat is left untouched, so a restart | ||
| 1397 | // picks up SQLite while the old file stays as a fallback); "Keep Berkeley DB" | ||
| 1398 | // just records that choice so the offer does not return; "Decide later" leaves | ||
| 1399 | // no marker and asks again next start. | ||
| 1400 | static bool g_showBackendWizard = false; | ||
| 1401 | static std::string g_wizardStatus; | ||
| 1402 | static bool g_wizardConverted = false; | ||
| 1403 | |||
| 1404 | static void DrawBackendWizardDialog() | ||
| 1405 | { | ||
| 1406 | if (!g_showBackendWizard) return; | ||
| 1407 | ImGui::SetNextWindowSize(ImVec2(560.0f, 0.0f), ImGuiCond_Always); | ||
| 1408 | ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), | ||
| 1409 | ImGuiCond_Always, ImVec2(0.5f, 0.5f)); | ||
| 1410 | if (ImGui::Begin("Wallet Storage", &g_showBackendWizard, | ||
| 1411 | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse)) | ||
| 1412 | { | ||
| 1413 | if (g_wizardConverted) | ||
| 1414 | { | ||
| 1415 | ImGui::TextColored(ImVec4(0.35f, 1.0f, 0.45f, 1.0f), "Converted to SQLite."); | ||
| 1416 | ImGui::TextWrapped("%s", g_wizardStatus.c_str()); | ||
| 1417 | ImGui::Spacing(); | ||
| 1418 | ImGui::TextWrapped( | ||
| 1419 | "Restart Bitflash to start using the SQLite wallet. Your Berkeley " | ||
| 1420 | "DB wallet.dat is kept untouched as a fallback."); | ||
| 1421 | ImGui::Spacing(); | ||
| 1422 | if (ImGui::Button("Close", ImVec2(120.0f, 0.0f))) | ||
| 1423 | g_showBackendWizard = false; | ||
| 1424 | } | ||
| 1425 | else | ||
| 1426 | { | ||
| 1427 | ImGui::TextWrapped( | ||
| 1428 | "Bitflash can store your wallet in a new SQLite format: a single " | ||
| 1429 | "file that is easier to back up and does not depend on the " | ||
| 1430 | "database/ directory beside it."); | ||
| 1431 | ImGui::Spacing(); | ||
| 1432 | ImGui::TextWrapped( | ||
| 1433 | "Your current Berkeley DB wallet (wallet.dat) is kept exactly as it " | ||
| 1434 | "is -- nothing is deleted, and you can go back to it at any time. " | ||
| 1435 | "The change takes effect after you restart."); | ||
| 1436 | ImGui::Spacing(); | ||
| 1437 | if (ImGui::Button("Convert to SQLite", ImVec2(180.0f, 0.0f))) | ||
| 1438 | { | ||
| 1439 | int nCopied = 0; | ||
| 1440 | std::string strError; | ||
| 1441 | if (DoConvertWalletToSQLite(nCopied, strError)) | ||
| 1442 | { | ||
| 1443 | g_wizardStatus = | ||
| 1444 | strprintf("%d record(s) copied to wallet.sqlite.", nCopied); | ||
| 1445 | g_wizardConverted = true; | ||
| 1446 | } | ||
| 1447 | else | ||
| 1448 | g_wizardStatus = "Conversion failed: " + strError; | ||
| 1449 | } | ||
| 1450 | ImGui::SameLine(); | ||
| 1451 | if (ImGui::Button("Keep Berkeley DB", ImVec2(160.0f, 0.0f))) | ||
| 1452 | { | ||
| 1453 | WriteWalletBackendMarker("bdb"); | ||
| 1454 | g_showBackendWizard = false; | ||
| 1455 | } | ||
| 1456 | ImGui::SameLine(); | ||
| 1457 | if (ImGui::Button("Decide later", ImVec2(120.0f, 0.0f))) | ||
| 1458 | g_showBackendWizard = false; | ||
| 1459 | |||
| 1460 | if (!g_wizardStatus.empty()) | ||
| 1461 | { | ||
| 1462 | ImGui::Spacing(); | ||
| 1463 | ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.45f, 0.35f, 1.0f)); | ||
| 1464 | ImGui::TextWrapped("%s", g_wizardStatus.c_str()); | ||
| 1465 | ImGui::PopStyleColor(); | ||
| 1466 | } | ||
| 1467 | } | ||
| 1468 | } | ||
| 1469 | ImGui::End(); | ||
| 1470 | } | ||
| 1471 | |||
| 1394 | // --------------------------------------------------------------------------- | 1472 | // --------------------------------------------------------------------------- |
| 1395 | // Entry point | 1473 | // Entry point |
| 1396 | // --------------------------------------------------------------------------- | 1474 | // --------------------------------------------------------------------------- |
| @@ -1457,6 +1535,17 @@ int RunGUI(int argc, char* argv[]) | |||
| 1457 | g_mineRadio = nMineMode; | 1535 | g_mineRadio = nMineMode; |
| 1458 | strncpy(g_participantPool, strParticipantPool.c_str(), sizeof(g_participantPool)-1); | 1536 | strncpy(g_participantPool, strParticipantPool.c_str(), sizeof(g_participantPool)-1); |
| 1459 | 1537 | ||
| 1538 | // First run on a Berkeley DB wallet with no recorded choice yet: offer to | ||
| 1539 | // convert to SQLite. Never when already on SQLite, once wallet.sqlite exists, | ||
| 1540 | // or once a choice has been recorded in the backend marker. | ||
| 1541 | if (!WalletSQLiteRuntimeActive() && | ||
| 1542 | FileExists((GetAppDir() + "/wallet.dat").c_str()) && | ||
| 1543 | !FileExists((GetAppDir() + "/wallet.sqlite").c_str()) && | ||
| 1544 | ReadWalletBackendMarker().empty()) | ||
| 1545 | { | ||
| 1546 | g_showBackendWizard = true; | ||
| 1547 | } | ||
| 1548 | |||
| 1460 | int frame = 0; | 1549 | int frame = 0; |
| 1461 | while (!glfwWindowShouldClose(window) && !fShutdown) | 1550 | while (!glfwWindowShouldClose(window) && !fShutdown) |
| 1462 | { | 1551 | { |
| @@ -1480,6 +1569,7 @@ int RunGUI(int argc, char* argv[]) | |||
| 1480 | DrawRestorePhraseDialog(); | 1569 | DrawRestorePhraseDialog(); |
| 1481 | DrawDiagnosticsDialog(); | 1570 | DrawDiagnosticsDialog(); |
| 1482 | DrawAboutDialog(); | 1571 | DrawAboutDialog(); |
| 1572 | DrawBackendWizardDialog(); | ||
| 1483 | DrawStatusBar(); | 1573 | DrawStatusBar(); |
| 1484 | 1574 | ||
| 1485 | ImGui::Render(); | 1575 | ImGui::Render(); |
diff --git a/src/main_gui.cpp b/src/main_gui.cpp index af6053f..6cd8b75 100644 --- a/src/main_gui.cpp +++ b/src/main_gui.cpp | |||
| @@ -743,14 +743,80 @@ int main(int argc, char* argv[]) | |||
| 743 | DBFlush(true); | 743 | DBFlush(true); |
| 744 | return 0; | 744 | return 0; |
| 745 | } | 745 | } |
| 746 | else if (!LoadWallet()) | 746 | else if (strWalletBackend == "bdb") |
| 747 | { | 747 | { |
| 748 | // LoadWallet() explains itself into strWalletLoadError when it | 748 | // Explicit -walletbackend=bdb overrides the marker and the default: |
| 749 | // knows why -- an unsupported wallet format, for one, which is the | 749 | // force Berkeley DB (LoadWallet creates wallet.dat if none exists). |
| 750 | // case this whole path exists to make visible. | 750 | if (!LoadWallet()) |
| 751 | FatalStartupError(fHeadlessStartup, "Cannot open wallet.dat.", | 751 | { |
| 752 | strWalletLoadError); | 752 | FatalStartupError(fHeadlessStartup, "Cannot open wallet.dat.", |
| 753 | return 1; | 753 | strWalletLoadError); |
| 754 | return 1; | ||
| 755 | } | ||
| 756 | } | ||
| 757 | else | ||
| 758 | { | ||
| 759 | // No explicit -walletbackend on the command line. Resolve it from | ||
| 760 | // the persistent marker, then defaults: an existing wallet.sqlite | ||
| 761 | // stays on SQLite, an existing wallet.dat stays on Berkeley DB (the | ||
| 762 | // GUI offers to convert), and a fresh install starts on SQLite -- | ||
| 763 | // the new default. wallet.dat is never touched by any of this. | ||
| 764 | string strSQLitePath = GetAppDir() + "/wallet.sqlite"; | ||
| 765 | bool fSQLiteExists = FileExists(strSQLitePath.c_str()); | ||
| 766 | bool fWalletDatExists = FileExists((GetAppDir() + "/wallet.dat").c_str()); | ||
| 767 | string strMarker = ReadWalletBackendMarker(); | ||
| 768 | |||
| 769 | bool fUseSQLite = false; | ||
| 770 | bool fCreateSQLite = false; | ||
| 771 | if (strMarker == "sqlite") | ||
| 772 | { | ||
| 773 | if (fSQLiteExists) fUseSQLite = true; | ||
| 774 | else if (fWalletDatExists) fUseSQLite = false; // marker stale; real wallet is BDB | ||
| 775 | else { fUseSQLite = true; fCreateSQLite = true; } | ||
| 776 | } | ||
| 777 | else if (strMarker == "bdb") | ||
| 778 | { | ||
| 779 | fUseSQLite = false; | ||
| 780 | } | ||
| 781 | else // no marker recorded yet | ||
| 782 | { | ||
| 783 | if (fSQLiteExists && !fWalletDatExists) fUseSQLite = true; | ||
| 784 | else if (fWalletDatExists) fUseSQLite = false; | ||
| 785 | else { fUseSQLite = true; fCreateSQLite = true; } | ||
| 786 | } | ||
| 787 | |||
| 788 | if (fUseSQLite) | ||
| 789 | { | ||
| 790 | AttachTerminal(); | ||
| 791 | bool fOk = fCreateSQLite | ||
| 792 | ? CreateNewSQLiteWallet(strSQLitePath) | ||
| 793 | : LoadWalletFromSQLiteRuntime(strSQLitePath); | ||
| 794 | if (!fOk) | ||
| 795 | { | ||
| 796 | FatalStartupError(fHeadlessStartup, | ||
| 797 | fCreateSQLite ? "Cannot create the SQLite wallet." | ||
| 798 | : "Cannot open the SQLite wallet.", | ||
| 799 | strWalletLoadError); | ||
| 800 | return 1; | ||
| 801 | } | ||
| 802 | // Record the choice so it is stable from here on. | ||
| 803 | if (ReadWalletBackendMarker() != "sqlite") | ||
| 804 | WriteWalletBackendMarker("sqlite"); | ||
| 805 | fprintf(stderr, "Wallet backend: SQLite\n %s\n%s", | ||
| 806 | strSQLitePath.c_str(), | ||
| 807 | fCreateSQLite ? "A new wallet was created here.\n" : ""); | ||
| 808 | fflush(stderr); | ||
| 809 | // Fall through and run the node; CWalletDB routes to this file. | ||
| 810 | } | ||
| 811 | else if (!LoadWallet()) | ||
| 812 | { | ||
| 813 | // LoadWallet() explains itself into strWalletLoadError when it | ||
| 814 | // knows why -- an unsupported wallet format, for one, which is | ||
| 815 | // the case this whole path exists to make visible. | ||
| 816 | FatalStartupError(fHeadlessStartup, "Cannot open wallet.dat.", | ||
| 817 | strWalletLoadError); | ||
| 818 | return 1; | ||
| 819 | } | ||
| 754 | } | 820 | } |
| 755 | 821 | ||
| 756 | // After the block index, so there is a chain to compare the wallet | 822 | // After the block index, so there is a chain to compare the wallet |
diff --git a/src/makefile.mingw b/src/makefile.mingw index 1cb47fb..55f186b 100644 --- a/src/makefile.mingw +++ b/src/makefile.mingw | |||
| @@ -113,6 +113,8 @@ tests: test_bip32.exe bitflash.exe | |||
| 113 | ./bitflash.exe -selftest=wallet-crypto | 113 | ./bitflash.exe -selftest=wallet-crypto |
| 114 | ./bitflash.exe -selftest=wallet-encrypt | 114 | ./bitflash.exe -selftest=wallet-encrypt |
| 115 | ./bitflash.exe -selftest=wallet-sqlite-encrypt | 115 | ./bitflash.exe -selftest=wallet-sqlite-encrypt |
| 116 | ./bitflash.exe -selftest=wallet-backend-default | ||
| 117 | ./bitflash.exe -selftest=wallet-convert | ||
| 116 | ./bitflash.exe -selftest=net-message | 118 | ./bitflash.exe -selftest=net-message |
| 117 | ./bitflash.exe -selftest=consensus-limits | 119 | ./bitflash.exe -selftest=consensus-limits |
| 118 | ./bitflash.exe -selftest=pool-stratum | 120 | ./bitflash.exe -selftest=pool-stratum |
diff --git a/src/selftest.cpp b/src/selftest.cpp index 90a30d2..a9c73fe 100644 --- a/src/selftest.cpp +++ b/src/selftest.cpp | |||
| @@ -911,6 +911,7 @@ static int RunWalletStorageSanitySelfTest() | |||
| 911 | vCreateArgs.push_back("-datadir=" + strScenarioDir); | 911 | vCreateArgs.push_back("-datadir=" + strScenarioDir); |
| 912 | vCreateArgs.push_back("-nomanagedtor"); | 912 | vCreateArgs.push_back("-nomanagedtor"); |
| 913 | vCreateArgs.push_back("-nogui"); | 913 | vCreateArgs.push_back("-nogui"); |
| 914 | vCreateArgs.push_back("-walletbackend=bdb"); // this suite is about wallet.dat | ||
| 914 | vCreateArgs.push_back("-newphrase"); | 915 | vCreateArgs.push_back("-newphrase"); |
| 915 | int nCreateRet = RunBitflashChild(strExe, vCreateArgs, NULL, &strCreateOut); | 916 | int nCreateRet = RunBitflashChild(strExe, vCreateArgs, NULL, &strCreateOut); |
| 916 | nFail += Check(nCreateRet == 0 && | 917 | nFail += Check(nCreateRet == 0 && |
| @@ -1474,6 +1475,7 @@ static int RunWalletSQLiteMigrationSelfTest() | |||
| 1474 | vCreateArgs.push_back("-datadir=" + strWalletDir); | 1475 | vCreateArgs.push_back("-datadir=" + strWalletDir); |
| 1475 | vCreateArgs.push_back("-nomanagedtor"); | 1476 | vCreateArgs.push_back("-nomanagedtor"); |
| 1476 | vCreateArgs.push_back("-nogui"); | 1477 | vCreateArgs.push_back("-nogui"); |
| 1478 | vCreateArgs.push_back("-walletbackend=bdb"); // migrate FROM a Berkeley DB wallet.dat | ||
| 1477 | vCreateArgs.push_back("-newphrase"); | 1479 | vCreateArgs.push_back("-newphrase"); |
| 1478 | int nCreateRet = RunBitflashChild(strExe, vCreateArgs, NULL, &strCreateOut); | 1480 | int nCreateRet = RunBitflashChild(strExe, vCreateArgs, NULL, &strCreateOut); |
| 1479 | nFail += Check(nCreateRet == 0 && | 1481 | nFail += Check(nCreateRet == 0 && |
| @@ -2534,6 +2536,238 @@ static int RunWalletSQLiteEncryptSelfTest() | |||
| 2534 | return nFail == 0 ? 0 : 1; | 2536 | return nFail == 0 ? 0 : 1; |
| 2535 | } | 2537 | } |
| 2536 | 2538 | ||
| 2539 | // Backend resolution when no -walletbackend flag is given: a fresh install | ||
| 2540 | // starts on SQLite (the new default), an existing wallet.dat stays on Berkeley | ||
| 2541 | // DB, the persistent marker overrides the default, and an explicit flag | ||
| 2542 | // overrides everything. Each scenario runs a real child process. | ||
| 2543 | static int RunWalletBackendDefaultSelfTest() | ||
| 2544 | { | ||
| 2545 | fflush(stdout); | ||
| 2546 | printf("wallet-backend-default self-test\n"); | ||
| 2547 | |||
| 2548 | std::string tmp, cwd; | ||
| 2549 | if (!MakeTempDir(tmp)) | ||
| 2550 | { | ||
| 2551 | printf(" FAIL could not create a temporary data directory\n"); | ||
| 2552 | return 1; | ||
| 2553 | } | ||
| 2554 | if (!GetCurrentDir(cwd) || !SetCurrentDir(tmp)) | ||
| 2555 | { | ||
| 2556 | printf(" FAIL could not move into the temporary data directory\n"); | ||
| 2557 | RemoveTree(tmp); | ||
| 2558 | return 1; | ||
| 2559 | } | ||
| 2560 | |||
| 2561 | int nFail = 0; | ||
| 2562 | strSetDataDir = tmp; | ||
| 2563 | printf(" temp datadir: %s\n", tmp.c_str()); | ||
| 2564 | |||
| 2565 | #ifdef _WIN32 | ||
| 2566 | string strExe = cwd + "\\bitflash.exe"; | ||
| 2567 | #else | ||
| 2568 | string strExe = cwd + "/bitflash-node"; | ||
| 2569 | #endif | ||
| 2570 | |||
| 2571 | try | ||
| 2572 | { | ||
| 2573 | // 1. Fresh datadir, no flag -> new wallet.sqlite, marker "sqlite", no wallet.dat. | ||
| 2574 | string d1 = tmp + "/fresh"; | ||
| 2575 | nFail += Check(MakeDirLocal(d1), "fresh scenario dir can be created") ? 0 : 1; | ||
| 2576 | vector<string> a1; | ||
| 2577 | a1.push_back("-datadir=" + d1); | ||
| 2578 | a1.push_back("-nomanagedtor"); | ||
| 2579 | a1.push_back("-nogui"); | ||
| 2580 | a1.push_back("-newaddress"); | ||
| 2581 | int r1 = RunBitflashChild(strExe, a1); | ||
| 2582 | nFail += Check(r1 == 0 && | ||
| 2583 | FileExists((d1 + "/wallet.sqlite").c_str()) && | ||
| 2584 | !FileExists((d1 + "/wallet.dat").c_str()) && | ||
| 2585 | FileContainsText(d1 + "/wallet-backend", "sqlite"), | ||
| 2586 | "a fresh install starts on SQLite (new default) and records the marker") ? 0 : 1; | ||
| 2587 | |||
| 2588 | // 1b. Second run reuses it, still SQLite, still no wallet.dat. | ||
| 2589 | int r1b = RunBitflashChild(strExe, a1); | ||
| 2590 | nFail += Check(r1b == 0 && | ||
| 2591 | FileExists((d1 + "/wallet.sqlite").c_str()) && | ||
| 2592 | !FileExists((d1 + "/wallet.dat").c_str()), | ||
| 2593 | "the recorded SQLite choice survives a restart") ? 0 : 1; | ||
| 2594 | |||
| 2595 | // 2. Existing wallet.dat, no flag -> stays on Berkeley DB. | ||
| 2596 | string d2 = tmp + "/existing-bdb"; | ||
| 2597 | nFail += Check(MakeDirLocal(d2), "existing-bdb scenario dir can be created") ? 0 : 1; | ||
| 2598 | vector<string> a2c; | ||
| 2599 | a2c.push_back("-datadir=" + d2); | ||
| 2600 | a2c.push_back("-nomanagedtor"); | ||
| 2601 | a2c.push_back("-nogui"); | ||
| 2602 | a2c.push_back("-walletbackend=bdb"); | ||
| 2603 | a2c.push_back("-newphrase"); | ||
| 2604 | RunBitflashChild(strExe, a2c); | ||
| 2605 | vector<string> a2; | ||
| 2606 | a2.push_back("-datadir=" + d2); | ||
| 2607 | a2.push_back("-nomanagedtor"); | ||
| 2608 | a2.push_back("-nogui"); | ||
| 2609 | a2.push_back("-newaddress"); | ||
| 2610 | int r2 = RunBitflashChild(strExe, a2); | ||
| 2611 | nFail += Check(r2 == 0 && | ||
| 2612 | FileExists((d2 + "/wallet.dat").c_str()) && | ||
| 2613 | !FileExists((d2 + "/wallet.sqlite").c_str()), | ||
| 2614 | "an existing wallet.dat keeps a flagless start on Berkeley DB") ? 0 : 1; | ||
| 2615 | |||
| 2616 | // 3. Marker "bdb" forces Berkeley DB even on a fresh datadir. | ||
| 2617 | string d3 = tmp + "/marker-bdb"; | ||
| 2618 | nFail += Check(MakeDirLocal(d3), "marker-bdb scenario dir can be created") ? 0 : 1; | ||
| 2619 | nFail += Check(WriteTextFile(d3 + "/wallet-backend", "bdb\n"), | ||
| 2620 | "a bdb marker can be written") ? 0 : 1; | ||
| 2621 | vector<string> a3; | ||
| 2622 | a3.push_back("-datadir=" + d3); | ||
| 2623 | a3.push_back("-nomanagedtor"); | ||
| 2624 | a3.push_back("-nogui"); | ||
| 2625 | a3.push_back("-newaddress"); | ||
| 2626 | int r3 = RunBitflashChild(strExe, a3); | ||
| 2627 | nFail += Check(r3 == 0 && | ||
| 2628 | FileExists((d3 + "/wallet.dat").c_str()) && | ||
| 2629 | !FileExists((d3 + "/wallet.sqlite").c_str()), | ||
| 2630 | "a bdb marker forces Berkeley DB on a fresh datadir") ? 0 : 1; | ||
| 2631 | |||
| 2632 | // 4. Explicit -walletbackend=bdb on a fresh datadir -> wallet.dat. | ||
| 2633 | string d4 = tmp + "/explicit-bdb"; | ||
| 2634 | nFail += Check(MakeDirLocal(d4), "explicit-bdb scenario dir can be created") ? 0 : 1; | ||
| 2635 | vector<string> a4; | ||
| 2636 | a4.push_back("-datadir=" + d4); | ||
| 2637 | a4.push_back("-nomanagedtor"); | ||
| 2638 | a4.push_back("-nogui"); | ||
| 2639 | a4.push_back("-walletbackend=bdb"); | ||
| 2640 | a4.push_back("-newaddress"); | ||
| 2641 | int r4 = RunBitflashChild(strExe, a4); | ||
| 2642 | nFail += Check(r4 == 0 && | ||
| 2643 | FileExists((d4 + "/wallet.dat").c_str()) && | ||
| 2644 | !FileExists((d4 + "/wallet.sqlite").c_str()), | ||
| 2645 | "an explicit -walletbackend=bdb creates wallet.dat on a fresh datadir") ? 0 : 1; | ||
| 2646 | } | ||
| 2647 | catch (const std::exception& e) | ||
| 2648 | { | ||
| 2649 | printf(" FAIL exception: %s\n", e.what()); | ||
| 2650 | nFail++; | ||
| 2651 | } | ||
| 2652 | catch (...) | ||
| 2653 | { | ||
| 2654 | printf(" FAIL unknown exception\n"); | ||
| 2655 | nFail++; | ||
| 2656 | } | ||
| 2657 | |||
| 2658 | DBFlush(true); | ||
| 2659 | SetCurrentDir(cwd); | ||
| 2660 | RemoveTree(tmp); | ||
| 2661 | printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED", | ||
| 2662 | nFail, nFail == 1 ? "" : "s"); | ||
| 2663 | fflush(stdout); | ||
| 2664 | return nFail == 0 ? 0 : 1; | ||
| 2665 | } | ||
| 2666 | |||
| 2667 | // The wizard's conversion action, headless: build a Berkeley DB wallet, run | ||
| 2668 | // DoConvertWalletToSQLite() (what the GUI button calls), and prove it produced a | ||
| 2669 | // usable wallet.sqlite + backend marker while leaving wallet.dat untouched, then | ||
| 2670 | // that a flagless restart opens the converted SQLite wallet. | ||
| 2671 | static int RunWalletConvertSelfTest() | ||
| 2672 | { | ||
| 2673 | fflush(stdout); | ||
| 2674 | printf("wallet-convert self-test\n"); | ||
| 2675 | |||
| 2676 | std::string tmp, cwd; | ||
| 2677 | if (!MakeTempDir(tmp)) | ||
| 2678 | { | ||
| 2679 | printf(" FAIL could not create a temporary data directory\n"); | ||
| 2680 | return 1; | ||
| 2681 | } | ||
| 2682 | if (!GetCurrentDir(cwd) || !SetCurrentDir(tmp)) | ||
| 2683 | { | ||
| 2684 | printf(" FAIL could not move into the temporary data directory\n"); | ||
| 2685 | RemoveTree(tmp); | ||
| 2686 | return 1; | ||
| 2687 | } | ||
| 2688 | |||
| 2689 | int nFail = 0; | ||
| 2690 | strSetDataDir = tmp; | ||
| 2691 | printf(" temp datadir: %s\n", tmp.c_str()); | ||
| 2692 | |||
| 2693 | #ifdef _WIN32 | ||
| 2694 | string strExe = cwd + "\\bitflash.exe"; | ||
| 2695 | #else | ||
| 2696 | string strExe = cwd + "/bitflash-node"; | ||
| 2697 | #endif | ||
| 2698 | |||
| 2699 | try | ||
| 2700 | { | ||
| 2701 | if (!LoadWallet()) | ||
| 2702 | throw std::runtime_error("LoadWallet failed"); | ||
| 2703 | |||
| 2704 | string strError; | ||
| 2705 | nFail += Check(SetHDSeedFromMnemonic( | ||
| 2706 | "abandon abandon abandon abandon abandon abandon abandon " | ||
| 2707 | "abandon abandon abandon abandon about", strError), | ||
| 2708 | "a phrase can be installed before conversion") ? 0 : 1; | ||
| 2709 | TopUpKeyPool(); | ||
| 2710 | |||
| 2711 | vector<unsigned char> vchDefaultPubKey = keyUser.GetPubKey(); | ||
| 2712 | CPrivKey vchDefaultPrivKey; | ||
| 2713 | nFail += Check(GetWalletPrivKey(vchDefaultPubKey, vchDefaultPrivKey, strError), | ||
| 2714 | "the default private key is readable before conversion") ? 0 : 1; | ||
| 2715 | vector<unsigned char> vchDefaultPrivBytes(vchDefaultPrivKey.begin(), | ||
| 2716 | vchDefaultPrivKey.end()); | ||
| 2717 | |||
| 2718 | // The conversion action the GUI button invokes. | ||
| 2719 | int nCopied = 0; | ||
| 2720 | string strConvertError; | ||
| 2721 | bool fConverted = DoConvertWalletToSQLite(nCopied, strConvertError); | ||
| 2722 | nFail += Check(fConverted && nCopied > 0, | ||
| 2723 | "DoConvertWalletToSQLite exports the wallet") ? 0 : 1; | ||
| 2724 | nFail += Check(FileExists((tmp + "/wallet.sqlite").c_str()) && | ||
| 2725 | FileContainsText(tmp + "/wallet-backend", "sqlite"), | ||
| 2726 | "conversion writes wallet.sqlite and records the sqlite marker") ? 0 : 1; | ||
| 2727 | nFail += Check(FileContainsBytes(tmp + "/wallet.dat", vchDefaultPrivBytes), | ||
| 2728 | "wallet.dat is left untouched by the conversion") ? 0 : 1; | ||
| 2729 | |||
| 2730 | // Idempotence guard: a second conversion refuses rather than overwrite. | ||
| 2731 | int nCopied2 = 0; | ||
| 2732 | string strSecondError; | ||
| 2733 | nFail += Check(!DoConvertWalletToSQLite(nCopied2, strSecondError), | ||
| 2734 | "a second conversion refuses to overwrite wallet.sqlite") ? 0 : 1; | ||
| 2735 | |||
| 2736 | DBFlush(true); | ||
| 2737 | |||
| 2738 | // A flagless restart now opens the converted SQLite wallet (the marker | ||
| 2739 | // routes it) and finds the same recovery phrase. | ||
| 2740 | string strAudit = tmp + "/converted-recovery-audit.txt"; | ||
| 2741 | vector<string> vAuditArgs; | ||
| 2742 | vAuditArgs.push_back("-datadir=" + tmp); | ||
| 2743 | vAuditArgs.push_back("-nomanagedtor"); | ||
| 2744 | vAuditArgs.push_back("-nogui"); | ||
| 2745 | vAuditArgs.push_back("-recoveryaudit"); | ||
| 2746 | int nAuditRet = RunBitflashChild(strExe, vAuditArgs, NULL, &strAudit); | ||
| 2747 | nFail += Check(nAuditRet == 0 && | ||
| 2748 | FileContainsText(strAudit, "recovery phrase: present"), | ||
| 2749 | "a flagless restart opens the converted SQLite wallet") ? 0 : 1; | ||
| 2750 | } | ||
| 2751 | catch (const std::exception& e) | ||
| 2752 | { | ||
| 2753 | printf(" FAIL exception: %s\n", e.what()); | ||
| 2754 | nFail++; | ||
| 2755 | } | ||
| 2756 | catch (...) | ||
| 2757 | { | ||
| 2758 | printf(" FAIL unknown exception\n"); | ||
| 2759 | nFail++; | ||
| 2760 | } | ||
| 2761 | |||
| 2762 | DBFlush(true); | ||
| 2763 | SetCurrentDir(cwd); | ||
| 2764 | RemoveTree(tmp); | ||
| 2765 | printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED", | ||
| 2766 | nFail, nFail == 1 ? "" : "s"); | ||
| 2767 | fflush(stdout); | ||
| 2768 | return nFail == 0 ? 0 : 1; | ||
| 2769 | } | ||
| 2770 | |||
| 2537 | static int RunWalletPortabilitySelfTest() | 2771 | static int RunWalletPortabilitySelfTest() |
| 2538 | { | 2772 | { |
| 2539 | fflush(stdout); | 2773 | fflush(stdout); |
| @@ -2576,6 +2810,7 @@ static int RunWalletPortabilitySelfTest() | |||
| 2576 | vCreateArgs.push_back("-datadir=" + strOriginal); | 2810 | vCreateArgs.push_back("-datadir=" + strOriginal); |
| 2577 | vCreateArgs.push_back("-nomanagedtor"); | 2811 | vCreateArgs.push_back("-nomanagedtor"); |
| 2578 | vCreateArgs.push_back("-nogui"); | 2812 | vCreateArgs.push_back("-nogui"); |
| 2813 | vCreateArgs.push_back("-walletbackend=bdb"); // this suite is about wallet.dat portability | ||
| 2579 | vCreateArgs.push_back("-newphrase"); | 2814 | vCreateArgs.push_back("-newphrase"); |
| 2580 | int nCreateRet = RunBitflashChild(strExe, vCreateArgs, NULL, &strCreateOut); | 2815 | int nCreateRet = RunBitflashChild(strExe, vCreateArgs, NULL, &strCreateOut); |
| 2581 | nFail += Check(nCreateRet == 0 && | 2816 | nFail += Check(nCreateRet == 0 && |
| @@ -3265,6 +3500,10 @@ int RunSelfTest(const std::string& name) | |||
| 3265 | return RunWalletEncryptSelfTest(); | 3500 | return RunWalletEncryptSelfTest(); |
| 3266 | if (name == "wallet-sqlite-encrypt") | 3501 | if (name == "wallet-sqlite-encrypt") |
| 3267 | return RunWalletSQLiteEncryptSelfTest(); | 3502 | return RunWalletSQLiteEncryptSelfTest(); |
| 3503 | if (name == "wallet-backend-default") | ||
| 3504 | return RunWalletBackendDefaultSelfTest(); | ||
| 3505 | if (name == "wallet-convert") | ||
| 3506 | return RunWalletConvertSelfTest(); | ||
| 3268 | if (name == "wallet-portability") | 3507 | if (name == "wallet-portability") |
| 3269 | return RunWalletPortabilitySelfTest(); | 3508 | return RunWalletPortabilitySelfTest(); |
| 3270 | if (name == "net-message") | 3509 | if (name == "net-message") |
| @@ -3281,6 +3520,6 @@ int RunSelfTest(const std::string& name) | |||
| 3281 | return RunManagedTorSelfTest(); | 3520 | return RunManagedTorSelfTest(); |
| 3282 | 3521 | ||
| 3283 | printf("Unknown self-test '%s'\n", name.c_str()); | 3522 | printf("Unknown self-test '%s'\n", name.c_str()); |
| 3284 | printf("Known self-tests: wallet-keypool, wallet-hd, wallet-format, wallet-storage-sanity, db-env-reopen, wallet-sqlite, wallet-sqlite-migration, wallet-crypto, wallet-encrypt, wallet-sqlite-encrypt, wallet-portability, net-message, consensus-limits, pool-stratum, parse-money, socks5-proxy, managed-tor\n"); | 3523 | printf("Known self-tests: wallet-keypool, wallet-hd, wallet-format, wallet-storage-sanity, db-env-reopen, wallet-sqlite, wallet-sqlite-migration, wallet-crypto, wallet-encrypt, wallet-sqlite-encrypt, wallet-backend-default, wallet-convert, wallet-portability, net-message, consensus-limits, pool-stratum, parse-money, socks5-proxy, managed-tor\n"); |
| 3285 | return 1; | 3524 | return 1; |
| 3286 | } | 3525 | } |
diff --git a/src/walletcmd.cpp b/src/walletcmd.cpp index 374b2cc..1f1fd27 100644 --- a/src/walletcmd.cpp +++ b/src/walletcmd.cpp | |||
| @@ -1070,79 +1070,126 @@ public: | |||
| 1070 | 1070 | ||
| 1071 | } // namespace | 1071 | } // namespace |
| 1072 | 1072 | ||
| 1073 | int CmdWalletSQLiteExport(const std::string& strDest) | 1073 | // Export the currently-loaded wallet to a SQLite file. Shared by the |
| 1074 | // -walletsqliteexport command and the GUI conversion wizard: opens the | ||
| 1075 | // destination, copies every record inside one transaction, checkpoints, and | ||
| 1076 | // verifies the stored count matches. Returns false with strErrorRet set on any | ||
| 1077 | // failure and leaves no partial file behind; the source wallet is only read. | ||
| 1078 | bool ExportActiveWalletToSQLite(const std::string& strDest, int& nCopiedRet, | ||
| 1079 | std::string& strErrorRet) | ||
| 1074 | { | 1080 | { |
| 1075 | AttachTerminal(); | 1081 | nCopiedRet = 0; |
| 1076 | |||
| 1077 | if (strDest.empty()) | 1082 | if (strDest.empty()) |
| 1078 | { | 1083 | { |
| 1079 | fprintf(stderr, "Missing SQLite wallet export path.\n"); | 1084 | strErrorRet = "no SQLite export path was given"; |
| 1080 | return 1; | 1085 | return false; |
| 1081 | } | 1086 | } |
| 1082 | if (FileExists(strDest.c_str())) | 1087 | if (FileExists(strDest.c_str())) |
| 1083 | { | 1088 | { |
| 1084 | fprintf(stderr, "Refusing to overwrite existing SQLite wallet export: %s\n", | 1089 | strErrorRet = strprintf("refusing to overwrite existing file: %s", strDest.c_str()); |
| 1085 | strDest.c_str()); | 1090 | return false; |
| 1086 | return 1; | ||
| 1087 | } | 1091 | } |
| 1088 | 1092 | ||
| 1089 | string strError; | ||
| 1090 | CWalletDBSQLite db; | 1093 | CWalletDBSQLite db; |
| 1091 | if (!db.Open(strDest, strError)) | 1094 | if (!db.Open(strDest, strErrorRet)) |
| 1092 | { | 1095 | { |
| 1093 | RemoveSQLiteExportFiles(strDest); | 1096 | RemoveSQLiteExportFiles(strDest); |
| 1094 | fprintf(stderr, "Cannot open SQLite wallet export: %s\n", strError.c_str()); | 1097 | return false; |
| 1095 | return 1; | ||
| 1096 | } | 1098 | } |
| 1097 | if (!db.BeginTransaction(strError)) | 1099 | if (!db.BeginTransaction(strErrorRet)) |
| 1098 | { | 1100 | { |
| 1099 | db.Close(); | 1101 | db.Close(); |
| 1100 | RemoveSQLiteExportFiles(strDest); | 1102 | RemoveSQLiteExportFiles(strDest); |
| 1101 | fprintf(stderr, "Cannot begin SQLite wallet export: %s\n", strError.c_str()); | 1103 | return false; |
| 1102 | return 1; | ||
| 1103 | } | 1104 | } |
| 1104 | 1105 | ||
| 1105 | int nCopied = 0; | 1106 | CWalletSQLiteExportVisitor visitor(db, nCopiedRet); |
| 1106 | CWalletSQLiteExportVisitor visitor(db, nCopied); | 1107 | if (!ScanWalletRecords(visitor, strErrorRet)) |
| 1107 | if (!ScanWalletRecords(visitor, strError)) | ||
| 1108 | { | 1108 | { |
| 1109 | string strRollbackError; | 1109 | string strRollbackError; |
| 1110 | db.RollbackTransaction(strRollbackError); | 1110 | db.RollbackTransaction(strRollbackError); |
| 1111 | db.Close(); | 1111 | db.Close(); |
| 1112 | RemoveSQLiteExportFiles(strDest); | 1112 | RemoveSQLiteExportFiles(strDest); |
| 1113 | fprintf(stderr, "Cannot export wallet.dat to SQLite: %s\n", strError.c_str()); | 1113 | return false; |
| 1114 | return 1; | ||
| 1115 | } | 1114 | } |
| 1116 | if (!db.CommitTransaction(strError)) | 1115 | if (!db.CommitTransaction(strErrorRet) || !db.Checkpoint(strErrorRet)) |
| 1117 | { | 1116 | { |
| 1118 | db.Close(); | 1117 | db.Close(); |
| 1119 | RemoveSQLiteExportFiles(strDest); | 1118 | RemoveSQLiteExportFiles(strDest); |
| 1120 | fprintf(stderr, "Cannot commit SQLite wallet export: %s\n", strError.c_str()); | 1119 | return false; |
| 1121 | return 1; | ||
| 1122 | } | 1120 | } |
| 1123 | if (!db.Checkpoint(strError)) | 1121 | |
| 1122 | int nCount = 0; | ||
| 1123 | if (!db.CountRecords(nCount, strErrorRet)) | ||
| 1124 | { | 1124 | { |
| 1125 | db.Close(); | 1125 | db.Close(); |
| 1126 | RemoveSQLiteExportFiles(strDest); | 1126 | RemoveSQLiteExportFiles(strDest); |
| 1127 | fprintf(stderr, "Cannot checkpoint SQLite wallet export: %s\n", strError.c_str()); | 1127 | return false; |
| 1128 | return 1; | ||
| 1129 | } | 1128 | } |
| 1130 | 1129 | if (nCount != nCopiedRet) | |
| 1131 | int nCount = 0; | ||
| 1132 | if (!db.CountRecords(nCount, strError)) | ||
| 1133 | { | 1130 | { |
| 1131 | strErrorRet = strprintf("export count mismatch: copied %d, stored %d", | ||
| 1132 | nCopiedRet, nCount); | ||
| 1134 | db.Close(); | 1133 | db.Close(); |
| 1135 | RemoveSQLiteExportFiles(strDest); | 1134 | RemoveSQLiteExportFiles(strDest); |
| 1136 | fprintf(stderr, "Cannot verify SQLite wallet export count: %s\n", | 1135 | return false; |
| 1137 | strError.c_str()); | 1136 | } |
| 1137 | db.Close(); | ||
| 1138 | return true; | ||
| 1139 | } | ||
| 1140 | |||
| 1141 | // Convert the running Berkeley DB wallet to SQLite for the GUI wizard: export | ||
| 1142 | // wallet.dat to <datadir>/wallet.sqlite, then record the backend marker so the | ||
| 1143 | // next start opens SQLite. wallet.dat is never modified -- it stays as the | ||
| 1144 | // fallback. Refuses if already on SQLite or if a wallet.sqlite already exists. | ||
| 1145 | bool DoConvertWalletToSQLite(int& nCopiedRet, std::string& strErrorRet) | ||
| 1146 | { | ||
| 1147 | nCopiedRet = 0; | ||
| 1148 | if (WalletSQLiteRuntimeActive()) | ||
| 1149 | { | ||
| 1150 | strErrorRet = "this wallet is already on the SQLite backend"; | ||
| 1151 | return false; | ||
| 1152 | } | ||
| 1153 | string strDest = GetAppDir() + "/wallet.sqlite"; | ||
| 1154 | if (FileExists(strDest.c_str())) | ||
| 1155 | { | ||
| 1156 | strErrorRet = strprintf("a SQLite wallet already exists at %s", strDest.c_str()); | ||
| 1157 | return false; | ||
| 1158 | } | ||
| 1159 | if (!ExportActiveWalletToSQLite(strDest, nCopiedRet, strErrorRet)) | ||
| 1160 | return false; | ||
| 1161 | if (!WriteWalletBackendMarker("sqlite")) | ||
| 1162 | { | ||
| 1163 | strErrorRet = "the wallet was exported but the backend choice could not be saved"; | ||
| 1164 | return false; | ||
| 1165 | } | ||
| 1166 | return true; | ||
| 1167 | } | ||
| 1168 | |||
| 1169 | int CmdWalletSQLiteExport(const std::string& strDest) | ||
| 1170 | { | ||
| 1171 | AttachTerminal(); | ||
| 1172 | |||
| 1173 | // Keep the command's own specific messages for the two conditions the | ||
| 1174 | // shared helper folds into a generic error, so scripts and the tests that | ||
| 1175 | // read this output see the same wording as before. | ||
| 1176 | if (strDest.empty()) | ||
| 1177 | { | ||
| 1178 | fprintf(stderr, "Missing SQLite wallet export path.\n"); | ||
| 1138 | return 1; | 1179 | return 1; |
| 1139 | } | 1180 | } |
| 1140 | if (nCount != nCopied) | 1181 | if (FileExists(strDest.c_str())) |
| 1141 | { | 1182 | { |
| 1142 | db.Close(); | 1183 | fprintf(stderr, "Refusing to overwrite existing SQLite wallet export: %s\n", |
| 1143 | RemoveSQLiteExportFiles(strDest); | 1184 | strDest.c_str()); |
| 1144 | fprintf(stderr, "SQLite wallet export count mismatch: copied %d, stored %d\n", | 1185 | return 1; |
| 1145 | nCopied, nCount); | 1186 | } |
| 1187 | |||
| 1188 | int nCopied = 0; | ||
| 1189 | string strError; | ||
| 1190 | if (!ExportActiveWalletToSQLite(strDest, nCopied, strError)) | ||
| 1191 | { | ||
| 1192 | fprintf(stderr, "Cannot export wallet to SQLite: %s\n", strError.c_str()); | ||
| 1146 | return 1; | 1193 | return 1; |
| 1147 | } | 1194 | } |
| 1148 | 1195 | ||
diff --git a/src/walletcmd.h b/src/walletcmd.h index d993856..38cbfdb 100644 --- a/src/walletcmd.h +++ b/src/walletcmd.h | |||
| @@ -75,6 +75,15 @@ int CmdWalletStorageCheck(); | |||
| 75 | // not change which backend the runtime wallet opens. | 75 | // not change which backend the runtime wallet opens. |
| 76 | int CmdWalletSQLiteExport(const std::string& strDest); | 76 | int CmdWalletSQLiteExport(const std::string& strDest); |
| 77 | 77 | ||
| 78 | // Export the currently-loaded wallet to a SQLite file (shared by the command | ||
| 79 | // above and the GUI wizard). Returns false with an error on any failure. | ||
| 80 | bool ExportActiveWalletToSQLite(const std::string& strDest, int& nCopiedRet, | ||
| 81 | std::string& strErrorRet); | ||
| 82 | |||
| 83 | // Convert the running Berkeley DB wallet to SQLite and record the backend | ||
| 84 | // marker so the next start opens it. wallet.dat is left untouched as fallback. | ||
| 85 | bool DoConvertWalletToSQLite(int& nCopiedRet, std::string& strErrorRet); | ||
| 86 | |||
| 78 | // Diagnostic/migration staging: compare a SQLite wallet export with the current | 87 | // Diagnostic/migration staging: compare a SQLite wallet export with the current |
| 79 | // wallet.dat raw records. Prints counts only, never record keys or values. | 88 | // wallet.dat raw records. Prints counts only, never record keys or values. |
| 80 | int CmdWalletSQLiteVerify(const std::string& strPath); | 89 | int CmdWalletSQLiteVerify(const std::string& strPath); |