upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src
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 /src
parent0c58896a045246c3518d5d379f217172defdfd91 (diff)
wallet: verify SQLite wallet exports
Diffstat (limited to 'src')
-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
4 files changed, 163 insertions, 0 deletions
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);