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:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile1
-rw-r--r--src/main_gui.cpp15
-rw-r--r--src/makefile.mingw1
-rw-r--r--src/selftest.cpp148
-rw-r--r--src/wallet_sqlite.cpp10
-rw-r--r--src/wallet_sqlite.h1
-rw-r--r--src/walletcmd.cpp97
-rw-r--r--src/walletcmd.h5
8 files changed, 275 insertions, 3 deletions
diff --git a/src/Makefile b/src/Makefile
index cfa40f6..b83ff58 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -189,6 +189,7 @@ tests: test_bip32 bitflash-node
189 ./bitflash-node -selftest=wallet-hd 189 ./bitflash-node -selftest=wallet-hd
190 ./bitflash-node -selftest=wallet-format 190 ./bitflash-node -selftest=wallet-format
191 ./bitflash-node -selftest=wallet-sqlite 191 ./bitflash-node -selftest=wallet-sqlite
192 ./bitflash-node -selftest=wallet-sqlite-migration
192 ./bitflash-node -selftest=wallet-crypto 193 ./bitflash-node -selftest=wallet-crypto
193 ./bitflash-node -selftest=wallet-encrypt 194 ./bitflash-node -selftest=wallet-encrypt
194 ./bitflash-node -selftest=net-message 195 ./bitflash-node -selftest=net-message
diff --git a/src/main_gui.cpp b/src/main_gui.cpp
index 965c818..6e89270 100644
--- a/src/main_gui.cpp
+++ b/src/main_gui.cpp
@@ -114,8 +114,8 @@ static void PrintUsage()
114 printf(" /gen\n"); 114 printf(" /gen\n");
115 printf(" /nogui or /daemon\n"); 115 printf(" /nogui or /daemon\n");
116 printf(" /selftest=wallet-keypool, wallet-hd, wallet-format, wallet-storage-sanity,\n"); 116 printf(" /selftest=wallet-keypool, wallet-hd, wallet-format, wallet-storage-sanity,\n");
117 printf(" db-env-reopen, wallet-sqlite, wallet-crypto, wallet-encrypt,\n"); 117 printf(" db-env-reopen, wallet-sqlite, wallet-sqlite-migration,\n");
118 printf(" wallet-portability,\n"); 118 printf(" wallet-crypto, wallet-encrypt, wallet-portability,\n");
119 printf(" net-message,\n"); 119 printf(" net-message,\n");
120 printf(" consensus-limits, pool-stratum,\n"); 120 printf(" consensus-limits, pool-stratum,\n");
121 printf(" parse-money, socks5-proxy, or managed-tor\n"); 121 printf(" parse-money, socks5-proxy, or managed-tor\n");
@@ -181,6 +181,8 @@ static void PrintUsage()
181 printf(" (write the same audit as deterministic JSON)\n"); 181 printf(" (write the same audit as deterministic JSON)\n");
182 printf(" /walletstoragecheck (fail closed on inconsistent or unsafe\n"); 182 printf(" /walletstoragecheck (fail closed on inconsistent or unsafe\n");
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");
185 printf(" experimental SQLite store, then exit)\n");
184 printf(" /rescan (walk the chain for coins this wallet owns\n"); 186 printf(" /rescan (walk the chain for coins this wallet owns\n");
185 printf(" but never recorded, then exit)\n"); 187 printf(" but never recorded, then exit)\n");
186 printf("\n"); 188 printf("\n");
@@ -529,6 +531,15 @@ int main(int argc, char* argv[])
529 return nRet; 531 return nRet;
530 } 532 }
531 533
534 string strWalletSQLiteExport =
535 argval2(argc, argv, "/walletsqliteexport", "-walletsqliteexport");
536 if (!strWalletSQLiteExport.empty())
537 {
538 int nRet = CmdWalletSQLiteExport(strWalletSQLiteExport);
539 DBFlush(true);
540 return nRet;
541 }
542
532 // Berkeley DB reports failure by throwing, and CDB's constructor lets it 543 // Berkeley DB reports failure by throwing, and CDB's constructor lets it
533 // through. Nothing on this path caught anything, so an unreadable 544 // through. Nothing on this path caught anything, so an unreadable
534 // blkindex.dat or wallet.dat -- one written by another platform's Berkeley 545 // blkindex.dat or wallet.dat -- one written by another platform's Berkeley
diff --git a/src/makefile.mingw b/src/makefile.mingw
index 2ff4e11..14e6b32 100644
--- a/src/makefile.mingw
+++ b/src/makefile.mingw
@@ -109,6 +109,7 @@ tests: test_bip32.exe bitflash.exe
109 ./bitflash.exe -selftest=wallet-hd 109 ./bitflash.exe -selftest=wallet-hd
110 ./bitflash.exe -selftest=wallet-format 110 ./bitflash.exe -selftest=wallet-format
111 ./bitflash.exe -selftest=wallet-sqlite 111 ./bitflash.exe -selftest=wallet-sqlite
112 ./bitflash.exe -selftest=wallet-sqlite-migration
112 ./bitflash.exe -selftest=wallet-crypto 113 ./bitflash.exe -selftest=wallet-crypto
113 ./bitflash.exe -selftest=wallet-encrypt 114 ./bitflash.exe -selftest=wallet-encrypt
114 ./bitflash.exe -selftest=net-message 115 ./bitflash.exe -selftest=net-message
diff --git a/src/selftest.cpp b/src/selftest.cpp
index f9c4a8e..7a54271 100644
--- a/src/selftest.cpp
+++ b/src/selftest.cpp
@@ -1134,6 +1134,150 @@ static int RunWalletSQLiteSelfTest()
1134 return nFail == 0 ? 0 : 1; 1134 return nFail == 0 ? 0 : 1;
1135} 1135}
1136 1136
1137class CWalletRecordMapVisitor : public CWalletRecordVisitor
1138{
1139public:
1140 std::map<vector<unsigned char>, vector<unsigned char> >& mapRecords;
1141
1142 explicit CWalletRecordMapVisitor(
1143 std::map<vector<unsigned char>, vector<unsigned char> >& mapRecordsIn)
1144 : mapRecords(mapRecordsIn) { }
1145
1146 bool VisitWalletRecord(const CDataStream& ssKey,
1147 const CDataStream& ssValue,
1148 string& strErrorRet)
1149 {
1150 vector<unsigned char> vchKey = DataStreamBytes(ssKey);
1151 if (mapRecords.count(vchKey))
1152 {
1153 strErrorRet = "duplicate serialized wallet key";
1154 return false;
1155 }
1156 mapRecords[vchKey] = DataStreamBytes(ssValue);
1157 return true;
1158 }
1159};
1160
1161static int RunWalletSQLiteMigrationSelfTest()
1162{
1163 fflush(stdout);
1164 printf("wallet-sqlite-migration self-test\n");
1165
1166 std::string tmp;
1167 std::string cwd;
1168 if (!MakeTempDir(tmp))
1169 {
1170 printf(" FAIL could not create a temporary data directory\n");
1171 return 1;
1172 }
1173 if (!GetCurrentDir(cwd))
1174 {
1175 printf(" FAIL could not read current directory\n");
1176 RemoveTree(tmp);
1177 return 1;
1178 }
1179
1180 int nFail = 0;
1181 printf(" temp root: %s\n", tmp.c_str());
1182
1183 try
1184 {
1185#ifdef _WIN32
1186 string strExe = cwd + "\\bitflash.exe";
1187#else
1188 string strExe = cwd + "/bitflash-node";
1189#endif
1190 string strWalletDir = tmp + "/wallet";
1191 nFail += Check(MakeDirLocal(strWalletDir),
1192 "temporary wallet directory can be created") ? 0 : 1;
1193
1194 string strCreateOut = tmp + "/create.txt";
1195 vector<string> vCreateArgs;
1196 vCreateArgs.push_back("-datadir=" + strWalletDir);
1197 vCreateArgs.push_back("-nomanagedtor");
1198 vCreateArgs.push_back("-nogui");
1199 vCreateArgs.push_back("-newphrase");
1200 int nCreateRet = RunBitflashChild(strExe, vCreateArgs, NULL, &strCreateOut);
1201 nFail += Check(nCreateRet == 0 &&
1202 FileContainsText(strCreateOut, "Write these twelve words down"),
1203 "a phrase wallet can be created for SQLite export") ? 0 : 1;
1204
1205 string strSQLite = tmp + "/wallet.sqlite";
1206 string strExportOut = tmp + "/sqlite-export.txt";
1207 vector<string> vExportArgs;
1208 vExportArgs.push_back("-datadir=" + strWalletDir);
1209 vExportArgs.push_back("-nomanagedtor");
1210 vExportArgs.push_back("-nogui");
1211 vExportArgs.push_back("-walletsqliteexport=" + strSQLite);
1212 int nExportRet = RunBitflashChild(strExe, vExportArgs, NULL, &strExportOut);
1213 nFail += Check(nExportRet == 0 &&
1214 FileExists(strSQLite.c_str()) &&
1215 FileContainsText(strExportOut, "SQLite wallet export written"),
1216 "wallet.dat can be exported to SQLite") ? 0 : 1;
1217
1218 string strOverwriteOut = tmp + "/sqlite-export-overwrite.txt";
1219 int nOverwriteRet =
1220 RunBitflashChild(strExe, vExportArgs, NULL, &strOverwriteOut);
1221 nFail += Check(nOverwriteRet != 0 &&
1222 FileContainsText(strOverwriteOut,
1223 "Refusing to overwrite existing SQLite wallet export"),
1224 "SQLite wallet export refuses to overwrite") ? 0 : 1;
1225 remove((strSQLite + "-wal").c_str());
1226 remove((strSQLite + "-shm").c_str());
1227
1228 std::map<vector<unsigned char>, vector<unsigned char> > mapBDB;
1229 string strOldDataDir = strSetDataDir;
1230 string strScanError;
1231 strSetDataDir = strWalletDir;
1232 CWalletRecordMapVisitor visitor(mapBDB);
1233 bool fScanOk = ScanWalletRecords(visitor, strScanError);
1234 DBFlush(true);
1235 strSetDataDir = strOldDataDir;
1236 nFail += Check(fScanOk && !mapBDB.empty(),
1237 "wallet.dat records can be scanned for comparison") ? 0 : 1;
1238
1239 CWalletDBSQLite db;
1240 string strError;
1241 nFail += Check(db.Open(strSQLite, strError),
1242 "exported SQLite wallet opens") ? 0 : 1;
1243 int nSQLiteRecords = 0;
1244 nFail += Check(db.CountRecords(nSQLiteRecords, strError) &&
1245 nSQLiteRecords == (int)mapBDB.size(),
1246 "SQLite export has the same record count as wallet.dat") ? 0 : 1;
1247
1248 bool fAllMatch = true;
1249 for (std::map<vector<unsigned char>, vector<unsigned char> >::const_iterator it =
1250 mapBDB.begin(); it != mapBDB.end(); ++it)
1251 {
1252 vector<unsigned char> vchSQLiteValue;
1253 if (!db.ReadRecord(it->first, vchSQLiteValue, strError) ||
1254 vchSQLiteValue != it->second)
1255 {
1256 fAllMatch = false;
1257 break;
1258 }
1259 }
1260 nFail += Check(fAllMatch,
1261 "SQLite export preserves every wallet.dat key/value byte-for-byte") ? 0 : 1;
1262 }
1263 catch (const std::exception& e)
1264 {
1265 printf(" FAIL exception: %s\n", e.what());
1266 nFail++;
1267 }
1268 catch (...)
1269 {
1270 printf(" FAIL unknown exception\n");
1271 nFail++;
1272 }
1273
1274 RemoveTree(tmp);
1275 printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED",
1276 nFail, nFail == 1 ? "" : "s");
1277 fflush(stdout);
1278 return nFail == 0 ? 0 : 1;
1279}
1280
1137static void ClearWalletRuntimeForTest() 1281static void ClearWalletRuntimeForTest()
1138{ 1282{
1139 CRITICAL_BLOCK(cs_mapKeys) 1283 CRITICAL_BLOCK(cs_mapKeys)
@@ -2395,6 +2539,8 @@ int RunSelfTest(const std::string& name)
2395 return RunDbEnvReopenSelfTest(); 2539 return RunDbEnvReopenSelfTest();
2396 if (name == "wallet-sqlite") 2540 if (name == "wallet-sqlite")
2397 return RunWalletSQLiteSelfTest(); 2541 return RunWalletSQLiteSelfTest();
2542 if (name == "wallet-sqlite-migration")
2543 return RunWalletSQLiteMigrationSelfTest();
2398 if (name == "wallet-crypto") 2544 if (name == "wallet-crypto")
2399 return RunWalletCryptoSelfTest(); 2545 return RunWalletCryptoSelfTest();
2400 if (name == "wallet-encrypt") 2546 if (name == "wallet-encrypt")
@@ -2415,6 +2561,6 @@ int RunSelfTest(const std::string& name)
2415 return RunManagedTorSelfTest(); 2561 return RunManagedTorSelfTest();
2416 2562
2417 printf("Unknown self-test '%s'\n", name.c_str()); 2563 printf("Unknown self-test '%s'\n", name.c_str());
2418 printf("Known self-tests: wallet-keypool, wallet-hd, wallet-format, wallet-storage-sanity, db-env-reopen, wallet-sqlite, wallet-crypto, wallet-encrypt, wallet-portability, net-message, consensus-limits, pool-stratum, parse-money, socks5-proxy, managed-tor\n"); 2564 printf("Known self-tests: wallet-keypool, wallet-hd, wallet-format, wallet-storage-sanity, db-env-reopen, wallet-sqlite, wallet-sqlite-migration, wallet-crypto, wallet-encrypt, wallet-portability, net-message, consensus-limits, pool-stratum, parse-money, socks5-proxy, managed-tor\n");
2419 return 1; 2565 return 1;
2420} 2566}
diff --git a/src/wallet_sqlite.cpp b/src/wallet_sqlite.cpp
index 4104ac6..4369af6 100644
--- a/src/wallet_sqlite.cpp
+++ b/src/wallet_sqlite.cpp
@@ -97,6 +97,16 @@ bool CWalletDBSQLite::RollbackTransaction(string& strError)
97 return Exec("ROLLBACK;", strError); 97 return Exec("ROLLBACK;", strError);
98} 98}
99 99
100bool CWalletDBSQLite::Checkpoint(string& strError)
101{
102 if (!pdb)
103 {
104 strError = "SQLite wallet is not open";
105 return false;
106 }
107 return Exec("PRAGMA wal_checkpoint(TRUNCATE);", strError);
108}
109
100bool CWalletDBSQLite::Exec(const char* pszSql, string& strError) 110bool CWalletDBSQLite::Exec(const char* pszSql, string& strError)
101{ 111{
102 char* pszErr = NULL; 112 char* pszErr = NULL;
diff --git a/src/wallet_sqlite.h b/src/wallet_sqlite.h
index 9812d3a..5ef862d 100644
--- a/src/wallet_sqlite.h
+++ b/src/wallet_sqlite.h
@@ -18,6 +18,7 @@ public:
18 bool BeginTransaction(string& strError); 18 bool BeginTransaction(string& strError);
19 bool CommitTransaction(string& strError); 19 bool CommitTransaction(string& strError);
20 bool RollbackTransaction(string& strError); 20 bool RollbackTransaction(string& strError);
21 bool Checkpoint(string& strError);
21 22
22 bool WriteRecord(const vector<unsigned char>& vchKey, 23 bool WriteRecord(const vector<unsigned char>& vchKey,
23 const vector<unsigned char>& vchValue, 24 const vector<unsigned char>& vchValue,
diff --git a/src/walletcmd.cpp b/src/walletcmd.cpp
index 6fb09a3..20e7e33 100644
--- a/src/walletcmd.cpp
+++ b/src/walletcmd.cpp
@@ -12,6 +12,7 @@
12#include "headers_core.h" 12#include "headers_core.h"
13#include "bip32.h" 13#include "bip32.h"
14#include "walletcmd.h" 14#include "walletcmd.h"
15#include "wallet_sqlite.h"
15 16
16#undef printf 17#undef printf
17 18
@@ -790,6 +791,102 @@ int CmdWalletStorageAudit(const std::string& strJsonOut)
790 return 0; 791 return 0;
791} 792}
792 793
794static vector<unsigned char> DataStreamBytesLocal(const CDataStream& ss)
795{
796 return vector<unsigned char>(ss.begin(), ss.end());
797}
798
799class CWalletSQLiteExportVisitor : public CWalletRecordVisitor
800{
801public:
802 CWalletDBSQLite& db;
803 int& nCopied;
804
805 CWalletSQLiteExportVisitor(CWalletDBSQLite& dbIn, int& nCopiedIn)
806 : db(dbIn), nCopied(nCopiedIn) { }
807
808 bool VisitWalletRecord(const CDataStream& ssKey,
809 const CDataStream& ssValue,
810 string& strErrorRet)
811 {
812 if (!db.WriteRecord(DataStreamBytesLocal(ssKey),
813 DataStreamBytesLocal(ssValue),
814 strErrorRet))
815 return false;
816 nCopied++;
817 return true;
818 }
819};
820
821int CmdWalletSQLiteExport(const std::string& strDest)
822{
823 AttachTerminal();
824
825 if (strDest.empty())
826 {
827 fprintf(stderr, "Missing SQLite wallet export path.\n");
828 return 1;
829 }
830 if (FileExists(strDest.c_str()))
831 {
832 fprintf(stderr, "Refusing to overwrite existing SQLite wallet export: %s\n",
833 strDest.c_str());
834 return 1;
835 }
836
837 string strError;
838 CWalletDBSQLite db;
839 if (!db.Open(strDest, strError))
840 {
841 fprintf(stderr, "Cannot open SQLite wallet export: %s\n", strError.c_str());
842 return 1;
843 }
844 if (!db.BeginTransaction(strError))
845 {
846 fprintf(stderr, "Cannot begin SQLite wallet export: %s\n", strError.c_str());
847 return 1;
848 }
849
850 int nCopied = 0;
851 CWalletSQLiteExportVisitor visitor(db, nCopied);
852 if (!ScanWalletRecords(visitor, strError))
853 {
854 string strRollbackError;
855 db.RollbackTransaction(strRollbackError);
856 fprintf(stderr, "Cannot export wallet.dat to SQLite: %s\n", strError.c_str());
857 return 1;
858 }
859 if (!db.CommitTransaction(strError))
860 {
861 fprintf(stderr, "Cannot commit SQLite wallet export: %s\n", strError.c_str());
862 return 1;
863 }
864 if (!db.Checkpoint(strError))
865 {
866 fprintf(stderr, "Cannot checkpoint SQLite wallet export: %s\n", strError.c_str());
867 return 1;
868 }
869
870 int nCount = 0;
871 if (!db.CountRecords(nCount, strError))
872 {
873 fprintf(stderr, "Cannot verify SQLite wallet export count: %s\n",
874 strError.c_str());
875 return 1;
876 }
877 if (nCount != nCopied)
878 {
879 fprintf(stderr, "SQLite wallet export count mismatch: copied %d, stored %d\n",
880 nCopied, nCount);
881 return 1;
882 }
883
884 printf("SQLite wallet export written to %s\n", strDest.c_str());
885 printf(" records copied: %d\n", nCopied);
886 fflush(stdout);
887 return 0;
888}
889
793int CmdEncryptWallet(const std::string& strPassphrase) 890int CmdEncryptWallet(const std::string& strPassphrase)
794{ 891{
795 AttachTerminal(); 892 AttachTerminal();
diff --git a/src/walletcmd.h b/src/walletcmd.h
index 6077669..b1a29d2 100644
--- a/src/walletcmd.h
+++ b/src/walletcmd.h
@@ -70,6 +70,11 @@ int CmdWalletStorageAudit(const std::string& strJsonOut = "");
70// or migration-unsafe mix of storage records. 70// or migration-unsafe mix of storage records.
71int CmdWalletStorageCheck(); 71int CmdWalletStorageCheck();
72 72
73// Diagnostic/migration staging: copy every raw wallet.dat record into an
74// experimental SQLite key/value store, preserving serialized bytes. This does
75// not change which backend the runtime wallet opens.
76int CmdWalletSQLiteExport(const std::string& strDest);
77
73// Rewrite wallet.dat so private keys and the HD seed are encrypted with the 78// Rewrite wallet.dat so private keys and the HD seed are encrypted with the
74// given passphrase. Returns 0 on success and exits without starting the node. 79// given passphrase. Returns 0 on success and exits without starting the node.
75int CmdEncryptWallet(const std::string& strPassphrase); 80int CmdEncryptWallet(const std::string& strPassphrase);