upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/selftest.cpp
diff options
context:
space:
mode:
authorbitflash-maintainer <noreply@bitflash.invalid>2026-09-01 17:14:12 +0000
committerbitflash-maintainer <noreply@bitflash.invalid>2026-09-01 17:14:12 +0000
commitb4f0bc693ef7175702f82bcb2110b627922e8cbd (patch)
tree73435feb1a076dfba94d8143d7d7b7784908c82f /src/selftest.cpp
parenta1d867ecbcfc9e218408d0a11863a450243a01f9 (diff)
parent6aa3dade77632f13189d2a3ef29897f29a5f9572 (diff)
Merge upstream v1.2.20 into the fork
Merges upstream tag v1.2.20 (6aa3dade) into our 1.2.19+sec tree. The merge base is bc3de30's descendant 9e39a50 -- upstream's own v1.2.19 -- so this is an ordinary three-way merge, not a replay. Five conflict blocks in four files, all resolved below. No consensus change. CheckBlock, ConnectBlock, DisconnectBlock, ConnectInputs, AcceptBlock, AcceptTransaction, GetNextWorkRequired and GetBlockValue hash identical to v1.2.19; script.cpp and script.h are byte-identical on both sides. main.cpp is +3 lines and main.h +2, all wallet-backend plumbing. rpc.cpp is untouched, so the payout ledger arrives unchanged. Makefile, VERSION. OURS, renumbered. 1.2.20+sec, for the same reason 1.2.19 carried +sec: this tree is not the upstream release of that number and the string names every release artifact. Makefile, AppImage recipe. OURS for the AppDir path, THEIRS for the bundled library. Upstream's hunk reverts the icon paths to a hardcoded /tmp/bitflash.AppDir, which is the local-user pre-placement hazard the fork moved out of /tmp in the first place; taking their side wholesale would have reintroduced it silently, inside a conflict about something else. The AppDir stays $(APPDIR), and libsqlite3.so.0 joins the bundled list, which the new backend genuinely needs. src/Makefile, src/makefile.mingw, HEADERS. BOTH. Ours drops market.h, theirs adds wallet_sqlite.h; the resolution does both. NODE_OBJS auto-merged correctly in both files -- obj/wallet_sqlite.o present, obj/market.o still gone -- and was verified rather than assumed, because that line has trapped this fork before. src/selftest.cpp, known-tests list. BOTH, union. Upstream adds eight wallet-storage tests, we keep payout-ledger. The dispatch chain itself auto-merged and still routes payout-ledger to RunPayoutLedgerSelfTest. Hardening verified intact after merge: the TLS 1.2 floor in nostr.cpp, the /rvrelay pin, opt-in managed Tor, strict sig3-on-onion descriptor verification, and the marketplace removal. Upstream shipped no build logs or __pycache__ this time; the three added files are docs/storage.md and src/wallet_sqlite.{cpp,h}. The SQLite backend this release introduces is NOT enabled here. An existing wallet.dat resolves to Berkeley DB on every branch of the new selection logic, and the conversion wizard is GUI-only and not compiled into the headless node. The review findings that gate ever turning it on -- the non-atomic backend marker, the connection-wide WriteHDMaster transaction, and the encrypt-swap crash window -- are recorded on the tracker, not fixed here.
Diffstat (limited to 'src/selftest.cpp')
-rw-r--r--src/selftest.cpp1910
1 files changed, 1905 insertions, 5 deletions
diff --git a/src/selftest.cpp b/src/selftest.cpp
index 9f7106f..2ada8f3 100644
--- a/src/selftest.cpp
+++ b/src/selftest.cpp
@@ -10,9 +10,13 @@
10#include "bip32.h" 10#include "bip32.h"
11#include "proxy.h" 11#include "proxy.h"
12#include "selftest.h" 12#include "selftest.h"
13#ifndef _WIN32
14#include <sys/wait.h> // WIFEXITED/WEXITSTATUS for the child-process helper
15#endif
13#include "sockcount.h" 16#include "sockcount.h"
14#include "tor.h" 17#include "tor.h"
15#include "walletcmd.h" 18#include "walletcmd.h"
19#include "wallet_sqlite.h"
16 20
17extern int RunPoolStratumSelfTest(); 21extern int RunPoolStratumSelfTest();
18extern int RunPayoutLedgerSelfTest(); 22extern int RunPayoutLedgerSelfTest();
@@ -35,6 +39,7 @@ extern int RunPayoutLedgerSelfTest();
35#undef printf 39#undef printf
36 40
37#include <mutex> 41#include <mutex>
42#include <cerrno>
38#include <stdexcept> 43#include <stdexcept>
39#include <thread> 44#include <thread>
40 45
@@ -62,6 +67,11 @@ static std::string HexStrLocal(const std::vector<unsigned char>& v)
62 return out; 67 return out;
63} 68}
64 69
70static vector<unsigned char> DataStreamBytes(const CDataStream& ss)
71{
72 return vector<unsigned char>(ss.begin(), ss.end());
73}
74
65static bool MakeTempDir(std::string& out) 75static bool MakeTempDir(std::string& out)
66{ 76{
67#ifdef _WIN32 77#ifdef _WIN32
@@ -736,6 +746,1017 @@ static int RunWalletFormatSelfTest()
736 return nFail == 0 ? 0 : 1; 746 return nFail == 0 ? 0 : 1;
737} 747}
738 748
749static bool FileContainsText(const string& strPath, const string& strNeedle);
750static bool MakeDirLocal(const string& strPath);
751static int RunBitflashChild(const string& strExe,
752 const vector<string>& vArgs,
753 const string* pStdinFile,
754 const string* pOutputFile);
755
756class CWalletStorageSanityRawDB : public CWalletDB
757{
758public:
759 CWalletStorageSanityRawDB(const char* pszMode="r+")
760 : CWalletDB(pszMode) { }
761
762 bool WriteUnknownRecord()
763 {
764 return Write(string("storage-sanity-unknown"), 1);
765 }
766
767 bool WriteMalformedKeyRecord()
768 {
769 if (!pdb)
770 return false;
771 unsigned char chKey = 0xff;
772 unsigned char chValue = 0x01;
773 Dbt datKey(&chKey, 1);
774 Dbt datValue(&chValue, 1);
775 return pdb->put(GetTxn(), &datKey, &datValue, 0) == 0;
776 }
777
778 bool MakePlainHDSeedIncomplete()
779 {
780 return Erase(string("hdchaincode"));
781 }
782
783 bool AddEncryptedMarkerToPlainWallet()
784 {
785 CWalletMasterKey kMasterKey;
786 kMasterKey.vchCryptedKey.resize(48);
787 for (size_t i = 0; i < kMasterKey.vchCryptedKey.size(); i++)
788 kMasterKey.vchCryptedKey[i] = (unsigned char)(0x80 + (i & 0x3f));
789 for (size_t i = 0; i < kMasterKey.vchSalt.size(); i++)
790 kMasterKey.vchSalt[i] = (unsigned char)(0x40 + i);
791 return WriteWalletMinVersion(WALLET_FORMAT_ENCRYPTED) &&
792 WriteMasterKey(1, kMasterKey);
793 }
794};
795
796static const char* SELFTEST_MUTATE_WALLET_MARKER =
797 ".bitflash-selftest-wallet-mutate-ok";
798static const char* SELFTEST_MUTATE_WALLET_MARKER_TEXT =
799 "bitflash destructive wallet storage self-test\n";
800
801static string SelfTestMutationMarkerPath()
802{
803 return GetAppDir() + "/" + SELFTEST_MUTATE_WALLET_MARKER;
804}
805
806static bool WriteSelfTestMutationMarker(const string& strDir)
807{
808 string strPath = strDir + "/" + SELFTEST_MUTATE_WALLET_MARKER;
809 FILE* pf = fopen(strPath.c_str(), "wb");
810 if (!pf)
811 return false;
812 bool fOk = fwrite(SELFTEST_MUTATE_WALLET_MARKER_TEXT, 1,
813 strlen(SELFTEST_MUTATE_WALLET_MARKER_TEXT), pf) ==
814 strlen(SELFTEST_MUTATE_WALLET_MARKER_TEXT);
815 if (fclose(pf) != 0)
816 fOk = false;
817 return fOk;
818}
819
820int RunSelfTestMutateWallet(const std::string& name)
821{
822 if (!FileContainsText(SelfTestMutationMarkerPath(),
823 SELFTEST_MUTATE_WALLET_MARKER_TEXT))
824 {
825 fprintf(stderr, "Refusing destructive wallet storage mutation outside "
826 "a marked self-test datadir.\n");
827 return 1;
828 }
829
830 bool fOk = false;
831 try
832 {
833 CWalletStorageSanityRawDB walletdb;
834 if (name == "unknown-record")
835 fOk = walletdb.WriteUnknownRecord();
836 else if (name == "malformed-key")
837 fOk = walletdb.WriteMalformedKeyRecord();
838 else if (name == "incomplete-plain-hd")
839 fOk = walletdb.MakePlainHDSeedIncomplete();
840 else if (name == "encrypted-marker-with-plain-keys")
841 fOk = walletdb.AddEncryptedMarkerToPlainWallet();
842 else
843 {
844 fprintf(stderr, "Unknown wallet storage mutation '%s'\n", name.c_str());
845 return 1;
846 }
847 }
848 catch (const std::exception& e)
849 {
850 fprintf(stderr, "Cannot mutate wallet storage: %s\n", e.what());
851 return 1;
852 }
853 catch (...)
854 {
855 fprintf(stderr, "Cannot mutate wallet storage: unknown exception\n");
856 return 1;
857 }
858
859 DBFlush(true);
860 return fOk ? 0 : 1;
861}
862
863static int RunWalletStorageSanitySelfTest()
864{
865 fflush(stdout);
866 printf("wallet-storage-sanity self-test\n");
867
868 std::string tmp;
869 std::string cwd;
870 if (!MakeTempDir(tmp))
871 {
872 printf(" FAIL could not create a temporary data directory\n");
873 return 1;
874 }
875 if (!GetCurrentDir(cwd))
876 {
877 printf(" FAIL could not read current directory\n");
878 RemoveTree(tmp);
879 return 1;
880 }
881
882 int nFail = 0;
883 printf(" temp root: %s\n", tmp.c_str());
884
885 try
886 {
887#ifdef _WIN32
888 string strExe = cwd + "\\bitflash.exe";
889#else
890 string strExe = cwd + "/bitflash-node";
891#endif
892
893 struct StorageScenario
894 {
895 const char* pszName;
896 const char* pszExpectedFailure;
897 };
898
899 StorageScenario scenarios[] = {
900 { "unknown-record", "wallet.dat contains unknown records" },
901 { "malformed-key", "wallet.dat contains malformed records" },
902 { "incomplete-plain-hd", "plain HD seed is incomplete" },
903 { "encrypted-marker-with-plain-keys",
904 "encrypted wallet still contains plain private key records" },
905 };
906
907 for (size_t i = 0; i < sizeof(scenarios)/sizeof(scenarios[0]); i++)
908 {
909 string strScenarioDir = tmp + "/" + scenarios[i].pszName;
910 nFail += Check(MakeDirLocal(strScenarioDir),
911 "scenario datadir can be created") ? 0 : 1;
912
913 string strCreateOut = tmp + "/" + scenarios[i].pszName + "-create.txt";
914 vector<string> vCreateArgs;
915 vCreateArgs.push_back("-datadir=" + strScenarioDir);
916 vCreateArgs.push_back("-nomanagedtor");
917 vCreateArgs.push_back("-nogui");
918 vCreateArgs.push_back("-walletbackend=bdb"); // this suite is about wallet.dat
919 vCreateArgs.push_back("-newphrase");
920 int nCreateRet = RunBitflashChild(strExe, vCreateArgs, NULL, &strCreateOut);
921 nFail += Check(nCreateRet == 0 &&
922 FileContainsText(strCreateOut, "Write these twelve words down"),
923 "scenario phrase wallet can be created") ? 0 : 1;
924
925 if (i == 0)
926 {
927 string strBlockedOut = tmp + "/" + scenarios[i].pszName + "-blocked-mutate.txt";
928 vector<string> vBlockedArgs;
929 vBlockedArgs.push_back("-datadir=" + strScenarioDir);
930 vBlockedArgs.push_back("-nomanagedtor");
931 vBlockedArgs.push_back("-nogui");
932 vBlockedArgs.push_back("-selftestmutatewallet=" + string(scenarios[i].pszName));
933 int nBlockedRet = RunBitflashChild(strExe, vBlockedArgs, NULL, &strBlockedOut);
934 nFail += Check(nBlockedRet != 0 &&
935 FileContainsText(strBlockedOut, "Refusing destructive wallet storage mutation"),
936 "wallet mutation helper is blocked without marker") ? 0 : 1;
937 }
938
939 nFail += Check(WriteSelfTestMutationMarker(strScenarioDir),
940 "scenario mutation marker can be written") ? 0 : 1;
941
942 string strMutateOut = tmp + "/" + scenarios[i].pszName + "-mutate.txt";
943 vector<string> vMutateArgs;
944 vMutateArgs.push_back("-datadir=" + strScenarioDir);
945 vMutateArgs.push_back("-nomanagedtor");
946 vMutateArgs.push_back("-nogui");
947 vMutateArgs.push_back("-selftestmutatewallet=" + string(scenarios[i].pszName));
948 int nMutateRet = RunBitflashChild(strExe, vMutateArgs, NULL, &strMutateOut);
949 nFail += Check(nMutateRet == 0, scenarios[i].pszName) ? 0 : 1;
950
951 string strCheckOut = tmp + "/" + scenarios[i].pszName + "-check.txt";
952 vector<string> vCheckArgs;
953 vCheckArgs.push_back("-datadir=" + strScenarioDir);
954 vCheckArgs.push_back("-nomanagedtor");
955 vCheckArgs.push_back("-nogui");
956 vCheckArgs.push_back("-walletstoragecheck");
957 int nCheckRet = RunBitflashChild(strExe, vCheckArgs, NULL, &strCheckOut);
958 nFail += Check(nCheckRet == 2 &&
959 FileContainsText(strCheckOut, "storage sanity: failed") &&
960 FileContainsText(strCheckOut, scenarios[i].pszExpectedFailure),
961 scenarios[i].pszExpectedFailure) ? 0 : 1;
962 }
963 }
964 catch (const std::exception& e)
965 {
966 printf(" FAIL exception: %s\n", e.what());
967 nFail++;
968 }
969 catch (...)
970 {
971 printf(" FAIL unknown exception\n");
972 nFail++;
973 }
974
975 RemoveTree(tmp);
976 printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED",
977 nFail, nFail == 1 ? "" : "s");
978 fflush(stdout);
979 return nFail == 0 ? 0 : 1;
980}
981
982static int RunDbEnvReopenSelfTest()
983{
984 fflush(stdout);
985 printf("db-env-reopen self-test\n");
986
987 std::string tmp;
988 if (!MakeTempDir(tmp))
989 {
990 printf(" FAIL could not create a temporary data directory\n");
991 return 1;
992 }
993
994 int nFail = 0;
995 std::string strOldDataDir = strSetDataDir;
996 strSetDataDir = tmp;
997 printf(" temp datadir: %s\n", tmp.c_str());
998
999 try
1000 {
1001 for (int i = 0; i < 5; i++)
1002 {
1003 int64 nValue = 1000 + i;
1004 {
1005 CWalletDB walletdb("cr+");
1006 nFail += Check(walletdb.WriteSetting("db-env-reopen-cycle", nValue),
1007 "wallet setting can be written before shutdown") ? 0 : 1;
1008 }
1009
1010 DBFlush(true);
1011
1012 int64 nRead = 0;
1013 {
1014 CWalletDB walletdb("r");
1015 nFail += Check(walletdb.ReadSetting("db-env-reopen-cycle", nRead),
1016 "wallet setting can be read after env reopen") ? 0 : 1;
1017 }
1018 nFail += Check(nRead == nValue,
1019 "reopened environment reads the last committed value") ? 0 : 1;
1020
1021 DBFlush(true);
1022 }
1023 }
1024 catch (const std::exception& e)
1025 {
1026 printf(" FAIL exception: %s\n", e.what());
1027 nFail++;
1028 }
1029 catch (...)
1030 {
1031 printf(" FAIL unknown exception\n");
1032 nFail++;
1033 }
1034
1035 DBFlush(true);
1036 strSetDataDir = strOldDataDir;
1037 RemoveTree(tmp);
1038 printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED",
1039 nFail, nFail == 1 ? "" : "s");
1040 fflush(stdout);
1041 return nFail == 0 ? 0 : 1;
1042}
1043
1044static int RunWalletSQLiteSelfTest()
1045{
1046 fflush(stdout);
1047 printf("wallet-sqlite self-test\n");
1048
1049 std::string tmp;
1050 if (!MakeTempDir(tmp))
1051 {
1052 printf(" FAIL could not create a temporary data directory\n");
1053 return 1;
1054 }
1055
1056 int nFail = 0;
1057 string strPath = tmp + "/wallet.sqlite";
1058 string strError;
1059 string strOldRuntimeDataDir;
1060 bool fRestoreRuntimeDataDir = false;
1061
1062 try
1063 {
1064 CDataStream ssKeySetting(SER_DISK);
1065 ssKeySetting << make_pair(string("setting"), string("sqlite-roundtrip"));
1066 CDataStream ssValueSetting(SER_DISK);
1067 ssValueSetting << (int64)424242;
1068
1069 vector<unsigned char> vchPubKey;
1070 vchPubKey.push_back(0x04);
1071 for (int i = 0; i < 64; i++)
1072 vchPubKey.push_back((unsigned char)i);
1073 CDataStream ssKeyDefault(SER_DISK);
1074 ssKeyDefault << string("defaultkey");
1075 CDataStream ssValueDefault(SER_DISK);
1076 ssValueDefault << vchPubKey;
1077
1078 {
1079 CWalletDBSQLite db;
1080 nFail += Check(db.Open(strPath, strError),
1081 "SQLite wallet opens and creates schema") ? 0 : 1;
1082 nFail += Check(db.BeginTransaction(strError),
1083 "SQLite wallet begins a write transaction") ? 0 : 1;
1084 nFail += Check(db.WriteRecord(DataStreamBytes(ssKeySetting),
1085 DataStreamBytes(ssValueSetting),
1086 strError),
1087 "SQLite wallet writes a serialized setting record") ? 0 : 1;
1088 nFail += Check(db.WriteRecord(DataStreamBytes(ssKeyDefault),
1089 DataStreamBytes(ssValueDefault),
1090 strError),
1091 "SQLite wallet writes a serialized defaultkey record") ? 0 : 1;
1092 nFail += Check(db.CommitTransaction(strError),
1093 "SQLite wallet commits a write transaction") ? 0 : 1;
1094 int nCount = 0;
1095 nFail += Check(db.CountRecords(nCount, strError) && nCount == 2,
1096 "SQLite wallet counts raw records") ? 0 : 1;
1097 }
1098
1099 {
1100 CWalletDBSQLite db;
1101 nFail += Check(db.Open(strPath, strError),
1102 "SQLite wallet reopens") ? 0 : 1;
1103 vector<unsigned char> vchRead;
1104 nFail += Check(db.ReadRecord(DataStreamBytes(ssKeySetting),
1105 vchRead,
1106 strError),
1107 "SQLite wallet reads a serialized setting record") ? 0 : 1;
1108 int64 nReadValue = 0;
1109 CDataStream ssRead(vchRead, SER_DISK);
1110 ssRead >> nReadValue;
1111 nFail += Check(!ssRead.fail() && nReadValue == 424242,
1112 "SQLite wallet preserves serialized values byte-for-byte") ? 0 : 1;
1113
1114 vchRead.clear();
1115 nFail += Check(db.ReadRecord(DataStreamBytes(ssKeyDefault),
1116 vchRead,
1117 strError),
1118 "SQLite wallet reads a serialized defaultkey record") ? 0 : 1;
1119 vector<unsigned char> vchReadPubKey;
1120 CDataStream ssReadDefault(vchRead, SER_DISK);
1121 ssReadDefault >> vchReadPubKey;
1122 nFail += Check(!ssReadDefault.fail() && vchReadPubKey == vchPubKey,
1123 "SQLite wallet preserves public key blobs") ? 0 : 1;
1124 }
1125
1126 {
1127 CWalletDBSQLite db;
1128 nFail += Check(db.Open(strPath, strError),
1129 "SQLite wallet reopens for typed records") ? 0 : 1;
1130
1131 nFail += Check(db.WriteTypedRecord(make_pair(string("setting"),
1132 string("sqlite-typed")),
1133 (int64)777, strError),
1134 "SQLite wallet writes a typed setting record") ? 0 : 1;
1135
1136 int64 nTypedRead = 0;
1137 nFail += Check(db.ReadTypedRecord(make_pair(string("setting"),
1138 string("sqlite-typed")),
1139 nTypedRead, strError) &&
1140 nTypedRead == 777,
1141 "SQLite wallet reads a typed setting record") ? 0 : 1;
1142
1143 nFail += Check(!db.WriteTypedRecord(make_pair(string("setting"),
1144 string("sqlite-typed")),
1145 (int64)888, strError, false),
1146 "SQLite wallet refuses typed overwrite when asked") ? 0 : 1;
1147
1148 nTypedRead = 0;
1149 nFail += Check(db.ReadTypedRecord(make_pair(string("setting"),
1150 string("sqlite-typed")),
1151 nTypedRead, strError) &&
1152 nTypedRead == 777,
1153 "SQLite wallet preserves the old typed value after overwrite refusal") ? 0 : 1;
1154
1155 nFail += Check(db.EraseTypedRecord(make_pair(string("setting"),
1156 string("sqlite-typed")),
1157 strError),
1158 "SQLite wallet erases a typed record") ? 0 : 1;
1159
1160 nTypedRead = 0;
1161 nFail += Check(!db.ReadTypedRecord(make_pair(string("setting"),
1162 string("sqlite-typed")),
1163 nTypedRead, strError),
1164 "SQLite wallet typed erase removes the record") ? 0 : 1;
1165
1166 nFail += Check(db.WriteTypedRecord(string("defaultkey"), vchPubKey,
1167 strError),
1168 "SQLite wallet writes a typed defaultkey record") ? 0 : 1;
1169
1170 vector<unsigned char> vchTypedPubKey;
1171 nFail += Check(db.ReadTypedRecord(string("defaultkey"), vchTypedPubKey,
1172 strError) &&
1173 vchTypedPubKey == vchPubKey,
1174 "SQLite wallet reads a typed defaultkey record") ? 0 : 1;
1175 }
1176
1177 {
1178 string strRuntimePath = tmp + "/runtime-wallet.sqlite";
1179 string strRuntimeDir = tmp + "/runtime-datadir";
1180 nFail += Check(MakeDirLocal(strRuntimeDir),
1181 "SQLite runtime datadir can be created") ? 0 : 1;
1182
1183 {
1184 CWalletDBSQLite db;
1185 nFail += Check(db.Open(strRuntimePath, strError),
1186 "SQLite runtime wallet file can be created") ? 0 : 1;
1187 }
1188
1189 string strOldDataDir = strSetDataDir;
1190 strSetDataDir = strRuntimeDir;
1191 strOldRuntimeDataDir = strOldDataDir;
1192 fRestoreRuntimeDataDir = true;
1193 bool fRuntimeOpened = WalletSQLiteRuntimeOpen(strRuntimePath, strError);
1194 nFail += Check(fRuntimeOpened,
1195 "SQLite runtime wallet can be activated") ? 0 : 1;
1196
1197 if (fRuntimeOpened)
1198 {
1199 CKey key;
1200 key.MakeNewKey();
1201 vector<unsigned char> vchRuntimePubKey = key.GetPubKey();
1202 CPrivKey vchRuntimePrivKey = key.GetPrivKey();
1203 string strRuntimeAddress = PubKeyToAddress(vchRuntimePubKey);
1204
1205 nFail += Check(CWalletDB().WriteSetting("runtime-sqlite-setting",
1206 (int64)909090) &&
1207 CWalletDB().WriteDefaultKey(vchRuntimePubKey) &&
1208 CWalletDB().WriteKey(vchRuntimePubKey,
1209 vchRuntimePrivKey) &&
1210 CWalletDB().WriteName(strRuntimeAddress,
1211 "SQLite Runtime Address"),
1212 "CWalletDB writes route to active SQLite runtime") ? 0 : 1;
1213
1214 int64 nRuntimeSetting = 0;
1215 vector<unsigned char> vchRuntimeDefaultKey;
1216 CPrivKey vchRuntimeReadPrivKey;
1217 string strRuntimeName;
1218 nFail += Check(CWalletDB("r").ReadSetting("runtime-sqlite-setting",
1219 nRuntimeSetting) &&
1220 nRuntimeSetting == 909090 &&
1221 CWalletDB("r").ReadDefaultKey(vchRuntimeDefaultKey) &&
1222 vchRuntimeDefaultKey == vchRuntimePubKey &&
1223 CWalletDB("r").ReadKey(vchRuntimePubKey,
1224 vchRuntimeReadPrivKey) &&
1225 vchRuntimeReadPrivKey == vchRuntimePrivKey &&
1226 CWalletDB("r").ReadName(strRuntimeAddress,
1227 strRuntimeName) &&
1228 strRuntimeName == "SQLite Runtime Address",
1229 "CWalletDB reads route to active SQLite runtime") ? 0 : 1;
1230
1231 nFail += Check(CWalletDB().EraseName(strRuntimeAddress) &&
1232 !CWalletDB("r").ReadName(strRuntimeAddress,
1233 strRuntimeName),
1234 "CWalletDB erases route to active SQLite runtime") ? 0 : 1;
1235
1236 nFail += Check(!FileExists((strRuntimeDir + "/wallet.dat").c_str()),
1237 "active SQLite runtime does not create wallet.dat") ? 0 : 1;
1238
1239 // Transaction bracket: a rolled-back group leaves nothing, a
1240 // committed group persists. This is the all-or-nothing that
1241 // WriteHDMaster relies on so a half-written seed cannot survive
1242 // a failure between its two records.
1243 bool fTxnBegan = WalletSQLiteRuntimeBeginTxn();
1244 bool fTxnWrote = fTxnBegan &&
1245 CWalletDB().WriteSetting("runtime-txn-rollback", (int64)111);
1246 if (fTxnBegan)
1247 WalletSQLiteRuntimeRollbackTxn();
1248 nFail += Check(fTxnBegan && fTxnWrote,
1249 "a SQLite runtime transaction opens and rolls back") ? 0 : 1;
1250 int64 nRolledBack = 0;
1251 nFail += Check(!CWalletDB("r").ReadSetting("runtime-txn-rollback",
1252 nRolledBack),
1253 "a rolled-back SQLite runtime write leaves no record") ? 0 : 1;
1254
1255 bool fCommitOk = WalletSQLiteRuntimeBeginTxn() &&
1256 CWalletDB().WriteSetting("runtime-txn-commit", (int64)222) &&
1257 WalletSQLiteRuntimeCommitTxn();
1258 int64 nCommitted = 0;
1259 nFail += Check(fCommitOk &&
1260 CWalletDB("r").ReadSetting("runtime-txn-commit",
1261 nCommitted) &&
1262 nCommitted == 222,
1263 "a committed SQLite runtime write persists") ? 0 : 1;
1264
1265 // WriteHDMaster brackets its two records in that transaction;
1266 // both halves of the seed must land.
1267 vector<unsigned char> vchTxnMaster(32, 0x5a);
1268 vector<unsigned char> vchTxnChainCode(32, 0xa5);
1269 vector<unsigned char> vchReadMaster, vchReadChainCode;
1270 nFail += Check(CWalletDB().WriteHDMaster(vchTxnMaster, vchTxnChainCode) &&
1271 CWalletDB("r").ReadHDMaster(vchReadMaster, vchReadChainCode) &&
1272 vchReadMaster == vchTxnMaster &&
1273 vchReadChainCode == vchTxnChainCode,
1274 "WriteHDMaster commits both halves of the seed under SQLite") ? 0 : 1;
1275
1276 // Durability: close the backend (checkpoints the WAL), reopen
1277 // the same file, and confirm what CWalletDB wrote is still
1278 // there. This is the write-survives-a-restart property the real
1279 // -walletbackend=sqlite node depends on.
1280 WalletSQLiteRuntimeClose();
1281 bool fReopened =
1282 WalletSQLiteRuntimeOpen(strRuntimePath, strError);
1283 nFail += Check(fReopened,
1284 "SQLite runtime wallet reopens after close") ? 0 : 1;
1285 if (fReopened)
1286 {
1287 int64 nPersisted = 0;
1288 CPrivKey vchPersistedPrivKey;
1289 vector<unsigned char> vchPersistedDefaultKey;
1290 nFail += Check(CWalletDB("r").ReadSetting("runtime-sqlite-setting",
1291 nPersisted) &&
1292 nPersisted == 909090 &&
1293 CWalletDB("r").ReadDefaultKey(vchPersistedDefaultKey) &&
1294 vchPersistedDefaultKey == vchRuntimePubKey &&
1295 CWalletDB("r").ReadKey(vchRuntimePubKey,
1296 vchPersistedPrivKey) &&
1297 vchPersistedPrivKey == vchRuntimePrivKey,
1298 "SQLite runtime writes persist across a close and reopen") ? 0 : 1;
1299 }
1300 }
1301
1302 WalletSQLiteRuntimeClose();
1303 strSetDataDir = strOldDataDir;
1304 fRestoreRuntimeDataDir = false;
1305 }
1306 }
1307 catch (const std::exception& e)
1308 {
1309 WalletSQLiteRuntimeClose();
1310 if (fRestoreRuntimeDataDir)
1311 strSetDataDir = strOldRuntimeDataDir;
1312 printf(" FAIL exception: %s\n", e.what());
1313 nFail++;
1314 }
1315 catch (...)
1316 {
1317 WalletSQLiteRuntimeClose();
1318 if (fRestoreRuntimeDataDir)
1319 strSetDataDir = strOldRuntimeDataDir;
1320 printf(" FAIL unknown exception\n");
1321 nFail++;
1322 }
1323
1324 RemoveTree(tmp);
1325 printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED",
1326 nFail, nFail == 1 ? "" : "s");
1327 fflush(stdout);
1328 return nFail == 0 ? 0 : 1;
1329}
1330
1331namespace {
1332
1333class CWalletRecordMapVisitor : public CWalletRecordVisitor
1334{
1335public:
1336 std::map<vector<unsigned char>, vector<unsigned char> >& mapRecords;
1337
1338 explicit CWalletRecordMapVisitor(
1339 std::map<vector<unsigned char>, vector<unsigned char> >& mapRecordsIn)
1340 : mapRecords(mapRecordsIn) { }
1341
1342 bool VisitWalletRecord(const CDataStream& ssKey,
1343 const CDataStream& ssValue,
1344 string& strErrorRet)
1345 {
1346 vector<unsigned char> vchKey = DataStreamBytes(ssKey);
1347 if (mapRecords.count(vchKey))
1348 {
1349 strErrorRet = "duplicate serialized wallet key";
1350 return false;
1351 }
1352 mapRecords[vchKey] = DataStreamBytes(ssValue);
1353 return true;
1354 }
1355};
1356
1357} // namespace
1358
1359static bool CreateSQLiteWalletWithEmptyKey(const string& strPath)
1360{
1361 sqlite3* pdb = NULL;
1362 int ret = sqlite3_open_v2(strPath.c_str(), &pdb,
1363 SQLITE_OPEN_READWRITE |
1364 SQLITE_OPEN_CREATE |
1365 SQLITE_OPEN_FULLMUTEX,
1366 NULL);
1367 if (ret != SQLITE_OK || !pdb)
1368 {
1369 if (pdb)
1370 sqlite3_close(pdb);
1371 return false;
1372 }
1373
1374 char* pszErr = NULL;
1375 bool fOk =
1376 sqlite3_exec(pdb,
1377 "CREATE TABLE wallet_records ("
1378 "key BLOB PRIMARY KEY NOT NULL,"
1379 "value BLOB NOT NULL"
1380 ");",
1381 NULL, NULL, &pszErr) == SQLITE_OK &&
1382 sqlite3_exec(pdb,
1383 "INSERT INTO wallet_records(key, value) "
1384 "VALUES(x'', x'01');",
1385 NULL, NULL, &pszErr) == SQLITE_OK;
1386 if (pszErr)
1387 sqlite3_free(pszErr);
1388 sqlite3_close(pdb);
1389 return fOk;
1390}
1391
1392static bool CreateSQLiteWalletWithFutureMinVersion(const string& strPath)
1393{
1394 string strError;
1395 CWalletDBSQLite db;
1396 CDataStream ssKey(SER_DISK);
1397 ssKey << string("walletminversion");
1398 CDataStream ssValue(SER_DISK);
1399 ssValue << (WALLET_FORMAT_SUPPORTED + 1);
1400
1401 if (!db.Open(strPath, strError))
1402 return false;
1403 if (!db.BeginTransaction(strError))
1404 return false;
1405 if (!db.WriteRecord(DataStreamBytes(ssKey), DataStreamBytes(ssValue), strError))
1406 return false;
1407 if (!db.CommitTransaction(strError))
1408 return false;
1409 if (!db.Checkpoint(strError))
1410 return false;
1411 return true;
1412}
1413
1414static bool CreateTypedSQLiteWallet(const string& strPath)
1415{
1416 string strError;
1417 CWalletDBSQLite db;
1418 CKey key;
1419 key.MakeNewKey();
1420
1421 vector<unsigned char> vchPubKey = key.GetPubKey();
1422 CPrivKey vchPrivKey = key.GetPrivKey();
1423 string strAddress = PubKeyToAddress(vchPubKey);
1424
1425 if (!db.Open(strPath, strError))
1426 return false;
1427 if (!db.BeginTransaction(strError))
1428 return false;
1429 if (!db.WriteTypedRecord(make_pair(string("key"), vchPubKey),
1430 vchPrivKey, strError, false))
1431 return false;
1432 if (!db.WriteTypedRecord(string("defaultkey"), vchPubKey, strError))
1433 return false;
1434 if (!db.WriteTypedRecord(make_pair(string("name"), strAddress),
1435 string("Typed SQLite Address"), strError))
1436 return false;
1437 if (!db.CommitTransaction(strError))
1438 return false;
1439 if (!db.Checkpoint(strError))
1440 return false;
1441 return true;
1442}
1443
1444static int RunWalletSQLiteMigrationSelfTest()
1445{
1446 fflush(stdout);
1447 printf("wallet-sqlite-migration self-test\n");
1448
1449 std::string tmp;
1450 std::string cwd;
1451 if (!MakeTempDir(tmp))
1452 {
1453 printf(" FAIL could not create a temporary data directory\n");
1454 return 1;
1455 }
1456 if (!GetCurrentDir(cwd))
1457 {
1458 printf(" FAIL could not read current directory\n");
1459 RemoveTree(tmp);
1460 return 1;
1461 }
1462
1463 int nFail = 0;
1464 printf(" temp root: %s\n", tmp.c_str());
1465
1466 try
1467 {
1468#ifdef _WIN32
1469 string strExe = cwd + "\\bitflash.exe";
1470#else
1471 string strExe = cwd + "/bitflash-node";
1472#endif
1473 string strWalletDir = tmp + "/wallet";
1474 nFail += Check(MakeDirLocal(strWalletDir),
1475 "temporary wallet directory can be created") ? 0 : 1;
1476
1477 string strCreateOut = tmp + "/create.txt";
1478 vector<string> vCreateArgs;
1479 vCreateArgs.push_back("-datadir=" + strWalletDir);
1480 vCreateArgs.push_back("-nomanagedtor");
1481 vCreateArgs.push_back("-nogui");
1482 vCreateArgs.push_back("-walletbackend=bdb"); // migrate FROM a Berkeley DB wallet.dat
1483 vCreateArgs.push_back("-newphrase");
1484 int nCreateRet = RunBitflashChild(strExe, vCreateArgs, NULL, &strCreateOut);
1485 nFail += Check(nCreateRet == 0 &&
1486 FileContainsText(strCreateOut, "Write these twelve words down"),
1487 "a phrase wallet can be created for SQLite export") ? 0 : 1;
1488
1489 string strSQLite = tmp + "/wallet.sqlite";
1490 string strExportOut = tmp + "/sqlite-export.txt";
1491 vector<string> vExportArgs;
1492 vExportArgs.push_back("-datadir=" + strWalletDir);
1493 vExportArgs.push_back("-nomanagedtor");
1494 vExportArgs.push_back("-nogui");
1495 vExportArgs.push_back("-walletsqliteexport=" + strSQLite);
1496 int nExportRet = RunBitflashChild(strExe, vExportArgs, NULL, &strExportOut);
1497 nFail += Check(nExportRet == 0 &&
1498 FileExists(strSQLite.c_str()) &&
1499 FileContainsText(strExportOut, "SQLite wallet export written"),
1500 "wallet.dat can be exported to SQLite") ? 0 : 1;
1501
1502 string strVerifyOut = tmp + "/sqlite-verify.txt";
1503 vector<string> vVerifyArgs;
1504 vVerifyArgs.push_back("-datadir=" + strWalletDir);
1505 vVerifyArgs.push_back("-nomanagedtor");
1506 vVerifyArgs.push_back("-nogui");
1507 vVerifyArgs.push_back("-walletsqliteverify=" + strSQLite);
1508 int nVerifyRet = RunBitflashChild(strExe, vVerifyArgs, NULL, &strVerifyOut);
1509 nFail += Check(nVerifyRet == 0 &&
1510 FileContainsText(strVerifyOut, "verification: ok"),
1511 "SQLite wallet export verifies against wallet.dat") ? 0 : 1;
1512
1513 string strLoadCheckOut = tmp + "/sqlite-loadcheck.txt";
1514 vector<string> vLoadCheckArgs;
1515 vLoadCheckArgs.push_back("-datadir=" + strWalletDir);
1516 vLoadCheckArgs.push_back("-nomanagedtor");
1517 vLoadCheckArgs.push_back("-nogui");
1518 vLoadCheckArgs.push_back("-walletsqliteloadcheck=" + strSQLite);
1519 int nLoadCheckRet =
1520 RunBitflashChild(strExe, vLoadCheckArgs, NULL, &strLoadCheckOut);
1521 nFail += Check(nLoadCheckRet == 0 &&
1522 FileContainsText(strLoadCheckOut,
1523 "load check: ok"),
1524 "SQLite wallet export passes the loader compatibility check") ? 0 : 1;
1525
1526 string strRuntimeOut = tmp + "/sqlite-runtime.txt";
1527 vector<string> vRuntimeArgs;
1528 vRuntimeArgs.push_back("-datadir=" + strWalletDir);
1529 vRuntimeArgs.push_back("-nomanagedtor");
1530 vRuntimeArgs.push_back("-nogui");
1531 vRuntimeArgs.push_back("-walletsqlite=" + strSQLite);
1532 int nRuntimeRet =
1533 RunBitflashChild(strExe, vRuntimeArgs, NULL, &strRuntimeOut);
1534 nFail += Check(nRuntimeRet == 0 &&
1535 FileContainsText(strRuntimeOut,
1536 "SQLite wallet loaded: read-only staging backend"),
1537 "SQLite wallet export loads through the runtime startup path") ? 0 : 1;
1538
1539 string strTypedSQLite = tmp + "/typed-wallet.sqlite";
1540 nFail += Check(CreateTypedSQLiteWallet(strTypedSQLite),
1541 "typed SQLite wallet can be created without Berkeley DB") ? 0 : 1;
1542
1543 string strTypedLoadCheckOut = tmp + "/sqlite-typed-loadcheck.txt";
1544 vector<string> vTypedLoadCheckArgs;
1545 vTypedLoadCheckArgs.push_back("-datadir=" + strWalletDir);
1546 vTypedLoadCheckArgs.push_back("-nomanagedtor");
1547 vTypedLoadCheckArgs.push_back("-nogui");
1548 vTypedLoadCheckArgs.push_back("-walletsqliteloadcheck=" + strTypedSQLite);
1549 int nTypedLoadCheckRet =
1550 RunBitflashChild(strExe, vTypedLoadCheckArgs, NULL,
1551 &strTypedLoadCheckOut);
1552 nFail += Check(nTypedLoadCheckRet == 0 &&
1553 FileContainsText(strTypedLoadCheckOut,
1554 "load check: ok"),
1555 "typed SQLite wallet passes the loader compatibility check") ? 0 : 1;
1556
1557 string strTypedRuntimeDir = tmp + "/typed-runtime-wallet";
1558 nFail += Check(MakeDirLocal(strTypedRuntimeDir),
1559 "typed SQLite runtime directory can be created") ? 0 : 1;
1560 string strTypedRuntimeOut = tmp + "/sqlite-typed-runtime.txt";
1561 vector<string> vTypedRuntimeArgs;
1562 vTypedRuntimeArgs.push_back("-datadir=" + strTypedRuntimeDir);
1563 vTypedRuntimeArgs.push_back("-nomanagedtor");
1564 vTypedRuntimeArgs.push_back("-nogui");
1565 vTypedRuntimeArgs.push_back("-walletsqlite=" + strTypedSQLite);
1566 int nTypedRuntimeRet =
1567 RunBitflashChild(strExe, vTypedRuntimeArgs, NULL,
1568 &strTypedRuntimeOut);
1569 nFail += Check(nTypedRuntimeRet == 0 &&
1570 FileContainsText(strTypedRuntimeOut,
1571 "SQLite wallet loaded: read-only staging backend") &&
1572 !FileExists((strTypedRuntimeDir + "/wallet.dat").c_str()),
1573 "typed SQLite wallet loads through runtime without creating wallet.dat") ? 0 : 1;
1574
1575 string strRestoredDir = tmp + "/restored-wallet";
1576 nFail += Check(MakeDirLocal(strRestoredDir),
1577 "restored wallet directory can be created") ? 0 : 1;
1578 string strRestoreOut = tmp + "/sqlite-restore.txt";
1579 vector<string> vRestoreArgs;
1580 vRestoreArgs.push_back("-datadir=" + strRestoredDir);
1581 vRestoreArgs.push_back("-nomanagedtor");
1582 vRestoreArgs.push_back("-nogui");
1583 vRestoreArgs.push_back("-walletsqliterestore=" + strSQLite);
1584 int nRestoreRet = RunBitflashChild(strExe, vRestoreArgs, NULL, &strRestoreOut);
1585 nFail += Check(nRestoreRet == 0 &&
1586 FileContainsText(strRestoreOut, "records restored:"),
1587 "SQLite wallet export restores into a fresh wallet.dat") ? 0 : 1;
1588
1589 string strVerifyRestoredOut = tmp + "/sqlite-verify-restored.txt";
1590 vector<string> vVerifyRestoredArgs;
1591 vVerifyRestoredArgs.push_back("-datadir=" + strRestoredDir);
1592 vVerifyRestoredArgs.push_back("-nomanagedtor");
1593 vVerifyRestoredArgs.push_back("-nogui");
1594 vVerifyRestoredArgs.push_back("-walletsqliteverify=" + strSQLite);
1595 int nVerifyRestoredRet =
1596 RunBitflashChild(strExe, vVerifyRestoredArgs, NULL, &strVerifyRestoredOut);
1597 nFail += Check(nVerifyRestoredRet == 0 &&
1598 FileContainsText(strVerifyRestoredOut,
1599 "verification: ok"),
1600 "restored wallet.dat verifies against the SQLite export") ? 0 : 1;
1601
1602 string strRestoreOverwriteOut = tmp + "/sqlite-restore-overwrite.txt";
1603 int nRestoreOverwriteRet =
1604 RunBitflashChild(strExe, vRestoreArgs, NULL, &strRestoreOverwriteOut);
1605 nFail += Check(nRestoreOverwriteRet == 1 &&
1606 FileContainsText(strRestoreOverwriteOut,
1607 "Refusing to overwrite existing wallet.dat"),
1608 "SQLite wallet restore refuses to overwrite wallet.dat") ? 0 : 1;
1609
1610 string strBadSQLite = tmp + "/bad-empty-key.sqlite";
1611 nFail += Check(CreateSQLiteWalletWithEmptyKey(strBadSQLite),
1612 "bad SQLite export with an empty key can be created") ? 0 : 1;
1613 string strBadRestoreDir = tmp + "/bad-restore-wallet";
1614 nFail += Check(MakeDirLocal(strBadRestoreDir),
1615 "bad restore wallet directory can be created") ? 0 : 1;
1616 string strBadRestoreOut = tmp + "/sqlite-restore-bad.txt";
1617 vector<string> vBadRestoreArgs;
1618 vBadRestoreArgs.push_back("-datadir=" + strBadRestoreDir);
1619 vBadRestoreArgs.push_back("-nomanagedtor");
1620 vBadRestoreArgs.push_back("-nogui");
1621 vBadRestoreArgs.push_back("-walletsqliterestore=" + strBadSQLite);
1622 int nBadRestoreRet =
1623 RunBitflashChild(strExe, vBadRestoreArgs, NULL, &strBadRestoreOut);
1624 nFail += Check(nBadRestoreRet == 1 &&
1625 FileContainsText(strBadRestoreOut,
1626 "Cannot restore SQLite wallet export") &&
1627 !FileExists((strBadRestoreDir + "/wallet.dat").c_str()),
1628 "failed SQLite wallet restore removes incomplete wallet.dat") ? 0 : 1;
1629
1630 string strFutureSQLite = tmp + "/future-wallet.sqlite";
1631 nFail += Check(CreateSQLiteWalletWithFutureMinVersion(strFutureSQLite),
1632 "SQLite export with a future wallet format can be created") ? 0 : 1;
1633 string strFutureLoadCheckOut = tmp + "/sqlite-loadcheck-future.txt";
1634 vector<string> vFutureLoadCheckArgs;
1635 vFutureLoadCheckArgs.push_back("-datadir=" + strWalletDir);
1636 vFutureLoadCheckArgs.push_back("-nomanagedtor");
1637 vFutureLoadCheckArgs.push_back("-nogui");
1638 vFutureLoadCheckArgs.push_back("-walletsqliteloadcheck=" + strFutureSQLite);
1639 int nFutureLoadCheckRet =
1640 RunBitflashChild(strExe, vFutureLoadCheckArgs, NULL,
1641 &strFutureLoadCheckOut);
1642 nFail += Check(nFutureLoadCheckRet == 2 &&
1643 FileContainsText(strFutureLoadCheckOut,
1644 "unsupported future wallet format") &&
1645 FileContainsText(strFutureLoadCheckOut,
1646 "load check: failed"),
1647 "SQLite wallet load check rejects future wallet formats") ? 0 : 1;
1648
1649 string strFutureRuntimeOut = tmp + "/sqlite-runtime-future.txt";
1650 vector<string> vFutureRuntimeArgs;
1651 vFutureRuntimeArgs.push_back("-datadir=" + strWalletDir);
1652 vFutureRuntimeArgs.push_back("-nomanagedtor");
1653 vFutureRuntimeArgs.push_back("-nogui");
1654 vFutureRuntimeArgs.push_back("-walletsqlite=" + strFutureSQLite);
1655 int nFutureRuntimeRet =
1656 RunBitflashChild(strExe, vFutureRuntimeArgs, NULL,
1657 &strFutureRuntimeOut);
1658 nFail += Check(nFutureRuntimeRet == 1 &&
1659 FileContainsText(strFutureRuntimeOut,
1660 "Cannot open SQLite wallet export") &&
1661 FileContainsText(strFutureRuntimeOut,
1662 "newer version of Bitflash"),
1663 "SQLite runtime startup rejects future wallet formats") ? 0 : 1;
1664
1665 string strOverwriteOut = tmp + "/sqlite-export-overwrite.txt";
1666 int nOverwriteRet =
1667 RunBitflashChild(strExe, vExportArgs, NULL, &strOverwriteOut);
1668 nFail += Check(nOverwriteRet != 0 &&
1669 FileContainsText(strOverwriteOut,
1670 "Refusing to overwrite existing SQLite wallet export"),
1671 "SQLite wallet export refuses to overwrite") ? 0 : 1;
1672 remove((strSQLite + "-wal").c_str());
1673 remove((strSQLite + "-shm").c_str());
1674
1675 std::map<vector<unsigned char>, vector<unsigned char> > mapBDB;
1676 string strOldDataDir = strSetDataDir;
1677 string strScanError;
1678 strSetDataDir = strWalletDir;
1679 CWalletRecordMapVisitor visitor(mapBDB);
1680 bool fScanOk = ScanWalletRecords(visitor, strScanError);
1681 DBFlush(true);
1682 strSetDataDir = strOldDataDir;
1683 nFail += Check(fScanOk && !mapBDB.empty(),
1684 "wallet.dat records can be scanned for comparison") ? 0 : 1;
1685
1686 CWalletDBSQLite db;
1687 string strError;
1688 nFail += Check(db.Open(strSQLite, strError),
1689 "exported SQLite wallet opens") ? 0 : 1;
1690 int nSQLiteRecords = 0;
1691 nFail += Check(db.CountRecords(nSQLiteRecords, strError) &&
1692 nSQLiteRecords == (int)mapBDB.size(),
1693 "SQLite export has the same record count as wallet.dat") ? 0 : 1;
1694
1695 bool fAllMatch = true;
1696 for (std::map<vector<unsigned char>, vector<unsigned char> >::const_iterator it =
1697 mapBDB.begin(); it != mapBDB.end(); ++it)
1698 {
1699 vector<unsigned char> vchSQLiteValue;
1700 if (!db.ReadRecord(it->first, vchSQLiteValue, strError) ||
1701 vchSQLiteValue != it->second)
1702 {
1703 fAllMatch = false;
1704 break;
1705 }
1706 }
1707 nFail += Check(fAllMatch,
1708 "SQLite export preserves every wallet.dat key/value byte-for-byte") ? 0 : 1;
1709
1710 std::map<vector<unsigned char>, vector<unsigned char> > mapSQLite;
1711 CWalletRecordMapVisitor sqliteVisitor(mapSQLite);
1712 nFail += Check(db.ScanRecords(sqliteVisitor, strError) &&
1713 mapSQLite == mapBDB,
1714 "SQLite wallet scanner streams every exported record byte-for-byte") ? 0 : 1;
1715
1716 db.Close();
1717 CWalletDBSQLite dbCorrupt;
1718 vector<unsigned char> vchBadValue;
1719 vchBadValue.push_back(0xba);
1720 vchBadValue.push_back(0xad);
1721 nFail += Check(!mapBDB.empty() &&
1722 dbCorrupt.Open(strSQLite, strError) &&
1723 dbCorrupt.BeginTransaction(strError) &&
1724 dbCorrupt.WriteRecord(mapBDB.begin()->first,
1725 vchBadValue,
1726 strError) &&
1727 dbCorrupt.CommitTransaction(strError) &&
1728 dbCorrupt.Checkpoint(strError),
1729 "SQLite export can be deliberately corrupted for verifier test") ? 0 : 1;
1730 dbCorrupt.Close();
1731
1732 string strVerifyBadOut = tmp + "/sqlite-verify-bad.txt";
1733 int nVerifyBadRet =
1734 RunBitflashChild(strExe, vVerifyArgs, NULL, &strVerifyBadOut);
1735 nFail += Check(nVerifyBadRet == 2 &&
1736 FileContainsText(strVerifyBadOut,
1737 "record value mismatches: 1") &&
1738 FileContainsText(strVerifyBadOut,
1739 "verification: failed"),
1740 "SQLite wallet verifier detects a mismatched record value") ? 0 : 1;
1741 }
1742 catch (const std::exception& e)
1743 {
1744 printf(" FAIL exception: %s\n", e.what());
1745 nFail++;
1746 }
1747 catch (...)
1748 {
1749 printf(" FAIL unknown exception\n");
1750 nFail++;
1751 }
1752
1753 RemoveTree(tmp);
1754 printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED",
1755 nFail, nFail == 1 ? "" : "s");
1756 fflush(stdout);
1757 return nFail == 0 ? 0 : 1;
1758}
1759
739static void ClearWalletRuntimeForTest() 1760static void ClearWalletRuntimeForTest()
740{ 1761{
741 CRITICAL_BLOCK(cs_mapKeys) 1762 CRITICAL_BLOCK(cs_mapKeys)
@@ -926,6 +1947,90 @@ static bool WriteTextFile(const string& strPath, const string& strText)
926 return fOk; 1947 return fOk;
927} 1948}
928 1949
1950static bool MakeDirLocal(const string& strPath)
1951{
1952#ifdef _WIN32
1953 return CreateDirectoryA(strPath.c_str(), NULL) || GetLastError() == ERROR_ALREADY_EXISTS;
1954#else
1955 return mkdir(strPath.c_str(), 0700) == 0 || errno == EEXIST;
1956#endif
1957}
1958
1959static bool CopyFileLocal(const string& strSrc, const string& strDst)
1960{
1961 FILE* in = fopen(strSrc.c_str(), "rb");
1962 if (!in)
1963 return false;
1964 FILE* out = fopen(strDst.c_str(), "wb");
1965 if (!out)
1966 {
1967 fclose(in);
1968 return false;
1969 }
1970
1971 bool fOk = true;
1972 char buf[65536];
1973 size_t n = 0;
1974 while ((n = fread(buf, 1, sizeof(buf), in)) > 0)
1975 {
1976 if (fwrite(buf, 1, n, out) != n)
1977 {
1978 fOk = false;
1979 break;
1980 }
1981 }
1982 if (ferror(in))
1983 fOk = false;
1984 if (fclose(out) != 0)
1985 fOk = false;
1986 fclose(in);
1987 return fOk;
1988}
1989
1990static bool DirectoryHasFileWithPrefix(const string& strDir, const string& strPrefix)
1991{
1992#ifdef _WIN32
1993 WIN32_FIND_DATAA findData;
1994 string pattern = strDir + "\\*";
1995 HANDLE hFind = FindFirstFileA(pattern.c_str(), &findData);
1996 if (hFind == INVALID_HANDLE_VALUE)
1997 return false;
1998 bool fFound = false;
1999 do
2000 {
2001 if (!(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
2002 {
2003 string name = findData.cFileName;
2004 if (name.compare(0, strPrefix.size(), strPrefix) == 0)
2005 {
2006 fFound = true;
2007 break;
2008 }
2009 }
2010 }
2011 while (FindNextFileA(hFind, &findData));
2012 FindClose(hFind);
2013 return fFound;
2014#else
2015 DIR* dir = opendir(strDir.c_str());
2016 if (!dir)
2017 return false;
2018 bool fFound = false;
2019 struct dirent* ent;
2020 while ((ent = readdir(dir)) != NULL)
2021 {
2022 string name = ent->d_name;
2023 if (name.compare(0, strPrefix.size(), strPrefix) == 0)
2024 {
2025 fFound = true;
2026 break;
2027 }
2028 }
2029 closedir(dir);
2030 return fFound;
2031#endif
2032}
2033
929static string QuoteCommandArg(const string& str) 2034static string QuoteCommandArg(const string& str)
930{ 2035{
931 string out = "\""; 2036 string out = "\"";
@@ -942,7 +2047,8 @@ static string QuoteCommandArg(const string& str)
942 2047
943static int RunBitflashChild(const string& strExe, 2048static int RunBitflashChild(const string& strExe,
944 const vector<string>& vArgs, 2049 const vector<string>& vArgs,
945 const string* pStdinFile = NULL) 2050 const string* pStdinFile = NULL,
2051 const string* pOutputFile = NULL)
946{ 2052{
947#ifdef _WIN32 2053#ifdef _WIN32
948 vector<char*> argv; 2054 vector<char*> argv;
@@ -955,8 +2061,16 @@ static int RunBitflashChild(const string& strExe,
955 int nOldErr = _dup(2); 2061 int nOldErr = _dup(2);
956 int nOldIn = _dup(0); 2062 int nOldIn = _dup(0);
957 int nNull = _open("NUL", _O_WRONLY); 2063 int nNull = _open("NUL", _O_WRONLY);
2064 int nOut = -1;
958 int nIn = -1; 2065 int nIn = -1;
959 if (nNull >= 0) 2066 if (pOutputFile)
2067 nOut = _open(pOutputFile->c_str(), _O_WRONLY|_O_CREAT|_O_TRUNC|_O_BINARY, _S_IREAD|_S_IWRITE);
2068 if (nOut >= 0)
2069 {
2070 _dup2(nOut, 1);
2071 _dup2(nOut, 2);
2072 }
2073 else if (nNull >= 0)
960 { 2074 {
961 _dup2(nNull, 1); 2075 _dup2(nNull, 1);
962 _dup2(nNull, 2); 2076 _dup2(nNull, 2);
@@ -970,6 +2084,8 @@ static int RunBitflashChild(const string& strExe,
970 int nRet = _spawnv(_P_WAIT, strExe.c_str(), &argv[0]); 2084 int nRet = _spawnv(_P_WAIT, strExe.c_str(), &argv[0]);
971 if (nIn >= 0) 2085 if (nIn >= 0)
972 _close(nIn); 2086 _close(nIn);
2087 if (nOut >= 0)
2088 _close(nOut);
973 if (nNull >= 0) 2089 if (nNull >= 0)
974 _close(nNull); 2090 _close(nNull);
975 if (nOldIn >= 0) 2091 if (nOldIn >= 0)
@@ -994,8 +2110,17 @@ static int RunBitflashChild(const string& strExe,
994 strCmd += " " + QuoteCommandArg(vArgs[i]); 2110 strCmd += " " + QuoteCommandArg(vArgs[i]);
995 if (pStdinFile) 2111 if (pStdinFile)
996 strCmd += " < " + QuoteCommandArg(*pStdinFile); 2112 strCmd += " < " + QuoteCommandArg(*pStdinFile);
997 strCmd += " > /dev/null 2>&1"; 2113 if (pOutputFile)
998 return system(strCmd.c_str()); 2114 strCmd += " > " + QuoteCommandArg(*pOutputFile) + " 2>&1";
2115 else
2116 strCmd += " > /dev/null 2>&1";
2117 // system() returns a wait status, not the exit code; unwrap it so callers
2118 // see the same value the Windows _spawnv path returns (e.g. exit code 2 is
2119 // 2, not 512). Without this every "== 2" assertion fails only on Linux.
2120 int nSysRet = system(strCmd.c_str());
2121 if (nSysRet != -1 && WIFEXITED(nSysRet))
2122 return WEXITSTATUS(nSysRet);
2123 return nSysRet;
999#endif 2124#endif
1000} 2125}
1001 2126
@@ -1036,6 +2161,13 @@ static int RunWalletEncryptSelfTest()
1036 TopUpKeyPool(); 2161 TopUpKeyPool();
1037 2162
1038 vector<unsigned char> vchDefaultPubKey = keyUser.GetPubKey(); 2163 vector<unsigned char> vchDefaultPubKey = keyUser.GetPubKey();
2164 CKey publicLabelKey;
2165 publicLabelKey.MakeNewKey();
2166 string strPublicLabelAddress = PubKeyToAddress(publicLabelKey.GetPubKey());
2167 nFail += Check(CWalletDB().WriteName(strPublicLabelAddress, "rewrite-public-name") &&
2168 CWalletDB().WriteSetting("rewrite-public-setting", (int64)424242),
2169 "public wallet records can be written before encryption") ? 0 : 1;
2170
1039 CPrivKey vchDefaultPrivKey; 2171 CPrivKey vchDefaultPrivKey;
1040 nFail += Check(GetWalletPrivKey(vchDefaultPubKey, vchDefaultPrivKey, strError), 2172 nFail += Check(GetWalletPrivKey(vchDefaultPubKey, vchDefaultPrivKey, strError),
1041 "the default private key is readable before encryption") ? 0 : 1; 2173 "the default private key is readable before encryption") ? 0 : 1;
@@ -1063,16 +2195,31 @@ static int RunWalletEncryptSelfTest()
1063#else 2195#else
1064 string strExe = cwd + "/bitflash-node"; 2196 string strExe = cwd + "/bitflash-node";
1065#endif 2197#endif
2198 string strStorageAudit = tmp + "/encrypted-storage-audit.txt";
1066 string strWrongDump = tmp + "/wrong-pass-dump.txt"; 2199 string strWrongDump = tmp + "/wrong-pass-dump.txt";
1067 string strRightDump = tmp + "/right-pass-dump.txt"; 2200 string strRightDump = tmp + "/right-pass-dump.txt";
1068 string strLiteralDump = tmp + "/literal-pass-dump.txt"; 2201 string strLiteralDump = tmp + "/literal-pass-dump.txt";
1069 string strStdinDump = tmp + "/stdin-pass-dump.txt"; 2202 string strStdinDump = tmp + "/stdin-pass-dump.txt";
2203 string strLockedAudit = tmp + "/locked-recovery-audit.txt";
2204 string strUnlockedAudit = tmp + "/unlocked-recovery-audit.txt";
1070 string strWrongPassFile = tmp + "/wrong-pass.txt"; 2205 string strWrongPassFile = tmp + "/wrong-pass.txt";
1071 string strRightPassFile = tmp + "/right-pass.txt"; 2206 string strRightPassFile = tmp + "/right-pass.txt";
1072 nFail += Check(WriteTextFile(strWrongPassFile, "wrong-passphrase\n") && 2207 nFail += Check(WriteTextFile(strWrongPassFile, "wrong-passphrase\n") &&
1073 WriteTextFile(strRightPassFile, "btf-test-passphrase\n"), 2208 WriteTextFile(strRightPassFile, "btf-test-passphrase\n"),
1074 "passphrase files can be written for restarted commands") ? 0 : 1; 2209 "passphrase files can be written for restarted commands") ? 0 : 1;
1075 2210
2211 vector<string> vStorageAuditArgs;
2212 vStorageAuditArgs.push_back("-datadir=" + tmp);
2213 vStorageAuditArgs.push_back("-walletstorageaudit");
2214 vStorageAuditArgs.push_back("-nogui");
2215 int nStorageAuditRet = RunBitflashChild(strExe, vStorageAuditArgs, NULL, &strStorageAudit);
2216 nFail += Check(nStorageAuditRet == 0 &&
2217 FileContainsText(strStorageAudit, "address book labels: 3"),
2218 "the encrypted rewrite keeps address-book records") ? 0 : 1;
2219 nFail += Check(nStorageAuditRet == 0 &&
2220 FileContainsText(strStorageAudit, "settings: 1"),
2221 "the encrypted rewrite keeps wallet setting records") ? 0 : 1;
2222
1076 vector<string> vWrongArgs; 2223 vector<string> vWrongArgs;
1077 vWrongArgs.push_back("-datadir=" + tmp); 2224 vWrongArgs.push_back("-datadir=" + tmp);
1078 vWrongArgs.push_back("-walletpassphrase=@" + strWrongPassFile); 2225 vWrongArgs.push_back("-walletpassphrase=@" + strWrongPassFile);
@@ -1082,6 +2229,17 @@ static int RunWalletEncryptSelfTest()
1082 nFail += Check(nWrongRet != 0 && !FileExists(strWrongDump.c_str()), 2229 nFail += Check(nWrongRet != 0 && !FileExists(strWrongDump.c_str()),
1083 "a restarted wallet rejects the wrong-passphrase") ? 0 : 1; 2230 "a restarted wallet rejects the wrong-passphrase") ? 0 : 1;
1084 2231
2232 vector<string> vLockedAuditArgs;
2233 vLockedAuditArgs.push_back("-datadir=" + tmp);
2234 vLockedAuditArgs.push_back("-recoveryaudit");
2235 vLockedAuditArgs.push_back("-nogui");
2236 int nLockedAuditRet = RunBitflashChild(strExe, vLockedAuditArgs, NULL, &strLockedAudit);
2237 nFail += Check(nLockedAuditRet == 2 &&
2238 FileContainsText(strLockedAudit, "recovery phrase: encrypted, unlock wallet to audit") &&
2239 FileContainsText(strLockedAudit, "recovery coverage: unavailable while wallet is locked") &&
2240 FileContainsText(strLockedAudit, "Unlock the wallet with /walletpassphrase"),
2241 "a locked encrypted wallet reports that phrase coverage needs unlock") ? 0 : 1;
2242
1085 vector<string> vLiteralArgs; 2243 vector<string> vLiteralArgs;
1086 vLiteralArgs.push_back("-datadir=" + tmp); 2244 vLiteralArgs.push_back("-datadir=" + tmp);
1087 vLiteralArgs.push_back("-walletpassphrase=btf-test-passphrase"); 2245 vLiteralArgs.push_back("-walletpassphrase=btf-test-passphrase");
@@ -1102,6 +2260,17 @@ static int RunWalletEncryptSelfTest()
1102 nFail += Check(FileContainsText(strRightDump, HexStrLocal(vchDefaultPrivBytes)), 2260 nFail += Check(FileContainsText(strRightDump, HexStrLocal(vchDefaultPrivBytes)),
1103 "the restarted wallet can decrypt and dump the original key") ? 0 : 1; 2261 "the restarted wallet can decrypt and dump the original key") ? 0 : 1;
1104 2262
2263 vector<string> vUnlockedAuditArgs;
2264 vUnlockedAuditArgs.push_back("-datadir=" + tmp);
2265 vUnlockedAuditArgs.push_back("-walletpassphrase=@" + strRightPassFile);
2266 vUnlockedAuditArgs.push_back("-recoveryaudit");
2267 vUnlockedAuditArgs.push_back("-nogui");
2268 int nUnlockedAuditRet = RunBitflashChild(strExe, vUnlockedAuditArgs, NULL, &strUnlockedAudit);
2269 nFail += Check(nUnlockedAuditRet == 0 &&
2270 FileContainsText(strUnlockedAudit, "recovery phrase: present") &&
2271 !FileContainsText(strUnlockedAudit, "recovery phrase: not installed"),
2272 "an unlocked encrypted wallet audits the installed recovery phrase") ? 0 : 1;
2273
1105 vector<string> vStdinArgs; 2274 vector<string> vStdinArgs;
1106 vStdinArgs.push_back("-datadir=" + tmp); 2275 vStdinArgs.push_back("-datadir=" + tmp);
1107 vStdinArgs.push_back("-walletpassphrase"); 2276 vStdinArgs.push_back("-walletpassphrase");
@@ -1132,6 +2301,721 @@ static int RunWalletEncryptSelfTest()
1132 return nFail == 0 ? 0 : 1; 2301 return nFail == 0 ? 0 : 1;
1133} 2302}
1134 2303
2304// Encrypting a wallet while it runs on the SQLite backend. Unlike the Berkeley
2305// DB encrypt test above, this drives the real command-line path end to end in
2306// child processes: build a Berkeley DB wallet, export it to SQLite, then run
2307// -walletbackend=sqlite -encryptwallet and prove the SQLite wallet came out
2308// encrypted, unlockable, and with wallet.dat left untouched.
2309static int RunWalletSQLiteEncryptSelfTest()
2310{
2311 fflush(stdout);
2312 printf("wallet-sqlite-encrypt self-test\n");
2313
2314 std::string tmp;
2315 std::string cwd;
2316 if (!MakeTempDir(tmp))
2317 {
2318 printf(" FAIL could not create a temporary data directory\n");
2319 return 1;
2320 }
2321 if (!GetCurrentDir(cwd) || !SetCurrentDir(tmp))
2322 {
2323 printf(" FAIL could not move into the temporary data directory\n");
2324 RemoveTree(tmp);
2325 return 1;
2326 }
2327
2328 int nFail = 0;
2329 strSetDataDir = tmp;
2330 printf(" temp datadir: %s\n", tmp.c_str());
2331
2332 try
2333 {
2334 // Build a normal Berkeley DB wallet with a recovery phrase, a key pool,
2335 // and a couple of public records, then release it. Everything after this
2336 // runs the real command-line paths in child processes.
2337 if (!LoadWallet())
2338 throw std::runtime_error("LoadWallet failed");
2339
2340 string strError;
2341 const string strMnemonic =
2342 "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
2343 nFail += Check(SetHDSeedFromMnemonic(strMnemonic, strError),
2344 "a phrase can be installed before encryption") ? 0 : 1;
2345 TopUpKeyPool();
2346
2347 vector<unsigned char> vchDefaultPubKey = keyUser.GetPubKey();
2348 CKey publicLabelKey;
2349 publicLabelKey.MakeNewKey();
2350 string strPublicLabelAddress = PubKeyToAddress(publicLabelKey.GetPubKey());
2351 nFail += Check(CWalletDB().WriteName(strPublicLabelAddress, "rewrite-public-name") &&
2352 CWalletDB().WriteSetting("rewrite-public-setting", (int64)424242),
2353 "public wallet records can be written before encryption") ? 0 : 1;
2354
2355 CPrivKey vchDefaultPrivKey;
2356 nFail += Check(GetWalletPrivKey(vchDefaultPubKey, vchDefaultPrivKey, strError),
2357 "the default private key is readable before encryption") ? 0 : 1;
2358 vector<unsigned char> vchDefaultPrivBytes(vchDefaultPrivKey.begin(), vchDefaultPrivKey.end());
2359 vector<unsigned char> vchPlainHDMaster = vchHDMaster;
2360 vector<unsigned char> vchPlainHDChainCode = vchHDChainCode;
2361
2362 // Release wallet.dat so a child process can read it for the export.
2363 DBFlush(true);
2364
2365#ifdef _WIN32
2366 string strExe = cwd + "\\bitflash.exe";
2367#else
2368 string strExe = cwd + "/bitflash-node";
2369#endif
2370 string strSQLitePath = tmp + "/wallet.sqlite";
2371 string strWalletDatPath = tmp + "/wallet.dat";
2372 string strRightPassFile = tmp + "/right-pass.txt";
2373 string strWrongPassFile = tmp + "/wrong-pass.txt";
2374 nFail += Check(WriteTextFile(strRightPassFile, "btf-test-passphrase\n") &&
2375 WriteTextFile(strWrongPassFile, "wrong-passphrase\n"),
2376 "passphrase files can be written for restarted commands") ? 0 : 1;
2377
2378 // Step 1: export the Berkeley DB wallet to SQLite.
2379 vector<string> vExportArgs;
2380 vExportArgs.push_back("-datadir=" + tmp);
2381 vExportArgs.push_back("-walletsqliteexport=" + strSQLitePath);
2382 vExportArgs.push_back("-nogui");
2383 int nExportRet = RunBitflashChild(strExe, vExportArgs);
2384 nFail += Check(nExportRet == 0 && FileExists(strSQLitePath.c_str()),
2385 "the wallet exports to a SQLite file") ? 0 : 1;
2386 nFail += Check(FileContainsBytes(strSQLitePath, vchDefaultPrivBytes),
2387 "the exported SQLite wallet holds the plaintext key before encryption") ? 0 : 1;
2388
2389 // Step 2: encrypt through the real SQLite backend command path.
2390 string strEncryptOut = tmp + "/sqlite-encrypt-out.txt";
2391 vector<string> vEncryptArgs;
2392 vEncryptArgs.push_back("-datadir=" + tmp);
2393 vEncryptArgs.push_back("-walletbackend=sqlite");
2394 vEncryptArgs.push_back("-encryptwallet=@" + strRightPassFile);
2395 vEncryptArgs.push_back("-nogui");
2396 int nEncryptRet = RunBitflashChild(strExe, vEncryptArgs, NULL, &strEncryptOut);
2397 nFail += Check(nEncryptRet == 0,
2398 "the SQLite wallet can be encrypted through -walletbackend=sqlite") ? 0 : 1;
2399 nFail += Check(FileContainsText(strEncryptOut, "Wallet backend: SQLite"),
2400 "encryption took the SQLite persistence path") ? 0 : 1;
2401 nFail += Check(FileContainsText(strEncryptOut, "wallet.sqlite.before-encrypt"),
2402 "the plaintext SQLite wallet is preserved as an explicit backup") ? 0 : 1;
2403
2404 // The encrypted SQLite wallet must no longer hold the plaintext secrets.
2405 nFail += Check(!FileContainsBytes(strSQLitePath, vchDefaultPrivBytes),
2406 "the encrypted SQLite wallet no longer contains the default private key bytes") ? 0 : 1;
2407 nFail += Check(!FileContainsBytes(strSQLitePath, vchPlainHDMaster),
2408 "the encrypted SQLite wallet no longer contains the HD master key bytes") ? 0 : 1;
2409 nFail += Check(!FileContainsBytes(strSQLitePath, vchPlainHDChainCode),
2410 "the encrypted SQLite wallet no longer contains the HD chain-code bytes") ? 0 : 1;
2411
2412 // The Berkeley DB wallet.dat is a separate file: the SQLite encrypt path
2413 // must never touch it, so its plaintext key is still exactly there.
2414 nFail += Check(FileContainsBytes(strWalletDatPath, vchDefaultPrivBytes),
2415 "the original Berkeley DB wallet.dat is left untouched by the SQLite encrypt") ? 0 : 1;
2416
2417 // Step 3: a locked encrypted SQLite wallet reports that phrase coverage
2418 // needs an unlock, exactly as the Berkeley DB path does (exit code 2).
2419 string strLockedAudit = tmp + "/locked-recovery-audit.txt";
2420 vector<string> vLockedArgs;
2421 vLockedArgs.push_back("-datadir=" + tmp);
2422 vLockedArgs.push_back("-walletbackend=sqlite");
2423 vLockedArgs.push_back("-recoveryaudit");
2424 vLockedArgs.push_back("-nogui");
2425 int nLockedRet = RunBitflashChild(strExe, vLockedArgs, NULL, &strLockedAudit);
2426 nFail += Check(nLockedRet == 2 &&
2427 FileContainsText(strLockedAudit, "recovery phrase: encrypted, unlock wallet to audit"),
2428 "a locked encrypted SQLite wallet reports that phrase coverage needs unlock") ? 0 : 1;
2429
2430 // The wrong passphrase is rejected and reveals nothing.
2431 string strWrongDump = tmp + "/wrong-dump.txt";
2432 vector<string> vWrongArgs;
2433 vWrongArgs.push_back("-datadir=" + tmp);
2434 vWrongArgs.push_back("-walletbackend=sqlite");
2435 vWrongArgs.push_back("-walletpassphrase=@" + strWrongPassFile);
2436 vWrongArgs.push_back("-dumpwallet=" + strWrongDump);
2437 vWrongArgs.push_back("-nogui");
2438 int nWrongRet = RunBitflashChild(strExe, vWrongArgs);
2439 nFail += Check(nWrongRet != 0 && !FileExists(strWrongDump.c_str()),
2440 "a restarted SQLite wallet rejects the wrong passphrase") ? 0 : 1;
2441
2442 // The right passphrase unlocks and can dump the original key.
2443 string strRightDump = tmp + "/right-dump.txt";
2444 vector<string> vRightArgs;
2445 vRightArgs.push_back("-datadir=" + tmp);
2446 vRightArgs.push_back("-walletbackend=sqlite");
2447 vRightArgs.push_back("-walletpassphrase=@" + strRightPassFile);
2448 vRightArgs.push_back("-dumpwallet=" + strRightDump);
2449 vRightArgs.push_back("-nogui");
2450 int nRightRet = RunBitflashChild(strExe, vRightArgs);
2451 nFail += Check(nRightRet == 0 && FileExists(strRightDump.c_str()),
2452 "a restarted SQLite wallet unlocks with the right passphrase") ? 0 : 1;
2453 nFail += Check(FileContainsText(strRightDump, HexStrLocal(vchDefaultPrivBytes)),
2454 "the unlocked SQLite wallet decrypts and dumps the original key") ? 0 : 1;
2455
2456 // The recovery phrase survives the encryption.
2457 string strAudit = tmp + "/recovery-audit.txt";
2458 vector<string> vAuditArgs;
2459 vAuditArgs.push_back("-datadir=" + tmp);
2460 vAuditArgs.push_back("-walletbackend=sqlite");
2461 vAuditArgs.push_back("-walletpassphrase=@" + strRightPassFile);
2462 vAuditArgs.push_back("-recoveryaudit");
2463 vAuditArgs.push_back("-nogui");
2464 int nAuditRet = RunBitflashChild(strExe, vAuditArgs, NULL, &strAudit);
2465 nFail += Check(nAuditRet == 0 &&
2466 FileContainsText(strAudit, "recovery phrase: present"),
2467 "the encrypted SQLite wallet still has its recovery phrase") ? 0 : 1;
2468
2469 // One-shot storage commands honour -walletbackend. The SQLite wallet is
2470 // encrypted; the wallet.dat beside it is still plaintext. Same datadir,
2471 // opposite answers -> the flag routes the diagnostic to the right file.
2472 string strSqliteAuditJson = tmp + "/sqlite-audit.json";
2473 vector<string> vSqliteAuditArgs;
2474 vSqliteAuditArgs.push_back("-datadir=" + tmp);
2475 vSqliteAuditArgs.push_back("-walletbackend=sqlite");
2476 vSqliteAuditArgs.push_back("-walletstorageauditjson=" + strSqliteAuditJson);
2477 vSqliteAuditArgs.push_back("-nogui");
2478 int nSqliteAuditRet = RunBitflashChild(strExe, vSqliteAuditArgs);
2479 nFail += Check(nSqliteAuditRet == 0 &&
2480 FileContainsText(strSqliteAuditJson, "\"encrypted_hd_seed\": \"complete\"") &&
2481 FileContainsText(strSqliteAuditJson, "\"plain_hd_seed\": \"none\""),
2482 "-walletstorageaudit under SQLite reads the encrypted wallet.sqlite") ? 0 : 1;
2483
2484 string strBdbAuditJson = tmp + "/bdb-audit.json";
2485 vector<string> vBdbAuditArgs;
2486 vBdbAuditArgs.push_back("-datadir=" + tmp);
2487 vBdbAuditArgs.push_back("-walletstorageauditjson=" + strBdbAuditJson);
2488 vBdbAuditArgs.push_back("-nogui");
2489 int nBdbAuditRet = RunBitflashChild(strExe, vBdbAuditArgs);
2490 nFail += Check(nBdbAuditRet == 0 &&
2491 FileContainsText(strBdbAuditJson, "\"plain_hd_seed\": \"complete\"") &&
2492 FileContainsText(strBdbAuditJson, "\"encrypted_hd_seed\": \"none\""),
2493 "-walletstorageaudit without the flag still reads the plaintext wallet.dat") ? 0 : 1;
2494
2495 string strSqliteCheckOut = tmp + "/sqlite-check.txt";
2496 vector<string> vSqliteCheckArgs;
2497 vSqliteCheckArgs.push_back("-datadir=" + tmp);
2498 vSqliteCheckArgs.push_back("-walletbackend=sqlite");
2499 vSqliteCheckArgs.push_back("-walletstoragecheck");
2500 vSqliteCheckArgs.push_back("-nogui");
2501 int nSqliteCheckRet = RunBitflashChild(strExe, vSqliteCheckArgs, NULL, &strSqliteCheckOut);
2502 nFail += Check(nSqliteCheckRet == 0,
2503 "-walletstoragecheck under SQLite passes on the encrypted wallet.sqlite") ? 0 : 1;
2504
2505 // Backup under SQLite copies wallet.sqlite, not the plaintext wallet.dat.
2506 // The copy is a standalone SQLite wallet (loadcheck accepts it) and holds
2507 // none of the plaintext key that wallet.dat still contains.
2508 string strBackupDest = tmp + "/backup.sqlite";
2509 vector<string> vBackupArgs;
2510 vBackupArgs.push_back("-datadir=" + tmp);
2511 vBackupArgs.push_back("-walletbackend=sqlite");
2512 vBackupArgs.push_back("-backupwallet=" + strBackupDest);
2513 vBackupArgs.push_back("-nogui");
2514 int nBackupRet = RunBitflashChild(strExe, vBackupArgs);
2515 nFail += Check(nBackupRet == 0 && FileExists(strBackupDest.c_str()),
2516 "-backupwallet under SQLite writes a backup file") ? 0 : 1;
2517 nFail += Check(!FileContainsBytes(strBackupDest, vchDefaultPrivBytes),
2518 "the SQLite backup holds no plaintext key (it copied wallet.sqlite, not wallet.dat)") ? 0 : 1;
2519
2520 string strBackupLoadCheck = tmp + "/backup-loadcheck.txt";
2521 vector<string> vLoadCheckArgs;
2522 vLoadCheckArgs.push_back("-datadir=" + tmp);
2523 vLoadCheckArgs.push_back("-walletsqliteloadcheck=" + strBackupDest);
2524 vLoadCheckArgs.push_back("-nogui");
2525 int nBackupLoadRet = RunBitflashChild(strExe, vLoadCheckArgs, NULL, &strBackupLoadCheck);
2526 nFail += Check(nBackupLoadRet == 0,
2527 "the SQLite backup is a valid standalone SQLite wallet") ? 0 : 1;
2528 }
2529 catch (const std::exception& e)
2530 {
2531 printf(" FAIL exception: %s\n", e.what());
2532 nFail++;
2533 }
2534 catch (...)
2535 {
2536 printf(" FAIL unknown exception\n");
2537 nFail++;
2538 }
2539
2540 DBFlush(true);
2541 SetCurrentDir(cwd);
2542 RemoveTree(tmp);
2543 printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED",
2544 nFail, nFail == 1 ? "" : "s");
2545 fflush(stdout);
2546 return nFail == 0 ? 0 : 1;
2547}
2548
2549// Backend resolution when no -walletbackend flag is given: a fresh install
2550// starts on SQLite (the new default), an existing wallet.dat stays on Berkeley
2551// DB, the persistent marker overrides the default, and an explicit flag
2552// overrides everything. Each scenario runs a real child process.
2553static int RunWalletBackendDefaultSelfTest()
2554{
2555 fflush(stdout);
2556 printf("wallet-backend-default self-test\n");
2557
2558 std::string tmp, cwd;
2559 if (!MakeTempDir(tmp))
2560 {
2561 printf(" FAIL could not create a temporary data directory\n");
2562 return 1;
2563 }
2564 if (!GetCurrentDir(cwd) || !SetCurrentDir(tmp))
2565 {
2566 printf(" FAIL could not move into the temporary data directory\n");
2567 RemoveTree(tmp);
2568 return 1;
2569 }
2570
2571 int nFail = 0;
2572 strSetDataDir = tmp;
2573 printf(" temp datadir: %s\n", tmp.c_str());
2574
2575#ifdef _WIN32
2576 string strExe = cwd + "\\bitflash.exe";
2577#else
2578 string strExe = cwd + "/bitflash-node";
2579#endif
2580
2581 try
2582 {
2583 // 1. Fresh datadir, no flag -> new wallet.sqlite, marker "sqlite", no wallet.dat.
2584 string d1 = tmp + "/fresh";
2585 nFail += Check(MakeDirLocal(d1), "fresh scenario dir can be created") ? 0 : 1;
2586 vector<string> a1;
2587 a1.push_back("-datadir=" + d1);
2588 a1.push_back("-nomanagedtor");
2589 a1.push_back("-nogui");
2590 a1.push_back("-newaddress");
2591 int r1 = RunBitflashChild(strExe, a1);
2592 nFail += Check(r1 == 0 &&
2593 FileExists((d1 + "/wallet.sqlite").c_str()) &&
2594 !FileExists((d1 + "/wallet.dat").c_str()) &&
2595 FileContainsText(d1 + "/wallet-backend", "sqlite"),
2596 "a fresh install starts on SQLite (new default) and records the marker") ? 0 : 1;
2597
2598 // 1b. Second run reuses it, still SQLite, still no wallet.dat.
2599 int r1b = RunBitflashChild(strExe, a1);
2600 nFail += Check(r1b == 0 &&
2601 FileExists((d1 + "/wallet.sqlite").c_str()) &&
2602 !FileExists((d1 + "/wallet.dat").c_str()),
2603 "the recorded SQLite choice survives a restart") ? 0 : 1;
2604
2605 // 2. Existing wallet.dat, no flag -> stays on Berkeley DB.
2606 string d2 = tmp + "/existing-bdb";
2607 nFail += Check(MakeDirLocal(d2), "existing-bdb scenario dir can be created") ? 0 : 1;
2608 vector<string> a2c;
2609 a2c.push_back("-datadir=" + d2);
2610 a2c.push_back("-nomanagedtor");
2611 a2c.push_back("-nogui");
2612 a2c.push_back("-walletbackend=bdb");
2613 a2c.push_back("-newphrase");
2614 RunBitflashChild(strExe, a2c);
2615 vector<string> a2;
2616 a2.push_back("-datadir=" + d2);
2617 a2.push_back("-nomanagedtor");
2618 a2.push_back("-nogui");
2619 a2.push_back("-newaddress");
2620 int r2 = RunBitflashChild(strExe, a2);
2621 nFail += Check(r2 == 0 &&
2622 FileExists((d2 + "/wallet.dat").c_str()) &&
2623 !FileExists((d2 + "/wallet.sqlite").c_str()),
2624 "an existing wallet.dat keeps a flagless start on Berkeley DB") ? 0 : 1;
2625
2626 // 3. Marker "bdb" forces Berkeley DB even on a fresh datadir.
2627 string d3 = tmp + "/marker-bdb";
2628 nFail += Check(MakeDirLocal(d3), "marker-bdb scenario dir can be created") ? 0 : 1;
2629 nFail += Check(WriteTextFile(d3 + "/wallet-backend", "bdb\n"),
2630 "a bdb marker can be written") ? 0 : 1;
2631 vector<string> a3;
2632 a3.push_back("-datadir=" + d3);
2633 a3.push_back("-nomanagedtor");
2634 a3.push_back("-nogui");
2635 a3.push_back("-newaddress");
2636 int r3 = RunBitflashChild(strExe, a3);
2637 nFail += Check(r3 == 0 &&
2638 FileExists((d3 + "/wallet.dat").c_str()) &&
2639 !FileExists((d3 + "/wallet.sqlite").c_str()),
2640 "a bdb marker forces Berkeley DB on a fresh datadir") ? 0 : 1;
2641
2642 // 4. Explicit -walletbackend=bdb on a fresh datadir -> wallet.dat.
2643 string d4 = tmp + "/explicit-bdb";
2644 nFail += Check(MakeDirLocal(d4), "explicit-bdb scenario dir can be created") ? 0 : 1;
2645 vector<string> a4;
2646 a4.push_back("-datadir=" + d4);
2647 a4.push_back("-nomanagedtor");
2648 a4.push_back("-nogui");
2649 a4.push_back("-walletbackend=bdb");
2650 a4.push_back("-newaddress");
2651 int r4 = RunBitflashChild(strExe, a4);
2652 nFail += Check(r4 == 0 &&
2653 FileExists((d4 + "/wallet.dat").c_str()) &&
2654 !FileExists((d4 + "/wallet.sqlite").c_str()),
2655 "an explicit -walletbackend=bdb creates wallet.dat on a fresh datadir") ? 0 : 1;
2656 }
2657 catch (const std::exception& e)
2658 {
2659 printf(" FAIL exception: %s\n", e.what());
2660 nFail++;
2661 }
2662 catch (...)
2663 {
2664 printf(" FAIL unknown exception\n");
2665 nFail++;
2666 }
2667
2668 DBFlush(true);
2669 SetCurrentDir(cwd);
2670 RemoveTree(tmp);
2671 printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED",
2672 nFail, nFail == 1 ? "" : "s");
2673 fflush(stdout);
2674 return nFail == 0 ? 0 : 1;
2675}
2676
2677// The wizard's conversion action, headless: build a Berkeley DB wallet, run
2678// DoConvertWalletToSQLite() (what the GUI button calls), and prove it produced a
2679// usable wallet.sqlite + backend marker while leaving wallet.dat untouched, then
2680// that a flagless restart opens the converted SQLite wallet.
2681static int RunWalletConvertSelfTest()
2682{
2683 fflush(stdout);
2684 printf("wallet-convert self-test\n");
2685
2686 std::string tmp, cwd;
2687 if (!MakeTempDir(tmp))
2688 {
2689 printf(" FAIL could not create a temporary data directory\n");
2690 return 1;
2691 }
2692 if (!GetCurrentDir(cwd) || !SetCurrentDir(tmp))
2693 {
2694 printf(" FAIL could not move into the temporary data directory\n");
2695 RemoveTree(tmp);
2696 return 1;
2697 }
2698
2699 int nFail = 0;
2700 strSetDataDir = tmp;
2701 printf(" temp datadir: %s\n", tmp.c_str());
2702
2703#ifdef _WIN32
2704 string strExe = cwd + "\\bitflash.exe";
2705#else
2706 string strExe = cwd + "/bitflash-node";
2707#endif
2708
2709 try
2710 {
2711 if (!LoadWallet())
2712 throw std::runtime_error("LoadWallet failed");
2713
2714 string strError;
2715 nFail += Check(SetHDSeedFromMnemonic(
2716 "abandon abandon abandon abandon abandon abandon abandon "
2717 "abandon abandon abandon abandon about", strError),
2718 "a phrase can be installed before conversion") ? 0 : 1;
2719 TopUpKeyPool();
2720
2721 vector<unsigned char> vchDefaultPubKey = keyUser.GetPubKey();
2722 CPrivKey vchDefaultPrivKey;
2723 nFail += Check(GetWalletPrivKey(vchDefaultPubKey, vchDefaultPrivKey, strError),
2724 "the default private key is readable before conversion") ? 0 : 1;
2725 vector<unsigned char> vchDefaultPrivBytes(vchDefaultPrivKey.begin(),
2726 vchDefaultPrivKey.end());
2727
2728 // The conversion action the GUI button invokes.
2729 int nCopied = 0;
2730 string strConvertError;
2731 bool fConverted = DoConvertWalletToSQLite(nCopied, strConvertError);
2732 nFail += Check(fConverted && nCopied > 0,
2733 "DoConvertWalletToSQLite exports the wallet") ? 0 : 1;
2734 nFail += Check(FileExists((tmp + "/wallet.sqlite").c_str()) &&
2735 FileContainsText(tmp + "/wallet-backend", "sqlite"),
2736 "conversion writes wallet.sqlite and records the sqlite marker") ? 0 : 1;
2737 nFail += Check(FileContainsBytes(tmp + "/wallet.dat", vchDefaultPrivBytes),
2738 "wallet.dat is left untouched by the conversion") ? 0 : 1;
2739
2740 // Idempotence guard: a second conversion refuses rather than overwrite.
2741 int nCopied2 = 0;
2742 string strSecondError;
2743 nFail += Check(!DoConvertWalletToSQLite(nCopied2, strSecondError),
2744 "a second conversion refuses to overwrite wallet.sqlite") ? 0 : 1;
2745
2746 DBFlush(true);
2747
2748 // A flagless restart now opens the converted SQLite wallet (the marker
2749 // routes it) and finds the same recovery phrase.
2750 string strAudit = tmp + "/converted-recovery-audit.txt";
2751 vector<string> vAuditArgs;
2752 vAuditArgs.push_back("-datadir=" + tmp);
2753 vAuditArgs.push_back("-nomanagedtor");
2754 vAuditArgs.push_back("-nogui");
2755 vAuditArgs.push_back("-recoveryaudit");
2756 int nAuditRet = RunBitflashChild(strExe, vAuditArgs, NULL, &strAudit);
2757 nFail += Check(nAuditRet == 0 &&
2758 FileContainsText(strAudit, "recovery phrase: present"),
2759 "a flagless restart opens the converted SQLite wallet") ? 0 : 1;
2760 }
2761 catch (const std::exception& e)
2762 {
2763 printf(" FAIL exception: %s\n", e.what());
2764 nFail++;
2765 }
2766 catch (...)
2767 {
2768 printf(" FAIL unknown exception\n");
2769 nFail++;
2770 }
2771
2772 DBFlush(true);
2773 SetCurrentDir(cwd);
2774 RemoveTree(tmp);
2775 printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED",
2776 nFail, nFail == 1 ? "" : "s");
2777 fflush(stdout);
2778 return nFail == 0 ? 0 : 1;
2779}
2780
2781static int RunWalletPortabilitySelfTest()
2782{
2783 fflush(stdout);
2784 printf("wallet-portability self-test\n");
2785
2786 std::string tmp;
2787 std::string cwd;
2788 if (!MakeTempDir(tmp))
2789 {
2790 printf(" FAIL could not create a temporary data directory\n");
2791 return 1;
2792 }
2793 if (!GetCurrentDir(cwd))
2794 {
2795 printf(" FAIL could not read current directory\n");
2796 RemoveTree(tmp);
2797 return 1;
2798 }
2799
2800 int nFail = 0;
2801 printf(" temp root: %s\n", tmp.c_str());
2802
2803 try
2804 {
2805#ifdef _WIN32
2806 string strExe = cwd + "\\bitflash.exe";
2807#else
2808 string strExe = cwd + "/bitflash-node";
2809#endif
2810 string strOriginal = tmp + "/original";
2811 string strRawCopy = tmp + "/raw-copy";
2812 string strBackupCopy = tmp + "/backup-copy";
2813 nFail += Check(MakeDirLocal(strOriginal) &&
2814 MakeDirLocal(strRawCopy) &&
2815 MakeDirLocal(strBackupCopy),
2816 "temporary wallet directories can be created") ? 0 : 1;
2817
2818 string strCreateOut = tmp + "/create.txt";
2819 vector<string> vCreateArgs;
2820 vCreateArgs.push_back("-datadir=" + strOriginal);
2821 vCreateArgs.push_back("-nomanagedtor");
2822 vCreateArgs.push_back("-nogui");
2823 vCreateArgs.push_back("-walletbackend=bdb"); // this suite is about wallet.dat portability
2824 vCreateArgs.push_back("-newphrase");
2825 int nCreateRet = RunBitflashChild(strExe, vCreateArgs, NULL, &strCreateOut);
2826 nFail += Check(nCreateRet == 0 &&
2827 FileContainsText(strCreateOut, "Write these twelve words down"),
2828 "a phrase wallet can be created in a fresh datadir") ? 0 : 1;
2829
2830 string strOriginalAudit = tmp + "/original-audit.txt";
2831 vector<string> vOriginalAuditArgs;
2832 vOriginalAuditArgs.push_back("-datadir=" + strOriginal);
2833 vOriginalAuditArgs.push_back("-nomanagedtor");
2834 vOriginalAuditArgs.push_back("-nogui");
2835 vOriginalAuditArgs.push_back("-recoveryaudit");
2836 int nOriginalAuditRet = RunBitflashChild(strExe, vOriginalAuditArgs, NULL, &strOriginalAudit);
2837 nFail += Check(nOriginalAuditRet == 0 &&
2838 FileContainsText(strOriginalAudit, "recovery phrase: present") &&
2839 FileContainsText(strOriginalAudit, "SLIP-0044 BITFLASH"),
2840 "the original wallet reports its recovery phrase") ? 0 : 1;
2841
2842 string strOriginalStorageAudit = tmp + "/original-storage-audit.txt";
2843 vector<string> vOriginalStorageAuditArgs;
2844 vOriginalStorageAuditArgs.push_back("-datadir=" + strOriginal);
2845 vOriginalStorageAuditArgs.push_back("-nomanagedtor");
2846 vOriginalStorageAuditArgs.push_back("-nogui");
2847 vOriginalStorageAuditArgs.push_back("-walletstorageaudit");
2848 int nOriginalStorageAuditRet =
2849 RunBitflashChild(strExe, vOriginalStorageAuditArgs, NULL, &strOriginalStorageAudit);
2850 nFail += Check(nOriginalStorageAuditRet == 0 &&
2851 FileContainsText(strOriginalStorageAudit, "Wallet storage audit") &&
2852 FileContainsText(strOriginalStorageAudit, "plain HD seed: complete") &&
2853 FileContainsText(strOriginalStorageAudit, "encrypted HD seed: none") &&
2854 FileContainsText(strOriginalStorageAudit, "malformed records: 0"),
2855 "the storage audit recognizes a plaintext phrase wallet") ? 0 : 1;
2856
2857 string strOriginalStorageCheck = tmp + "/original-storage-check.txt";
2858 vector<string> vOriginalStorageCheckArgs;
2859 vOriginalStorageCheckArgs.push_back("-datadir=" + strOriginal);
2860 vOriginalStorageCheckArgs.push_back("-nomanagedtor");
2861 vOriginalStorageCheckArgs.push_back("-nogui");
2862 vOriginalStorageCheckArgs.push_back("-walletstoragecheck");
2863 int nOriginalStorageCheckRet =
2864 RunBitflashChild(strExe, vOriginalStorageCheckArgs, NULL, &strOriginalStorageCheck);
2865 nFail += Check(nOriginalStorageCheckRet == 0 &&
2866 FileContainsText(strOriginalStorageCheck, "storage sanity: ok") &&
2867 FileContainsText(strOriginalStorageCheck, "plain HD seed: complete"),
2868 "the storage sanity check accepts a plaintext phrase wallet") ? 0 : 1;
2869
2870 string strOriginalStorageAuditJson = tmp + "/original-storage-audit.json";
2871 vector<string> vOriginalStorageAuditJsonArgs;
2872 vOriginalStorageAuditJsonArgs.push_back("-datadir=" + strOriginal);
2873 vOriginalStorageAuditJsonArgs.push_back("-nomanagedtor");
2874 vOriginalStorageAuditJsonArgs.push_back("-nogui");
2875 vOriginalStorageAuditJsonArgs.push_back("-walletstorageauditjson=" +
2876 strOriginalStorageAuditJson);
2877 int nOriginalStorageAuditJsonRet =
2878 RunBitflashChild(strExe, vOriginalStorageAuditJsonArgs);
2879 nFail += Check(nOriginalStorageAuditJsonRet == 0 &&
2880 FileContainsText(strOriginalStorageAuditJson,
2881 "\"format\": \"bitflash-wallet-storage-audit-v1\"") &&
2882 FileContainsText(strOriginalStorageAuditJson,
2883 "\"plain_hd_seed\": \"complete\"") &&
2884 FileContainsText(strOriginalStorageAuditJson,
2885 "\"encrypted_hd_seed\": \"none\""),
2886 "the storage audit can write plaintext-wallet JSON") ? 0 : 1;
2887
2888 string strPortableWallet = tmp + "/portable-wallet.dat";
2889 string strBackupOut = tmp + "/backupwallet.txt";
2890 vector<string> vBackupArgs;
2891 vBackupArgs.push_back("-datadir=" + strOriginal);
2892 vBackupArgs.push_back("-nomanagedtor");
2893 vBackupArgs.push_back("-nogui");
2894 vBackupArgs.push_back("-backupwallet=" + strPortableWallet);
2895 int nBackupRet = RunBitflashChild(strExe, vBackupArgs, NULL, &strBackupOut);
2896 nFail += Check(nBackupRet == 0 && FileExists(strPortableWallet.c_str()),
2897 "/backupwallet writes a portable wallet.dat") ? 0 : 1;
2898
2899 string strBackupOut2 = tmp + "/backupwallet-overwrite.txt";
2900 int nBackupRet2 = RunBitflashChild(strExe, vBackupArgs, NULL, &strBackupOut2);
2901 nFail += Check(nBackupRet2 == 0 && FileExists(strPortableWallet.c_str()) &&
2902 !DirectoryHasFileWithPrefix(tmp, "portable-wallet.dat.tmp.") &&
2903 !DirectoryHasFileWithPrefix(tmp, "portable-wallet.dat.old."),
2904 "/backupwallet replaces an existing backup without leaving temp files") ? 0 : 1;
2905
2906 nFail += Check(CopyFileLocal(strOriginal + "/wallet.dat",
2907 strRawCopy + "/wallet.dat"),
2908 "a raw wallet.dat can be copied without database logs") ? 0 : 1;
2909 string strRawAudit = tmp + "/raw-copy-audit.txt";
2910 vector<string> vRawAuditArgs;
2911 vRawAuditArgs.push_back("-datadir=" + strRawCopy);
2912 vRawAuditArgs.push_back("-nomanagedtor");
2913 vRawAuditArgs.push_back("-nogui");
2914 vRawAuditArgs.push_back("-recoveryaudit");
2915 int nRawAuditRet = RunBitflashChild(strExe, vRawAuditArgs, NULL, &strRawAudit);
2916 nFail += Check(nRawAuditRet == 0 &&
2917 FileContainsText(strRawAudit, "recovery phrase: present") &&
2918 !FileContainsText(strRawAudit, "Cannot open wallet.dat"),
2919 "a raw wallet.dat copy opens in a new datadir") ? 0 : 1;
2920
2921 nFail += Check(CopyFileLocal(strPortableWallet,
2922 strBackupCopy + "/wallet.dat"),
2923 "the portable backup can be installed as wallet.dat") ? 0 : 1;
2924 string strBackupAudit = tmp + "/backup-copy-audit.txt";
2925 vector<string> vBackupAuditArgs;
2926 vBackupAuditArgs.push_back("-datadir=" + strBackupCopy);
2927 vBackupAuditArgs.push_back("-nomanagedtor");
2928 vBackupAuditArgs.push_back("-nogui");
2929 vBackupAuditArgs.push_back("-recoveryaudit");
2930 int nBackupAuditRet = RunBitflashChild(strExe, vBackupAuditArgs, NULL, &strBackupAudit);
2931 nFail += Check(nBackupAuditRet == 0 &&
2932 FileContainsText(strBackupAudit, "recovery phrase: present") &&
2933 FileContainsText(strBackupAudit, "SLIP-0044 BITFLASH"),
2934 "a /backupwallet copy opens in a new datadir") ? 0 : 1;
2935
2936 string strPassFile = tmp + "/encrypt-pass.txt";
2937 string strEncryptOut = tmp + "/encryptwallet.txt";
2938 nFail += Check(WriteTextFile(strPassFile, "portable-test-passphrase\n"),
2939 "an encryption passphrase file can be written") ? 0 : 1;
2940 vector<string> vEncryptArgs;
2941 vEncryptArgs.push_back("-datadir=" + strOriginal);
2942 vEncryptArgs.push_back("-nomanagedtor");
2943 vEncryptArgs.push_back("-nogui");
2944 vEncryptArgs.push_back("-encryptwallet=@" + strPassFile);
2945 int nEncryptRet = RunBitflashChild(strExe, vEncryptArgs, NULL, &strEncryptOut);
2946 nFail += Check(nEncryptRet == 0 &&
2947 !DirectoryHasFileWithPrefix(strOriginal + "/database", "log."),
2948 "the encryptwallet command purges Berkeley DB environment logs") ? 0 : 1;
2949
2950 string strEncryptedStorageAudit = tmp + "/encrypted-storage-audit.txt";
2951 vector<string> vEncryptedStorageAuditArgs;
2952 vEncryptedStorageAuditArgs.push_back("-datadir=" + strOriginal);
2953 vEncryptedStorageAuditArgs.push_back("-nomanagedtor");
2954 vEncryptedStorageAuditArgs.push_back("-nogui");
2955 vEncryptedStorageAuditArgs.push_back("-walletstorageaudit");
2956 int nEncryptedStorageAuditRet =
2957 RunBitflashChild(strExe, vEncryptedStorageAuditArgs, NULL, &strEncryptedStorageAudit);
2958 nFail += Check(nEncryptedStorageAuditRet == 0 &&
2959 FileContainsText(strEncryptedStorageAudit, "plain private keys: 0") &&
2960 FileContainsText(strEncryptedStorageAudit, "encrypted private keys:") &&
2961 FileContainsText(strEncryptedStorageAudit, "plain HD seed: none") &&
2962 FileContainsText(strEncryptedStorageAudit, "encrypted HD seed: complete") &&
2963 FileContainsText(strEncryptedStorageAudit, "wallet minimum version: present") &&
2964 FileContainsText(strEncryptedStorageAudit, "malformed records: 0"),
2965 "the storage audit recognizes an encrypted wallet") ? 0 : 1;
2966
2967 string strEncryptedStorageCheck = tmp + "/encrypted-storage-check.txt";
2968 vector<string> vEncryptedStorageCheckArgs;
2969 vEncryptedStorageCheckArgs.push_back("-datadir=" + strOriginal);
2970 vEncryptedStorageCheckArgs.push_back("-nomanagedtor");
2971 vEncryptedStorageCheckArgs.push_back("-nogui");
2972 vEncryptedStorageCheckArgs.push_back("-walletstoragecheck");
2973 int nEncryptedStorageCheckRet =
2974 RunBitflashChild(strExe, vEncryptedStorageCheckArgs, NULL, &strEncryptedStorageCheck);
2975 nFail += Check(nEncryptedStorageCheckRet == 0 &&
2976 FileContainsText(strEncryptedStorageCheck, "storage sanity: ok") &&
2977 FileContainsText(strEncryptedStorageCheck, "plain private keys: 0") &&
2978 FileContainsText(strEncryptedStorageCheck, "encrypted HD seed: complete"),
2979 "the storage sanity check accepts an encrypted wallet") ? 0 : 1;
2980
2981 string strEncryptedStorageAuditJson = tmp + "/encrypted-storage-audit.json";
2982 vector<string> vEncryptedStorageAuditJsonArgs;
2983 vEncryptedStorageAuditJsonArgs.push_back("-datadir=" + strOriginal);
2984 vEncryptedStorageAuditJsonArgs.push_back("-nomanagedtor");
2985 vEncryptedStorageAuditJsonArgs.push_back("-nogui");
2986 vEncryptedStorageAuditJsonArgs.push_back("-walletstorageauditjson=" +
2987 strEncryptedStorageAuditJson);
2988 int nEncryptedStorageAuditJsonRet =
2989 RunBitflashChild(strExe, vEncryptedStorageAuditJsonArgs);
2990 nFail += Check(nEncryptedStorageAuditJsonRet == 0 &&
2991 FileContainsText(strEncryptedStorageAuditJson,
2992 "\"plain_private_keys\": 0") &&
2993 FileContainsText(strEncryptedStorageAuditJson,
2994 "\"plain_hd_seed\": \"none\"") &&
2995 FileContainsText(strEncryptedStorageAuditJson,
2996 "\"encrypted_hd_seed\": \"complete\"") &&
2997 FileContainsText(strEncryptedStorageAuditJson,
2998 "\"wallet_minimum_version\": \"present\""),
2999 "the storage audit can write encrypted-wallet JSON") ? 0 : 1;
3000 }
3001 catch (const std::exception& e)
3002 {
3003 printf(" FAIL exception: %s\n", e.what());
3004 nFail++;
3005 }
3006 catch (...)
3007 {
3008 printf(" FAIL unknown exception\n");
3009 nFail++;
3010 }
3011
3012 RemoveTree(tmp);
3013 printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED",
3014 nFail, nFail == 1 ? "" : "s");
3015 fflush(stdout);
3016 return nFail == 0 ? 0 : 1;
3017}
3018
1135static int RunNetMessageSelfTest() 3019static int RunNetMessageSelfTest()
1136{ 3020{
1137 fflush(stdout); 3021 fflush(stdout);
@@ -1637,10 +3521,26 @@ int RunSelfTest(const std::string& name)
1637 return RunWalletHDSelfTest(); 3521 return RunWalletHDSelfTest();
1638 if (name == "wallet-format") 3522 if (name == "wallet-format")
1639 return RunWalletFormatSelfTest(); 3523 return RunWalletFormatSelfTest();
3524 if (name == "wallet-storage-sanity")
3525 return RunWalletStorageSanitySelfTest();
3526 if (name == "db-env-reopen")
3527 return RunDbEnvReopenSelfTest();
3528 if (name == "wallet-sqlite")
3529 return RunWalletSQLiteSelfTest();
3530 if (name == "wallet-sqlite-migration")
3531 return RunWalletSQLiteMigrationSelfTest();
1640 if (name == "wallet-crypto") 3532 if (name == "wallet-crypto")
1641 return RunWalletCryptoSelfTest(); 3533 return RunWalletCryptoSelfTest();
1642 if (name == "wallet-encrypt") 3534 if (name == "wallet-encrypt")
1643 return RunWalletEncryptSelfTest(); 3535 return RunWalletEncryptSelfTest();
3536 if (name == "wallet-sqlite-encrypt")
3537 return RunWalletSQLiteEncryptSelfTest();
3538 if (name == "wallet-backend-default")
3539 return RunWalletBackendDefaultSelfTest();
3540 if (name == "wallet-convert")
3541 return RunWalletConvertSelfTest();
3542 if (name == "wallet-portability")
3543 return RunWalletPortabilitySelfTest();
1644 if (name == "net-message") 3544 if (name == "net-message")
1645 return RunNetMessageSelfTest(); 3545 return RunNetMessageSelfTest();
1646 if (name == "consensus-limits") 3546 if (name == "consensus-limits")
@@ -1657,6 +3557,6 @@ int RunSelfTest(const std::string& name)
1657 return RunManagedTorSelfTest(); 3557 return RunManagedTorSelfTest();
1658 3558
1659 printf("Unknown self-test '%s'\n", name.c_str()); 3559 printf("Unknown self-test '%s'\n", name.c_str());
1660 printf("Known self-tests: wallet-keypool, wallet-hd, wallet-format, wallet-crypto, wallet-encrypt, net-message, consensus-limits, pool-stratum, payout-ledger, parse-money, socks5-proxy, managed-tor\n"); 3560 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-sqlite-encrypt, wallet-backend-default, wallet-convert, wallet-portability, net-message, consensus-limits, pool-stratum, payout-ledger, parse-money, socks5-proxy, managed-tor\n");
1661 return 1; 3561 return 1;
1662} 3562}