upleb.uk

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

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBitflash <admin@bitflash.network>2026-08-14 17:32:07 -0300
committerBitflash <admin@bitflash.network>2026-08-14 17:32:07 -0300
commit307d1a79d4f9bdecd3f834f9ab4987f46d16d177 (patch)
treee0f5626e7d70caad3f568a04d2264cfc7bd15e9e
parent3227564b26d0a9c5b8f1bcc5e21bb572a2968e5f (diff)
parentf7f9885e89e2c2527933b99ec713592ebf930483 (diff)
Merge pull request 'wallet: clean up failed SQLite migrations' (#52) from wallet-sqlite-cleanup-failures into main
-rw-r--r--docs/storage.md3
-rw-r--r--src/selftest.cpp53
-rw-r--r--src/walletcmd.cpp32
3 files changed, 87 insertions, 1 deletions
diff --git a/docs/storage.md b/docs/storage.md
index 4947575..aca3f6e 100644
--- a/docs/storage.md
+++ b/docs/storage.md
@@ -115,7 +115,8 @@ streams without introducing a SQLite runtime wallet loader yet.
115SQLite export and prints only record counts and mismatch counts. 115SQLite export and prints only record counts and mismatch counts.
116`-walletsqliterestore=FILE` rebuilds `wallet.dat` from a SQLite export only in 116`-walletsqliterestore=FILE` rebuilds `wallet.dat` from a SQLite export only in
117an empty data directory, then the verifier can prove the restored BDB file still 117an empty data directory, then the verifier can prove the restored BDB file still
118matches the export byte-for-byte. 118matches the export byte-for-byte. Failed exports remove the incomplete SQLite
119file and failed restores remove the incomplete `wallet.dat`.
119 120
120## `blkindex.dat` 121## `blkindex.dat`
121 122
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
1165static 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
1165static int RunWalletSQLiteMigrationSelfTest() 1198static 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
799namespace { 799namespace {
800 800
801static 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
808static void RemoveFailedRestoreWallet(const string& strWalletPath)
809{
810 DBFlush(true);
811 remove(strWalletPath.c_str());
812}
813
801class CWalletSQLiteExportVisitor : public CWalletRecordVisitor 814class CWalletSQLiteExportVisitor : public CWalletRecordVisitor
802{ 815{
803public: 816public:
@@ -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 }