diff options
| author | Nsable-sh <nsable-sh@noreply.git.bitflash.network> | 2026-08-14 17:02:35 -0300 |
|---|---|---|
| committer | Nsable-sh <nsable-sh@noreply.git.bitflash.network> | 2026-08-14 17:02:35 -0300 |
| commit | df129cc672b9b894ff2558d3e9361ddab082433d (patch) | |
| tree | a72cd83e2981deb95a2f17d0e02a7dfd64ab2eca /src | |
| parent | 35f6c752f9f879ca94478985978e4a9c862ede3e (diff) | |
wallet: restore BDB wallets from SQLite exports
Diffstat (limited to 'src')
| -rw-r--r-- | src/main_gui.cpp | 11 | ||||
| -rw-r--r-- | src/selftest.cpp | 35 | ||||
| -rw-r--r-- | src/walletcmd.cpp | 126 | ||||
| -rw-r--r-- | src/walletcmd.h | 4 |
4 files changed, 176 insertions, 0 deletions
diff --git a/src/main_gui.cpp b/src/main_gui.cpp index caacf13..cf96822 100644 --- a/src/main_gui.cpp +++ b/src/main_gui.cpp | |||
| @@ -185,6 +185,8 @@ static void PrintUsage() | |||
| 185 | printf(" experimental SQLite store, then exit)\n"); | 185 | printf(" experimental SQLite store, then exit)\n"); |
| 186 | printf(" /walletsqliteverify=FILE (compare wallet.dat with a SQLite export,\n"); | 186 | printf(" /walletsqliteverify=FILE (compare wallet.dat with a SQLite export,\n"); |
| 187 | printf(" printing counts only, then exit)\n"); | 187 | printf(" printing counts only, then exit)\n"); |
| 188 | printf(" /walletsqliterestore=FILE (rebuild wallet.dat from a SQLite export in\n"); | ||
| 189 | printf(" an empty data directory, then exit)\n"); | ||
| 188 | printf(" /rescan (walk the chain for coins this wallet owns\n"); | 190 | printf(" /rescan (walk the chain for coins this wallet owns\n"); |
| 189 | printf(" but never recorded, then exit)\n"); | 191 | printf(" but never recorded, then exit)\n"); |
| 190 | printf("\n"); | 192 | printf("\n"); |
| @@ -551,6 +553,15 @@ int main(int argc, char* argv[]) | |||
| 551 | return nRet; | 553 | return nRet; |
| 552 | } | 554 | } |
| 553 | 555 | ||
| 556 | string strWalletSQLiteRestore = | ||
| 557 | argval2(argc, argv, "/walletsqliterestore", "-walletsqliterestore"); | ||
| 558 | if (!strWalletSQLiteRestore.empty()) | ||
| 559 | { | ||
| 560 | int nRet = CmdWalletSQLiteRestore(strWalletSQLiteRestore); | ||
| 561 | DBFlush(true); | ||
| 562 | return nRet; | ||
| 563 | } | ||
| 564 | |||
| 554 | // Berkeley DB reports failure by throwing, and CDB's constructor lets it | 565 | // Berkeley DB reports failure by throwing, and CDB's constructor lets it |
| 555 | // through. Nothing on this path caught anything, so an unreadable | 566 | // through. Nothing on this path caught anything, so an unreadable |
| 556 | // blkindex.dat or wallet.dat -- one written by another platform's Berkeley | 567 | // blkindex.dat or wallet.dat -- one written by another platform's Berkeley |
diff --git a/src/selftest.cpp b/src/selftest.cpp index 4ba90c3..7d3a96e 100644 --- a/src/selftest.cpp +++ b/src/selftest.cpp | |||
| @@ -1230,6 +1230,41 @@ static int RunWalletSQLiteMigrationSelfTest() | |||
| 1230 | FileContainsText(strVerifyOut, "verification: ok"), | 1230 | FileContainsText(strVerifyOut, "verification: ok"), |
| 1231 | "SQLite wallet export verifies against wallet.dat") ? 0 : 1; | 1231 | "SQLite wallet export verifies against wallet.dat") ? 0 : 1; |
| 1232 | 1232 | ||
| 1233 | string strRestoredDir = tmp + "/restored-wallet"; | ||
| 1234 | nFail += Check(MakeDirLocal(strRestoredDir), | ||
| 1235 | "restored wallet directory can be created") ? 0 : 1; | ||
| 1236 | string strRestoreOut = tmp + "/sqlite-restore.txt"; | ||
| 1237 | vector<string> vRestoreArgs; | ||
| 1238 | vRestoreArgs.push_back("-datadir=" + strRestoredDir); | ||
| 1239 | vRestoreArgs.push_back("-nomanagedtor"); | ||
| 1240 | vRestoreArgs.push_back("-nogui"); | ||
| 1241 | vRestoreArgs.push_back("-walletsqliterestore=" + strSQLite); | ||
| 1242 | int nRestoreRet = RunBitflashChild(strExe, vRestoreArgs, NULL, &strRestoreOut); | ||
| 1243 | nFail += Check(nRestoreRet == 0 && | ||
| 1244 | FileContainsText(strRestoreOut, "records restored:"), | ||
| 1245 | "SQLite wallet export restores into a fresh wallet.dat") ? 0 : 1; | ||
| 1246 | |||
| 1247 | string strVerifyRestoredOut = tmp + "/sqlite-verify-restored.txt"; | ||
| 1248 | vector<string> vVerifyRestoredArgs; | ||
| 1249 | vVerifyRestoredArgs.push_back("-datadir=" + strRestoredDir); | ||
| 1250 | vVerifyRestoredArgs.push_back("-nomanagedtor"); | ||
| 1251 | vVerifyRestoredArgs.push_back("-nogui"); | ||
| 1252 | vVerifyRestoredArgs.push_back("-walletsqliteverify=" + strSQLite); | ||
| 1253 | int nVerifyRestoredRet = | ||
| 1254 | RunBitflashChild(strExe, vVerifyRestoredArgs, NULL, &strVerifyRestoredOut); | ||
| 1255 | nFail += Check(nVerifyRestoredRet == 0 && | ||
| 1256 | FileContainsText(strVerifyRestoredOut, | ||
| 1257 | "verification: ok"), | ||
| 1258 | "restored wallet.dat verifies against the SQLite export") ? 0 : 1; | ||
| 1259 | |||
| 1260 | string strRestoreOverwriteOut = tmp + "/sqlite-restore-overwrite.txt"; | ||
| 1261 | int nRestoreOverwriteRet = | ||
| 1262 | RunBitflashChild(strExe, vRestoreArgs, NULL, &strRestoreOverwriteOut); | ||
| 1263 | nFail += Check(nRestoreOverwriteRet == 1 && | ||
| 1264 | FileContainsText(strRestoreOverwriteOut, | ||
| 1265 | "Refusing to overwrite existing wallet.dat"), | ||
| 1266 | "SQLite wallet restore refuses to overwrite wallet.dat") ? 0 : 1; | ||
| 1267 | |||
| 1233 | string strOverwriteOut = tmp + "/sqlite-export-overwrite.txt"; | 1268 | string strOverwriteOut = tmp + "/sqlite-export-overwrite.txt"; |
| 1234 | int nOverwriteRet = | 1269 | int nOverwriteRet = |
| 1235 | RunBitflashChild(strExe, vExportArgs, NULL, &strOverwriteOut); | 1270 | RunBitflashChild(strExe, vExportArgs, NULL, &strOverwriteOut); |
diff --git a/src/walletcmd.cpp b/src/walletcmd.cpp index 36f6783..d1bad98 100644 --- a/src/walletcmd.cpp +++ b/src/walletcmd.cpp | |||
| @@ -844,6 +844,52 @@ public: | |||
| 844 | } | 844 | } |
| 845 | }; | 845 | }; |
| 846 | 846 | ||
| 847 | class CWalletRawRestoreDB : public CWalletDB | ||
| 848 | { | ||
| 849 | public: | ||
| 850 | CWalletRawRestoreDB(const char* pszMode="cr+", bool fTxn=true) | ||
| 851 | : CWalletDB(pszMode, fTxn) { } | ||
| 852 | |||
| 853 | bool WriteRaw(CDataStream ssKey, CDataStream ssValue) | ||
| 854 | { | ||
| 855 | if (!pdb || ssKey.empty()) | ||
| 856 | return false; | ||
| 857 | |||
| 858 | Dbt datKey((void*)&ssKey[0], ssKey.size()); | ||
| 859 | Dbt datValue(ssValue.empty() ? NULL : (void*)&ssValue[0], | ||
| 860 | ssValue.size()); | ||
| 861 | int ret = pdb->put(GetTxn(), &datKey, &datValue, 0); | ||
| 862 | |||
| 863 | memset(datKey.get_data(), 0, datKey.get_size()); | ||
| 864 | if (datValue.get_data()) | ||
| 865 | memset(datValue.get_data(), 0, datValue.get_size()); | ||
| 866 | return ret == 0; | ||
| 867 | } | ||
| 868 | }; | ||
| 869 | |||
| 870 | class CWalletSQLiteRestoreVisitor : public CWalletRecordVisitor | ||
| 871 | { | ||
| 872 | public: | ||
| 873 | CWalletRawRestoreDB& db; | ||
| 874 | int& nCopied; | ||
| 875 | |||
| 876 | CWalletSQLiteRestoreVisitor(CWalletRawRestoreDB& dbIn, int& nCopiedIn) | ||
| 877 | : db(dbIn), nCopied(nCopiedIn) { } | ||
| 878 | |||
| 879 | bool VisitWalletRecord(const CDataStream& ssKey, | ||
| 880 | const CDataStream& ssValue, | ||
| 881 | string& strErrorRet) | ||
| 882 | { | ||
| 883 | if (!db.WriteRaw(ssKey, ssValue)) | ||
| 884 | { | ||
| 885 | strErrorRet = "could not write raw wallet record"; | ||
| 886 | return false; | ||
| 887 | } | ||
| 888 | nCopied++; | ||
| 889 | return true; | ||
| 890 | } | ||
| 891 | }; | ||
| 892 | |||
| 847 | } // namespace | 893 | } // namespace |
| 848 | 894 | ||
| 849 | int CmdWalletSQLiteExport(const std::string& strDest) | 895 | int CmdWalletSQLiteExport(const std::string& strDest) |
| @@ -994,6 +1040,86 @@ int CmdWalletSQLiteVerify(const std::string& strPath) | |||
| 994 | return 2; | 1040 | return 2; |
| 995 | } | 1041 | } |
| 996 | 1042 | ||
| 1043 | int CmdWalletSQLiteRestore(const std::string& strPath) | ||
| 1044 | { | ||
| 1045 | AttachTerminal(); | ||
| 1046 | |||
| 1047 | if (strPath.empty()) | ||
| 1048 | { | ||
| 1049 | fprintf(stderr, "Missing SQLite wallet export path.\n"); | ||
| 1050 | return 1; | ||
| 1051 | } | ||
| 1052 | if (!FileExists(strPath.c_str())) | ||
| 1053 | { | ||
| 1054 | fprintf(stderr, "SQLite wallet export does not exist: %s\n", | ||
| 1055 | strPath.c_str()); | ||
| 1056 | return 1; | ||
| 1057 | } | ||
| 1058 | |||
| 1059 | string strWalletPath = GetAppDir() + "/wallet.dat"; | ||
| 1060 | if (FileExists(strWalletPath.c_str())) | ||
| 1061 | { | ||
| 1062 | fprintf(stderr, "Refusing to overwrite existing wallet.dat: %s\n", | ||
| 1063 | strWalletPath.c_str()); | ||
| 1064 | return 1; | ||
| 1065 | } | ||
| 1066 | |||
| 1067 | string strError; | ||
| 1068 | CWalletDBSQLite sqlite; | ||
| 1069 | if (!sqlite.Open(strPath, strError)) | ||
| 1070 | { | ||
| 1071 | fprintf(stderr, "Cannot open SQLite wallet export: %s\n", strError.c_str()); | ||
| 1072 | return 1; | ||
| 1073 | } | ||
| 1074 | |||
| 1075 | int nSQLiteRecords = 0; | ||
| 1076 | if (!sqlite.CountRecords(nSQLiteRecords, strError)) | ||
| 1077 | { | ||
| 1078 | fprintf(stderr, "Cannot count SQLite wallet export records: %s\n", | ||
| 1079 | strError.c_str()); | ||
| 1080 | return 1; | ||
| 1081 | } | ||
| 1082 | if (nSQLiteRecords <= 0) | ||
| 1083 | { | ||
| 1084 | fprintf(stderr, "SQLite wallet export contains no records.\n"); | ||
| 1085 | return 2; | ||
| 1086 | } | ||
| 1087 | |||
| 1088 | CWalletRawRestoreDB bdb("cr+", true); | ||
| 1089 | if (!bdb.TxnBegin()) | ||
| 1090 | { | ||
| 1091 | fprintf(stderr, "Cannot begin Berkeley DB restore transaction.\n"); | ||
| 1092 | return 1; | ||
| 1093 | } | ||
| 1094 | |||
| 1095 | int nCopied = 0; | ||
| 1096 | CWalletSQLiteRestoreVisitor visitor(bdb, nCopied); | ||
| 1097 | if (!sqlite.ScanRecords(visitor, strError)) | ||
| 1098 | { | ||
| 1099 | bdb.TxnAbort(); | ||
| 1100 | fprintf(stderr, "Cannot restore SQLite wallet export: %s\n", | ||
| 1101 | strError.c_str()); | ||
| 1102 | return 1; | ||
| 1103 | } | ||
| 1104 | if (nCopied != nSQLiteRecords) | ||
| 1105 | { | ||
| 1106 | bdb.TxnAbort(); | ||
| 1107 | fprintf(stderr, "SQLite wallet restore copied %d records, expected %d.\n", | ||
| 1108 | nCopied, nSQLiteRecords); | ||
| 1109 | return 2; | ||
| 1110 | } | ||
| 1111 | if (!bdb.TxnCommit()) | ||
| 1112 | { | ||
| 1113 | fprintf(stderr, "Cannot commit Berkeley DB restore transaction.\n"); | ||
| 1114 | return 1; | ||
| 1115 | } | ||
| 1116 | |||
| 1117 | printf("SQLite wallet export restored to wallet.dat\n"); | ||
| 1118 | printf(" records restored: %d\n", nCopied); | ||
| 1119 | fflush(stdout); | ||
| 1120 | return 0; | ||
| 1121 | } | ||
| 1122 | |||
| 997 | int CmdEncryptWallet(const std::string& strPassphrase) | 1123 | int CmdEncryptWallet(const std::string& strPassphrase) |
| 998 | { | 1124 | { |
| 999 | AttachTerminal(); | 1125 | AttachTerminal(); |
diff --git a/src/walletcmd.h b/src/walletcmd.h index 784c74c..50dc69e 100644 --- a/src/walletcmd.h +++ b/src/walletcmd.h | |||
| @@ -79,6 +79,10 @@ int CmdWalletSQLiteExport(const std::string& strDest); | |||
| 79 | // wallet.dat raw records. Prints counts only, never record keys or values. | 79 | // wallet.dat raw records. Prints counts only, never record keys or values. |
| 80 | int CmdWalletSQLiteVerify(const std::string& strPath); | 80 | int CmdWalletSQLiteVerify(const std::string& strPath); |
| 81 | 81 | ||
| 82 | // Diagnostic/migration staging: rebuild wallet.dat from a SQLite export in an | ||
| 83 | // empty data directory. Refuses to overwrite an existing wallet.dat. | ||
| 84 | int CmdWalletSQLiteRestore(const std::string& strPath); | ||
| 85 | |||
| 82 | // Rewrite wallet.dat so private keys and the HD seed are encrypted with the | 86 | // Rewrite wallet.dat so private keys and the HD seed are encrypted with the |
| 83 | // given passphrase. Returns 0 on success and exits without starting the node. | 87 | // given passphrase. Returns 0 on success and exits without starting the node. |
| 84 | int CmdEncryptWallet(const std::string& strPassphrase); | 88 | int CmdEncryptWallet(const std::string& strPassphrase); |