diff options
| author | Nsable-sh <nsable-sh@noreply.git.bitflash.network> | 2026-08-14 17:23:21 -0300 |
|---|---|---|
| committer | Nsable-sh <nsable-sh@noreply.git.bitflash.network> | 2026-08-14 17:23:21 -0300 |
| commit | f7f9885e89e2c2527933b99ec713592ebf930483 (patch) | |
| tree | e0f5626e7d70caad3f568a04d2264cfc7bd15e9e /src | |
| parent | 3227564b26d0a9c5b8f1bcc5e21bb572a2968e5f (diff) | |
wallet: clean up failed SQLite migrations
Diffstat (limited to 'src')
| -rw-r--r-- | src/selftest.cpp | 53 | ||||
| -rw-r--r-- | src/walletcmd.cpp | 32 |
2 files changed, 85 insertions, 0 deletions
diff --git a/src/selftest.cpp b/src/selftest.cpp index 7d3a96e..343d9c5 100644 --- a/src/selftest.cpp +++ b/src/selftest.cpp | |||
| @@ -1162,6 +1162,39 @@ public: | |||
| 1162 | 1162 | ||
| 1163 | } // namespace | 1163 | } // namespace |
| 1164 | 1164 | ||
| 1165 | static bool CreateSQLiteWalletWithEmptyKey(const string& strPath) | ||
| 1166 | { | ||
| 1167 | sqlite3* pdb = NULL; | ||
| 1168 | int ret = sqlite3_open_v2(strPath.c_str(), &pdb, | ||
| 1169 | SQLITE_OPEN_READWRITE | | ||
| 1170 | SQLITE_OPEN_CREATE | | ||
| 1171 | SQLITE_OPEN_FULLMUTEX, | ||
| 1172 | NULL); | ||
| 1173 | if (ret != SQLITE_OK || !pdb) | ||
| 1174 | { | ||
| 1175 | if (pdb) | ||
| 1176 | sqlite3_close(pdb); | ||
| 1177 | return false; | ||
| 1178 | } | ||
| 1179 | |||
| 1180 | char* pszErr = NULL; | ||
| 1181 | bool fOk = | ||
| 1182 | sqlite3_exec(pdb, | ||
| 1183 | "CREATE TABLE wallet_records (" | ||
| 1184 | "key BLOB PRIMARY KEY NOT NULL," | ||
| 1185 | "value BLOB NOT NULL" | ||
| 1186 | ");", | ||
| 1187 | NULL, NULL, &pszErr) == SQLITE_OK && | ||
| 1188 | sqlite3_exec(pdb, | ||
| 1189 | "INSERT INTO wallet_records(key, value) " | ||
| 1190 | "VALUES(x'', x'01');", | ||
| 1191 | NULL, NULL, &pszErr) == SQLITE_OK; | ||
| 1192 | if (pszErr) | ||
| 1193 | sqlite3_free(pszErr); | ||
| 1194 | sqlite3_close(pdb); | ||
| 1195 | return fOk; | ||
| 1196 | } | ||
| 1197 | |||
| 1165 | static int RunWalletSQLiteMigrationSelfTest() | 1198 | static int RunWalletSQLiteMigrationSelfTest() |
| 1166 | { | 1199 | { |
| 1167 | fflush(stdout); | 1200 | fflush(stdout); |
| @@ -1265,6 +1298,26 @@ static int RunWalletSQLiteMigrationSelfTest() | |||
| 1265 | "Refusing to overwrite existing wallet.dat"), | 1298 | "Refusing to overwrite existing wallet.dat"), |
| 1266 | "SQLite wallet restore refuses to overwrite wallet.dat") ? 0 : 1; | 1299 | "SQLite wallet restore refuses to overwrite wallet.dat") ? 0 : 1; |
| 1267 | 1300 | ||
| 1301 | string strBadSQLite = tmp + "/bad-empty-key.sqlite"; | ||
| 1302 | nFail += Check(CreateSQLiteWalletWithEmptyKey(strBadSQLite), | ||
| 1303 | "bad SQLite export with an empty key can be created") ? 0 : 1; | ||
| 1304 | string strBadRestoreDir = tmp + "/bad-restore-wallet"; | ||
| 1305 | nFail += Check(MakeDirLocal(strBadRestoreDir), | ||
| 1306 | "bad restore wallet directory can be created") ? 0 : 1; | ||
| 1307 | string strBadRestoreOut = tmp + "/sqlite-restore-bad.txt"; | ||
| 1308 | vector<string> vBadRestoreArgs; | ||
| 1309 | vBadRestoreArgs.push_back("-datadir=" + strBadRestoreDir); | ||
| 1310 | vBadRestoreArgs.push_back("-nomanagedtor"); | ||
| 1311 | vBadRestoreArgs.push_back("-nogui"); | ||
| 1312 | vBadRestoreArgs.push_back("-walletsqliterestore=" + strBadSQLite); | ||
| 1313 | int nBadRestoreRet = | ||
| 1314 | RunBitflashChild(strExe, vBadRestoreArgs, NULL, &strBadRestoreOut); | ||
| 1315 | nFail += Check(nBadRestoreRet == 1 && | ||
| 1316 | FileContainsText(strBadRestoreOut, | ||
| 1317 | "Cannot restore SQLite wallet export") && | ||
| 1318 | !FileExists((strBadRestoreDir + "/wallet.dat").c_str()), | ||
| 1319 | "failed SQLite wallet restore removes incomplete wallet.dat") ? 0 : 1; | ||
| 1320 | |||
| 1268 | string strOverwriteOut = tmp + "/sqlite-export-overwrite.txt"; | 1321 | string strOverwriteOut = tmp + "/sqlite-export-overwrite.txt"; |
| 1269 | int nOverwriteRet = | 1322 | int nOverwriteRet = |
| 1270 | RunBitflashChild(strExe, vExportArgs, NULL, &strOverwriteOut); | 1323 | RunBitflashChild(strExe, vExportArgs, NULL, &strOverwriteOut); |
diff --git a/src/walletcmd.cpp b/src/walletcmd.cpp index d1bad98..9e4a981 100644 --- a/src/walletcmd.cpp +++ b/src/walletcmd.cpp | |||
| @@ -798,6 +798,19 @@ static vector<unsigned char> DataStreamBytesLocal(const CDataStream& ss) | |||
| 798 | 798 | ||
| 799 | namespace { | 799 | namespace { |
| 800 | 800 | ||
| 801 | static void RemoveSQLiteExportFiles(const string& strPath) | ||
| 802 | { | ||
| 803 | remove(strPath.c_str()); | ||
| 804 | remove((strPath + "-wal").c_str()); | ||
| 805 | remove((strPath + "-shm").c_str()); | ||
| 806 | } | ||
| 807 | |||
| 808 | static void RemoveFailedRestoreWallet(const string& strWalletPath) | ||
| 809 | { | ||
| 810 | DBFlush(true); | ||
| 811 | remove(strWalletPath.c_str()); | ||
| 812 | } | ||
| 813 | |||
| 801 | class CWalletSQLiteExportVisitor : public CWalletRecordVisitor | 814 | class CWalletSQLiteExportVisitor : public CWalletRecordVisitor |
| 802 | { | 815 | { |
| 803 | public: | 816 | public: |
| @@ -912,11 +925,14 @@ int CmdWalletSQLiteExport(const std::string& strDest) | |||
| 912 | CWalletDBSQLite db; | 925 | CWalletDBSQLite db; |
| 913 | if (!db.Open(strDest, strError)) | 926 | if (!db.Open(strDest, strError)) |
| 914 | { | 927 | { |
| 928 | RemoveSQLiteExportFiles(strDest); | ||
| 915 | fprintf(stderr, "Cannot open SQLite wallet export: %s\n", strError.c_str()); | 929 | fprintf(stderr, "Cannot open SQLite wallet export: %s\n", strError.c_str()); |
| 916 | return 1; | 930 | return 1; |
| 917 | } | 931 | } |
| 918 | if (!db.BeginTransaction(strError)) | 932 | if (!db.BeginTransaction(strError)) |
| 919 | { | 933 | { |
| 934 | db.Close(); | ||
| 935 | RemoveSQLiteExportFiles(strDest); | ||
| 920 | fprintf(stderr, "Cannot begin SQLite wallet export: %s\n", strError.c_str()); | 936 | fprintf(stderr, "Cannot begin SQLite wallet export: %s\n", strError.c_str()); |
| 921 | return 1; | 937 | return 1; |
| 922 | } | 938 | } |
| @@ -927,16 +943,22 @@ int CmdWalletSQLiteExport(const std::string& strDest) | |||
| 927 | { | 943 | { |
| 928 | string strRollbackError; | 944 | string strRollbackError; |
| 929 | db.RollbackTransaction(strRollbackError); | 945 | db.RollbackTransaction(strRollbackError); |
| 946 | db.Close(); | ||
| 947 | RemoveSQLiteExportFiles(strDest); | ||
| 930 | fprintf(stderr, "Cannot export wallet.dat to SQLite: %s\n", strError.c_str()); | 948 | fprintf(stderr, "Cannot export wallet.dat to SQLite: %s\n", strError.c_str()); |
| 931 | return 1; | 949 | return 1; |
| 932 | } | 950 | } |
| 933 | if (!db.CommitTransaction(strError)) | 951 | if (!db.CommitTransaction(strError)) |
| 934 | { | 952 | { |
| 953 | db.Close(); | ||
| 954 | RemoveSQLiteExportFiles(strDest); | ||
| 935 | fprintf(stderr, "Cannot commit SQLite wallet export: %s\n", strError.c_str()); | 955 | fprintf(stderr, "Cannot commit SQLite wallet export: %s\n", strError.c_str()); |
| 936 | return 1; | 956 | return 1; |
| 937 | } | 957 | } |
| 938 | if (!db.Checkpoint(strError)) | 958 | if (!db.Checkpoint(strError)) |
| 939 | { | 959 | { |
| 960 | db.Close(); | ||
| 961 | RemoveSQLiteExportFiles(strDest); | ||
| 940 | fprintf(stderr, "Cannot checkpoint SQLite wallet export: %s\n", strError.c_str()); | 962 | fprintf(stderr, "Cannot checkpoint SQLite wallet export: %s\n", strError.c_str()); |
| 941 | return 1; | 963 | return 1; |
| 942 | } | 964 | } |
| @@ -944,12 +966,16 @@ int CmdWalletSQLiteExport(const std::string& strDest) | |||
| 944 | int nCount = 0; | 966 | int nCount = 0; |
| 945 | if (!db.CountRecords(nCount, strError)) | 967 | if (!db.CountRecords(nCount, strError)) |
| 946 | { | 968 | { |
| 969 | db.Close(); | ||
| 970 | RemoveSQLiteExportFiles(strDest); | ||
| 947 | fprintf(stderr, "Cannot verify SQLite wallet export count: %s\n", | 971 | fprintf(stderr, "Cannot verify SQLite wallet export count: %s\n", |
| 948 | strError.c_str()); | 972 | strError.c_str()); |
| 949 | return 1; | 973 | return 1; |
| 950 | } | 974 | } |
| 951 | if (nCount != nCopied) | 975 | if (nCount != nCopied) |
| 952 | { | 976 | { |
| 977 | db.Close(); | ||
| 978 | RemoveSQLiteExportFiles(strDest); | ||
| 953 | fprintf(stderr, "SQLite wallet export count mismatch: copied %d, stored %d\n", | 979 | fprintf(stderr, "SQLite wallet export count mismatch: copied %d, stored %d\n", |
| 954 | nCopied, nCount); | 980 | nCopied, nCount); |
| 955 | return 1; | 981 | return 1; |
| @@ -1097,6 +1123,8 @@ int CmdWalletSQLiteRestore(const std::string& strPath) | |||
| 1097 | if (!sqlite.ScanRecords(visitor, strError)) | 1123 | if (!sqlite.ScanRecords(visitor, strError)) |
| 1098 | { | 1124 | { |
| 1099 | bdb.TxnAbort(); | 1125 | bdb.TxnAbort(); |
| 1126 | bdb.Close(); | ||
| 1127 | RemoveFailedRestoreWallet(strWalletPath); | ||
| 1100 | fprintf(stderr, "Cannot restore SQLite wallet export: %s\n", | 1128 | fprintf(stderr, "Cannot restore SQLite wallet export: %s\n", |
| 1101 | strError.c_str()); | 1129 | strError.c_str()); |
| 1102 | return 1; | 1130 | return 1; |
| @@ -1104,12 +1132,16 @@ int CmdWalletSQLiteRestore(const std::string& strPath) | |||
| 1104 | if (nCopied != nSQLiteRecords) | 1132 | if (nCopied != nSQLiteRecords) |
| 1105 | { | 1133 | { |
| 1106 | bdb.TxnAbort(); | 1134 | bdb.TxnAbort(); |
| 1135 | bdb.Close(); | ||
| 1136 | RemoveFailedRestoreWallet(strWalletPath); | ||
| 1107 | fprintf(stderr, "SQLite wallet restore copied %d records, expected %d.\n", | 1137 | fprintf(stderr, "SQLite wallet restore copied %d records, expected %d.\n", |
| 1108 | nCopied, nSQLiteRecords); | 1138 | nCopied, nSQLiteRecords); |
| 1109 | return 2; | 1139 | return 2; |
| 1110 | } | 1140 | } |
| 1111 | if (!bdb.TxnCommit()) | 1141 | if (!bdb.TxnCommit()) |
| 1112 | { | 1142 | { |
| 1143 | bdb.Close(); | ||
| 1144 | RemoveFailedRestoreWallet(strWalletPath); | ||
| 1113 | fprintf(stderr, "Cannot commit Berkeley DB restore transaction.\n"); | 1145 | fprintf(stderr, "Cannot commit Berkeley DB restore transaction.\n"); |
| 1114 | return 1; | 1146 | return 1; |
| 1115 | } | 1147 | } |