upleb.uk

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

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNsable-sh <nsable-sh@noreply.git.bitflash.network>2026-08-14 16:34:43 -0300
committerNsable-sh <nsable-sh@noreply.git.bitflash.network>2026-08-14 16:47:43 -0300
commit99c6b5f2971da51cca1c739b5e982ae2022acc85 (patch)
treea32b59dde39832f1fc7a21a29681b77c95de40cb
parent0c58896a045246c3518d5d379f217172defdfd91 (diff)
wallet: verify SQLite wallet exports
-rw-r--r--docs/storage.md2
-rw-r--r--src/main_gui.cpp11
-rw-r--r--src/selftest.cpp41
-rw-r--r--src/walletcmd.cpp107
-rw-r--r--src/walletcmd.h4
5 files changed, 165 insertions, 0 deletions
diff --git a/docs/storage.md b/docs/storage.md
index 2d748fa..0a441bc 100644
--- a/docs/storage.md
+++ b/docs/storage.md
@@ -111,6 +111,8 @@ overwrite an existing export and still does not change the runtime wallet
111backend. `CWalletDBSQLite::ScanRecords()` can stream those exported records back 111backend. `CWalletDBSQLite::ScanRecords()` can stream those exported records back
112through the same visitor shape, so tests can compare BDB and SQLite record 112through the same visitor shape, so tests can compare BDB and SQLite record
113streams without introducing a SQLite runtime wallet loader yet. 113streams without introducing a SQLite runtime wallet loader yet.
114`-walletsqliteverify=FILE` compares the current `wallet.dat` stream with a
115SQLite export and prints only record counts and mismatch counts.
114 116
115## `blkindex.dat` 117## `blkindex.dat`
116 118
diff --git a/src/main_gui.cpp b/src/main_gui.cpp
index 6e89270..caacf13 100644
--- a/src/main_gui.cpp
+++ b/src/main_gui.cpp
@@ -183,6 +183,8 @@ static void PrintUsage()
183 printf(" wallet.dat storage state)\n"); 183 printf(" wallet.dat storage state)\n");
184 printf(" /walletsqliteexport=FILE (copy raw wallet.dat records into an\n"); 184 printf(" /walletsqliteexport=FILE (copy raw wallet.dat records into an\n");
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");
187 printf(" printing counts only, then exit)\n");
186 printf(" /rescan (walk the chain for coins this wallet owns\n"); 188 printf(" /rescan (walk the chain for coins this wallet owns\n");
187 printf(" but never recorded, then exit)\n"); 189 printf(" but never recorded, then exit)\n");
188 printf("\n"); 190 printf("\n");
@@ -540,6 +542,15 @@ int main(int argc, char* argv[])
540 return nRet; 542 return nRet;
541 } 543 }
542 544
545 string strWalletSQLiteVerify =
546 argval2(argc, argv, "/walletsqliteverify", "-walletsqliteverify");
547 if (!strWalletSQLiteVerify.empty())
548 {
549 int nRet = CmdWalletSQLiteVerify(strWalletSQLiteVerify);
550 DBFlush(true);
551 return nRet;
552 }
553
543 // Berkeley DB reports failure by throwing, and CDB's constructor lets it 554 // Berkeley DB reports failure by throwing, and CDB's constructor lets it
544 // through. Nothing on this path caught anything, so an unreadable 555 // through. Nothing on this path caught anything, so an unreadable
545 // blkindex.dat or wallet.dat -- one written by another platform's Berkeley 556 // blkindex.dat or wallet.dat -- one written by another platform's Berkeley
diff --git a/src/selftest.cpp b/src/selftest.cpp
index 408dd41..4ba90c3 100644
--- a/src/selftest.cpp
+++ b/src/selftest.cpp
@@ -1134,6 +1134,8 @@ static int RunWalletSQLiteSelfTest()
1134 return nFail == 0 ? 0 : 1; 1134 return nFail == 0 ? 0 : 1;
1135} 1135}
1136 1136
1137namespace {
1138
1137class CWalletRecordMapVisitor : public CWalletRecordVisitor 1139class CWalletRecordMapVisitor : public CWalletRecordVisitor
1138{ 1140{
1139public: 1141public:
@@ -1158,6 +1160,8 @@ public:
1158 } 1160 }
1159}; 1161};
1160 1162
1163} // namespace
1164
1161static int RunWalletSQLiteMigrationSelfTest() 1165static int RunWalletSQLiteMigrationSelfTest()
1162{ 1166{
1163 fflush(stdout); 1167 fflush(stdout);
@@ -1215,6 +1219,17 @@ static int RunWalletSQLiteMigrationSelfTest()
1215 FileContainsText(strExportOut, "SQLite wallet export written"), 1219 FileContainsText(strExportOut, "SQLite wallet export written"),
1216 "wallet.dat can be exported to SQLite") ? 0 : 1; 1220 "wallet.dat can be exported to SQLite") ? 0 : 1;
1217 1221
1222 string strVerifyOut = tmp + "/sqlite-verify.txt";
1223 vector<string> vVerifyArgs;
1224 vVerifyArgs.push_back("-datadir=" + strWalletDir);
1225 vVerifyArgs.push_back("-nomanagedtor");
1226 vVerifyArgs.push_back("-nogui");
1227 vVerifyArgs.push_back("-walletsqliteverify=" + strSQLite);
1228 int nVerifyRet = RunBitflashChild(strExe, vVerifyArgs, NULL, &strVerifyOut);
1229 nFail += Check(nVerifyRet == 0 &&
1230 FileContainsText(strVerifyOut, "verification: ok"),
1231 "SQLite wallet export verifies against wallet.dat") ? 0 : 1;
1232
1218 string strOverwriteOut = tmp + "/sqlite-export-overwrite.txt"; 1233 string strOverwriteOut = tmp + "/sqlite-export-overwrite.txt";
1219 int nOverwriteRet = 1234 int nOverwriteRet =
1220 RunBitflashChild(strExe, vExportArgs, NULL, &strOverwriteOut); 1235 RunBitflashChild(strExe, vExportArgs, NULL, &strOverwriteOut);
@@ -1265,6 +1280,32 @@ static int RunWalletSQLiteMigrationSelfTest()
1265 nFail += Check(db.ScanRecords(sqliteVisitor, strError) && 1280 nFail += Check(db.ScanRecords(sqliteVisitor, strError) &&
1266 mapSQLite == mapBDB, 1281 mapSQLite == mapBDB,
1267 "SQLite wallet scanner streams every exported record byte-for-byte") ? 0 : 1; 1282 "SQLite wallet scanner streams every exported record byte-for-byte") ? 0 : 1;
1283
1284 db.Close();
1285 CWalletDBSQLite dbCorrupt;
1286 vector<unsigned char> vchBadValue;
1287 vchBadValue.push_back(0xba);
1288 vchBadValue.push_back(0xad);
1289 nFail += Check(!mapBDB.empty() &&
1290 dbCorrupt.Open(strSQLite, strError) &&
1291 dbCorrupt.BeginTransaction(strError) &&
1292 dbCorrupt.WriteRecord(mapBDB.begin()->first,
1293 vchBadValue,
1294 strError) &&
1295 dbCorrupt.CommitTransaction(strError) &&
1296 dbCorrupt.Checkpoint(strError),
1297 "SQLite export can be deliberately corrupted for verifier test") ? 0 : 1;
1298 dbCorrupt.Close();
1299
1300 string strVerifyBadOut = tmp + "/sqlite-verify-bad.txt";
1301 int nVerifyBadRet =
1302 RunBitflashChild(strExe, vVerifyArgs, NULL, &strVerifyBadOut);
1303 nFail += Check(nVerifyBadRet == 2 &&
1304 FileContainsText(strVerifyBadOut,
1305 "record value mismatches: 1") &&
1306 FileContainsText(strVerifyBadOut,
1307 "verification: failed"),
1308 "SQLite wallet verifier detects a mismatched record value") ? 0 : 1;
1268 } 1309 }
1269 catch (const std::exception& e) 1310 catch (const std::exception& e)
1270 { 1311 {
diff --git a/src/walletcmd.cpp b/src/walletcmd.cpp
index 20e7e33..36f6783 100644
--- a/src/walletcmd.cpp
+++ b/src/walletcmd.cpp
@@ -796,6 +796,8 @@ static vector<unsigned char> DataStreamBytesLocal(const CDataStream& ss)
796 return vector<unsigned char>(ss.begin(), ss.end()); 796 return vector<unsigned char>(ss.begin(), ss.end());
797} 797}
798 798
799namespace {
800
799class CWalletSQLiteExportVisitor : public CWalletRecordVisitor 801class CWalletSQLiteExportVisitor : public CWalletRecordVisitor
800{ 802{
801public: 803public:
@@ -818,6 +820,32 @@ public:
818 } 820 }
819}; 821};
820 822
823class CWalletRecordMapVisitor : public CWalletRecordVisitor
824{
825public:
826 std::map<vector<unsigned char>, vector<unsigned char> >& mapRecords;
827
828 explicit CWalletRecordMapVisitor(
829 std::map<vector<unsigned char>, vector<unsigned char> >& mapRecordsIn)
830 : mapRecords(mapRecordsIn) { }
831
832 bool VisitWalletRecord(const CDataStream& ssKey,
833 const CDataStream& ssValue,
834 string& strErrorRet)
835 {
836 vector<unsigned char> vchKey = DataStreamBytesLocal(ssKey);
837 if (mapRecords.count(vchKey))
838 {
839 strErrorRet = "duplicate serialized wallet key";
840 return false;
841 }
842 mapRecords[vchKey] = DataStreamBytesLocal(ssValue);
843 return true;
844 }
845};
846
847} // namespace
848
821int CmdWalletSQLiteExport(const std::string& strDest) 849int CmdWalletSQLiteExport(const std::string& strDest)
822{ 850{
823 AttachTerminal(); 851 AttachTerminal();
@@ -887,6 +915,85 @@ int CmdWalletSQLiteExport(const std::string& strDest)
887 return 0; 915 return 0;
888} 916}
889 917
918int CmdWalletSQLiteVerify(const std::string& strPath)
919{
920 AttachTerminal();
921
922 if (strPath.empty())
923 {
924 fprintf(stderr, "Missing SQLite wallet export path.\n");
925 return 1;
926 }
927 if (!FileExists(strPath.c_str()))
928 {
929 fprintf(stderr, "SQLite wallet export does not exist: %s\n",
930 strPath.c_str());
931 return 1;
932 }
933
934 std::map<vector<unsigned char>, vector<unsigned char> > mapBDB;
935 std::map<vector<unsigned char>, vector<unsigned char> > mapSQLite;
936 string strError;
937
938 CWalletRecordMapVisitor bdbVisitor(mapBDB);
939 if (!ScanWalletRecords(bdbVisitor, strError))
940 {
941 fprintf(stderr, "Cannot scan wallet.dat records: %s\n", strError.c_str());
942 return 1;
943 }
944
945 CWalletDBSQLite db;
946 if (!db.Open(strPath, strError))
947 {
948 fprintf(stderr, "Cannot open SQLite wallet export: %s\n", strError.c_str());
949 return 1;
950 }
951 CWalletRecordMapVisitor sqliteVisitor(mapSQLite);
952 if (!db.ScanRecords(sqliteVisitor, strError))
953 {
954 fprintf(stderr, "Cannot scan SQLite wallet export: %s\n", strError.c_str());
955 return 1;
956 }
957
958 int nMissing = 0;
959 int nMismatched = 0;
960 for (std::map<vector<unsigned char>, vector<unsigned char> >::const_iterator it =
961 mapBDB.begin(); it != mapBDB.end(); ++it)
962 {
963 std::map<vector<unsigned char>, vector<unsigned char> >::const_iterator sit =
964 mapSQLite.find(it->first);
965 if (sit == mapSQLite.end())
966 nMissing++;
967 else if (sit->second != it->second)
968 nMismatched++;
969 }
970
971 int nExtra = 0;
972 for (std::map<vector<unsigned char>, vector<unsigned char> >::const_iterator it =
973 mapSQLite.begin(); it != mapSQLite.end(); ++it)
974 {
975 if (!mapBDB.count(it->first))
976 nExtra++;
977 }
978
979 printf("SQLite wallet export verification\n");
980 printf(" wallet.dat records: %u\n", (unsigned int)mapBDB.size());
981 printf(" SQLite records: %u\n", (unsigned int)mapSQLite.size());
982 printf(" missing records: %d\n", nMissing);
983 printf(" extra records: %d\n", nExtra);
984 printf(" record value mismatches: %d\n", nMismatched);
985 if (nMissing == 0 && nExtra == 0 && nMismatched == 0)
986 {
987 printf(" verification: ok\n");
988 fflush(stdout);
989 return 0;
990 }
991
992 printf(" verification: failed\n");
993 fflush(stdout);
994 return 2;
995}
996
890int CmdEncryptWallet(const std::string& strPassphrase) 997int CmdEncryptWallet(const std::string& strPassphrase)
891{ 998{
892 AttachTerminal(); 999 AttachTerminal();
diff --git a/src/walletcmd.h b/src/walletcmd.h
index b1a29d2..784c74c 100644
--- a/src/walletcmd.h
+++ b/src/walletcmd.h
@@ -75,6 +75,10 @@ int CmdWalletStorageCheck();
75// not change which backend the runtime wallet opens. 75// not change which backend the runtime wallet opens.
76int CmdWalletSQLiteExport(const std::string& strDest); 76int CmdWalletSQLiteExport(const std::string& strDest);
77 77
78// Diagnostic/migration staging: compare a SQLite wallet export with the current
79// wallet.dat raw records. Prints counts only, never record keys or values.
80int CmdWalletSQLiteVerify(const std::string& strPath);
81
78// Rewrite wallet.dat so private keys and the HD seed are encrypted with the 82// Rewrite wallet.dat so private keys and the HD seed are encrypted with the
79// given passphrase. Returns 0 on success and exits without starting the node. 83// given passphrase. Returns 0 on success and exits without starting the node.
80int CmdEncryptWallet(const std::string& strPassphrase); 84int CmdEncryptWallet(const std::string& strPassphrase);