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-05 22:46:52 +0000
committerbitflash-maintainer <noreply@bitflash.invalid>2026-09-05 22:46:52 +0000
commitceae3d076b5ad11ee84729ae43f14e9312896646 (patch)
tree7e9132aa871fdc01cb95268d2f5ea3bb11853ddf /src/selftest.cpp
parentbe63e83d79e7e00e6b8ca889a70e6a19886aa1d8 (diff)
parent66e0b9158d050c46b1ff2509faeee3f4573cac1c (diff)
Bring main onto the line this project actually ships
main had drifted to a history with no ancestor in common with the tree we build and deploy. It sat at be63e83 (2026-08-09) with 28 commits; the shipped line, deploy/soak-2026-08-21, had 383. The two shared nothing -- git merge-base returns no result at all -- so anyone cloning this repository landed on a default branch that was neither current nor related to the running node. The 28 commits on main were audited before this merge: every one is already present in the tree by content, including the two that are hardest to see because they were rewritten afterwards -- the payout reorg-safety work (PayoutBlockStillOnChain, since evolved to re-check on every retry rather than once at maturity) and the nostr relay certificate verification (SSL_set1_host, now with a Windows root-store path). Nothing on main is lost by this merge. Recorded as a merge rather than a force-push: main only ever moves forward, its old history stays reachable as the first parent, and this is revertible. The tree is taken wholesale from the merged branch, so main's content is now exactly what was built and tested. What this brings onto main: - compactblock: BIP152-style compact block relay ported from upstream 1.2.23, primitives and unit test only, with the RAND_bytes failure path closed and the reconstruction allocation bounded. - upstream/main 17741d9: isolated testnet parameters and genesis, optional 2 MB large pages for RandomX. Mainnet genesis verified byte-identical through upstream's BuildGenesisBlock() refactor. - deploy: every image built on bookworm so binaries run on Debian 12, and the container build now honours the RandomX and libsecp256k1 pins it had been silently ignoring since before 1.2.15. Gates at this tree: 15/15 selftests, compactblock's 17 checks, consensus constants unchanged against e4f898c apart from the pre-existing MAX_BLOCK_SIZE_GEN, mainnet genesis intact, money-log check ok.
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}