upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/walletcmd.cpp
diff options
context:
space:
mode:
authorbitflash <dev@bitflash.network>2026-08-18 16:26:34 -0300
committerbitflash <dev@bitflash.network>2026-08-18 16:26:34 -0300
commit477d57b9a01102b6f79ad9b10b2dcfd280faa1cb (patch)
treec320e5c1f6e486affdf255a1a4ac806a01b9da7c /src/walletcmd.cpp
parent3f94ec0649f935d00de49ad7b55026317dc64a27 (diff)
gui: offer to convert a Berkeley DB wallet to SQLite on first run
A first-run dialog for an existing wallet.dat with no backend choice yet: "Convert to SQLite" exports the wallet and records the sqlite marker so the next start opens it; "Keep Berkeley DB" records that choice so the offer does not return; "Decide later" leaves no marker and asks again. wallet.dat is never touched, and the change takes effect on restart -- matching the restart-to-apply model. The conversion itself is DoConvertWalletToSQLite(), a headless-testable function the button calls: it reuses the export path (extracted as ExportActiveWalletToSQLite, shared with -walletsqliteexport, which keeps its own specific messages), refuses if already on SQLite or if a wallet.sqlite exists, and writes the marker only after a verified export. New wallet-convert self-test proves the action end to end: build a Berkeley DB wallet, convert, and confirm wallet.sqlite + the marker are written, wallet.dat is left untouched, a second convert refuses, and a flagless restart opens the converted SQLite wallet with its phrase intact. All wallet suites pass. The modal itself is thin UI over this tested action.
Diffstat (limited to 'src/walletcmd.cpp')
-rw-r--r--src/walletcmd.cpp119
1 files changed, 83 insertions, 36 deletions
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
1073int 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.
1078bool 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.
1145bool 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
1169int 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