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-06 01:53:03 +0000
committerbitflash-maintainer <noreply@bitflash.invalid>2026-09-06 01:53:03 +0000
commitef9f06d43fc6577c15df2256abbb25b20d4227b8 (patch)
tree7e9132aa871fdc01cb95268d2f5ea3bb11853ddf /src/selftest.cpp
parentbe63e83d79e7e00e6b8ca889a70e6a19886aa1d8 (diff)
parentceae3d076b5ad11ee84729ae43f14e9312896646 (diff)
Merge branch 'chore/main-catchup-2026-08-26' into 'main'HEADmain
Bring main onto the line this project actually ships (incl. the 1.2.23 compactblock port) Closes #6, #7, #8, #11, #35, #34, #29, #28, #27, and #16 See merge request !16
Diffstat (limited to 'src/selftest.cpp')
-rw-r--r--src/selftest.cpp2611
1 files changed, 2594 insertions, 17 deletions
diff --git a/src/selftest.cpp b/src/selftest.cpp
index ff0289b..357d532 100644
--- a/src/selftest.cpp
+++ b/src/selftest.cpp
@@ -6,9 +6,20 @@
6// and against a temporary data directory. 6// and against a temporary data directory.
7 7
8#include "headers_core.h" 8#include "headers_core.h"
9#include "btfaddr.h"
9#include "bip32.h" 10#include "bip32.h"
11#include "proxy.h"
10#include "selftest.h" 12#include "selftest.h"
13#ifndef _WIN32
14#include <sys/wait.h> // WIFEXITED/WEXITSTATUS for the child-process helper
15#endif
16#include "sockcount.h"
17#include "tor.h"
11#include "walletcmd.h" 18#include "walletcmd.h"
19#include "wallet_sqlite.h"
20
21extern int RunPoolStratumSelfTest();
22extern int RunPayoutLedgerSelfTest();
12 23
13// Test results go to the terminal, not to debug.log. 24// Test results go to the terminal, not to debug.log.
14// 25//
@@ -28,6 +39,7 @@
28#undef printf 39#undef printf
29 40
30#include <mutex> 41#include <mutex>
42#include <cerrno>
31#include <stdexcept> 43#include <stdexcept>
32#include <thread> 44#include <thread>
33 45
@@ -55,6 +67,11 @@ static std::string HexStrLocal(const std::vector<unsigned char>& v)
55 return out; 67 return out;
56} 68}
57 69
70static vector<unsigned char> DataStreamBytes(const CDataStream& ss)
71{
72 return vector<unsigned char>(ss.begin(), ss.end());
73}
74
58static bool MakeTempDir(std::string& out) 75static bool MakeTempDir(std::string& out)
59{ 76{
60#ifdef _WIN32 77#ifdef _WIN32
@@ -348,17 +365,17 @@ static int RunWalletHDSelfTest()
348 nFail += Check(HaveHDSeed(), "the wallet reports having a seed") ? 0 : 1; 365 nFail += Check(HaveHDSeed(), "the wallet reports having a seed") ? 0 : 1;
349 nFail += Check(nHDKeySchema == HD_SCHEMA_BIP44, 366 nFail += Check(nHDKeySchema == HD_SCHEMA_BIP44,
350 "a new recovery phrase records the BIP44 HD schema") ? 0 : 1; 367 "a new recovery phrase records the BIP44 HD schema") ? 0 : 1;
351 nFail += Check(nHDCoinType == HD_BIP44_COIN_TYPE_BITFLASH_PROVISIONAL, 368 nFail += Check(nHDCoinType == HD_BIP44_COIN_TYPE_BITFLASH,
352 "a new recovery phrase records the provisional BIP44 coin type") ? 0 : 1; 369 "a new recovery phrase records the SLIP-0044 BIP44 coin type") ? 0 : 1;
353 nFail += Check(nHDReceiveNext == 1 && nHDChangeNext == 0, 370 nFail += Check(nHDReceiveNext == 1 && nHDChangeNext == 0,
354 "BIP44 receive/change counters reserve the default receive key") ? 0 : 1; 371 "BIP44 receive/change counters reserve the default receive key") ? 0 : 1;
355 std::vector<unsigned int> vBIP44Path = HDBIP44Path(HD_BIP44_COIN_TYPE_BITFLASH_PROVISIONAL, 372 std::vector<unsigned int> vBIP44Path = HDBIP44Path(HD_BIP44_COIN_TYPE_BITFLASH,
356 HD_BIP44_ACCOUNT, 373 HD_BIP44_ACCOUNT,
357 HD_BIP44_CHAIN_RECEIVE, 374 HD_BIP44_CHAIN_RECEIVE,
358 0); 375 0);
359 nFail += Check(vBIP44Path.size() == 5 && 376 nFail += Check(vBIP44Path.size() == 5 &&
360 vBIP44Path[0] == (HD_BIP44_PURPOSE | bitflash::BIP32_HARDENED) && 377 vBIP44Path[0] == (HD_BIP44_PURPOSE | bitflash::BIP32_HARDENED) &&
361 vBIP44Path[1] == (HD_BIP44_COIN_TYPE_BITFLASH_PROVISIONAL | bitflash::BIP32_HARDENED) && 378 vBIP44Path[1] == (HD_BIP44_COIN_TYPE_BITFLASH | bitflash::BIP32_HARDENED) &&
362 vBIP44Path[2] == (HD_BIP44_ACCOUNT | bitflash::BIP32_HARDENED) && 379 vBIP44Path[2] == (HD_BIP44_ACCOUNT | bitflash::BIP32_HARDENED) &&
363 vBIP44Path[3] == HD_BIP44_CHAIN_RECEIVE && 380 vBIP44Path[3] == HD_BIP44_CHAIN_RECEIVE &&
364 vBIP44Path[4] == 0, 381 vBIP44Path[4] == 0,
@@ -380,7 +397,7 @@ static int RunWalletHDSelfTest()
380 hdParent.chainCode = vchHDChainCode; 397 hdParent.chainCode = vchHDChainCode;
381 bitflash::BIP32PrivateNode hdBIP44ReceiveChild; 398 bitflash::BIP32PrivateNode hdBIP44ReceiveChild;
382 if (!bitflash::BIP32DerivePath(hdParent, 399 if (!bitflash::BIP32DerivePath(hdParent,
383 HDBIP44Path(HD_BIP44_COIN_TYPE_BITFLASH_PROVISIONAL, 400 HDBIP44Path(HD_BIP44_COIN_TYPE_BITFLASH,
384 HD_BIP44_ACCOUNT, 401 HD_BIP44_ACCOUNT,
385 HD_BIP44_CHAIN_RECEIVE, 402 HD_BIP44_CHAIN_RECEIVE,
386 0), 403 0),
@@ -568,7 +585,7 @@ static int RunWalletHDSelfTest()
568 nFail += Check(audit.fHaveSeed, "the recovery audit reports the phrase") ? 0 : 1; 585 nFail += Check(audit.fHaveSeed, "the recovery audit reports the phrase") ? 0 : 1;
569 nFail += Check(audit.nSchema == HD_SCHEMA_BIP44, 586 nFail += Check(audit.nSchema == HD_SCHEMA_BIP44,
570 "the recovery audit reports the derivation schema") ? 0 : 1; 587 "the recovery audit reports the derivation schema") ? 0 : 1;
571 nFail += Check(audit.nCoinType == HD_BIP44_COIN_TYPE_BITFLASH_PROVISIONAL, 588 nFail += Check(audit.nCoinType == HD_BIP44_COIN_TYPE_BITFLASH,
572 "the recovery audit reports the BIP44 coin type") ? 0 : 1; 589 "the recovery audit reports the BIP44 coin type") ? 0 : 1;
573 nFail += Check(audit.nReceiveNext == nHDReceiveNext && audit.nChangeNext == 1, 590 nFail += Check(audit.nReceiveNext == nHDReceiveNext && audit.nChangeNext == 1,
574 "the recovery audit reports receive/change counters") ? 0 : 1; 591 "the recovery audit reports receive/change counters") ? 0 : 1;
@@ -582,6 +599,34 @@ static int RunWalletHDSelfTest()
582 "the recovery audit finds wallet.dat-only immature mining rewards") ? 0 : 1; 599 "the recovery audit finds wallet.dat-only immature mining rewards") ? 0 : 1;
583 nFail += Check(audit.nLegacyImmatureTx == 1, 600 nFail += Check(audit.nLegacyImmatureTx == 1,
584 "the recovery audit counts wallet.dat-only immature mining rewards") ? 0 : 1; 601 "the recovery audit counts wallet.dat-only immature mining rewards") ? 0 : 1;
602
603 CKey keyLegacyHD11;
604 nSchemaForLegacyAudit = nHDKeySchema;
605 nHDKeySchema = HD_SCHEMA_LEGACY;
606 if (!DeriveHDKey(11, keyLegacyHD11, strError))
607 throw std::runtime_error("legacy compatibility index 11 derivation failed: " + strError);
608 nHDKeySchema = nSchemaForLegacyAudit;
609 if (!AddKey(keyLegacyHD11))
610 throw std::runtime_error("could not store the legacy compatibility index 11 key");
611
612 std::map<unsigned int, std::vector<unsigned char> > mapLegacyProbe;
613 mapLegacyProbe[0] = keyAuditLegacyHD.GetPubKey();
614 mapLegacyProbe[11] = keyLegacyHD11.GetPubKey();
615
616 CWalletTx wtxLegacyHD11;
617 wtxLegacyHD11.vout.push_back(CTxOut(19 * COIN, CScript() << keyLegacyHD11.GetPubKey() << OP_CHECKSIG));
618 CRITICAL_BLOCK(cs_mapWallet)
619 {
620 mapWallet.clear();
621 mapWallet[wtxLegacyHD11.GetHash()] = wtxLegacyHD11;
622 }
623 nFail += Check(WalletLastUsedPubKeyIndexNext(mapLegacyProbe) == 12,
624 "restore collapses legacy hdnext to the last used compatibility index plus one") ? 0 : 1;
625
626 CRITICAL_BLOCK(cs_mapWallet)
627 mapWallet.clear();
628 nFail += Check(WalletLastUsedPubKeyIndexNext(mapLegacyProbe) == 0,
629 "restore does not persist legacy hdnext at the scan depth when no compatibility key was used") ? 0 : 1;
585 } 630 }
586 catch (const std::exception& e) 631 catch (const std::exception& e)
587 { 632 {
@@ -701,6 +746,1017 @@ static int RunWalletFormatSelfTest()
701 return nFail == 0 ? 0 : 1; 746 return nFail == 0 ? 0 : 1;
702} 747}
703 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
704static void ClearWalletRuntimeForTest() 1760static void ClearWalletRuntimeForTest()
705{ 1761{
706 CRITICAL_BLOCK(cs_mapKeys) 1762 CRITICAL_BLOCK(cs_mapKeys)
@@ -891,6 +1947,90 @@ static bool WriteTextFile(const string& strPath, const string& strText)
891 return fOk; 1947 return fOk;
892} 1948}
893 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
894static string QuoteCommandArg(const string& str) 2034static string QuoteCommandArg(const string& str)
895{ 2035{
896 string out = "\""; 2036 string out = "\"";
@@ -907,7 +2047,8 @@ static string QuoteCommandArg(const string& str)
907 2047
908static int RunBitflashChild(const string& strExe, 2048static int RunBitflashChild(const string& strExe,
909 const vector<string>& vArgs, 2049 const vector<string>& vArgs,
910 const string* pStdinFile = NULL) 2050 const string* pStdinFile = NULL,
2051 const string* pOutputFile = NULL)
911{ 2052{
912#ifdef _WIN32 2053#ifdef _WIN32
913 vector<char*> argv; 2054 vector<char*> argv;
@@ -920,8 +2061,16 @@ static int RunBitflashChild(const string& strExe,
920 int nOldErr = _dup(2); 2061 int nOldErr = _dup(2);
921 int nOldIn = _dup(0); 2062 int nOldIn = _dup(0);
922 int nNull = _open("NUL", _O_WRONLY); 2063 int nNull = _open("NUL", _O_WRONLY);
2064 int nOut = -1;
923 int nIn = -1; 2065 int nIn = -1;
924 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)
925 { 2074 {
926 _dup2(nNull, 1); 2075 _dup2(nNull, 1);
927 _dup2(nNull, 2); 2076 _dup2(nNull, 2);
@@ -935,6 +2084,8 @@ static int RunBitflashChild(const string& strExe,
935 int nRet = _spawnv(_P_WAIT, strExe.c_str(), &argv[0]); 2084 int nRet = _spawnv(_P_WAIT, strExe.c_str(), &argv[0]);
936 if (nIn >= 0) 2085 if (nIn >= 0)
937 _close(nIn); 2086 _close(nIn);
2087 if (nOut >= 0)
2088 _close(nOut);
938 if (nNull >= 0) 2089 if (nNull >= 0)
939 _close(nNull); 2090 _close(nNull);
940 if (nOldIn >= 0) 2091 if (nOldIn >= 0)
@@ -959,8 +2110,17 @@ static int RunBitflashChild(const string& strExe,
959 strCmd += " " + QuoteCommandArg(vArgs[i]); 2110 strCmd += " " + QuoteCommandArg(vArgs[i]);
960 if (pStdinFile) 2111 if (pStdinFile)
961 strCmd += " < " + QuoteCommandArg(*pStdinFile); 2112 strCmd += " < " + QuoteCommandArg(*pStdinFile);
962 strCmd += " > /dev/null 2>&1"; 2113 if (pOutputFile)
963 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;
964#endif 2124#endif
965} 2125}
966 2126
@@ -1001,6 +2161,13 @@ static int RunWalletEncryptSelfTest()
1001 TopUpKeyPool(); 2161 TopUpKeyPool();
1002 2162
1003 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
1004 CPrivKey vchDefaultPrivKey; 2171 CPrivKey vchDefaultPrivKey;
1005 nFail += Check(GetWalletPrivKey(vchDefaultPubKey, vchDefaultPrivKey, strError), 2172 nFail += Check(GetWalletPrivKey(vchDefaultPubKey, vchDefaultPrivKey, strError),
1006 "the default private key is readable before encryption") ? 0 : 1; 2173 "the default private key is readable before encryption") ? 0 : 1;
@@ -1028,16 +2195,31 @@ static int RunWalletEncryptSelfTest()
1028#else 2195#else
1029 string strExe = cwd + "/bitflash-node"; 2196 string strExe = cwd + "/bitflash-node";
1030#endif 2197#endif
2198 string strStorageAudit = tmp + "/encrypted-storage-audit.txt";
1031 string strWrongDump = tmp + "/wrong-pass-dump.txt"; 2199 string strWrongDump = tmp + "/wrong-pass-dump.txt";
1032 string strRightDump = tmp + "/right-pass-dump.txt"; 2200 string strRightDump = tmp + "/right-pass-dump.txt";
1033 string strLiteralDump = tmp + "/literal-pass-dump.txt"; 2201 string strLiteralDump = tmp + "/literal-pass-dump.txt";
1034 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";
1035 string strWrongPassFile = tmp + "/wrong-pass.txt"; 2205 string strWrongPassFile = tmp + "/wrong-pass.txt";
1036 string strRightPassFile = tmp + "/right-pass.txt"; 2206 string strRightPassFile = tmp + "/right-pass.txt";
1037 nFail += Check(WriteTextFile(strWrongPassFile, "wrong-passphrase\n") && 2207 nFail += Check(WriteTextFile(strWrongPassFile, "wrong-passphrase\n") &&
1038 WriteTextFile(strRightPassFile, "btf-test-passphrase\n"), 2208 WriteTextFile(strRightPassFile, "btf-test-passphrase\n"),
1039 "passphrase files can be written for restarted commands") ? 0 : 1; 2209 "passphrase files can be written for restarted commands") ? 0 : 1;
1040 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
1041 vector<string> vWrongArgs; 2223 vector<string> vWrongArgs;
1042 vWrongArgs.push_back("-datadir=" + tmp); 2224 vWrongArgs.push_back("-datadir=" + tmp);
1043 vWrongArgs.push_back("-walletpassphrase=@" + strWrongPassFile); 2225 vWrongArgs.push_back("-walletpassphrase=@" + strWrongPassFile);
@@ -1047,6 +2229,17 @@ static int RunWalletEncryptSelfTest()
1047 nFail += Check(nWrongRet != 0 && !FileExists(strWrongDump.c_str()), 2229 nFail += Check(nWrongRet != 0 && !FileExists(strWrongDump.c_str()),
1048 "a restarted wallet rejects the wrong-passphrase") ? 0 : 1; 2230 "a restarted wallet rejects the wrong-passphrase") ? 0 : 1;
1049 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
1050 vector<string> vLiteralArgs; 2243 vector<string> vLiteralArgs;
1051 vLiteralArgs.push_back("-datadir=" + tmp); 2244 vLiteralArgs.push_back("-datadir=" + tmp);
1052 vLiteralArgs.push_back("-walletpassphrase=btf-test-passphrase"); 2245 vLiteralArgs.push_back("-walletpassphrase=btf-test-passphrase");
@@ -1067,6 +2260,17 @@ static int RunWalletEncryptSelfTest()
1067 nFail += Check(FileContainsText(strRightDump, HexStrLocal(vchDefaultPrivBytes)), 2260 nFail += Check(FileContainsText(strRightDump, HexStrLocal(vchDefaultPrivBytes)),
1068 "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;
1069 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
1070 vector<string> vStdinArgs; 2274 vector<string> vStdinArgs;
1071 vStdinArgs.push_back("-datadir=" + tmp); 2275 vStdinArgs.push_back("-datadir=" + tmp);
1072 vStdinArgs.push_back("-walletpassphrase"); 2276 vStdinArgs.push_back("-walletpassphrase");
@@ -1097,6 +2301,737 @@ static int RunWalletEncryptSelfTest()
1097 return nFail == 0 ? 0 : 1; 2301 return nFail == 0 ? 0 : 1;
1098} 2302}
1099 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 -> this fork refuses, where upstream creates.
2584 // An empty datadir on a headless node is far more often a mount or
2585 // backup fault than a first run, and creating a wallet also pins the
2586 // backend marker, after which restoring the real wallet is a no-op.
2587 // Nothing may be left behind: no wallet of either kind, no marker.
2588 string d1 = tmp + "/fresh";
2589 nFail += Check(MakeDirLocal(d1), "fresh scenario dir can be created") ? 0 : 1;
2590 vector<string> a1;
2591 a1.push_back("-datadir=" + d1);
2592 a1.push_back("-nomanagedtor");
2593 a1.push_back("-nogui");
2594 a1.push_back("-newaddress");
2595 int r0 = RunBitflashChild(strExe, a1);
2596 nFail += Check(r0 != 0 &&
2597 !FileExists((d1 + "/wallet.sqlite").c_str()) &&
2598 !FileExists((d1 + "/wallet.dat").c_str()) &&
2599 !FileExists((d1 + "/wallet-backend").c_str()),
2600 "a flagless headless start refuses to create a wallet") ? 0 : 1;
2601
2602 // 1a. -createwallet says it was meant: new wallet.sqlite, marker
2603 // "sqlite", no wallet.dat.
2604 vector<string> a1c = a1;
2605 a1c.push_back("-createwallet");
2606 int r1 = RunBitflashChild(strExe, a1c);
2607 nFail += Check(r1 == 0 &&
2608 FileExists((d1 + "/wallet.sqlite").c_str()) &&
2609 !FileExists((d1 + "/wallet.dat").c_str()) &&
2610 FileContainsText(d1 + "/wallet-backend", "sqlite"),
2611 "-createwallet starts a fresh install on SQLite and records the marker") ? 0 : 1;
2612
2613 // 1b. Second run reuses it without the flag -- the refusal only guards
2614 // creation, so an existing wallet still starts flagless.
2615 int r1b = RunBitflashChild(strExe, a1);
2616 nFail += Check(r1b == 0 &&
2617 FileExists((d1 + "/wallet.sqlite").c_str()) &&
2618 !FileExists((d1 + "/wallet.dat").c_str()),
2619 "the recorded SQLite choice survives a flagless restart") ? 0 : 1;
2620
2621 // 2. Existing wallet.dat, no flag -> stays on Berkeley DB.
2622 string d2 = tmp + "/existing-bdb";
2623 nFail += Check(MakeDirLocal(d2), "existing-bdb scenario dir can be created") ? 0 : 1;
2624 vector<string> a2c;
2625 a2c.push_back("-datadir=" + d2);
2626 a2c.push_back("-nomanagedtor");
2627 a2c.push_back("-nogui");
2628 a2c.push_back("-walletbackend=bdb");
2629 a2c.push_back("-newphrase");
2630 RunBitflashChild(strExe, a2c);
2631 vector<string> a2;
2632 a2.push_back("-datadir=" + d2);
2633 a2.push_back("-nomanagedtor");
2634 a2.push_back("-nogui");
2635 a2.push_back("-newaddress");
2636 int r2 = RunBitflashChild(strExe, a2);
2637 nFail += Check(r2 == 0 &&
2638 FileExists((d2 + "/wallet.dat").c_str()) &&
2639 !FileExists((d2 + "/wallet.sqlite").c_str()),
2640 "an existing wallet.dat keeps a flagless start on Berkeley DB") ? 0 : 1;
2641
2642 // 3. Marker "bdb" forces Berkeley DB even on a fresh datadir.
2643 string d3 = tmp + "/marker-bdb";
2644 nFail += Check(MakeDirLocal(d3), "marker-bdb scenario dir can be created") ? 0 : 1;
2645 nFail += Check(WriteTextFile(d3 + "/wallet-backend", "bdb\n"),
2646 "a bdb marker can be written") ? 0 : 1;
2647 vector<string> a3;
2648 a3.push_back("-datadir=" + d3);
2649 a3.push_back("-nomanagedtor");
2650 a3.push_back("-nogui");
2651 a3.push_back("-newaddress");
2652 int r3 = RunBitflashChild(strExe, a3);
2653 nFail += Check(r3 == 0 &&
2654 FileExists((d3 + "/wallet.dat").c_str()) &&
2655 !FileExists((d3 + "/wallet.sqlite").c_str()),
2656 "a bdb marker forces Berkeley DB on a fresh datadir") ? 0 : 1;
2657
2658 // 4. Explicit -walletbackend=bdb on a fresh datadir -> wallet.dat.
2659 string d4 = tmp + "/explicit-bdb";
2660 nFail += Check(MakeDirLocal(d4), "explicit-bdb scenario dir can be created") ? 0 : 1;
2661 vector<string> a4;
2662 a4.push_back("-datadir=" + d4);
2663 a4.push_back("-nomanagedtor");
2664 a4.push_back("-nogui");
2665 a4.push_back("-walletbackend=bdb");
2666 a4.push_back("-newaddress");
2667 int r4 = RunBitflashChild(strExe, a4);
2668 nFail += Check(r4 == 0 &&
2669 FileExists((d4 + "/wallet.dat").c_str()) &&
2670 !FileExists((d4 + "/wallet.sqlite").c_str()),
2671 "an explicit -walletbackend=bdb creates wallet.dat on a fresh datadir") ? 0 : 1;
2672 }
2673 catch (const std::exception& e)
2674 {
2675 printf(" FAIL exception: %s\n", e.what());
2676 nFail++;
2677 }
2678 catch (...)
2679 {
2680 printf(" FAIL unknown exception\n");
2681 nFail++;
2682 }
2683
2684 DBFlush(true);
2685 SetCurrentDir(cwd);
2686 RemoveTree(tmp);
2687 printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED",
2688 nFail, nFail == 1 ? "" : "s");
2689 fflush(stdout);
2690 return nFail == 0 ? 0 : 1;
2691}
2692
2693// The wizard's conversion action, headless: build a Berkeley DB wallet, run
2694// DoConvertWalletToSQLite() (what the GUI button calls), and prove it produced a
2695// usable wallet.sqlite + backend marker while leaving wallet.dat untouched, then
2696// that a flagless restart opens the converted SQLite wallet.
2697static int RunWalletConvertSelfTest()
2698{
2699 fflush(stdout);
2700 printf("wallet-convert self-test\n");
2701
2702 std::string tmp, cwd;
2703 if (!MakeTempDir(tmp))
2704 {
2705 printf(" FAIL could not create a temporary data directory\n");
2706 return 1;
2707 }
2708 if (!GetCurrentDir(cwd) || !SetCurrentDir(tmp))
2709 {
2710 printf(" FAIL could not move into the temporary data directory\n");
2711 RemoveTree(tmp);
2712 return 1;
2713 }
2714
2715 int nFail = 0;
2716 strSetDataDir = tmp;
2717 printf(" temp datadir: %s\n", tmp.c_str());
2718
2719#ifdef _WIN32
2720 string strExe = cwd + "\\bitflash.exe";
2721#else
2722 string strExe = cwd + "/bitflash-node";
2723#endif
2724
2725 try
2726 {
2727 if (!LoadWallet())
2728 throw std::runtime_error("LoadWallet failed");
2729
2730 string strError;
2731 nFail += Check(SetHDSeedFromMnemonic(
2732 "abandon abandon abandon abandon abandon abandon abandon "
2733 "abandon abandon abandon abandon about", strError),
2734 "a phrase can be installed before conversion") ? 0 : 1;
2735 TopUpKeyPool();
2736
2737 vector<unsigned char> vchDefaultPubKey = keyUser.GetPubKey();
2738 CPrivKey vchDefaultPrivKey;
2739 nFail += Check(GetWalletPrivKey(vchDefaultPubKey, vchDefaultPrivKey, strError),
2740 "the default private key is readable before conversion") ? 0 : 1;
2741 vector<unsigned char> vchDefaultPrivBytes(vchDefaultPrivKey.begin(),
2742 vchDefaultPrivKey.end());
2743
2744 // The conversion action the GUI button invokes.
2745 int nCopied = 0;
2746 string strConvertError;
2747 bool fConverted = DoConvertWalletToSQLite(nCopied, strConvertError);
2748 nFail += Check(fConverted && nCopied > 0,
2749 "DoConvertWalletToSQLite exports the wallet") ? 0 : 1;
2750 nFail += Check(FileExists((tmp + "/wallet.sqlite").c_str()) &&
2751 FileContainsText(tmp + "/wallet-backend", "sqlite"),
2752 "conversion writes wallet.sqlite and records the sqlite marker") ? 0 : 1;
2753 nFail += Check(FileContainsBytes(tmp + "/wallet.dat", vchDefaultPrivBytes),
2754 "wallet.dat is left untouched by the conversion") ? 0 : 1;
2755
2756 // Idempotence guard: a second conversion refuses rather than overwrite.
2757 int nCopied2 = 0;
2758 string strSecondError;
2759 nFail += Check(!DoConvertWalletToSQLite(nCopied2, strSecondError),
2760 "a second conversion refuses to overwrite wallet.sqlite") ? 0 : 1;
2761
2762 DBFlush(true);
2763
2764 // A flagless restart now opens the converted SQLite wallet (the marker
2765 // routes it) and finds the same recovery phrase.
2766 string strAudit = tmp + "/converted-recovery-audit.txt";
2767 vector<string> vAuditArgs;
2768 vAuditArgs.push_back("-datadir=" + tmp);
2769 vAuditArgs.push_back("-nomanagedtor");
2770 vAuditArgs.push_back("-nogui");
2771 vAuditArgs.push_back("-recoveryaudit");
2772 int nAuditRet = RunBitflashChild(strExe, vAuditArgs, NULL, &strAudit);
2773 nFail += Check(nAuditRet == 0 &&
2774 FileContainsText(strAudit, "recovery phrase: present"),
2775 "a flagless restart opens the converted SQLite wallet") ? 0 : 1;
2776 }
2777 catch (const std::exception& e)
2778 {
2779 printf(" FAIL exception: %s\n", e.what());
2780 nFail++;
2781 }
2782 catch (...)
2783 {
2784 printf(" FAIL unknown exception\n");
2785 nFail++;
2786 }
2787
2788 DBFlush(true);
2789 SetCurrentDir(cwd);
2790 RemoveTree(tmp);
2791 printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED",
2792 nFail, nFail == 1 ? "" : "s");
2793 fflush(stdout);
2794 return nFail == 0 ? 0 : 1;
2795}
2796
2797static int RunWalletPortabilitySelfTest()
2798{
2799 fflush(stdout);
2800 printf("wallet-portability self-test\n");
2801
2802 std::string tmp;
2803 std::string cwd;
2804 if (!MakeTempDir(tmp))
2805 {
2806 printf(" FAIL could not create a temporary data directory\n");
2807 return 1;
2808 }
2809 if (!GetCurrentDir(cwd))
2810 {
2811 printf(" FAIL could not read current directory\n");
2812 RemoveTree(tmp);
2813 return 1;
2814 }
2815
2816 int nFail = 0;
2817 printf(" temp root: %s\n", tmp.c_str());
2818
2819 try
2820 {
2821#ifdef _WIN32
2822 string strExe = cwd + "\\bitflash.exe";
2823#else
2824 string strExe = cwd + "/bitflash-node";
2825#endif
2826 string strOriginal = tmp + "/original";
2827 string strRawCopy = tmp + "/raw-copy";
2828 string strBackupCopy = tmp + "/backup-copy";
2829 nFail += Check(MakeDirLocal(strOriginal) &&
2830 MakeDirLocal(strRawCopy) &&
2831 MakeDirLocal(strBackupCopy),
2832 "temporary wallet directories can be created") ? 0 : 1;
2833
2834 string strCreateOut = tmp + "/create.txt";
2835 vector<string> vCreateArgs;
2836 vCreateArgs.push_back("-datadir=" + strOriginal);
2837 vCreateArgs.push_back("-nomanagedtor");
2838 vCreateArgs.push_back("-nogui");
2839 vCreateArgs.push_back("-walletbackend=bdb"); // this suite is about wallet.dat portability
2840 vCreateArgs.push_back("-newphrase");
2841 int nCreateRet = RunBitflashChild(strExe, vCreateArgs, NULL, &strCreateOut);
2842 nFail += Check(nCreateRet == 0 &&
2843 FileContainsText(strCreateOut, "Write these twelve words down"),
2844 "a phrase wallet can be created in a fresh datadir") ? 0 : 1;
2845
2846 string strOriginalAudit = tmp + "/original-audit.txt";
2847 vector<string> vOriginalAuditArgs;
2848 vOriginalAuditArgs.push_back("-datadir=" + strOriginal);
2849 vOriginalAuditArgs.push_back("-nomanagedtor");
2850 vOriginalAuditArgs.push_back("-nogui");
2851 vOriginalAuditArgs.push_back("-recoveryaudit");
2852 int nOriginalAuditRet = RunBitflashChild(strExe, vOriginalAuditArgs, NULL, &strOriginalAudit);
2853 nFail += Check(nOriginalAuditRet == 0 &&
2854 FileContainsText(strOriginalAudit, "recovery phrase: present") &&
2855 FileContainsText(strOriginalAudit, "SLIP-0044 BITFLASH"),
2856 "the original wallet reports its recovery phrase") ? 0 : 1;
2857
2858 string strOriginalStorageAudit = tmp + "/original-storage-audit.txt";
2859 vector<string> vOriginalStorageAuditArgs;
2860 vOriginalStorageAuditArgs.push_back("-datadir=" + strOriginal);
2861 vOriginalStorageAuditArgs.push_back("-nomanagedtor");
2862 vOriginalStorageAuditArgs.push_back("-nogui");
2863 vOriginalStorageAuditArgs.push_back("-walletstorageaudit");
2864 int nOriginalStorageAuditRet =
2865 RunBitflashChild(strExe, vOriginalStorageAuditArgs, NULL, &strOriginalStorageAudit);
2866 nFail += Check(nOriginalStorageAuditRet == 0 &&
2867 FileContainsText(strOriginalStorageAudit, "Wallet storage audit") &&
2868 FileContainsText(strOriginalStorageAudit, "plain HD seed: complete") &&
2869 FileContainsText(strOriginalStorageAudit, "encrypted HD seed: none") &&
2870 FileContainsText(strOriginalStorageAudit, "malformed records: 0"),
2871 "the storage audit recognizes a plaintext phrase wallet") ? 0 : 1;
2872
2873 string strOriginalStorageCheck = tmp + "/original-storage-check.txt";
2874 vector<string> vOriginalStorageCheckArgs;
2875 vOriginalStorageCheckArgs.push_back("-datadir=" + strOriginal);
2876 vOriginalStorageCheckArgs.push_back("-nomanagedtor");
2877 vOriginalStorageCheckArgs.push_back("-nogui");
2878 vOriginalStorageCheckArgs.push_back("-walletstoragecheck");
2879 int nOriginalStorageCheckRet =
2880 RunBitflashChild(strExe, vOriginalStorageCheckArgs, NULL, &strOriginalStorageCheck);
2881 nFail += Check(nOriginalStorageCheckRet == 0 &&
2882 FileContainsText(strOriginalStorageCheck, "storage sanity: ok") &&
2883 FileContainsText(strOriginalStorageCheck, "plain HD seed: complete"),
2884 "the storage sanity check accepts a plaintext phrase wallet") ? 0 : 1;
2885
2886 string strOriginalStorageAuditJson = tmp + "/original-storage-audit.json";
2887 vector<string> vOriginalStorageAuditJsonArgs;
2888 vOriginalStorageAuditJsonArgs.push_back("-datadir=" + strOriginal);
2889 vOriginalStorageAuditJsonArgs.push_back("-nomanagedtor");
2890 vOriginalStorageAuditJsonArgs.push_back("-nogui");
2891 vOriginalStorageAuditJsonArgs.push_back("-walletstorageauditjson=" +
2892 strOriginalStorageAuditJson);
2893 int nOriginalStorageAuditJsonRet =
2894 RunBitflashChild(strExe, vOriginalStorageAuditJsonArgs);
2895 nFail += Check(nOriginalStorageAuditJsonRet == 0 &&
2896 FileContainsText(strOriginalStorageAuditJson,
2897 "\"format\": \"bitflash-wallet-storage-audit-v1\"") &&
2898 FileContainsText(strOriginalStorageAuditJson,
2899 "\"plain_hd_seed\": \"complete\"") &&
2900 FileContainsText(strOriginalStorageAuditJson,
2901 "\"encrypted_hd_seed\": \"none\""),
2902 "the storage audit can write plaintext-wallet JSON") ? 0 : 1;
2903
2904 string strPortableWallet = tmp + "/portable-wallet.dat";
2905 string strBackupOut = tmp + "/backupwallet.txt";
2906 vector<string> vBackupArgs;
2907 vBackupArgs.push_back("-datadir=" + strOriginal);
2908 vBackupArgs.push_back("-nomanagedtor");
2909 vBackupArgs.push_back("-nogui");
2910 vBackupArgs.push_back("-backupwallet=" + strPortableWallet);
2911 int nBackupRet = RunBitflashChild(strExe, vBackupArgs, NULL, &strBackupOut);
2912 nFail += Check(nBackupRet == 0 && FileExists(strPortableWallet.c_str()),
2913 "/backupwallet writes a portable wallet.dat") ? 0 : 1;
2914
2915 string strBackupOut2 = tmp + "/backupwallet-overwrite.txt";
2916 int nBackupRet2 = RunBitflashChild(strExe, vBackupArgs, NULL, &strBackupOut2);
2917 nFail += Check(nBackupRet2 == 0 && FileExists(strPortableWallet.c_str()) &&
2918 !DirectoryHasFileWithPrefix(tmp, "portable-wallet.dat.tmp.") &&
2919 !DirectoryHasFileWithPrefix(tmp, "portable-wallet.dat.old."),
2920 "/backupwallet replaces an existing backup without leaving temp files") ? 0 : 1;
2921
2922 nFail += Check(CopyFileLocal(strOriginal + "/wallet.dat",
2923 strRawCopy + "/wallet.dat"),
2924 "a raw wallet.dat can be copied without database logs") ? 0 : 1;
2925 string strRawAudit = tmp + "/raw-copy-audit.txt";
2926 vector<string> vRawAuditArgs;
2927 vRawAuditArgs.push_back("-datadir=" + strRawCopy);
2928 vRawAuditArgs.push_back("-nomanagedtor");
2929 vRawAuditArgs.push_back("-nogui");
2930 vRawAuditArgs.push_back("-recoveryaudit");
2931 int nRawAuditRet = RunBitflashChild(strExe, vRawAuditArgs, NULL, &strRawAudit);
2932 nFail += Check(nRawAuditRet == 0 &&
2933 FileContainsText(strRawAudit, "recovery phrase: present") &&
2934 !FileContainsText(strRawAudit, "Cannot open wallet.dat"),
2935 "a raw wallet.dat copy opens in a new datadir") ? 0 : 1;
2936
2937 nFail += Check(CopyFileLocal(strPortableWallet,
2938 strBackupCopy + "/wallet.dat"),
2939 "the portable backup can be installed as wallet.dat") ? 0 : 1;
2940 string strBackupAudit = tmp + "/backup-copy-audit.txt";
2941 vector<string> vBackupAuditArgs;
2942 vBackupAuditArgs.push_back("-datadir=" + strBackupCopy);
2943 vBackupAuditArgs.push_back("-nomanagedtor");
2944 vBackupAuditArgs.push_back("-nogui");
2945 vBackupAuditArgs.push_back("-recoveryaudit");
2946 int nBackupAuditRet = RunBitflashChild(strExe, vBackupAuditArgs, NULL, &strBackupAudit);
2947 nFail += Check(nBackupAuditRet == 0 &&
2948 FileContainsText(strBackupAudit, "recovery phrase: present") &&
2949 FileContainsText(strBackupAudit, "SLIP-0044 BITFLASH"),
2950 "a /backupwallet copy opens in a new datadir") ? 0 : 1;
2951
2952 string strPassFile = tmp + "/encrypt-pass.txt";
2953 string strEncryptOut = tmp + "/encryptwallet.txt";
2954 nFail += Check(WriteTextFile(strPassFile, "portable-test-passphrase\n"),
2955 "an encryption passphrase file can be written") ? 0 : 1;
2956 vector<string> vEncryptArgs;
2957 vEncryptArgs.push_back("-datadir=" + strOriginal);
2958 vEncryptArgs.push_back("-nomanagedtor");
2959 vEncryptArgs.push_back("-nogui");
2960 vEncryptArgs.push_back("-encryptwallet=@" + strPassFile);
2961 int nEncryptRet = RunBitflashChild(strExe, vEncryptArgs, NULL, &strEncryptOut);
2962 nFail += Check(nEncryptRet == 0 &&
2963 !DirectoryHasFileWithPrefix(strOriginal + "/database", "log."),
2964 "the encryptwallet command purges Berkeley DB environment logs") ? 0 : 1;
2965
2966 string strEncryptedStorageAudit = tmp + "/encrypted-storage-audit.txt";
2967 vector<string> vEncryptedStorageAuditArgs;
2968 vEncryptedStorageAuditArgs.push_back("-datadir=" + strOriginal);
2969 vEncryptedStorageAuditArgs.push_back("-nomanagedtor");
2970 vEncryptedStorageAuditArgs.push_back("-nogui");
2971 vEncryptedStorageAuditArgs.push_back("-walletstorageaudit");
2972 int nEncryptedStorageAuditRet =
2973 RunBitflashChild(strExe, vEncryptedStorageAuditArgs, NULL, &strEncryptedStorageAudit);
2974 nFail += Check(nEncryptedStorageAuditRet == 0 &&
2975 FileContainsText(strEncryptedStorageAudit, "plain private keys: 0") &&
2976 FileContainsText(strEncryptedStorageAudit, "encrypted private keys:") &&
2977 FileContainsText(strEncryptedStorageAudit, "plain HD seed: none") &&
2978 FileContainsText(strEncryptedStorageAudit, "encrypted HD seed: complete") &&
2979 FileContainsText(strEncryptedStorageAudit, "wallet minimum version: present") &&
2980 FileContainsText(strEncryptedStorageAudit, "malformed records: 0"),
2981 "the storage audit recognizes an encrypted wallet") ? 0 : 1;
2982
2983 string strEncryptedStorageCheck = tmp + "/encrypted-storage-check.txt";
2984 vector<string> vEncryptedStorageCheckArgs;
2985 vEncryptedStorageCheckArgs.push_back("-datadir=" + strOriginal);
2986 vEncryptedStorageCheckArgs.push_back("-nomanagedtor");
2987 vEncryptedStorageCheckArgs.push_back("-nogui");
2988 vEncryptedStorageCheckArgs.push_back("-walletstoragecheck");
2989 int nEncryptedStorageCheckRet =
2990 RunBitflashChild(strExe, vEncryptedStorageCheckArgs, NULL, &strEncryptedStorageCheck);
2991 nFail += Check(nEncryptedStorageCheckRet == 0 &&
2992 FileContainsText(strEncryptedStorageCheck, "storage sanity: ok") &&
2993 FileContainsText(strEncryptedStorageCheck, "plain private keys: 0") &&
2994 FileContainsText(strEncryptedStorageCheck, "encrypted HD seed: complete"),
2995 "the storage sanity check accepts an encrypted wallet") ? 0 : 1;
2996
2997 string strEncryptedStorageAuditJson = tmp + "/encrypted-storage-audit.json";
2998 vector<string> vEncryptedStorageAuditJsonArgs;
2999 vEncryptedStorageAuditJsonArgs.push_back("-datadir=" + strOriginal);
3000 vEncryptedStorageAuditJsonArgs.push_back("-nomanagedtor");
3001 vEncryptedStorageAuditJsonArgs.push_back("-nogui");
3002 vEncryptedStorageAuditJsonArgs.push_back("-walletstorageauditjson=" +
3003 strEncryptedStorageAuditJson);
3004 int nEncryptedStorageAuditJsonRet =
3005 RunBitflashChild(strExe, vEncryptedStorageAuditJsonArgs);
3006 nFail += Check(nEncryptedStorageAuditJsonRet == 0 &&
3007 FileContainsText(strEncryptedStorageAuditJson,
3008 "\"plain_private_keys\": 0") &&
3009 FileContainsText(strEncryptedStorageAuditJson,
3010 "\"plain_hd_seed\": \"none\"") &&
3011 FileContainsText(strEncryptedStorageAuditJson,
3012 "\"encrypted_hd_seed\": \"complete\"") &&
3013 FileContainsText(strEncryptedStorageAuditJson,
3014 "\"wallet_minimum_version\": \"present\""),
3015 "the storage audit can write encrypted-wallet JSON") ? 0 : 1;
3016 }
3017 catch (const std::exception& e)
3018 {
3019 printf(" FAIL exception: %s\n", e.what());
3020 nFail++;
3021 }
3022 catch (...)
3023 {
3024 printf(" FAIL unknown exception\n");
3025 nFail++;
3026 }
3027
3028 RemoveTree(tmp);
3029 printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED",
3030 nFail, nFail == 1 ? "" : "s");
3031 fflush(stdout);
3032 return nFail == 0 ? 0 : 1;
3033}
3034
1100static int RunNetMessageSelfTest() 3035static int RunNetMessageSelfTest()
1101{ 3036{
1102 fflush(stdout); 3037 fflush(stdout);
@@ -1105,6 +3040,13 @@ static int RunNetMessageSelfTest()
1105 int nFail = 0; 3040 int nFail = 0;
1106 try 3041 try
1107 { 3042 {
3043 // A peer that has not yet been kept waiting must not look like one that
3044 // has been waiting since the epoch. This is the whole bug: the stall
3045 // clock lives in CNode, so only the constructor can start it at zero.
3046 CNode opened(INVALID_SOCKET, CAddress("127.0.0.1"));
3047 nFail += Check(opened.nMsgWaitSince == 0 && opened.nMsgWaitSize == 0,
3048 "a new peer starts with its stall clock stopped") ? 0 : 1;
3049
1108 CNode complete(INVALID_SOCKET, CAddress("127.0.0.1")); 3050 CNode complete(INVALID_SOCKET, CAddress("127.0.0.1"));
1109 complete.nVersion = VERSION; 3051 complete.nVersion = VERSION;
1110 complete.vRecv << CMessageHeader("ping", 0); 3052 complete.vRecv << CMessageHeader("ping", 0);
@@ -1119,20 +3061,38 @@ static int RunNetMessageSelfTest()
1119 nFail += Check(ProcessMessages(&partial), "an incomplete message returns cleanly") ? 0 : 1; 3061 nFail += Check(ProcessMessages(&partial), "an incomplete message returns cleanly") ? 0 : 1;
1120 nFail += Check(partial.vRecv.size() == nPartialBefore, 3062 nFail += Check(partial.vRecv.size() == nPartialBefore,
1121 "an incomplete message keeps one header in the buffer") ? 0 : 1; 3063 "an incomplete message keeps one header in the buffer") ? 0 : 1;
1122 nFail += Check(partial.nIncompleteMessageStart != 0, 3064 nFail += Check(partial.nMsgWaitSince != 0,
1123 "an incomplete message starts a timeout clock") ? 0 : 1; 3065 "an incomplete message starts the stall clock") ? 0 : 1;
3066 nFail += Check(partial.nMsgWaitSize == 0,
3067 "the stall clock records how much payload had arrived") ? 0 : 1;
1124 nFail += Check(!partial.fDisconnect, 3068 nFail += Check(!partial.fDisconnect,
1125 "a fresh incomplete message does not disconnect immediately") ? 0 : 1; 3069 "a fresh incomplete message does not disconnect immediately") ? 0 : 1;
1126 3070
3071 // Still arriving, just slowly: more payload is here than when the clock
3072 // was started, so the clock restarts however old it was. Timing the
3073 // wait instead of the stall would drop this peer.
3074 CNode slow(INVALID_SOCKET, CAddress("127.0.0.1"));
3075 slow.nVersion = VERSION;
3076 slow.vRecv << CMessageHeader("block", 100);
3077 slow.vRecv.insert(slow.vRecv.end(), 50, '\0');
3078 slow.nMsgWaitSince = GetTime() - BTF_MSG_WAIT_MAX_SECS - 1;
3079 slow.nMsgWaitSize = 10;
3080 ProcessMessages(&slow);
3081 nFail += Check(!slow.fDisconnect,
3082 "a peer still sending payload is not dropped") ? 0 : 1;
3083 nFail += Check(slow.nMsgWaitSize == 50,
3084 "progress restarts the stall clock") ? 0 : 1;
3085
3086 // Nothing more has arrived since the clock started, and the clock is
3087 // older than the limit.
1127 CNode stale(INVALID_SOCKET, CAddress("127.0.0.1")); 3088 CNode stale(INVALID_SOCKET, CAddress("127.0.0.1"));
1128 stale.nVersion = VERSION; 3089 stale.nVersion = VERSION;
1129 stale.vRecv << CMessageHeader("block", 100); 3090 stale.vRecv << CMessageHeader("block", 100);
1130 stale.nIncompleteMessageStart = GetTime() - BTF_INCOMPLETE_MESSAGE_TIMEOUT_SECS - 1; 3091 stale.nMsgWaitSince = GetTime() - BTF_MSG_WAIT_MAX_SECS - 1;
1131 stale.nIncompleteMessageSize = 100; 3092 stale.nMsgWaitSize = 0;
1132 stale.strIncompleteMessageCommand = "block";
1133 ProcessMessages(&stale); 3093 ProcessMessages(&stale);
1134 nFail += Check(stale.fDisconnect, 3094 nFail += Check(stale.fDisconnect,
1135 "a stale incomplete message disconnects the peer") ? 0 : 1; 3095 "a stalled incomplete message disconnects the peer") ? 0 : 1;
1136 3096
1137 CNode oversized(INVALID_SOCKET, CAddress("127.0.0.1")); 3097 CNode oversized(INVALID_SOCKET, CAddress("127.0.0.1"));
1138 oversized.nVersion = VERSION; 3098 oversized.nVersion = VERSION;
@@ -1158,6 +3118,87 @@ static int RunNetMessageSelfTest()
1158 return nFail == 0 ? 0 : 1; 3118 return nFail == 0 ? 0 : 1;
1159} 3119}
1160 3120
3121static int RunNetworkParamsSelfTest()
3122{
3123 fflush(stdout);
3124 printf("network-params self-test\n");
3125
3126 int nFail = 0;
3127 std::string strOldDataDir = strSetDataDir;
3128 try
3129 {
3130 SelectChainParams(false);
3131 nFail += Check(!IsTestNet(), "starts on mainnet after explicit selection") ? 0 : 1;
3132 nFail += Check(ntohs(GetDefaultPort()) == MAINNET_PORT,
3133 "mainnet default P2P port is 8433") ? 0 : 1;
3134 CAddress mainAddr("127.0.0.1");
3135 nFail += Check(mainAddr.port == htons(MAINNET_PORT),
3136 "mainnet address parsing uses the mainnet default port") ? 0 : 1;
3137 CMessageHeader mainHeader("ping", 0);
3138 char mainMagic[4];
3139 memcpy(mainMagic, mainHeader.pchMessageStart, sizeof(mainMagic));
3140 uint256 mainGenesis = hashGenesisBlock;
3141 uint256 mainMerkle = hashGenesisMerkleRoot;
3142 unsigned int mainNonce = GENESIS_NONCE;
3143
3144 SelectChainParams(true);
3145 nFail += Check(IsTestNet(), "testnet selection is sticky") ? 0 : 1;
3146 nFail += Check(ntohs(GetDefaultPort()) == TESTNET_PORT,
3147 "testnet default P2P port is 18433") ? 0 : 1;
3148 CAddress testAddr("127.0.0.1");
3149 nFail += Check(testAddr.port == htons(TESTNET_PORT),
3150 "testnet address parsing uses the testnet default port") ? 0 : 1;
3151 CMessageHeader testHeader("ping", 0);
3152 nFail += Check(memcmp(mainMagic, testHeader.pchMessageStart, sizeof(mainMagic)) != 0,
3153 "testnet message magic differs from mainnet") ? 0 : 1;
3154 nFail += Check(hashGenesisBlock != mainGenesis &&
3155 hashGenesisMerkleRoot != mainMerkle &&
3156 GENESIS_NONCE != mainNonce,
3157 "testnet genesis constants differ from mainnet") ? 0 : 1;
3158 nFail += Check(hashGenesisBlock == uint256("0xa9d11c6d697bcb7aea0653bc088b12dcf9f44e732df205162cf085fd9d9963eb") &&
3159 hashGenesisMerkleRoot == uint256("0x510b293105e3ff85b8d4c3920e357f85d1a86bf199eb4e7d11f04f380030ec5c") &&
3160 GENESIS_NONCE == 3199,
3161 "testnet genesis constants are pinned") ? 0 : 1;
3162
3163 std::string tmp;
3164 if (!MakeTempDir(tmp))
3165 {
3166 nFail += Check(false, "creates a temporary data directory") ? 0 : 1;
3167 }
3168 else
3169 {
3170 strSetDataDir = tmp;
3171 SelectChainParams(false);
3172 std::string mainDir = GetAppDir();
3173 SelectChainParams(true);
3174 std::string testDir = GetAppDir();
3175 nFail += Check(mainDir == tmp,
3176 "mainnet uses the selected data directory") ? 0 : 1;
3177 nFail += Check(testDir == tmp + "/testnet",
3178 "testnet stores data below a testnet subdirectory") ? 0 : 1;
3179 RemoveTree(tmp);
3180 }
3181 }
3182 catch (const std::exception& e)
3183 {
3184 printf(" FAIL exception: %s\n", e.what());
3185 nFail++;
3186 }
3187 catch (...)
3188 {
3189 printf(" FAIL unknown exception\n");
3190 nFail++;
3191 }
3192
3193 strSetDataDir = strOldDataDir;
3194 SelectChainParams(false);
3195
3196 printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED",
3197 nFail, nFail == 1 ? "" : "s");
3198 fflush(stdout);
3199 return nFail == 0 ? 0 : 1;
3200}
3201
1161static CBlock MakeSizedConsensusBlock(size_t nScriptBytes) 3202static CBlock MakeSizedConsensusBlock(size_t nScriptBytes)
1162{ 3203{
1163 CTransaction tx; 3204 CTransaction tx;
@@ -1175,6 +3216,74 @@ static CBlock MakeSizedConsensusBlock(size_t nScriptBytes)
1175 return block; 3216 return block;
1176} 3217}
1177 3218
3219// A distinct transaction of a chosen weight and verification cost. nUnique
3220// only keeps the txids apart, so a mempool built from these is not a pile of
3221// duplicates a block could never carry anyway.
3222static CTransaction MakeSizedTx(size_t nPadBytes, unsigned int nSigOps, int nUnique)
3223{
3224 CTransaction tx;
3225 tx.vin.push_back(CTxIn(COutPoint(), CScript() << (int64)nUnique << 1));
3226 tx.vout.push_back(CTxOut(0, CScript()));
3227 for (unsigned int i = 0; i < nSigOps; i++)
3228 tx.vout[0].scriptPubKey << OP_CHECKSIG;
3229 tx.vout[0].scriptPubKey.insert(tx.vout[0].scriptPubKey.end(), nPadBytes, 0);
3230 return tx;
3231}
3232
3233static CBlock MakeCoinbaseOnlyTemplate()
3234{
3235 CTransaction txNew;
3236 txNew.vin.resize(1);
3237 txNew.vin[0].prevout.SetNull();
3238 txNew.vin[0].scriptSig << 1 << 1 << 1;
3239 txNew.vout.resize(1);
3240 txNew.vout[0].scriptPubKey << OP_CHECKSIG;
3241
3242 CBlock block;
3243 block.vtx.push_back(txNew);
3244 block.nVersion = 1;
3245 block.nTime = GetAdjustedTime();
3246 block.nBits = bnProofOfWorkLimit.GetCompact();
3247 block.nNonce = 0;
3248 return block;
3249}
3250
3251// The sum CheckBlock() takes, spelled the same way.
3252static unsigned int BlockSigOpCount(const CBlock& block)
3253{
3254 unsigned int n = 0;
3255 for (int i = 0; i < (int)block.vtx.size(); i++)
3256 n += block.vtx[i].GetSigOpCount();
3257 return n;
3258}
3259
3260// Drive the selection rule the way both assemblers drive it: ask
3261// BlockTemplateFits before appending, skip what does not fit, never stop early
3262// on one that does not.
3263//
3264// This mirrors the loops in BitcoinMiner() and RebuildJob() rather than calling
3265// them -- one needs a CTxDB, a wallet key and cs_main, the other is static in
3266// rpc.cpp, so neither is reachable from here. What that leaves proven is the
3267// rule the two of them share and the property it is meant to guarantee, which
3268// is where the defect actually lived: the budget was right and the enforcement
3269// was not.
3270static void SelectIntoTemplate(CBlock& block, const vector<CTransaction>& vMempool)
3271{
3272 unsigned int nBlockSize = ::GetSerializeSize(block, SER_DISK);
3273 unsigned int nBlockSigOps = block.vtx[0].GetSigOpCount();
3274 for (int i = 0; i < (int)vMempool.size(); i++)
3275 {
3276 const CTransaction& tx = vMempool[i];
3277 unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK);
3278 unsigned int nTxSigOps = tx.GetSigOpCount();
3279 if (!BlockTemplateFits(nBlockSize, nBlockSigOps, nTxSize, nTxSigOps))
3280 continue;
3281 block.vtx.push_back(tx);
3282 nBlockSize += nTxSize;
3283 nBlockSigOps += nTxSigOps;
3284 }
3285}
3286
1178static int RunConsensusLimitsSelfTest() 3287static int RunConsensusLimitsSelfTest()
1179{ 3288{
1180 fflush(stdout); 3289 fflush(stdout);
@@ -1198,6 +3307,95 @@ static int RunConsensusLimitsSelfTest()
1198 "the test block sits between 1 MB and the old 32 MB cap") ? 0 : 1; 3307 "the test block sits between 1 MB and the old 32 MB cap") ? 0 : 1;
1199 nFail += Check(!oversized.CheckSizeLimits(), 3308 nFail += Check(!oversized.CheckSizeLimits(),
1200 "a block above 1 MB is outside the consensus size limit") ? 0 : 1; 3309 "a block above 1 MB is outside the consensus size limit") ? 0 : 1;
3310
3311 // The other half of the cap. Everything above asks what validation
3312 // refuses; nothing asked what assembly produces, which is why a miner
3313 // that could overshoot the cap by an entire mempool went unnoticed.
3314 nFail += Check(MAX_BLOCK_SIZE_GEN < MAX_BLOCK_SIZE,
3315 "the assembly budget stays under the consensus cap") ? 0 : 1;
3316
3317 // The rule itself, at its edges. The sigop comparison has to match
3318 // CheckBlock(), which fails on `> MAX_BLOCK_SIGOPS` and so accepts a
3319 // block sitting exactly on the limit.
3320 nFail += Check(BlockTemplateFits(MAX_BLOCK_SIZE_GEN - 100, 0, 100, 0),
3321 "a transaction that exactly fills the size budget fits") ? 0 : 1;
3322 nFail += Check(!BlockTemplateFits(MAX_BLOCK_SIZE_GEN - 100, 0, 101, 0),
3323 "one byte past the size budget does not fit") ? 0 : 1;
3324 nFail += Check(BlockTemplateFits(0, 0, 0, MAX_BLOCK_SIGOPS),
3325 "sigops exactly on the limit fit, as CheckBlock() allows") ? 0 : 1;
3326 nFail += Check(!BlockTemplateFits(0, 0, 0, MAX_BLOCK_SIGOPS + 1),
3327 "one sigop past the limit does not fit") ? 0 : 1;
3328
3329 // A mempool several times the consensus cap, in ordinary-sized
3330 // transactions. The old miner tested its budget only on the outer
3331 // loop, so a single pass appended all of this.
3332 {
3333 CBlock block = MakeCoinbaseOnlyTemplate();
3334 vector<CTransaction> vMempool;
3335 for (int i = 0; i < 300; i++)
3336 vMempool.push_back(MakeSizedTx(10000, 1, i));
3337 SelectIntoTemplate(block, vMempool);
3338 block.hashMerkleRoot = block.BuildMerkleTree();
3339
3340 nFail += Check(block.vtx.size() > 1,
3341 "a full mempool still fills a template") ? 0 : 1;
3342 nFail += Check(::GetSerializeSize(block, SER_DISK) <= MAX_BLOCK_SIZE_GEN,
3343 "a template built from a 3 MB mempool respects the budget") ? 0 : 1;
3344 nFail += Check(block.CheckSizeLimits(),
3345 "that template is one CheckBlock() would accept on size") ? 0 : 1;
3346 }
3347
3348 // One transaction too big for the budget. Nothing on the path into the
3349 // mempool caps transaction size, so this is a transaction a peer can
3350 // simply send. It must be skipped and the rest still taken -- skipped,
3351 // not stopped on.
3352 {
3353 CBlock block = MakeCoinbaseOnlyTemplate();
3354 vector<CTransaction> vMempool;
3355 vMempool.push_back(MakeSizedTx(1500000, 1, 9999));
3356 for (int i = 0; i < 10; i++)
3357 vMempool.push_back(MakeSizedTx(1000, 1, i));
3358
3359 unsigned int nHugeSize = ::GetSerializeSize(vMempool[0], SER_NETWORK);
3360 nFail += Check(nHugeSize > MAX_BLOCK_SIZE && nHugeSize <= MAX_SIZE,
3361 "the outsized transaction is one the mempool would hold") ? 0 : 1;
3362
3363 SelectIntoTemplate(block, vMempool);
3364 block.hashMerkleRoot = block.BuildMerkleTree();
3365
3366 bool fTookHuge = false;
3367 for (int i = 0; i < (int)block.vtx.size(); i++)
3368 if (block.vtx[i].GetHash() == vMempool[0].GetHash())
3369 fTookHuge = true;
3370
3371 nFail += Check(!fTookHuge,
3372 "a transaction larger than the budget is left out") ? 0 : 1;
3373 nFail += Check(block.vtx.size() == 11,
3374 "the ten that do fit are taken anyway") ? 0 : 1;
3375 nFail += Check(block.CheckSizeLimits(),
3376 "one outsized transaction cannot invalidate a template") ? 0 : 1;
3377 }
3378
3379 // Transactions that are cheap in bytes and expensive to verify. Size
3380 // alone does not bound what a block costs, and CheckBlock() refuses on
3381 // the count regardless of how small the block is.
3382 {
3383 CBlock block = MakeCoinbaseOnlyTemplate();
3384 vector<CTransaction> vMempool;
3385 for (int i = 0; i < 100; i++)
3386 vMempool.push_back(MakeSizedTx(0, 1000, i));
3387 SelectIntoTemplate(block, vMempool);
3388 block.hashMerkleRoot = block.BuildMerkleTree();
3389
3390 nFail += Check(block.vtx.size() > 1,
3391 "sigop-heavy transactions are still selected") ? 0 : 1;
3392 nFail += Check(BlockSigOpCount(block) <= MAX_BLOCK_SIGOPS,
3393 "selection stops at the sigop cap CheckBlock() enforces") ? 0 : 1;
3394 nFail += Check(::GetSerializeSize(block, SER_DISK) < MAX_BLOCK_SIZE_GEN,
3395 "and stops on sigops while still well inside the size budget") ? 0 : 1;
3396 nFail += Check(block.CheckSizeLimits(),
3397 "the sigop-bounded template is within the size limit too") ? 0 : 1;
3398 }
1201 } 3399 }
1202 catch (const std::exception& e) 3400 catch (const std::exception& e)
1203 { 3401 {
@@ -1216,6 +3414,357 @@ static int RunConsensusLimitsSelfTest()
1216 return nFail == 0 ? 0 : 1; 3414 return nFail == 0 ? 0 : 1;
1217} 3415}
1218 3416
3417static int RunParseMoneySelfTest()
3418{
3419 printf("parse-money self-test\n");
3420 int nFail = 0;
3421 struct Case { const char* in; bool ok; int64 val; };
3422 Case cases[] = {
3423 {"0.5", true, 50000000LL},
3424 {"1", true, 100000000LL},
3425 {"1.23456789", true, 123456789LL},
3426 {"0.00000001", true, 1LL},
3427 {"10.00000000", true, 1000000000LL},
3428 {"1,000", true, 100000000000LL},
3429 {"1.5 ", true, 150000000LL},
3430 {"0.123456789", false, 0LL},
3431 {"1.2.3", false, 0LL},
3432 {"abc", false, 0LL},
3433 {"-1", false, 0LL},
3434 {"1e5", false, 0LL},
3435 };
3436 for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++)
3437 {
3438 int64 v = -1;
3439 bool r = ParseMoney(cases[i].in, v);
3440 bool pass = (r == cases[i].ok) && (!r || v == cases[i].val);
3441 char desc[160];
3442 snprintf(desc, sizeof(desc), "ParseMoney(\"%s\") %s and value matches",
3443 cases[i].in, cases[i].ok ? "accepts" : "rejects");
3444 nFail += Check(pass, desc) ? 0 : 1;
3445 }
3446 printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED",
3447 nFail, nFail == 1 ? "" : "s");
3448 fflush(stdout);
3449 return nFail == 0 ? 0 : 1;
3450}
3451
3452static bool SelfTestReadN(SOCKET s, void* buf, int n)
3453{
3454 char* p = (char*)buf;
3455 int off = 0;
3456 while (off < n)
3457 {
3458 int r = recv(s, p + off, n - off, 0);
3459 if (r <= 0)
3460 return false;
3461 off += r;
3462 }
3463 return true;
3464}
3465
3466static bool SelfTestWriteN(SOCKET s, const void* buf, int n)
3467{
3468 const char* p = (const char*)buf;
3469 int off = 0;
3470 while (off < n)
3471 {
3472 int r = send(s, p + off, n - off, 0);
3473 if (r <= 0)
3474 return false;
3475 off += r;
3476 }
3477 return true;
3478}
3479
3480static void SelfTestSetSocketTimeout(SOCKET s, int nTimeoutSecs)
3481{
3482#ifdef _WIN32
3483 DWORD tv = (DWORD)nTimeoutSecs * 1000;
3484 setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv));
3485 setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tv, sizeof(tv));
3486#else
3487 struct timeval tv;
3488 tv.tv_sec = nTimeoutSecs;
3489 tv.tv_usec = 0;
3490 setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
3491 setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
3492#endif
3493}
3494
3495#ifdef _WIN32
3496struct SelfTestWinsock
3497{
3498 bool fStarted;
3499 SelfTestWinsock() : fStarted(false)
3500 {
3501 WSADATA wsadata;
3502 fStarted = (WSAStartup(MAKEWORD(2,2), &wsadata) == 0);
3503 }
3504 ~SelfTestWinsock()
3505 {
3506 if (fStarted)
3507 WSACleanup();
3508 }
3509};
3510#endif
3511
3512static bool RunSocks5HandshakeProbe(std::string& errOut)
3513{
3514 errOut.clear();
3515#ifdef _WIN32
3516 SelfTestWinsock winsock;
3517 if (!winsock.fStarted)
3518 {
3519 errOut = "could not start Winsock";
3520 return false;
3521 }
3522#endif
3523
3524 SOCKET listener = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
3525 if (listener == INVALID_SOCKET)
3526 {
3527 errOut = "could not create listener";
3528 return false;
3529 }
3530 SelfTestSetSocketTimeout(listener, 5);
3531
3532 struct sockaddr_in addr;
3533 memset(&addr, 0, sizeof(addr));
3534 addr.sin_family = AF_INET;
3535 addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
3536 addr.sin_port = 0;
3537 if (bind(listener, (struct sockaddr*)&addr, sizeof(addr)) != 0 ||
3538 listen(listener, 1) != 0)
3539 {
3540 closesocket(listener);
3541 errOut = "could not bind/listen on loopback";
3542 return false;
3543 }
3544
3545#ifdef _WIN32
3546 int addrLen = sizeof(addr);
3547#else
3548 socklen_t addrLen = sizeof(addr);
3549#endif
3550 if (getsockname(listener, (struct sockaddr*)&addr, &addrLen) != 0)
3551 {
3552 closesocket(listener);
3553 errOut = "could not read listener port";
3554 return false;
3555 }
3556 unsigned short proxyPort = ntohs(addr.sin_port);
3557
3558 bool fServerOk = false;
3559 std::string serverErr;
3560 std::string requestedHost;
3561 unsigned short requestedPort = 0;
3562 std::thread server([&]() {
3563 SOCKET s = accept(listener, NULL, NULL);
3564 if (s == INVALID_SOCKET)
3565 {
3566 serverErr = "accept failed";
3567 return;
3568 }
3569 SelfTestSetSocketTimeout(s, 5);
3570
3571 unsigned char hello[3] = {0,0,0};
3572 if (!SelfTestReadN(s, hello, sizeof(hello)) ||
3573 hello[0] != 0x05 || hello[1] != 0x01 || hello[2] != 0x00)
3574 {
3575 serverErr = "bad SOCKS5 greeting";
3576 closesocket(s);
3577 return;
3578 }
3579 unsigned char choice[2] = {0x05, 0x00};
3580 if (!SelfTestWriteN(s, choice, sizeof(choice)))
3581 {
3582 serverErr = "could not write method choice";
3583 closesocket(s);
3584 return;
3585 }
3586
3587 unsigned char hdr[5] = {0,0,0,0,0};
3588 if (!SelfTestReadN(s, hdr, sizeof(hdr)) ||
3589 hdr[0] != 0x05 || hdr[1] != 0x01 || hdr[2] != 0x00 || hdr[3] != 0x03)
3590 {
3591 serverErr = "CONNECT did not use domain-name address type";
3592 closesocket(s);
3593 return;
3594 }
3595
3596 unsigned char len = hdr[4];
3597 std::vector<unsigned char> rest((size_t)len + 2);
3598 if (!SelfTestReadN(s, &rest[0], (int)rest.size()))
3599 {
3600 serverErr = "could not read CONNECT target";
3601 closesocket(s);
3602 return;
3603 }
3604 requestedHost.assign((const char*)&rest[0], (size_t)len);
3605 requestedPort = ((unsigned short)rest[len] << 8) | rest[len + 1];
3606
3607 unsigned char reply[10] = {0x05, 0x00, 0x00, 0x01, 0,0,0,0, 0,0};
3608 if (!SelfTestWriteN(s, reply, sizeof(reply)))
3609 {
3610 serverErr = "could not write CONNECT reply";
3611 closesocket(s);
3612 return;
3613 }
3614 fServerOk = true;
3615 closesocket(s);
3616 });
3617
3618 std::string err;
3619 BtfClearSocks5Proxy();
3620 bool fClientOk = BtfSetSocks5Proxy(strprintf("127.0.0.1:%u", (unsigned)proxyPort), err);
3621 SOCKET client = INVALID_SOCKET;
3622 if (fClientOk)
3623 client = BtfConnectSocket("relay.example", 443, SOCK_NOSTR, 5);
3624 if (client != INVALID_SOCKET)
3625 BtfCloseSocket(client);
3626 BtfClearSocks5Proxy();
3627 closesocket(listener);
3628 server.join();
3629
3630 if (!fClientOk)
3631 errOut = strprintf("could not enable proxy: %s", err.c_str());
3632 else if (client == INVALID_SOCKET)
3633 errOut = "client SOCKS5 connect failed";
3634 else if (!fServerOk)
3635 errOut = serverErr.empty() ? "mock server failed" : serverErr;
3636 else if (requestedHost != "relay.example" || requestedPort != 443)
3637 errOut = strprintf("unexpected CONNECT target %s:%u", requestedHost.c_str(), (unsigned)requestedPort);
3638 else
3639 return true;
3640 return false;
3641}
3642
3643static int RunSocks5ProxySelfTest()
3644{
3645 printf("socks5-proxy self-test\n");
3646 int nFail = 0;
3647 std::string host, err;
3648 unsigned short port = 0;
3649
3650 nFail += Check(BtfParseSocks5Proxy("127.0.0.1:9050", host, port, err) &&
3651 host == "127.0.0.1" && port == 9050,
3652 "parses local Tor proxy endpoint") ? 0 : 1;
3653 nFail += Check(BtfParseSocks5Proxy("localhost:9050", host, port, err) &&
3654 host == "localhost" && port == 9050,
3655 "parses hostname proxy endpoint") ? 0 : 1;
3656 nFail += Check(BtfParseSocks5Proxy("[::1]:9050", host, port, err) &&
3657 host == "::1" && port == 9050,
3658 "parses bracketed IPv6 proxy endpoint") ? 0 : 1;
3659 nFail += Check(!BtfParseSocks5Proxy("::1:9050", host, port, err),
3660 "rejects unbracketed IPv6 proxy endpoint") ? 0 : 1;
3661 nFail += Check(!BtfParseSocks5Proxy("localhost", host, port, err),
3662 "rejects missing port") ? 0 : 1;
3663 nFail += Check(!BtfParseSocks5Proxy("localhost:0", host, port, err),
3664 "rejects zero port") ? 0 : 1;
3665 nFail += Check(!BtfParseSocks5Proxy("localhost:70000", host, port, err),
3666 "rejects out-of-range port") ? 0 : 1;
3667 nFail += Check(!BtfParseSocks5Proxy("localhost:999999999999999999999999999999", host, port, err),
3668 "rejects overflowing port") ? 0 : 1;
3669 nFail += Check(!BtfParseSocks5Proxy("user:pass@localhost:9050", host, port, err),
3670 "rejects unsupported authenticated proxy syntax") ? 0 : 1;
3671 nFail += Check(BtfIsTorOnionHost("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd.onion") &&
3672 BtfIsTorOnionHost("Relay.Example.ONION.") &&
3673 !BtfIsTorOnionHost("relay.example") &&
3674 !BtfIsTorOnionHost(".onion"),
3675 "detects .onion hosts without accepting lookalikes") ? 0 : 1;
3676 std::string onion;
3677 nFail += Check(btf::NormalizeOnionEndpoint("ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCD.onion.:8433", onion) &&
3678 onion == "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd.onion:8433",
3679 "normalizes v3 onion peer endpoint") ? 0 : 1;
3680 nFail += Check(!btf::NormalizeOnionEndpoint("short.onion:8433", onion) &&
3681 !btf::NormalizeOnionEndpoint("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd.onion:0", onion) &&
3682 !btf::NormalizeOnionEndpoint("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabc1.onion:8433", onion),
3683 "rejects malformed onion peer endpoints") ? 0 : 1;
3684
3685 BtfClearSocks5Proxy();
3686 nFail += Check(!BtfSocks5ProxyEnabled(), "starts disabled after clear") ? 0 : 1;
3687 nFail += Check(BtfSetSocks5Proxy("127.0.0.1:9050", err) &&
3688 BtfSocks5ProxyEnabled() &&
3689 BtfSocks5ProxyName() == "127.0.0.1:9050",
3690 "enables parsed SOCKS5 proxy") ? 0 : 1;
3691 BtfClearSocks5Proxy();
3692 nFail += Check(!BtfSocks5ProxyEnabled(), "clear disables proxy") ? 0 : 1;
3693
3694 nFail += Check(BtfSetSocks5Proxy("[::1]:9050", err) &&
3695 BtfSocks5ProxyEnabled() &&
3696 BtfSocks5ProxyName() == "[::1]:9050",
3697 "enables bracketed IPv6 proxy endpoint") ? 0 : 1;
3698 BtfClearSocks5Proxy();
3699
3700 nFail += Check(BtfEnableTorProxy("", err) &&
3701 BtfSocks5ProxyEnabled() &&
3702 BtfTorProxyEnabled() &&
3703 BtfSocks5ProxyName() == "127.0.0.1:9050",
3704 "enables Tor mode on the default local SOCKS5 endpoint") ? 0 : 1;
3705 BtfClearSocks5Proxy();
3706
3707 nFail += Check(BtfEnableTorProxy("[::1]:9050", err) &&
3708 BtfSocks5ProxyEnabled() &&
3709 BtfTorProxyEnabled() &&
3710 BtfSocks5ProxyName() == "[::1]:9050",
3711 "enables Tor mode on a custom IPv6 endpoint") ? 0 : 1;
3712 BtfClearSocks5Proxy();
3713
3714 nFail += Check(BtfSetSocks5Proxy("127.0.0.1:9051", err) &&
3715 BtfSocks5ProxyEnabled() &&
3716 !BtfTorProxyEnabled(),
3717 "plain SOCKS5 mode is distinct from Tor mode") ? 0 : 1;
3718 BtfClearSocks5Proxy();
3719
3720 std::string handshakeErr;
3721 bool fHandshakeOk = RunSocks5HandshakeProbe(handshakeErr);
3722 nFail += Check(fHandshakeOk, handshakeErr.empty() ?
3723 "completes SOCKS5 domain-name CONNECT handshake" :
3724 strprintf("completes SOCKS5 domain-name CONNECT handshake (%s)",
3725 handshakeErr.c_str()).c_str()) ? 0 : 1;
3726
3727 printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED",
3728 nFail, nFail == 1 ? "" : "s");
3729 fflush(stdout);
3730 return nFail == 0 ? 0 : 1;
3731}
3732
3733static int RunManagedTorSelfTest()
3734{
3735 printf("managed-tor self-test\n");
3736 int nFail = 0;
3737
3738 std::string torrc = BtfBuildManagedTorrcForTest("C:/Bitflash Managed Tor/data",
3739 "C:/Bitflash Managed Tor/onion-service",
3740 19050, 19051, 8433);
3741 nFail += Check(torrc.find("DataDirectory \"C:/Bitflash Managed Tor/data\"") != std::string::npos,
3742 "quotes the Tor data directory") ? 0 : 1;
3743 nFail += Check(torrc.find("SocksPort 127.0.0.1:19050") != std::string::npos,
3744 "binds SOCKS5 to localhost only") ? 0 : 1;
3745 nFail += Check(torrc.find("ControlPort 127.0.0.1:19051") != std::string::npos &&
3746 torrc.find("CookieAuthentication 1") != std::string::npos,
3747 "binds an authenticated Tor control port to localhost only") ? 0 : 1;
3748 nFail += Check(torrc.find("IsolateSOCKSAuth") != std::string::npos &&
3749 torrc.find("IsolateClientAddr") != std::string::npos &&
3750 torrc.find("IsolateDestAddr") != std::string::npos &&
3751 torrc.find("IsolateDestPort") != std::string::npos,
3752 "enables strict Tor stream isolation") ? 0 : 1;
3753 nFail += Check(torrc.find("HiddenServiceDir \"C:/Bitflash Managed Tor/onion-service\"") != std::string::npos,
3754 "writes a hidden service directory") ? 0 : 1;
3755 nFail += Check(torrc.find("HiddenServiceVersion 3") != std::string::npos,
3756 "requests a v3 onion service") ? 0 : 1;
3757 nFail += Check(torrc.find("HiddenServicePort 8433 127.0.0.1:8433") != std::string::npos,
3758 "maps the onion service to the Bitflash P2P listener") ? 0 : 1;
3759 nFail += Check(BtfManagedTorStatus() == "disabled",
3760 "managed Tor starts disabled") ? 0 : 1;
3761
3762 printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED",
3763 nFail, nFail == 1 ? "" : "s");
3764 fflush(stdout);
3765 return nFail == 0 ? 0 : 1;
3766}
3767
1219int RunSelfTest(const std::string& name) 3768int RunSelfTest(const std::string& name)
1220{ 3769{
1221 AttachTerminal(); 3770 AttachTerminal();
@@ -1226,16 +3775,44 @@ int RunSelfTest(const std::string& name)
1226 return RunWalletHDSelfTest(); 3775 return RunWalletHDSelfTest();
1227 if (name == "wallet-format") 3776 if (name == "wallet-format")
1228 return RunWalletFormatSelfTest(); 3777 return RunWalletFormatSelfTest();
3778 if (name == "wallet-storage-sanity")
3779 return RunWalletStorageSanitySelfTest();
3780 if (name == "db-env-reopen")
3781 return RunDbEnvReopenSelfTest();
3782 if (name == "wallet-sqlite")
3783 return RunWalletSQLiteSelfTest();
3784 if (name == "wallet-sqlite-migration")
3785 return RunWalletSQLiteMigrationSelfTest();
1229 if (name == "wallet-crypto") 3786 if (name == "wallet-crypto")
1230 return RunWalletCryptoSelfTest(); 3787 return RunWalletCryptoSelfTest();
1231 if (name == "wallet-encrypt") 3788 if (name == "wallet-encrypt")
1232 return RunWalletEncryptSelfTest(); 3789 return RunWalletEncryptSelfTest();
3790 if (name == "wallet-sqlite-encrypt")
3791 return RunWalletSQLiteEncryptSelfTest();
3792 if (name == "wallet-backend-default")
3793 return RunWalletBackendDefaultSelfTest();
3794 if (name == "wallet-convert")
3795 return RunWalletConvertSelfTest();
3796 if (name == "wallet-portability")
3797 return RunWalletPortabilitySelfTest();
1233 if (name == "net-message") 3798 if (name == "net-message")
1234 return RunNetMessageSelfTest(); 3799 return RunNetMessageSelfTest();
3800 if (name == "network-params")
3801 return RunNetworkParamsSelfTest();
1235 if (name == "consensus-limits") 3802 if (name == "consensus-limits")
1236 return RunConsensusLimitsSelfTest(); 3803 return RunConsensusLimitsSelfTest();
3804 if (name == "pool-stratum")
3805 return RunPoolStratumSelfTest();
3806 if (name == "payout-ledger")
3807 return RunPayoutLedgerSelfTest();
3808 if (name == "parse-money")
3809 return RunParseMoneySelfTest();
3810 if (name == "socks5-proxy")
3811 return RunSocks5ProxySelfTest();
3812 if (name == "managed-tor")
3813 return RunManagedTorSelfTest();
1237 3814
1238 printf("Unknown self-test '%s'\n", name.c_str()); 3815 printf("Unknown self-test '%s'\n", name.c_str());
1239 printf("Known self-tests: wallet-keypool, wallet-hd, wallet-format, wallet-crypto, wallet-encrypt, net-message, consensus-limits\n"); 3816 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, network-params, consensus-limits, pool-stratum, payout-ledger, parse-money, socks5-proxy, managed-tor\n");
1240 return 1; 3817 return 1;
1241} 3818}