diff options
Diffstat (limited to 'src')
118 files changed, 1709 insertions, 958 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 66cb7cec2a..e2ed70556d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -351,6 +351,7 @@ libbitcoin_server_a_SOURCES = \ node/ui_interface.cpp \ noui.cpp \ policy/fees.cpp \ + policy/packages.cpp \ policy/rbf.cpp \ policy/settings.cpp \ pow.cpp \ diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 105d09f730..fc2fd80166 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -35,11 +35,12 @@ BITCOIN_TEST_SUITE = \ $(TEST_UTIL_H) FUZZ_SUITE_LD_COMMON = \ + $(LIBTEST_UTIL) \ + $(LIBTEST_FUZZ) \ $(LIBBITCOIN_SERVER) \ + $(LIBBITCOIN_WALLET) \ $(LIBBITCOIN_COMMON) \ $(LIBBITCOIN_UTIL) \ - $(LIBTEST_UTIL) \ - $(LIBTEST_FUZZ) \ $(LIBBITCOIN_CONSENSUS) \ $(LIBBITCOIN_CRYPTO) \ $(LIBBITCOIN_CLI) \ @@ -160,7 +161,6 @@ BITCOIN_TESTS += \ wallet/test/scriptpubkeyman_tests.cpp FUZZ_SUITE_LD_COMMON +=\ - $(LIBBITCOIN_WALLET) \ $(SQLITE_LIBS) \ $(BDB_LIBS) diff --git a/src/addrdb.cpp b/src/addrdb.cpp index c376aced10..bf2f6c7614 100644 --- a/src/addrdb.cpp +++ b/src/addrdb.cpp @@ -23,7 +23,7 @@ bool SerializeDB(Stream& stream, const Data& data) { // Write and commit header, data try { - CHashWriter hasher(SER_DISK, CLIENT_VERSION); + CHashWriter hasher(stream.GetType(), stream.GetVersion()); stream << Params().MessageStart() << data; hasher << Params().MessageStart() << data; stream << hasher.GetHash(); @@ -35,7 +35,7 @@ bool SerializeDB(Stream& stream, const Data& data) } template <typename Data> -bool SerializeFileDB(const std::string& prefix, const fs::path& path, const Data& data) +bool SerializeFileDB(const std::string& prefix, const fs::path& path, const Data& data, int version) { // Generate random temporary filename uint16_t randv = 0; @@ -45,7 +45,7 @@ bool SerializeFileDB(const std::string& prefix, const fs::path& path, const Data // open temp output file, and associate with CAutoFile fs::path pathTmp = gArgs.GetDataDirNet() / tmpfn; FILE *file = fsbridge::fopen(pathTmp, "wb"); - CAutoFile fileout(file, SER_DISK, CLIENT_VERSION); + CAutoFile fileout(file, SER_DISK, version); if (fileout.IsNull()) { fileout.fclose(); remove(pathTmp); @@ -106,11 +106,11 @@ bool DeserializeDB(Stream& stream, Data& data, bool fCheckSum = true) } template <typename Data> -bool DeserializeFileDB(const fs::path& path, Data& data) +bool DeserializeFileDB(const fs::path& path, Data& data, int version) { // open input file, and associate with CAutoFile FILE* file = fsbridge::fopen(path, "rb"); - CAutoFile filein(file, SER_DISK, CLIENT_VERSION); + CAutoFile filein(file, SER_DISK, version); if (filein.IsNull()) { LogPrintf("Missing or invalid file %s\n", path.string()); return false; @@ -125,12 +125,12 @@ CBanDB::CBanDB(fs::path ban_list_path) : m_ban_list_path(std::move(ban_list_path bool CBanDB::Write(const banmap_t& banSet) { - return SerializeFileDB("banlist", m_ban_list_path, banSet); + return SerializeFileDB("banlist", m_ban_list_path, banSet, CLIENT_VERSION); } bool CBanDB::Read(banmap_t& banSet) { - return DeserializeFileDB(m_ban_list_path, banSet); + return DeserializeFileDB(m_ban_list_path, banSet, CLIENT_VERSION); } CAddrDB::CAddrDB() @@ -140,12 +140,12 @@ CAddrDB::CAddrDB() bool CAddrDB::Write(const CAddrMan& addr) { - return SerializeFileDB("peers", pathAddr, addr); + return SerializeFileDB("peers", pathAddr, addr, CLIENT_VERSION); } bool CAddrDB::Read(CAddrMan& addr) { - return DeserializeFileDB(pathAddr, addr); + return DeserializeFileDB(pathAddr, addr, CLIENT_VERSION); } bool CAddrDB::Read(CAddrMan& addr, CDataStream& ssPeers) @@ -161,13 +161,13 @@ bool CAddrDB::Read(CAddrMan& addr, CDataStream& ssPeers) void DumpAnchors(const fs::path& anchors_db_path, const std::vector<CAddress>& anchors) { LOG_TIME_SECONDS(strprintf("Flush %d outbound block-relay-only peer addresses to anchors.dat", anchors.size())); - SerializeFileDB("anchors", anchors_db_path, anchors); + SerializeFileDB("anchors", anchors_db_path, anchors, CLIENT_VERSION | ADDRV2_FORMAT); } std::vector<CAddress> ReadAnchors(const fs::path& anchors_db_path) { std::vector<CAddress> anchors; - if (DeserializeFileDB(anchors_db_path, anchors)) { + if (DeserializeFileDB(anchors_db_path, anchors, CLIENT_VERSION | ADDRV2_FORMAT)) { LogPrintf("Loaded %i addresses from %s\n", anchors.size(), anchors_db_path.filename()); } else { anchors.clear(); diff --git a/src/addrman.cpp b/src/addrman.cpp index 14b412a038..8f702b5a8c 100644 --- a/src/addrman.cpp +++ b/src/addrman.cpp @@ -12,6 +12,8 @@ #include <cmath> #include <optional> +#include <unordered_map> +#include <unordered_set> int CAddrInfo::GetTriedBucket(const uint256& nKey, const std::vector<bool> &asmap) const { @@ -77,12 +79,14 @@ double CAddrInfo::GetChance(int64_t nNow) const CAddrInfo* CAddrMan::Find(const CNetAddr& addr, int* pnId) { - std::map<CNetAddr, int>::iterator it = mapAddr.find(addr); + AssertLockHeld(cs); + + const auto it = mapAddr.find(addr); if (it == mapAddr.end()) return nullptr; if (pnId) *pnId = (*it).second; - std::map<int, CAddrInfo>::iterator it2 = mapInfo.find((*it).second); + const auto it2 = mapInfo.find((*it).second); if (it2 != mapInfo.end()) return &(*it2).second; return nullptr; @@ -90,6 +94,8 @@ CAddrInfo* CAddrMan::Find(const CNetAddr& addr, int* pnId) CAddrInfo* CAddrMan::Create(const CAddress& addr, const CNetAddr& addrSource, int* pnId) { + AssertLockHeld(cs); + int nId = nIdCount++; mapInfo[nId] = CAddrInfo(addr, addrSource); mapAddr[addr] = nId; @@ -102,6 +108,8 @@ CAddrInfo* CAddrMan::Create(const CAddress& addr, const CNetAddr& addrSource, in void CAddrMan::SwapRandom(unsigned int nRndPos1, unsigned int nRndPos2) { + AssertLockHeld(cs); + if (nRndPos1 == nRndPos2) return; @@ -122,6 +130,8 @@ void CAddrMan::SwapRandom(unsigned int nRndPos1, unsigned int nRndPos2) void CAddrMan::Delete(int nId) { + AssertLockHeld(cs); + assert(mapInfo.count(nId) != 0); CAddrInfo& info = mapInfo[nId]; assert(!info.fInTried); @@ -136,6 +146,8 @@ void CAddrMan::Delete(int nId) void CAddrMan::ClearNew(int nUBucket, int nUBucketPos) { + AssertLockHeld(cs); + // if there is an entry in the specified bucket, delete it. if (vvNew[nUBucket][nUBucketPos] != -1) { int nIdDelete = vvNew[nUBucket][nUBucketPos]; @@ -151,6 +163,8 @@ void CAddrMan::ClearNew(int nUBucket, int nUBucketPos) void CAddrMan::MakeTried(CAddrInfo& info, int nId) { + AssertLockHeld(cs); + // remove the entry from all new buckets for (int bucket = 0; bucket < ADDRMAN_NEW_BUCKET_COUNT; bucket++) { int pos = info.GetBucketPosition(nKey, true, bucket); @@ -199,6 +213,8 @@ void CAddrMan::MakeTried(CAddrInfo& info, int nId) void CAddrMan::Good_(const CService& addr, bool test_before_evict, int64_t nTime) { + AssertLockHeld(cs); + int nId; nLastGood = nTime; @@ -265,6 +281,8 @@ void CAddrMan::Good_(const CService& addr, bool test_before_evict, int64_t nTime bool CAddrMan::Add_(const CAddress& addr, const CNetAddr& source, int64_t nTimePenalty) { + AssertLockHeld(cs); + if (!addr.IsRoutable()) return false; @@ -338,6 +356,8 @@ bool CAddrMan::Add_(const CAddress& addr, const CNetAddr& source, int64_t nTimeP void CAddrMan::Attempt_(const CService& addr, bool fCountFailure, int64_t nTime) { + AssertLockHeld(cs); + CAddrInfo* pinfo = Find(addr); // if not found, bail out @@ -360,7 +380,9 @@ void CAddrMan::Attempt_(const CService& addr, bool fCountFailure, int64_t nTime) CAddrInfo CAddrMan::Select_(bool newOnly) { - if (size() == 0) + AssertLockHeld(cs); + + if (vRandom.empty()) return CAddrInfo(); if (newOnly && nNew == 0) @@ -408,8 +430,10 @@ CAddrInfo CAddrMan::Select_(bool newOnly) #ifdef DEBUG_ADDRMAN int CAddrMan::Check_() { - std::set<int> setTried; - std::map<int, int> mapNew; + AssertLockHeld(cs); + + std::unordered_set<int> setTried; + std::unordered_map<int, int> mapNew; if (vRandom.size() != (size_t)(nTried + nNew)) return -7; @@ -485,6 +509,8 @@ int CAddrMan::Check_() void CAddrMan::GetAddr_(std::vector<CAddress>& vAddr, size_t max_addresses, size_t max_pct, std::optional<Network> network) { + AssertLockHeld(cs); + size_t nNodes = vRandom.size(); if (max_pct != 0) { nNodes = max_pct * nNodes / 100; @@ -517,6 +543,8 @@ void CAddrMan::GetAddr_(std::vector<CAddress>& vAddr, size_t max_addresses, size void CAddrMan::Connected_(const CService& addr, int64_t nTime) { + AssertLockHeld(cs); + CAddrInfo* pinfo = Find(addr); // if not found, bail out @@ -537,6 +565,8 @@ void CAddrMan::Connected_(const CService& addr, int64_t nTime) void CAddrMan::SetServices_(const CService& addr, ServiceFlags nServices) { + AssertLockHeld(cs); + CAddrInfo* pinfo = Find(addr); // if not found, bail out @@ -555,6 +585,8 @@ void CAddrMan::SetServices_(const CService& addr, ServiceFlags nServices) void CAddrMan::ResolveCollisions_() { + AssertLockHeld(cs); + for (std::set<int>::iterator it = m_tried_collisions.begin(); it != m_tried_collisions.end();) { int id_new = *it; @@ -614,6 +646,8 @@ void CAddrMan::ResolveCollisions_() CAddrInfo CAddrMan::SelectTriedCollision_() { + AssertLockHeld(cs); + if (m_tried_collisions.size() == 0) return CAddrInfo(); std::set<int>::iterator it = m_tried_collisions.begin(); diff --git a/src/addrman.h b/src/addrman.h index 41994288db..665e253192 100644 --- a/src/addrman.h +++ b/src/addrman.h @@ -8,22 +8,22 @@ #include <clientversion.h> #include <config/bitcoin-config.h> +#include <fs.h> +#include <hash.h> #include <netaddress.h> #include <protocol.h> #include <random.h> +#include <streams.h> #include <sync.h> #include <timedata.h> #include <tinyformat.h> #include <util/system.h> -#include <fs.h> -#include <hash.h> #include <iostream> -#include <map> #include <optional> #include <set> #include <stdint.h> -#include <streams.h> +#include <unordered_map> #include <vector> /** @@ -231,6 +231,7 @@ public: */ template <typename Stream> void Serialize(Stream& s_) const + EXCLUSIVE_LOCKS_REQUIRED(!cs) { LOCK(cs); @@ -251,7 +252,7 @@ public: int nUBuckets = ADDRMAN_NEW_BUCKET_COUNT ^ (1 << 30); s << nUBuckets; - std::map<int, int> mapUnkIds; + std::unordered_map<int, int> mapUnkIds; int nIds = 0; for (const auto& entry : mapInfo) { mapUnkIds[entry.first] = nIds; @@ -296,10 +297,11 @@ public: template <typename Stream> void Unserialize(Stream& s_) + EXCLUSIVE_LOCKS_REQUIRED(!cs) { LOCK(cs); - Clear(); + assert(vRandom.empty()); Format format; s_ >> Using<CustomUintFormatter<1>>(format); @@ -435,13 +437,13 @@ public: // Prune new entries with refcount 0 (as a result of collisions). int nLostUnk = 0; - for (std::map<int, CAddrInfo>::const_iterator it = mapInfo.begin(); it != mapInfo.end(); ) { + for (auto it = mapInfo.cbegin(); it != mapInfo.cend(); ) { if (it->second.fInTried == false && it->second.nRefCount == 0) { - std::map<int, CAddrInfo>::const_iterator itCopy = it++; + const auto itCopy = it++; Delete(itCopy->first); - nLostUnk++; + ++nLostUnk; } else { - it++; + ++it; } } if (nLost + nLostUnk > 0) { @@ -452,6 +454,7 @@ public: } void Clear() + EXCLUSIVE_LOCKS_REQUIRED(!cs) { LOCK(cs); std::vector<int>().swap(vRandom); @@ -487,26 +490,15 @@ public: //! Return the number of (unique) addresses in all tables. size_t size() const + EXCLUSIVE_LOCKS_REQUIRED(!cs) { LOCK(cs); // TODO: Cache this in an atomic to avoid this overhead return vRandom.size(); } - //! Consistency check - void Check() - { -#ifdef DEBUG_ADDRMAN - { - LOCK(cs); - int err; - if ((err=Check_())) - LogPrintf("ADDRMAN CONSISTENCY CHECK FAILED!!! err=%i\n", err); - } -#endif - } - //! Add a single address. bool Add(const CAddress &addr, const CNetAddr& source, int64_t nTimePenalty = 0) + EXCLUSIVE_LOCKS_REQUIRED(!cs) { LOCK(cs); bool fRet = false; @@ -521,6 +513,7 @@ public: //! Add multiple addresses. bool Add(const std::vector<CAddress> &vAddr, const CNetAddr& source, int64_t nTimePenalty = 0) + EXCLUSIVE_LOCKS_REQUIRED(!cs) { LOCK(cs); int nAdd = 0; @@ -536,6 +529,7 @@ public: //! Mark an entry as accessible. void Good(const CService &addr, bool test_before_evict = true, int64_t nTime = GetAdjustedTime()) + EXCLUSIVE_LOCKS_REQUIRED(!cs) { LOCK(cs); Check(); @@ -545,6 +539,7 @@ public: //! Mark an entry as connection attempted to. void Attempt(const CService &addr, bool fCountFailure, int64_t nTime = GetAdjustedTime()) + EXCLUSIVE_LOCKS_REQUIRED(!cs) { LOCK(cs); Check(); @@ -554,6 +549,7 @@ public: //! See if any to-be-evicted tried table entries have been tested and if so resolve the collisions. void ResolveCollisions() + EXCLUSIVE_LOCKS_REQUIRED(!cs) { LOCK(cs); Check(); @@ -563,14 +559,12 @@ public: //! Randomly select an address in tried that another address is attempting to evict. CAddrInfo SelectTriedCollision() + EXCLUSIVE_LOCKS_REQUIRED(!cs) { - CAddrInfo ret; - { - LOCK(cs); - Check(); - ret = SelectTriedCollision_(); - Check(); - } + LOCK(cs); + Check(); + const CAddrInfo ret = SelectTriedCollision_(); + Check(); return ret; } @@ -578,14 +572,12 @@ public: * Choose an address to connect to. */ CAddrInfo Select(bool newOnly = false) + EXCLUSIVE_LOCKS_REQUIRED(!cs) { - CAddrInfo addrRet; - { - LOCK(cs); - Check(); - addrRet = Select_(newOnly); - Check(); - } + LOCK(cs); + Check(); + const CAddrInfo addrRet = Select_(newOnly); + Check(); return addrRet; } @@ -597,19 +589,19 @@ public: * @param[in] network Select only addresses of this network (nullopt = all). */ std::vector<CAddress> GetAddr(size_t max_addresses, size_t max_pct, std::optional<Network> network) + EXCLUSIVE_LOCKS_REQUIRED(!cs) { + LOCK(cs); Check(); std::vector<CAddress> vAddr; - { - LOCK(cs); - GetAddr_(vAddr, max_addresses, max_pct, network); - } + GetAddr_(vAddr, max_addresses, max_pct, network); Check(); return vAddr; } //! Outer function for Connected_() void Connected(const CService &addr, int64_t nTime = GetAdjustedTime()) + EXCLUSIVE_LOCKS_REQUIRED(!cs) { LOCK(cs); Check(); @@ -618,6 +610,7 @@ public: } void SetServices(const CService &addr, ServiceFlags nServices) + EXCLUSIVE_LOCKS_REQUIRED(!cs) { LOCK(cs); Check(); @@ -633,8 +626,8 @@ protected: FastRandomContext insecure_rand; private: - //! critical section to protect the inner data structures - mutable RecursiveMutex cs; + //! A mutex to protect the inner data structures. + mutable Mutex cs; //! Serialization versions. enum Format : uint8_t { @@ -662,10 +655,10 @@ private: int nIdCount GUARDED_BY(cs); //! table with information about all nIds - std::map<int, CAddrInfo> mapInfo GUARDED_BY(cs); + std::unordered_map<int, CAddrInfo> mapInfo GUARDED_BY(cs); //! find an nId based on its network address - std::map<CNetAddr, int> mapAddr GUARDED_BY(cs); + std::unordered_map<CNetAddr, int, CNetAddrHash> mapAddr GUARDED_BY(cs); //! randomly-ordered vector of all nIds std::vector<int> vRandom GUARDED_BY(cs); @@ -725,6 +718,19 @@ private: //! Return a random to-be-evicted tried table address. CAddrInfo SelectTriedCollision_() EXCLUSIVE_LOCKS_REQUIRED(cs); + //! Consistency check + void Check() + EXCLUSIVE_LOCKS_REQUIRED(cs) + { +#ifdef DEBUG_ADDRMAN + AssertLockHeld(cs); + const int err = Check_(); + if (err) { + LogPrintf("ADDRMAN CONSISTENCY CHECK FAILED!!! err=%i\n", err); + } +#endif + } + #ifdef DEBUG_ADDRMAN //! Perform consistency check. Returns an error code or zero. int Check_() EXCLUSIVE_LOCKS_REQUIRED(cs); diff --git a/src/bench/duplicate_inputs.cpp b/src/bench/duplicate_inputs.cpp index 4f6e1122b8..8703a1cf94 100644 --- a/src/bench/duplicate_inputs.cpp +++ b/src/bench/duplicate_inputs.cpp @@ -25,7 +25,6 @@ static void DuplicateInputs(benchmark::Bench& bench) CMutableTransaction naughtyTx{}; LOCK(cs_main); - assert(std::addressof(::ChainActive()) == std::addressof(testing_setup->m_node.chainman->ActiveChain())); CBlockIndex* pindexPrev = testing_setup->m_node.chainman->ActiveChain().Tip(); assert(pindexPrev != nullptr); block.nBits = GetNextWorkRequired(pindexPrev, &block, chainparams.GetConsensus()); diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp index 93ac4b8f7e..3fc87ae1ff 100644 --- a/src/bitcoin-tx.cpp +++ b/src/bitcoin-tx.cpp @@ -506,11 +506,12 @@ static void MutateTxDelOutput(CMutableTransaction& tx, const std::string& strOut tx.vout.erase(tx.vout.begin() + outIdx); } -static const unsigned int N_SIGHASH_OPTS = 6; +static const unsigned int N_SIGHASH_OPTS = 7; static const struct { const char *flagStr; int flags; } sighashOptions[N_SIGHASH_OPTS] = { + {"DEFAULT", SIGHASH_DEFAULT}, {"ALL", SIGHASH_ALL}, {"NONE", SIGHASH_NONE}, {"SINGLE", SIGHASH_SINGLE}, diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp index cf9e4fad44..654679af27 100644 --- a/src/bitcoind.cpp +++ b/src/bitcoind.cpp @@ -112,8 +112,8 @@ static bool AppInit(NodeContext& node, int argc, char* argv[]) util::ThreadSetInternalName("init"); // If Qt is used, parameters/bitcoin.conf are parsed in qt/bitcoin.cpp's main() - SetupServerArgs(node); ArgsManager& args = *Assert(node.args); + SetupServerArgs(args); std::string error; if (!args.ParseParameters(argc, argv, error)) { return InitError(Untranslated(strprintf("Error parsing command line arguments: %s\n", error))); diff --git a/src/core_read.cpp b/src/core_read.cpp index b5fc93886d..6108961010 100644 --- a/src/core_read.cpp +++ b/src/core_read.cpp @@ -260,6 +260,7 @@ int ParseSighashString(const UniValue& sighash) int hash_type = SIGHASH_ALL; if (!sighash.isNull()) { static std::map<std::string, int> map_sighash_values = { + {std::string("DEFAULT"), int(SIGHASH_DEFAULT)}, {std::string("ALL"), int(SIGHASH_ALL)}, {std::string("ALL|ANYONECANPAY"), int(SIGHASH_ALL|SIGHASH_ANYONECANPAY)}, {std::string("NONE"), int(SIGHASH_NONE)}, diff --git a/src/external_signer.cpp b/src/external_signer.cpp index f16d21fa60..d6388b759a 100644 --- a/src/external_signer.cpp +++ b/src/external_signer.cpp @@ -13,9 +13,7 @@ #include <string> #include <vector> -#ifdef ENABLE_EXTERNAL_SIGNER - -ExternalSigner::ExternalSigner(const std::string& command, const std::string& fingerprint, const std::string chain, const std::string name): m_command(command), m_fingerprint(fingerprint), m_chain(chain), m_name(name) {} +ExternalSigner::ExternalSigner(const std::string& command, const std::string chain, const std::string& fingerprint, const std::string name): m_command(command), m_chain(chain), m_fingerprint(fingerprint), m_name(name) {} const std::string ExternalSigner::NetworkArg() const { @@ -55,7 +53,7 @@ bool ExternalSigner::Enumerate(const std::string& command, std::vector<ExternalS if (model_field.isStr() && model_field.getValStr() != "") { name += model_field.getValStr(); } - signers.push_back(ExternalSigner(command, fingerprintStr, chain, name)); + signers.push_back(ExternalSigner(command, chain, fingerprintStr, name)); } return true; } @@ -116,5 +114,3 @@ bool ExternalSigner::SignTransaction(PartiallySignedTransaction& psbtx, std::str return true; } - -#endif // ENABLE_EXTERNAL_SIGNER diff --git a/src/external_signer.h b/src/external_signer.h index b3b202091a..e40fd7f010 100644 --- a/src/external_signer.h +++ b/src/external_signer.h @@ -11,8 +11,6 @@ #include <string> #include <vector> -#ifdef ENABLE_EXTERNAL_SIGNER - struct PartiallySignedTransaction; //! Enables interaction with an external signing device or service, such as @@ -23,24 +21,24 @@ private: //! The command which handles interaction with the external signer. std::string m_command; + //! Bitcoin mainnet, testnet, etc + std::string m_chain; + + const std::string NetworkArg() const; + public: //! @param[in] command the command which handles interaction with the external signer //! @param[in] fingerprint master key fingerprint of the signer //! @param[in] chain "main", "test", "regtest" or "signet" //! @param[in] name device name - ExternalSigner(const std::string& command, const std::string& fingerprint, const std::string chain, const std::string name); + ExternalSigner(const std::string& command, const std::string chain, const std::string& fingerprint, const std::string name); //! Master key fingerprint of the signer std::string m_fingerprint; - //! Bitcoin mainnet, testnet, etc - std::string m_chain; - //! Name of signer std::string m_name; - const std::string NetworkArg() const; - //! Obtain a list of signers. Calls `<command> enumerate`. //! @param[in] command the command which handles interaction with the external signer //! @param[in,out] signers vector to which new signers (with a unique master key fingerprint) are added @@ -65,6 +63,4 @@ public: bool SignTransaction(PartiallySignedTransaction& psbt, std::string& error); }; -#endif // ENABLE_EXTERNAL_SIGNER - #endif // BITCOIN_EXTERNAL_SIGNER_H diff --git a/src/index/base.cpp b/src/index/base.cpp index 3a61af28b7..6fd2701e2e 100644 --- a/src/index/base.cpp +++ b/src/index/base.cpp @@ -340,7 +340,6 @@ void BaseIndex::Interrupt() bool BaseIndex::Start(CChainState& active_chainstate) { - assert(std::addressof(::ChainstateActive()) == std::addressof(active_chainstate)); m_chainstate = &active_chainstate; // Need to register this ValidationInterface before running Init(), so that // callbacks are not missed if Init sets m_synced to true. diff --git a/src/init.cpp b/src/init.cpp index 7f64b1acfa..4dc82811f9 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -283,7 +283,7 @@ void Shutdown(NodeContext& node) init::UnsetGlobals(); node.mempool.reset(); node.fee_estimator.reset(); - node.chainman = nullptr; + node.chainman.reset(); node.scheduler.reset(); try { @@ -347,12 +347,8 @@ static void OnRPCStopped() LogPrint(BCLog::RPC, "RPC stopped.\n"); } -void SetupServerArgs(NodeContext& node) +void SetupServerArgs(ArgsManager& argsman) { - assert(!node.args); - node.args = &gArgs; - ArgsManager& argsman = *node.args; - SetupHelpOptions(argsman); argsman.AddArg("-help-debug", "Print help message with debugging options and exit", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); // server-only for now @@ -1179,8 +1175,8 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) node.mempool = std::make_unique<CTxMemPool>(node.fee_estimator.get(), check_ratio); assert(!node.chainman); - node.chainman = &g_chainman; - ChainstateManager& chainman = *Assert(node.chainman); + node.chainman = std::make_unique<ChainstateManager>(); + ChainstateManager& chainman = *node.chainman; assert(!node.peerman); node.peerman = PeerManager::make(chainparams, *node.connman, *node.addrman, node.banman.get(), @@ -1385,7 +1381,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) // If the loaded chain has a wrong genesis, bail out immediately // (we're likely using a testnet datadir, or the other way around). if (!chainman.BlockIndex().empty() && - !g_chainman.m_blockman.LookupBlockIndex(chainparams.GetConsensus().hashGenesisBlock)) { + !chainman.m_blockman.LookupBlockIndex(chainparams.GetConsensus().hashGenesisBlock)) { return InitError(_("Incorrect or no genesis block found. Wrong datadir for network?")); } @@ -1400,7 +1396,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) // If we're not mid-reindex (based on disk + args), add a genesis block on disk // (otherwise we use the one already on disk). // This is called again in ThreadImport after the reindex completes. - if (!fReindex && !::ChainstateActive().LoadGenesisBlock(chainparams)) { + if (!fReindex && !chainman.ActiveChainstate().LoadGenesisBlock(chainparams)) { strLoadError = _("Error initializing block database"); break; } @@ -1549,21 +1545,21 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) // ********************************************************* Step 8: start indexers if (args.GetBoolArg("-txindex", DEFAULT_TXINDEX)) { g_txindex = std::make_unique<TxIndex>(nTxIndexCache, false, fReindex); - if (!g_txindex->Start(::ChainstateActive())) { + if (!g_txindex->Start(chainman.ActiveChainstate())) { return false; } } for (const auto& filter_type : g_enabled_filter_types) { InitBlockFilterIndex(filter_type, filter_index_cache, false, fReindex); - if (!GetBlockFilterIndex(filter_type)->Start(::ChainstateActive())) { + if (!GetBlockFilterIndex(filter_type)->Start(chainman.ActiveChainstate())) { return false; } } if (args.GetBoolArg("-coinstatsindex", DEFAULT_COINSTATSINDEX)) { g_coin_stats_index = std::make_unique<CoinStatsIndex>(/* cache size */ 0, false, fReindex); - if (!g_coin_stats_index->Start(::ChainstateActive())) { + if (!g_coin_stats_index->Start(chainman.ActiveChainstate())) { return false; } } @@ -1611,7 +1607,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) // Either install a handler to notify us when genesis activates, or set fHaveGenesis directly. // No locking, as this happens before any background thread is started. boost::signals2::connection block_notify_genesis_wait_connection; - if (::ChainActive().Tip() == nullptr) { + if (chainman.ActiveChain().Tip() == nullptr) { block_notify_genesis_wait_connection = uiInterface.NotifyBlockTip_connect(std::bind(BlockNotifyGenesisWait, std::placeholders::_2)); } else { fHaveGenesis = true; diff --git a/src/init.h b/src/init.h index 328eda9c7e..b856468e5d 100644 --- a/src/init.h +++ b/src/init.h @@ -69,7 +69,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info /** * Register all arguments with the ArgsManager */ -void SetupServerArgs(NodeContext& node); +void SetupServerArgs(ArgsManager& argsman); /** Returns licensing information (for -version) */ std::string LicenseInfo(); diff --git a/src/init/bitcoin-node.cpp b/src/init/bitcoin-node.cpp index 49684ede83..6b6157c139 100644 --- a/src/init/bitcoin-node.cpp +++ b/src/init/bitcoin-node.cpp @@ -6,6 +6,7 @@ #include <interfaces/init.h> #include <interfaces/ipc.h> #include <node/context.h> +#include <util/system.h> #include <memory> @@ -20,6 +21,7 @@ public: : m_node(node), m_ipc(interfaces::MakeIpc(EXE_NAME, arg0, *this)) { + m_node.args = &gArgs; m_node.init = this; } std::unique_ptr<interfaces::Echo> makeEcho() override { return interfaces::MakeEcho(); } diff --git a/src/init/bitcoind.cpp b/src/init/bitcoind.cpp index 1e17ce4d3c..1d4504c24f 100644 --- a/src/init/bitcoind.cpp +++ b/src/init/bitcoind.cpp @@ -4,6 +4,7 @@ #include <interfaces/init.h> #include <node/context.h> +#include <util/system.h> #include <memory> @@ -14,6 +15,7 @@ class BitcoindInit : public interfaces::Init public: BitcoindInit(NodeContext& node) : m_node(node) { + m_node.args = &gArgs; m_node.init = this; } NodeContext& m_node; diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h index 3395741b1b..7cac435e96 100644 --- a/src/interfaces/chain.h +++ b/src/interfaces/chain.h @@ -277,6 +277,9 @@ public: //! to be prepared to handle this by ignoring notifications about unknown //! removed transactions and already added new transactions. virtual void requestMempoolTransactions(Notifications& notifications) = 0; + + //! Check if Taproot has activated + virtual bool isTaprootActive() const = 0; }; //! Interface to let node manage chain clients (wallets, or maybe tools for diff --git a/src/interfaces/node.h b/src/interfaces/node.h index 35b6160cea..77129423db 100644 --- a/src/interfaces/node.h +++ b/src/interfaces/node.h @@ -111,10 +111,8 @@ public: //! Disconnect node by id. virtual bool disconnectById(NodeId id) = 0; -#ifdef ENABLE_EXTERNAL_SIGNER //! List external signers virtual std::vector<ExternalSigner> externalSigners() = 0; -#endif //! Get total bytes recv. virtual int64_t getTotalBytesRecv() = 0; diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h index a0cb2787b7..fb1febc11b 100644 --- a/src/interfaces/wallet.h +++ b/src/interfaces/wallet.h @@ -198,9 +198,9 @@ public: virtual TransactionError fillPSBT(int sighash_type, bool sign, bool bip32derivs, + size_t* n_signed, PartiallySignedTransaction& psbtx, - bool& complete, - size_t* n_signed) = 0; + bool& complete) = 0; //! Get balances. virtual WalletBalances getBalances() = 0; diff --git a/src/key.cpp b/src/key.cpp index 5666adebb8..dcad386e77 100644 --- a/src/key.cpp +++ b/src/key.cpp @@ -7,10 +7,13 @@ #include <crypto/common.h> #include <crypto/hmac_sha512.h> +#include <hash.h> #include <random.h> #include <secp256k1.h> +#include <secp256k1_extrakeys.h> #include <secp256k1_recovery.h> +#include <secp256k1_schnorrsig.h> static secp256k1_context* secp256k1_context_sign = nullptr; @@ -258,6 +261,24 @@ bool CKey::SignCompact(const uint256 &hash, std::vector<unsigned char>& vchSig) return true; } +bool CKey::SignSchnorr(const uint256& hash, Span<unsigned char> sig, const uint256* merkle_root, const uint256* aux) const +{ + assert(sig.size() == 64); + secp256k1_keypair keypair; + if (!secp256k1_keypair_create(secp256k1_context_sign, &keypair, begin())) return false; + if (merkle_root) { + secp256k1_xonly_pubkey pubkey; + if (!secp256k1_keypair_xonly_pub(secp256k1_context_sign, &pubkey, nullptr, &keypair)) return false; + unsigned char pubkey_bytes[32]; + if (!secp256k1_xonly_pubkey_serialize(secp256k1_context_sign, pubkey_bytes, &pubkey)) return false; + uint256 tweak = XOnlyPubKey(pubkey_bytes).ComputeTapTweakHash(merkle_root->IsNull() ? nullptr : merkle_root); + if (!secp256k1_keypair_xonly_tweak_add(GetVerifyContext(), &keypair, tweak.data())) return false; + } + bool ret = secp256k1_schnorrsig_sign(secp256k1_context_sign, sig.data(), hash.data(), &keypair, secp256k1_nonce_function_bip340, aux ? (void*)aux->data() : nullptr); + memory_cleanse(&keypair, sizeof(keypair)); + return ret; +} + bool CKey::Load(const CPrivKey &seckey, const CPubKey &vchPubKey, bool fSkipCheck=false) { if (!ec_seckey_import_der(secp256k1_context_sign, (unsigned char*)begin(), seckey.data(), seckey.size())) return false; @@ -128,6 +128,18 @@ public: */ bool SignCompact(const uint256& hash, std::vector<unsigned char>& vchSig) const; + /** + * Create a BIP-340 Schnorr signature, for the xonly-pubkey corresponding to *this, + * optionally tweaked by *merkle_root. Additional nonce entropy can be provided through + * aux. + * + * When merkle_root is not nullptr, this results in a signature with a modified key as + * specified in BIP341: + * - If merkle_root->IsNull(): key + H_TapTweak(pubkey)*G + * - Otherwise: key + H_TapTweak(pubkey || *merkle_root) + */ + bool SignSchnorr(const uint256& hash, Span<unsigned char> sig, const uint256* merkle_root = nullptr, const uint256* aux = nullptr) const; + //! Derive BIP32 child key. bool Derive(CKey& keyChild, ChainCode &ccChild, unsigned int nChild, const ChainCode& cc) const; diff --git a/src/miner.cpp b/src/miner.cpp index eccddbb04f..0cf303eb3c 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -45,15 +45,7 @@ void RegenerateCommitments(CBlock& block, ChainstateManager& chainman) tx.vout.erase(tx.vout.begin() + GetWitnessCommitmentIndex(block)); block.vtx.at(0) = MakeTransactionRef(tx); - CBlockIndex* prev_block; - { - // TODO: Temporary scope to check correctness of refactored code. - // Should be removed manually after merge of - // https://github.com/bitcoin/bitcoin/pull/20158 - LOCK(::cs_main); - assert(std::addressof(g_chainman.m_blockman) == std::addressof(chainman.m_blockman)); - prev_block = chainman.m_blockman.LookupBlockIndex(block.hashPrevBlock); - } + CBlockIndex* prev_block = WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(block.hashPrevBlock)); GenerateCoinbaseCommitment(block, prev_block, Params().GetConsensus()); block.hashMerkleRoot = BlockMerkleRoot(block); @@ -124,7 +116,6 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc pblocktemplate->vTxSigOpsCost.push_back(-1); // updated at end LOCK2(cs_main, m_mempool.cs); - assert(std::addressof(*::ChainActive().Tip()) == std::addressof(*m_chainstate.m_chain.Tip())); CBlockIndex* pindexPrev = m_chainstate.m_chain.Tip(); assert(pindexPrev != nullptr); nHeight = pindexPrev->nHeight + 1; @@ -184,7 +175,6 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc pblocktemplate->vTxSigOpsCost[0] = WITNESS_SCALE_FACTOR * GetLegacySigOpCount(*pblock->vtx[0]); BlockValidationState state; - assert(std::addressof(::ChainstateActive()) == std::addressof(m_chainstate)); if (!TestBlockValidity(state, chainparams, m_chainstate, *pblock, pindexPrev, false, false)) { throw std::runtime_error(strprintf("%s: TestBlockValidity failed: %s", __func__, state.ToString())); } diff --git a/src/net.cpp b/src/net.cpp index 6f9f17ed4e..60059249ed 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -42,6 +42,7 @@ #endif #include <algorithm> +#include <array> #include <cstdint> #include <functional> #include <optional> @@ -841,18 +842,6 @@ static bool ReverseCompareNodeTimeConnected(const NodeEvictionCandidate &a, cons return a.nTimeConnected > b.nTimeConnected; } -static bool CompareLocalHostTimeConnected(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b) -{ - if (a.m_is_local != b.m_is_local) return b.m_is_local; - return a.nTimeConnected > b.nTimeConnected; -} - -static bool CompareOnionTimeConnected(const NodeEvictionCandidate& a, const NodeEvictionCandidate& b) -{ - if (a.m_is_onion != b.m_is_onion) return b.m_is_onion; - return a.nTimeConnected > b.nTimeConnected; -} - static bool CompareNetGroupKeyed(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b) { return a.nKeyedNetGroup < b.nKeyedNetGroup; } @@ -883,6 +872,26 @@ static bool CompareNodeBlockRelayOnlyTime(const NodeEvictionCandidate &a, const return a.nTimeConnected > b.nTimeConnected; } +/** + * Sort eviction candidates by network/localhost and connection uptime. + * Candidates near the beginning are more likely to be evicted, and those + * near the end are more likely to be protected, e.g. less likely to be evicted. + * - First, nodes that are not `is_local` and that do not belong to `network`, + * sorted by increasing uptime (from most recently connected to connected longer). + * - Then, nodes that are `is_local` or belong to `network`, sorted by increasing uptime. + */ +struct CompareNodeNetworkTime { + const bool m_is_local; + const Network m_network; + CompareNodeNetworkTime(bool is_local, Network network) : m_is_local(is_local), m_network(network) {} + bool operator()(const NodeEvictionCandidate& a, const NodeEvictionCandidate& b) const + { + if (m_is_local && a.m_is_local != b.m_is_local) return b.m_is_local; + if ((a.m_network == m_network) != (b.m_network == m_network)) return b.m_network == m_network; + return a.nTimeConnected > b.nTimeConnected; + }; +}; + //! Sort an array by the specified comparator, then erase the last K elements where predicate is true. template <typename T, typename Comparator> static void EraseLastKElements( @@ -894,40 +903,72 @@ static void EraseLastKElements( elements.erase(std::remove_if(elements.end() - eraseSize, elements.end(), predicate), elements.end()); } -void ProtectEvictionCandidatesByRatio(std::vector<NodeEvictionCandidate>& vEvictionCandidates) +void ProtectEvictionCandidatesByRatio(std::vector<NodeEvictionCandidate>& eviction_candidates) { // Protect the half of the remaining nodes which have been connected the longest. // This replicates the non-eviction implicit behavior, and precludes attacks that start later. - // To favorise the diversity of our peer connections, reserve up to (half + 2) of - // these protected spots for onion and localhost peers, if any, even if they're not - // longest uptime overall. This helps protect tor peers, which tend to be otherwise + // To favorise the diversity of our peer connections, reserve up to half of these protected + // spots for Tor/onion, localhost and I2P peers, even if they're not longest uptime overall. + // This helps protect these higher-latency peers that tend to be otherwise // disadvantaged under our eviction criteria. - const size_t initial_size = vEvictionCandidates.size(); - size_t total_protect_size = initial_size / 2; - const size_t onion_protect_size = total_protect_size / 2; - - if (onion_protect_size) { - // Pick out up to 1/4 peers connected via our onion service, sorted by longest uptime. - EraseLastKElements(vEvictionCandidates, CompareOnionTimeConnected, onion_protect_size, - [](const NodeEvictionCandidate& n) { return n.m_is_onion; }); - } - - const size_t localhost_min_protect_size{2}; - if (onion_protect_size >= localhost_min_protect_size) { - // Allocate any remaining slots of the 1/4, or minimum 2 additional slots, - // to localhost peers, sorted by longest uptime, as manually configured - // hidden services not using `-bind=addr[:port]=onion` will not be detected - // as inbound onion connections. - const size_t remaining_tor_slots{onion_protect_size - (initial_size - vEvictionCandidates.size())}; - const size_t localhost_protect_size{std::max(remaining_tor_slots, localhost_min_protect_size)}; - EraseLastKElements(vEvictionCandidates, CompareLocalHostTimeConnected, localhost_protect_size, - [](const NodeEvictionCandidate& n) { return n.m_is_local; }); + const size_t initial_size = eviction_candidates.size(); + const size_t total_protect_size{initial_size / 2}; + + // Disadvantaged networks to protect: I2P, localhost, Tor/onion. In case of equal counts, earlier + // array members have first opportunity to recover unused slots from the previous iteration. + struct Net { bool is_local; Network id; size_t count; }; + std::array<Net, 3> networks{ + {{false, NET_I2P, 0}, {/* localhost */ true, NET_MAX, 0}, {false, NET_ONION, 0}}}; + + // Count and store the number of eviction candidates per network. + for (Net& n : networks) { + n.count = std::count_if(eviction_candidates.cbegin(), eviction_candidates.cend(), + [&n](const NodeEvictionCandidate& c) { + return n.is_local ? c.m_is_local : c.m_network == n.id; + }); + } + // Sort `networks` by ascending candidate count, to give networks having fewer candidates + // the first opportunity to recover unused protected slots from the previous iteration. + std::stable_sort(networks.begin(), networks.end(), [](Net a, Net b) { return a.count < b.count; }); + + // Protect up to 25% of the eviction candidates by disadvantaged network. + const size_t max_protect_by_network{total_protect_size / 2}; + size_t num_protected{0}; + + while (num_protected < max_protect_by_network) { + const size_t disadvantaged_to_protect{max_protect_by_network - num_protected}; + const size_t protect_per_network{ + std::max(disadvantaged_to_protect / networks.size(), static_cast<size_t>(1))}; + + // Early exit flag if there are no remaining candidates by disadvantaged network. + bool protected_at_least_one{false}; + + for (const Net& n : networks) { + if (n.count == 0) continue; + const size_t before = eviction_candidates.size(); + EraseLastKElements(eviction_candidates, CompareNodeNetworkTime(n.is_local, n.id), + protect_per_network, [&n](const NodeEvictionCandidate& c) { + return n.is_local ? c.m_is_local : c.m_network == n.id; + }); + const size_t after = eviction_candidates.size(); + if (before > after) { + protected_at_least_one = true; + num_protected += before - after; + if (num_protected >= max_protect_by_network) { + break; + } + } + } + if (!protected_at_least_one) { + break; + } } // Calculate how many we removed, and update our total number of peers that // we want to protect based on uptime accordingly. - total_protect_size -= initial_size - vEvictionCandidates.size(); - EraseLastKElements(vEvictionCandidates, ReverseCompareNodeTimeConnected, total_protect_size); + assert(num_protected == initial_size - eviction_candidates.size()); + const size_t remaining_to_protect{total_protect_size - num_protected}; + EraseLastKElements(eviction_candidates, ReverseCompareNodeTimeConnected, remaining_to_protect); } [[nodiscard]] std::optional<NodeId> SelectNodeToEvict(std::vector<NodeEvictionCandidate>&& vEvictionCandidates) @@ -944,8 +985,7 @@ void ProtectEvictionCandidatesByRatio(std::vector<NodeEvictionCandidate>& vEvict // An attacker cannot manipulate this metric without performing useful work. EraseLastKElements(vEvictionCandidates, CompareNodeTXTime, 4); // Protect up to 8 non-tx-relay peers that have sent us novel blocks. - const size_t erase_size = std::min(size_t(8), vEvictionCandidates.size()); - EraseLastKElements(vEvictionCandidates, CompareNodeBlockRelayOnlyTime, erase_size, + EraseLastKElements(vEvictionCandidates, CompareNodeBlockRelayOnlyTime, 8, [](const NodeEvictionCandidate& n) { return !n.fRelayTxes && n.fRelevantServices; }); // Protect 4 nodes that most recently sent us novel blocks. @@ -1024,7 +1064,7 @@ bool CConnman::AttemptToEvictConnection() HasAllDesirableServiceFlags(node->nServices), peer_relay_txes, peer_filter_not_null, node->nKeyedNetGroup, node->m_prefer_evict, node->addr.IsLocal(), - node->m_inbound_onion}; + node->ConnectedThroughNetwork()}; vEvictionCandidates.push_back(candidate); } } @@ -2173,6 +2213,7 @@ void CConnman::OpenNetworkConnection(const CAddress& addrConnect, bool fCountFai void CConnman::ThreadMessageHandler() { + FastRandomContext rng; while (!flagInterruptMsgProc) { std::vector<CNode*> vNodesCopy; @@ -2186,6 +2227,11 @@ void CConnman::ThreadMessageHandler() bool fMoreWork = false; + // Randomize the order in which we process messages from/to our peers. + // This prevents attacks in which an attacker exploits having multiple + // consecutive connections in the vNodes list. + Shuffle(vNodesCopy.begin(), vNodesCopy.end(), rng); + for (CNode* pnode : vNodesCopy) { if (pnode->fDisconnect) @@ -1209,7 +1209,7 @@ struct NodeEvictionCandidate uint64_t nKeyedNetGroup; bool prefer_evict; bool m_is_local; - bool m_is_onion; + Network m_network; }; /** @@ -1227,20 +1227,20 @@ struct NodeEvictionCandidate * longest, to replicate the non-eviction implicit behavior and preclude attacks * that start later. * - * Half of these protected spots (1/4 of the total) are reserved for onion peers - * connected via our tor control service, if any, sorted by longest uptime, even - * if they're not longest uptime overall. Any remaining slots of the 1/4 are - * then allocated to protect localhost peers, if any (or up to 2 localhost peers - * if no slots remain and 2 or more onion peers were protected), sorted by - * longest uptime, as manually configured hidden services not using - * `-bind=addr[:port]=onion` will not be detected as inbound onion connections. + * Half of these protected spots (1/4 of the total) are reserved for the + * following categories of peers, sorted by longest uptime, even if they're not + * longest uptime overall: + * + * - onion peers connected via our tor control service + * + * - localhost peers, as manually configured hidden services not using + * `-bind=addr[:port]=onion` will not be detected as inbound onion connections * - * This helps protect onion peers, which tend to be otherwise disadvantaged - * under our eviction criteria for their higher min ping times relative to IPv4 - * and IPv6 peers, and favorise the diversity of peer connections. + * - I2P peers * - * This function was extracted from SelectNodeToEvict() to be able to test the - * ratio-based protection logic deterministically. + * This helps protect these privacy network peers, which tend to be otherwise + * disadvantaged under our eviction criteria for their higher min ping times + * relative to IPv4/IPv6 peers, and favorise the diversity of peer connections. */ void ProtectEvictionCandidatesByRatio(std::vector<NodeEvictionCandidate>& vEvictionCandidates); diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 65224b4259..b2112cfd2e 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -159,10 +159,10 @@ static constexpr size_t MAX_ADDR_TO_SEND{1000}; namespace { /** Blocks that are in flight, and that are in the queue to be downloaded. */ struct QueuedBlock { - uint256 hash; - const CBlockIndex* pindex; //!< Optional. - bool fValidatedHeaders; //!< Whether this block has validated headers at the time of request. - std::unique_ptr<PartiallyDownloadedBlock> partialBlock; //!< Optional, used for CMPCTBLOCK downloads + /** BlockIndex. We must have this since we only request blocks when we've already validated the header. */ + const CBlockIndex* pindex; + /** Optional, used for CMPCTBLOCK downloads */ + std::unique_ptr<PartiallyDownloadedBlock> partialBlock; }; /** @@ -463,16 +463,20 @@ private: Mutex m_recent_confirmed_transactions_mutex; std::unique_ptr<CRollingBloomFilter> m_recent_confirmed_transactions GUARDED_BY(m_recent_confirmed_transactions_mutex); - /* Returns a bool indicating whether we requested this block. - * Also used if a block was /not/ received and timed out or started with another peer + /** Have we requested this block from a peer */ + bool IsBlockRequested(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + + /** Remove this block from our tracked requested blocks. Called if: + * - the block has been recieved from a peer + * - the request for the block has timed out */ - bool MarkBlockAsReceived(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + void RemoveBlockRequest(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main); /* Mark a block as in flight * Returns false, still setting pit, if the block was already in flight from the same peer * pit will only be valid as long as the same cs_main lock is being held */ - bool MarkBlockAsInFlight(NodeId nodeid, const uint256& hash, const CBlockIndex* pindex = nullptr, std::list<QueuedBlock>::iterator** pit = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + bool BlockRequested(NodeId nodeid, const CBlockIndex& block, std::list<QueuedBlock>::iterator** pit = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_main); bool TipMayBeStale() EXCLUSIVE_LOCKS_REQUIRED(cs_main); @@ -512,7 +516,7 @@ private: std::list<NodeId> lNodesAnnouncingHeaderAndIDs GUARDED_BY(cs_main); /** Number of peers from which we're downloading blocks. */ - int nPeersWithValidatedDownloads GUARDED_BY(cs_main) = 0; + int m_peers_downloading_from GUARDED_BY(cs_main) = 0; /** Storage for orphan information */ TxOrphanage m_orphanage; @@ -627,7 +631,6 @@ struct CNodeState { //! When the first entry in vBlocksInFlight started downloading. Don't care when vBlocksInFlight is empty. std::chrono::microseconds m_downloading_since{0us}; int nBlocksInFlight{0}; - int nBlocksInFlightValidHeaders{0}; //! Whether we consider this a preferred download peer. bool fPreferredDownload{false}; //! Whether this peer wants invs or headers (when possible) for block announcements. @@ -758,32 +761,42 @@ static void UpdatePreferredDownload(const CNode& node, CNodeState* state) EXCLUS nPreferredDownload += state->fPreferredDownload; } -bool PeerManagerImpl::MarkBlockAsReceived(const uint256& hash) +bool PeerManagerImpl::IsBlockRequested(const uint256& hash) { - std::map<uint256, std::pair<NodeId, std::list<QueuedBlock>::iterator> >::iterator itInFlight = mapBlocksInFlight.find(hash); - if (itInFlight != mapBlocksInFlight.end()) { - CNodeState *state = State(itInFlight->second.first); - assert(state != nullptr); - state->nBlocksInFlightValidHeaders -= itInFlight->second.second->fValidatedHeaders; - if (state->nBlocksInFlightValidHeaders == 0 && itInFlight->second.second->fValidatedHeaders) { - // Last validated block on the queue was received. - nPeersWithValidatedDownloads--; - } - if (state->vBlocksInFlight.begin() == itInFlight->second.second) { - // First block on the queue was received, update the start download time for the next one - state->m_downloading_since = std::max(state->m_downloading_since, GetTime<std::chrono::microseconds>()); - } - state->vBlocksInFlight.erase(itInFlight->second.second); - state->nBlocksInFlight--; - state->m_stalling_since = 0us; - mapBlocksInFlight.erase(itInFlight); - return true; + return mapBlocksInFlight.find(hash) != mapBlocksInFlight.end(); +} + +void PeerManagerImpl::RemoveBlockRequest(const uint256& hash) +{ + auto it = mapBlocksInFlight.find(hash); + if (it == mapBlocksInFlight.end()) { + // Block was not requested + return; } - return false; + + auto [node_id, list_it] = it->second; + CNodeState *state = State(node_id); + assert(state != nullptr); + + if (state->vBlocksInFlight.begin() == list_it) { + // First block on the queue was received, update the start download time for the next one + state->m_downloading_since = std::max(state->m_downloading_since, GetTime<std::chrono::microseconds>()); + } + state->vBlocksInFlight.erase(list_it); + + state->nBlocksInFlight--; + if (state->nBlocksInFlight == 0) { + // Last validated block on the queue was received. + m_peers_downloading_from--; + } + state->m_stalling_since = 0us; + mapBlocksInFlight.erase(it); } -bool PeerManagerImpl::MarkBlockAsInFlight(NodeId nodeid, const uint256& hash, const CBlockIndex* pindex, std::list<QueuedBlock>::iterator** pit) +bool PeerManagerImpl::BlockRequested(NodeId nodeid, const CBlockIndex& block, std::list<QueuedBlock>::iterator** pit) { + const uint256& hash{block.GetBlockHash()}; + CNodeState *state = State(nodeid); assert(state != nullptr); @@ -797,22 +810,20 @@ bool PeerManagerImpl::MarkBlockAsInFlight(NodeId nodeid, const uint256& hash, co } // Make sure it's not listed somewhere already. - MarkBlockAsReceived(hash); + RemoveBlockRequest(hash); std::list<QueuedBlock>::iterator it = state->vBlocksInFlight.insert(state->vBlocksInFlight.end(), - {hash, pindex, pindex != nullptr, std::unique_ptr<PartiallyDownloadedBlock>(pit ? new PartiallyDownloadedBlock(&m_mempool) : nullptr)}); + {&block, std::unique_ptr<PartiallyDownloadedBlock>(pit ? new PartiallyDownloadedBlock(&m_mempool) : nullptr)}); state->nBlocksInFlight++; - state->nBlocksInFlightValidHeaders += it->fValidatedHeaders; if (state->nBlocksInFlight == 1) { // We're starting a block download (batch) from this peer. state->m_downloading_since = GetTime<std::chrono::microseconds>(); - } - if (state->nBlocksInFlightValidHeaders == 1 && pindex != nullptr) { - nPeersWithValidatedDownloads++; + m_peers_downloading_from++; } itInFlight = mapBlocksInFlight.insert(std::make_pair(hash, std::make_pair(nodeid, it))).first; - if (pit) + if (pit) { *pit = &itInFlight->second.second; + } return true; } @@ -978,7 +989,7 @@ void PeerManagerImpl::FindNextBlocksToDownload(NodeId nodeid, unsigned int count if (pindex->nStatus & BLOCK_HAVE_DATA || m_chainman.ActiveChain().Contains(pindex)) { if (pindex->HaveTxsDownloaded()) state->pindexLastCommonBlock = pindex; - } else if (mapBlocksInFlight.count(pindex->GetBlockHash()) == 0) { + } else if (!IsBlockRequested(pindex->GetBlockHash())) { // The block is not already downloaded, and not yet in flight. if (pindex->nHeight > nWindowEnd) { // We reached the end of the window. @@ -1129,13 +1140,13 @@ void PeerManagerImpl::FinalizeNode(const CNode& node) nSyncStarted--; for (const QueuedBlock& entry : state->vBlocksInFlight) { - mapBlocksInFlight.erase(entry.hash); + mapBlocksInFlight.erase(entry.pindex->GetBlockHash()); } WITH_LOCK(g_cs_orphans, m_orphanage.EraseForPeer(nodeid)); m_txrequest.DisconnectedPeer(nodeid); nPreferredDownload -= state->fPreferredDownload; - nPeersWithValidatedDownloads -= (state->nBlocksInFlightValidHeaders != 0); - assert(nPeersWithValidatedDownloads >= 0); + m_peers_downloading_from -= (state->nBlocksInFlight != 0); + assert(m_peers_downloading_from >= 0); m_outbound_peers_with_protect_from_disconnect -= state->m_chain_sync.m_protect; assert(m_outbound_peers_with_protect_from_disconnect >= 0); m_wtxid_relay_peers -= state->m_wtxid_relay; @@ -1147,7 +1158,7 @@ void PeerManagerImpl::FinalizeNode(const CNode& node) // Do a consistency check after the last peer is removed. assert(mapBlocksInFlight.empty()); assert(nPreferredDownload == 0); - assert(nPeersWithValidatedDownloads == 0); + assert(m_peers_downloading_from == 0); assert(m_outbound_peers_with_protect_from_disconnect == 0); assert(m_wtxid_relay_peers == 0); assert(m_txrequest.Size() == 0); @@ -1350,7 +1361,6 @@ PeerManagerImpl::PeerManagerImpl(const CChainParams& chainparams, CConnman& conn m_stale_tip_check_time(0), m_ignore_incoming_txs(ignore_incoming_txs) { - assert(std::addressof(g_chainman) == std::addressof(m_chainman)); // Initialize global variables that cannot be constructed at startup. recentRejects.reset(new CRollingBloomFilter(120000, 0.000001)); @@ -2056,7 +2066,7 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, const Peer& peer, // Calculate all the blocks we'd need to switch to pindexLast, up to a limit. while (pindexWalk && !m_chainman.ActiveChain().Contains(pindexWalk) && vToFetch.size() <= MAX_BLOCKS_IN_TRANSIT_PER_PEER) { if (!(pindexWalk->nStatus & BLOCK_HAVE_DATA) && - !mapBlocksInFlight.count(pindexWalk->GetBlockHash()) && + !IsBlockRequested(pindexWalk->GetBlockHash()) && (!IsWitnessEnabled(pindexWalk->pprev, m_chainparams.GetConsensus()) || State(pfrom.GetId())->fHaveWitness)) { // We don't have this block, and it's not yet in flight. vToFetch.push_back(pindexWalk); @@ -2081,7 +2091,7 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, const Peer& peer, } uint32_t nFetchFlags = GetFetchFlags(pfrom); vGetData.push_back(CInv(MSG_BLOCK | nFetchFlags, pindex->GetBlockHash())); - MarkBlockAsInFlight(pfrom.GetId(), pindex->GetBlockHash(), pindex); + BlockRequested(pfrom.GetId(), *pindex); LogPrint(BCLog::NET, "Requesting block %s from peer=%d\n", pindex->GetBlockHash().ToString(), pfrom.GetId()); } @@ -2827,7 +2837,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, LogPrint(BCLog::NET, "got inv: %s %s peer=%d\n", inv.ToString(), fAlreadyHave ? "have" : "new", pfrom.GetId()); UpdateBlockAvailability(pfrom.GetId(), inv.hash); - if (!fAlreadyHave && !fImporting && !fReindex && !mapBlocksInFlight.count(inv.hash)) { + if (!fAlreadyHave && !fImporting && !fReindex && !IsBlockRequested(inv.hash)) { // Headers-first is the primary method of announcement on // the network. If a node fell back to sending blocks by inv, // it's probably for a re-org. The final block hash @@ -3384,7 +3394,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, if ((!fAlreadyInFlight && nodestate->nBlocksInFlight < MAX_BLOCKS_IN_TRANSIT_PER_PEER) || (fAlreadyInFlight && blockInFlightIt->second.first == pfrom.GetId())) { std::list<QueuedBlock>::iterator* queuedBlockIt = nullptr; - if (!MarkBlockAsInFlight(pfrom.GetId(), pindex->GetBlockHash(), pindex, &queuedBlockIt)) { + if (!BlockRequested(pfrom.GetId(), *pindex, &queuedBlockIt)) { if (!(*queuedBlockIt)->partialBlock) (*queuedBlockIt)->partialBlock.reset(new PartiallyDownloadedBlock(&m_mempool)); else { @@ -3397,7 +3407,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, PartiallyDownloadedBlock& partialBlock = *(*queuedBlockIt)->partialBlock; ReadStatus status = partialBlock.InitData(cmpctblock, vExtraTxnForCompact); if (status == READ_STATUS_INVALID) { - MarkBlockAsReceived(pindex->GetBlockHash()); // Reset in-flight state in case Misbehaving does not result in a disconnect + RemoveBlockRequest(pindex->GetBlockHash()); // Reset in-flight state in case Misbehaving does not result in a disconnect Misbehaving(pfrom.GetId(), 100, "invalid compact block"); return; } else if (status == READ_STATUS_FAILED) { @@ -3492,7 +3502,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, // process from some other peer. We do this after calling // ProcessNewBlock so that a malleated cmpctblock announcement // can't be used to interfere with block relay. - MarkBlockAsReceived(pblock->GetHash()); + RemoveBlockRequest(pblock->GetHash()); } } return; @@ -3524,7 +3534,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, PartiallyDownloadedBlock& partialBlock = *it->second.second->partialBlock; ReadStatus status = partialBlock.FillBlock(*pblock, resp.txn); if (status == READ_STATUS_INVALID) { - MarkBlockAsReceived(resp.blockhash); // Reset in-flight state in case Misbehaving does not result in a disconnect + RemoveBlockRequest(resp.blockhash); // Reset in-flight state in case Misbehaving does not result in a disconnect Misbehaving(pfrom.GetId(), 100, "invalid compact block/non-matching block transactions"); return; } else if (status == READ_STATUS_FAILED) { @@ -3550,7 +3560,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, // though the block was successfully read, and rely on the // handling in ProcessNewBlock to ensure the block index is // updated, etc. - MarkBlockAsReceived(resp.blockhash); // it is now an empty pointer + RemoveBlockRequest(resp.blockhash); // it is now an empty pointer fBlockRead = true; // mapBlockSource is used for potentially punishing peers and // updating which peers send us compact blocks, so the race @@ -3615,9 +3625,10 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, const uint256 hash(pblock->GetHash()); { LOCK(cs_main); - // Also always process if we requested the block explicitly, as we may - // need it even though it is not a candidate for a new best tip. - forceProcessing |= MarkBlockAsReceived(hash); + // Always process the block if we requested it, since we may + // need it even when it's not a candidate for a new best tip. + forceProcessing = IsBlockRequested(hash); + RemoveBlockRequest(hash); // mapBlockSource is only used for punishing peers and setting // which peers send us compact blocks, so the race between here and // cs_main in ProcessNewBlock is fine. @@ -4712,9 +4723,9 @@ bool PeerManagerImpl::SendMessages(CNode* pto) // to unreasonably increase our timeout. if (state.vBlocksInFlight.size() > 0) { QueuedBlock &queuedBlock = state.vBlocksInFlight.front(); - int nOtherPeersWithValidatedDownloads = nPeersWithValidatedDownloads - (state.nBlocksInFlightValidHeaders > 0); + int nOtherPeersWithValidatedDownloads = m_peers_downloading_from - 1; if (current_time > state.m_downloading_since + std::chrono::seconds{consensusParams.nPowTargetSpacing} * (BLOCK_DOWNLOAD_TIMEOUT_BASE + BLOCK_DOWNLOAD_TIMEOUT_PER_PEER * nOtherPeersWithValidatedDownloads)) { - LogPrintf("Timeout downloading block %s from peer=%d, disconnecting\n", queuedBlock.hash.ToString(), pto->GetId()); + LogPrintf("Timeout downloading block %s from peer=%d, disconnecting\n", queuedBlock.pindex->GetBlockHash().ToString(), pto->GetId()); pto->fDisconnect = true; return true; } @@ -4767,7 +4778,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto) for (const CBlockIndex *pindex : vToDownload) { uint32_t nFetchFlags = GetFetchFlags(*pto); vGetData.push_back(CInv(MSG_BLOCK | nFetchFlags, pindex->GetBlockHash())); - MarkBlockAsInFlight(pto->GetId(), pindex->GetBlockHash(), pindex); + BlockRequested(pto->GetId(), *pindex); LogPrint(BCLog::NET, "Requesting block %s (%d) peer=%d\n", pindex->GetBlockHash().ToString(), pindex->nHeight, pto->GetId()); } diff --git a/src/netaddress.h b/src/netaddress.h index 0d04ab88fb..dd47ab5749 100644 --- a/src/netaddress.h +++ b/src/netaddress.h @@ -11,7 +11,9 @@ #include <attributes.h> #include <compat.h> +#include <crypto/siphash.h> #include <prevector.h> +#include <random.h> #include <serialize.h> #include <tinyformat.h> #include <util/strencodings.h> @@ -251,6 +253,7 @@ class CNetAddr } } + friend class CNetAddrHash; friend class CSubNet; private: @@ -464,6 +467,22 @@ class CNetAddr } }; +class CNetAddrHash +{ +public: + size_t operator()(const CNetAddr& a) const noexcept + { + CSipHasher hasher(m_salt_k0, m_salt_k1); + hasher.Write(a.m_net); + hasher.Write(a.m_addr.data(), a.m_addr.size()); + return static_cast<size_t>(hasher.Finalize()); + } + +private: + const uint64_t m_salt_k0 = GetRand(std::numeric_limits<uint64_t>::max()); + const uint64_t m_salt_k1 = GetRand(std::numeric_limits<uint64_t>::max()); +}; + class CSubNet { protected: diff --git a/src/node/blockstorage.cpp b/src/node/blockstorage.cpp index 6c66c565ad..013d61282b 100644 --- a/src/node/blockstorage.cpp +++ b/src/node/blockstorage.cpp @@ -248,7 +248,6 @@ bool FindBlockPos(FlatFilePos& pos, unsigned int nAddSize, unsigned int nHeight, // when the undo file is keeping up with the block file, we want to flush it explicitly // when it is lagging behind (more blocks arrive than are being connected), we let the // undo block write case handle it - assert(std::addressof(::ChainActive()) == std::addressof(active_chain)); finalize_undo = (vinfoBlockFile[nFile].nHeightLast == (unsigned int)active_chain.Tip()->nHeight); nFile++; if (vinfoBlockFile.size() <= nFile) { diff --git a/src/node/coin.cpp b/src/node/coin.cpp index 23d4fa2aae..50fddf3ab0 100644 --- a/src/node/coin.cpp +++ b/src/node/coin.cpp @@ -13,7 +13,6 @@ void FindCoins(const NodeContext& node, std::map<COutPoint, Coin>& coins) assert(node.mempool); assert(node.chainman); LOCK2(cs_main, node.mempool->cs); - assert(std::addressof(::ChainstateActive()) == std::addressof(node.chainman->ActiveChainstate())); CCoinsViewCache& chain_view = node.chainman->ActiveChainstate().CoinsTip(); CCoinsViewMemPool mempool_view(&chain_view, *node.mempool); for (auto& coin : coins) { diff --git a/src/node/coinstats.cpp b/src/node/coinstats.cpp index 38c1d29250..67e497c218 100644 --- a/src/node/coinstats.cpp +++ b/src/node/coinstats.cpp @@ -97,7 +97,6 @@ static bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats& if (!pindex) { { LOCK(cs_main); - assert(std::addressof(g_chainman.m_blockman) == std::addressof(blockman)); pindex = blockman.LookupBlockIndex(view->GetBestBlock()); } } diff --git a/src/node/context.cpp b/src/node/context.cpp index 6d22a6b110..9afadd09a9 100644 --- a/src/node/context.cpp +++ b/src/node/context.cpp @@ -12,6 +12,7 @@ #include <policy/fees.h> #include <scheduler.h> #include <txmempool.h> +#include <validation.h> NodeContext::NodeContext() {} NodeContext::~NodeContext() {} diff --git a/src/node/context.h b/src/node/context.h index 06adb33a80..135f9ea1c6 100644 --- a/src/node/context.h +++ b/src/node/context.h @@ -44,7 +44,7 @@ struct NodeContext { std::unique_ptr<CTxMemPool> mempool; std::unique_ptr<CBlockPolicyEstimator> fee_estimator; std::unique_ptr<PeerManager> peerman; - ChainstateManager* chainman{nullptr}; // Currently a raw pointer because the memory is not managed by this struct + std::unique_ptr<ChainstateManager> chainman; std::unique_ptr<BanMan> banman; ArgsManager* args{nullptr}; // Currently a raw pointer because the memory is not managed by this struct std::unique_ptr<interfaces::Chain> chain; diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index 171f15d4fb..fce3c1809c 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -6,6 +6,7 @@ #include <banman.h> #include <chain.h> #include <chainparams.h> +#include <external_signer.h> #include <init.h> #include <interfaces/chain.h> #include <interfaces/handler.h> @@ -170,16 +171,24 @@ public: } return false; } -#ifdef ENABLE_EXTERNAL_SIGNER std::vector<ExternalSigner> externalSigners() override { +#ifdef ENABLE_EXTERNAL_SIGNER std::vector<ExternalSigner> signers = {}; const std::string command = gArgs.GetArg("-signer", ""); if (command == "") return signers; ExternalSigner::Enumerate(command, signers, Params().NetworkIDString()); return signers; +#else + // This result is undistinguisable from a succesful call that returns + // no signers. For the current GUI this doesn't matter, because the wallet + // creation dialog disables the external signer checkbox in both + // cases. The return type could be changed to std::optional<std::vector> + // (or something that also includes error messages) if this distinction + // becomes important. + return {}; +#endif // ENABLE_EXTERNAL_SIGNER } -#endif int64_t getTotalBytesRecv() override { return m_context->connman ? m_context->connman->GetTotalBytesRecv() : 0; } int64_t getTotalBytesSent() override { return m_context->connman ? m_context->connman->GetTotalBytesSent() : 0; } size_t getMempoolSize() override { return m_context->mempool ? m_context->mempool->size() : 0; } @@ -197,26 +206,16 @@ public: int getNumBlocks() override { LOCK(::cs_main); - assert(std::addressof(::ChainActive()) == std::addressof(chainman().ActiveChain())); return chainman().ActiveChain().Height(); } uint256 getBestBlockHash() override { - const CBlockIndex* tip; - { - // TODO: Temporary scope to check correctness of refactored code. - // Should be removed manually after merge of - // https://github.com/bitcoin/bitcoin/pull/20158 - LOCK(cs_main); - assert(std::addressof(::ChainActive()) == std::addressof(chainman().ActiveChain())); - tip = chainman().ActiveChain().Tip(); - } + const CBlockIndex* tip = WITH_LOCK(::cs_main, return chainman().ActiveChain().Tip()); return tip ? tip->GetBlockHash() : Params().GenesisBlock().GetHash(); } int64_t getLastBlockTime() override { LOCK(::cs_main); - assert(std::addressof(::ChainActive()) == std::addressof(chainman().ActiveChain())); if (chainman().ActiveChain().Tip()) { return chainman().ActiveChain().Tip()->GetBlockTime(); } @@ -227,22 +226,12 @@ public: const CBlockIndex* tip; { LOCK(::cs_main); - assert(std::addressof(::ChainActive()) == std::addressof(chainman().ActiveChain())); tip = chainman().ActiveChain().Tip(); } return GuessVerificationProgress(Params().TxData(), tip); } bool isInitialBlockDownload() override { - const CChainState* active_chainstate; - { - // TODO: Temporary scope to check correctness of refactored code. - // Should be removed manually after merge of - // https://github.com/bitcoin/bitcoin/pull/20158 - LOCK(::cs_main); - active_chainstate = &m_context->chainman->ActiveChainstate(); - assert(std::addressof(::ChainstateActive()) == std::addressof(*active_chainstate)); - } - return active_chainstate->IsInitialBlockDownload(); + return chainman().ActiveChainstate().IsInitialBlockDownload(); } bool getReindex() override { return ::fReindex; } bool getImporting() override { return ::fImporting; } @@ -269,7 +258,6 @@ public: bool getUnspentOutput(const COutPoint& output, Coin& coin) override { LOCK(::cs_main); - assert(std::addressof(::ChainstateActive()) == std::addressof(chainman().ActiveChainstate())); return chainman().ActiveChainstate().CoinsTip().GetCoin(output, coin); } WalletClient& walletClient() override @@ -476,14 +464,12 @@ public: bool checkFinalTx(const CTransaction& tx) override { LOCK(cs_main); - assert(std::addressof(::ChainActive()) == std::addressof(chainman().ActiveChain())); return CheckFinalTx(chainman().ActiveChain().Tip(), tx); } std::optional<int> findLocatorFork(const CBlockLocator& locator) override { LOCK(cs_main); const CChain& active = Assert(m_node.chainman)->ActiveChain(); - assert(std::addressof(g_chainman) == std::addressof(*m_node.chainman)); if (CBlockIndex* fork = m_node.chainman->m_blockman.FindForkInGlobalIndex(active, locator)) { return fork->nHeight; } @@ -493,7 +479,6 @@ public: { WAIT_LOCK(cs_main, lock); const CChain& active = Assert(m_node.chainman)->ActiveChain(); - assert(std::addressof(g_chainman) == std::addressof(*m_node.chainman)); return FillBlock(m_node.chainman->m_blockman.LookupBlockIndex(hash), block, lock, active); } bool findFirstBlockWithTimeAndHeight(int64_t min_time, int min_height, const FoundBlock& block) override @@ -506,7 +491,6 @@ public: { WAIT_LOCK(cs_main, lock); const CChain& active = Assert(m_node.chainman)->ActiveChain(); - assert(std::addressof(g_chainman) == std::addressof(*m_node.chainman)); if (const CBlockIndex* block = m_node.chainman->m_blockman.LookupBlockIndex(block_hash)) { if (const CBlockIndex* ancestor = block->GetAncestor(ancestor_height)) { return FillBlock(ancestor, ancestor_out, lock, active); @@ -518,9 +502,7 @@ public: { WAIT_LOCK(cs_main, lock); const CChain& active = Assert(m_node.chainman)->ActiveChain(); - assert(std::addressof(g_chainman) == std::addressof(*m_node.chainman)); const CBlockIndex* block = m_node.chainman->m_blockman.LookupBlockIndex(block_hash); - assert(std::addressof(g_chainman) == std::addressof(*m_node.chainman)); const CBlockIndex* ancestor = m_node.chainman->m_blockman.LookupBlockIndex(ancestor_hash); if (block && ancestor && block->GetAncestor(ancestor->nHeight) != ancestor) ancestor = nullptr; return FillBlock(ancestor, ancestor_out, lock, active); @@ -529,9 +511,7 @@ public: { WAIT_LOCK(cs_main, lock); const CChain& active = Assert(m_node.chainman)->ActiveChain(); - assert(std::addressof(g_chainman) == std::addressof(*m_node.chainman)); const CBlockIndex* block1 = m_node.chainman->m_blockman.LookupBlockIndex(block_hash1); - assert(std::addressof(g_chainman) == std::addressof(*m_node.chainman)); const CBlockIndex* block2 = m_node.chainman->m_blockman.LookupBlockIndex(block_hash2); const CBlockIndex* ancestor = block1 && block2 ? LastCommonAncestor(block1, block2) : nullptr; // Using & instead of && below to avoid short circuiting and leaving @@ -542,7 +522,6 @@ public: double guessVerificationProgress(const uint256& block_hash) override { LOCK(cs_main); - assert(std::addressof(g_chainman.m_blockman) == std::addressof(chainman().m_blockman)); return GuessVerificationProgress(Params().TxData(), chainman().m_blockman.LookupBlockIndex(block_hash)); } bool hasBlocks(const uint256& block_hash, int min_height, std::optional<int> max_height) override @@ -555,7 +534,6 @@ public: // used to limit the range, and passing min_height that's too low or // max_height that's too high will not crash or change the result. LOCK(::cs_main); - assert(std::addressof(g_chainman.m_blockman) == std::addressof(chainman().m_blockman)); if (CBlockIndex* block = chainman().m_blockman.LookupBlockIndex(block_hash)) { if (max_height && block->nHeight >= *max_height) block = block->GetAncestor(*max_height); for (; block->nStatus & BLOCK_HAVE_DATA; block = block->pprev) { @@ -647,16 +625,7 @@ public: } bool isReadyToBroadcast() override { return !::fImporting && !::fReindex && !isInitialBlockDownload(); } bool isInitialBlockDownload() override { - const CChainState* active_chainstate; - { - // TODO: Temporary scope to check correctness of refactored code. - // Should be removed manually after merge of - // https://github.com/bitcoin/bitcoin/pull/20158 - LOCK(::cs_main); - active_chainstate = &chainman().ActiveChainstate(); - assert(std::addressof(::ChainstateActive()) == std::addressof(*active_chainstate)); - } - return active_chainstate->IsInitialBlockDownload(); + return chainman().ActiveChainstate().IsInitialBlockDownload(); } bool shutdownRequested() override { return ShutdownRequested(); } int64_t getAdjustedTime() override { return GetAdjustedTime(); } @@ -719,6 +688,12 @@ public: notifications.transactionAddedToMempool(entry.GetSharedTx(), 0 /* mempool_sequence */); } } + bool isTaprootActive() const override + { + LOCK(::cs_main); + const CBlockIndex* tip = Assert(m_node.chainman)->ActiveChain().Tip(); + return VersionBitsState(tip, Params().GetConsensus(), Consensus::DEPLOYMENT_TAPROOT, versionbitscache) == ThresholdState::ACTIVE; + } NodeContext& m_node; }; } // namespace diff --git a/src/node/psbt.cpp b/src/node/psbt.cpp index c189018268..b013b6d579 100644 --- a/src/node/psbt.cpp +++ b/src/node/psbt.cpp @@ -23,6 +23,8 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx) result.inputs.resize(psbtx.tx->vin.size()); + const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx); + for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { PSBTInput& input = psbtx.inputs[i]; PSBTInputAnalysis& input_analysis = result.inputs[i]; @@ -61,7 +63,7 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx) // Figure out what is missing SignatureData outdata; - bool complete = SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, 1, &outdata); + bool complete = SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, &txdata, 1, &outdata); // Things are missing if (!complete) { @@ -121,7 +123,7 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx) PSBTInput& input = psbtx.inputs[i]; Coin newcoin; - if (!SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, 1, nullptr, true) || !psbtx.GetInputUTXO(newcoin.out, i)) { + if (!SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, nullptr, 1) || !psbtx.GetInputUTXO(newcoin.out, i)) { success = false; break; } else { diff --git a/src/node/transaction.cpp b/src/node/transaction.cpp index a1e7a71e2c..f21b390915 100644 --- a/src/node/transaction.cpp +++ b/src/node/transaction.cpp @@ -40,7 +40,6 @@ TransactionError BroadcastTransaction(NodeContext& node, const CTransactionRef t { // cs_main scope assert(node.chainman); LOCK(cs_main); - assert(std::addressof(::ChainstateActive()) == std::addressof(node.chainman->ActiveChainstate())); // If the transaction is already confirmed in the chain, don't do anything // and return early. CCoinsViewCache &view = node.chainman->ActiveChainstate().CoinsTip(); diff --git a/src/policy/packages.cpp b/src/policy/packages.cpp new file mode 100644 index 0000000000..cfd0539965 --- /dev/null +++ b/src/policy/packages.cpp @@ -0,0 +1,62 @@ +// Copyright (c) 2021 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <consensus/validation.h> +#include <policy/packages.h> +#include <primitives/transaction.h> +#include <uint256.h> +#include <util/hasher.h> + +#include <numeric> +#include <unordered_set> + +bool CheckPackage(const Package& txns, PackageValidationState& state) +{ + const unsigned int package_count = txns.size(); + + if (package_count > MAX_PACKAGE_COUNT) { + return state.Invalid(PackageValidationResult::PCKG_POLICY, "package-too-many-transactions"); + } + + const int64_t total_size = std::accumulate(txns.cbegin(), txns.cend(), 0, + [](int64_t sum, const auto& tx) { return sum + GetVirtualTransactionSize(*tx); }); + // If the package only contains 1 tx, it's better to report the policy violation on individual tx size. + if (package_count > 1 && total_size > MAX_PACKAGE_SIZE * 1000) { + return state.Invalid(PackageValidationResult::PCKG_POLICY, "package-too-large"); + } + + // Require the package to be sorted in order of dependency, i.e. parents appear before children. + // An unsorted package will fail anyway on missing-inputs, but it's better to quit earlier and + // fail on something less ambiguous (missing-inputs could also be an orphan or trying to + // spend nonexistent coins). + std::unordered_set<uint256, SaltedTxidHasher> later_txids; + std::transform(txns.cbegin(), txns.cend(), std::inserter(later_txids, later_txids.end()), + [](const auto& tx) { return tx->GetHash(); }); + for (const auto& tx : txns) { + for (const auto& input : tx->vin) { + if (later_txids.find(input.prevout.hash) != later_txids.end()) { + // The parent is a subsequent transaction in the package. + return state.Invalid(PackageValidationResult::PCKG_POLICY, "package-not-sorted"); + } + } + later_txids.erase(tx->GetHash()); + } + + // Don't allow any conflicting transactions, i.e. spending the same inputs, in a package. + std::unordered_set<COutPoint, SaltedOutpointHasher> inputs_seen; + for (const auto& tx : txns) { + for (const auto& input : tx->vin) { + if (inputs_seen.find(input.prevout) != inputs_seen.end()) { + // This input is also present in another tx in the package. + return state.Invalid(PackageValidationResult::PCKG_POLICY, "conflict-in-package"); + } + } + // Batch-add all the inputs for a tx at a time. If we added them 1 at a time, we could + // catch duplicate inputs within a single tx. This is a more severe, consensus error, + // and we want to report that from CheckTransaction instead. + std::transform(tx->vin.cbegin(), tx->vin.cend(), std::inserter(inputs_seen, inputs_seen.end()), + [](const auto& input) { return input.prevout; }); + } + return true; +} diff --git a/src/policy/packages.h b/src/policy/packages.h index 4b1463dcb3..6b7ac3e450 100644 --- a/src/policy/packages.h +++ b/src/policy/packages.h @@ -6,6 +6,7 @@ #define BITCOIN_POLICY_PACKAGES_H #include <consensus/validation.h> +#include <policy/policy.h> #include <primitives/transaction.h> #include <vector> @@ -14,6 +15,7 @@ static constexpr uint32_t MAX_PACKAGE_COUNT{25}; /** Default maximum total virtual size of transactions in a package in KvB. */ static constexpr uint32_t MAX_PACKAGE_SIZE{101}; +static_assert(MAX_PACKAGE_SIZE * WITNESS_SCALE_FACTOR * 1000 >= MAX_STANDARD_TX_WEIGHT); /** A "reason" why a package was invalid. It may be that one or more of the included * transactions is invalid or the package itself violates our rules. @@ -31,4 +33,12 @@ using Package = std::vector<CTransactionRef>; class PackageValidationState : public ValidationState<PackageValidationResult> {}; +/** Context-free package policy checks: + * 1. The number of transactions cannot exceed MAX_PACKAGE_COUNT. + * 2. The total virtual size cannot exceed MAX_PACKAGE_SIZE. + * 3. If any dependencies exist between transactions, parents must appear before children. + * 4. Transactions cannot conflict, i.e., spend the same inputs. + */ +bool CheckPackage(const Package& txns, PackageValidationState& state); + #endif // BITCOIN_POLICY_PACKAGES_H diff --git a/src/protocol.h b/src/protocol.h index aaa9f1df40..f9248899dc 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -13,6 +13,7 @@ #include <netaddress.h> #include <primitives/transaction.h> #include <serialize.h> +#include <streams.h> #include <uint256.h> #include <version.h> @@ -358,6 +359,31 @@ class CAddress : public CService { static constexpr uint32_t TIME_INIT{100000000}; + /** Historically, CAddress disk serialization stored the CLIENT_VERSION, optionally OR'ed with + * the ADDRV2_FORMAT flag to indicate V2 serialization. The first field has since been + * disentangled from client versioning, and now instead: + * - The low bits (masked by DISK_VERSION_IGNORE_MASK) store the fixed value DISK_VERSION_INIT, + * (in case any code exists that treats it as a client version) but are ignored on + * deserialization. + * - The high bits (masked by ~DISK_VERSION_IGNORE_MASK) store actual serialization information. + * Only 0 or DISK_VERSION_ADDRV2 (equal to the historical ADDRV2_FORMAT) are valid now, and + * any other value triggers a deserialization failure. Other values can be added later if + * needed. + * + * For disk deserialization, ADDRV2_FORMAT in the stream version signals that ADDRV2 + * deserialization is permitted, but the actual format is determined by the high bits in the + * stored version field. For network serialization, the stream version having ADDRV2_FORMAT or + * not determines the actual format used (as it has no embedded version number). + */ + static constexpr uint32_t DISK_VERSION_INIT{220000}; + static constexpr uint32_t DISK_VERSION_IGNORE_MASK{0b00000000'00000111'11111111'11111111}; + /** The version number written in disk serialized addresses to indicate V2 serializations. + * It must be exactly 1<<29, as that is the value that historical versions used for this + * (they used their internal ADDRV2_FORMAT flag here). */ + static constexpr uint32_t DISK_VERSION_ADDRV2{1 << 29}; + static_assert((DISK_VERSION_INIT & ~DISK_VERSION_IGNORE_MASK) == 0, "DISK_VERSION_INIT must be covered by DISK_VERSION_IGNORE_MASK"); + static_assert((DISK_VERSION_ADDRV2 & DISK_VERSION_IGNORE_MASK) == 0, "DISK_VERSION_ADDRV2 must not be covered by DISK_VERSION_IGNORE_MASK"); + public: CAddress() : CService{} {}; CAddress(CService ipIn, ServiceFlags nServicesIn) : CService{ipIn}, nServices{nServicesIn} {}; @@ -365,22 +391,48 @@ public: SERIALIZE_METHODS(CAddress, obj) { - SER_READ(obj, obj.nTime = TIME_INIT); - int nVersion = s.GetVersion(); + // CAddress has a distinct network serialization and a disk serialization, but it should never + // be hashed (except through CHashWriter in addrdb.cpp, which sets SER_DISK), and it's + // ambiguous what that would mean. Make sure no code relying on that is introduced: + assert(!(s.GetType() & SER_GETHASH)); + bool use_v2; + bool store_time; if (s.GetType() & SER_DISK) { - READWRITE(nVersion); - } - if ((s.GetType() & SER_DISK) || - (nVersion != INIT_PROTO_VERSION && !(s.GetType() & SER_GETHASH))) { + // In the disk serialization format, the encoding (v1 or v2) is determined by a flag version + // that's part of the serialization itself. ADDRV2_FORMAT in the stream version only determines + // whether V2 is chosen/permitted at all. + uint32_t stored_format_version = DISK_VERSION_INIT; + if (s.GetVersion() & ADDRV2_FORMAT) stored_format_version |= DISK_VERSION_ADDRV2; + READWRITE(stored_format_version); + stored_format_version &= ~DISK_VERSION_IGNORE_MASK; // ignore low bits + if (stored_format_version == 0) { + use_v2 = false; + } else if (stored_format_version == DISK_VERSION_ADDRV2 && (s.GetVersion() & ADDRV2_FORMAT)) { + // Only support v2 deserialization if ADDRV2_FORMAT is set. + use_v2 = true; + } else { + throw std::ios_base::failure("Unsupported CAddress disk format version"); + } + store_time = true; + } else { + // In the network serialization format, the encoding (v1 or v2) is determined directly by + // the value of ADDRV2_FORMAT in the stream version, as no explicitly encoded version + // exists in the stream. + assert(s.GetType() & SER_NETWORK); + use_v2 = s.GetVersion() & ADDRV2_FORMAT; // The only time we serialize a CAddress object without nTime is in // the initial VERSION messages which contain two CAddress records. // At that point, the serialization version is INIT_PROTO_VERSION. // After the version handshake, serialization version is >= // MIN_PEER_PROTO_VERSION and all ADDR messages are serialized with // nTime. - READWRITE(obj.nTime); + store_time = s.GetVersion() != INIT_PROTO_VERSION; } - if (nVersion & ADDRV2_FORMAT) { + + SER_READ(obj, obj.nTime = TIME_INIT); + if (store_time) READWRITE(obj.nTime); + // nServices is serialized as CompactSize in V2; as uint64_t in V1. + if (use_v2) { uint64_t services_tmp; SER_WRITE(obj, services_tmp = obj.nServices); READWRITE(Using<CompactSizeFormatter<false>>(services_tmp)); @@ -388,13 +440,22 @@ public: } else { READWRITE(Using<CustomUintFormatter<8>>(obj.nServices)); } - READWRITEAS(CService, obj); + // Invoke V1/V2 serializer for CService parent object. + OverrideStream<Stream> os(&s, s.GetType(), use_v2 ? ADDRV2_FORMAT : 0); + SerReadWriteMany(os, ser_action, ReadWriteAsHelper<CService>(obj)); } - // disk and network only + //! Always included in serialization, except in the network format on INIT_PROTO_VERSION. uint32_t nTime{TIME_INIT}; - + //! Serialized as uint64_t in V1, and as CompactSize in V2. ServiceFlags nServices{NODE_NONE}; + + friend bool operator==(const CAddress& a, const CAddress& b) + { + return a.nTime == b.nTime && + a.nServices == b.nServices && + static_cast<const CService&>(a) == static_cast<const CService&>(b); + } }; /** getdata message type flags */ diff --git a/src/psbt.cpp b/src/psbt.cpp index a849b2ea53..5445bc8aa1 100644 --- a/src/psbt.cpp +++ b/src/psbt.cpp @@ -59,12 +59,15 @@ bool PartiallySignedTransaction::AddOutput(const CTxOut& txout, const PSBTOutput bool PartiallySignedTransaction::GetInputUTXO(CTxOut& utxo, int input_index) const { - PSBTInput input = inputs[input_index]; + const PSBTInput& input = inputs[input_index]; uint32_t prevout_index = tx->vin[input_index].prevout.n; if (input.non_witness_utxo) { if (prevout_index >= input.non_witness_utxo->vout.size()) { return false; } + if (input.non_witness_utxo->GetHash() != tx->vin[input_index].prevout.hash) { + return false; + } utxo = input.non_witness_utxo->vout[prevout_index]; } else if (!input.witness_utxo.IsNull()) { utxo = input.witness_utxo; @@ -227,7 +230,24 @@ void UpdatePSBTOutput(const SigningProvider& provider, PartiallySignedTransactio psbt_out.FromSignatureData(sigdata); } -bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, int sighash, SignatureData* out_sigdata, bool use_dummy) +PrecomputedTransactionData PrecomputePSBTData(const PartiallySignedTransaction& psbt) +{ + const CMutableTransaction& tx = *psbt.tx; + bool have_all_spent_outputs = true; + std::vector<CTxOut> utxos(tx.vin.size()); + for (size_t idx = 0; idx < tx.vin.size(); ++idx) { + if (!psbt.GetInputUTXO(utxos[idx], idx)) have_all_spent_outputs = false; + } + PrecomputedTransactionData txdata; + if (have_all_spent_outputs) { + txdata.Init(tx, std::move(utxos), true); + } else { + txdata.Init(tx, {}, true); + } + return txdata; +} + +bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, const PrecomputedTransactionData* txdata, int sighash, SignatureData* out_sigdata) { PSBTInput& input = psbt.inputs.at(index); const CMutableTransaction& tx = *psbt.tx; @@ -267,10 +287,10 @@ bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& sigdata.witness = false; bool sig_complete; - if (use_dummy) { + if (txdata == nullptr) { sig_complete = ProduceSignature(provider, DUMMY_SIGNATURE_CREATOR, utxo.scriptPubKey, sigdata); } else { - MutableTransactionSignatureCreator creator(&tx, index, utxo.nValue, sighash); + MutableTransactionSignatureCreator creator(&tx, index, utxo.nValue, txdata, sighash); sig_complete = ProduceSignature(provider, creator, utxo.scriptPubKey, sigdata); } // Verify that a witness signature was produced in case one was required. @@ -302,8 +322,9 @@ bool FinalizePSBT(PartiallySignedTransaction& psbtx) // PartiallySignedTransaction did not understand them), this will combine them into a final // script. bool complete = true; + const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx); for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { - complete &= SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, SIGHASH_ALL); + complete &= SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, &txdata, SIGHASH_ALL); } return complete; diff --git a/src/psbt.h b/src/psbt.h index 96ae39fdb8..f6b82b43de 100644 --- a/src/psbt.h +++ b/src/psbt.h @@ -567,11 +567,18 @@ enum class PSBTRole { std::string PSBTRoleName(PSBTRole role); +/** Compute a PrecomputedTransactionData object from a psbt. */ +PrecomputedTransactionData PrecomputePSBTData(const PartiallySignedTransaction& psbt); + /** Checks whether a PSBTInput is already signed. */ bool PSBTInputSigned(const PSBTInput& input); -/** Signs a PSBTInput, verifying that all provided data matches what is being signed. */ -bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, int sighash = SIGHASH_ALL, SignatureData* out_sigdata = nullptr, bool use_dummy = false); +/** Signs a PSBTInput, verifying that all provided data matches what is being signed. + * + * txdata should be the output of PrecomputePSBTData (which can be shared across + * multiple SignPSBTInput calls). If it is nullptr, a dummy signature will be created. + **/ +bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, const PrecomputedTransactionData* txdata, int sighash = SIGHASH_ALL, SignatureData* out_sigdata = nullptr); /** Counts the unsigned inputs of a PSBT. */ size_t CountPSBTUnsignedInputs(const PartiallySignedTransaction& psbt); diff --git a/src/pubkey.cpp b/src/pubkey.cpp index 51cc826b00..175a39b805 100644 --- a/src/pubkey.cpp +++ b/src/pubkey.cpp @@ -373,3 +373,7 @@ ECCVerifyHandle::~ECCVerifyHandle() secp256k1_context_verify = nullptr; } } + +const secp256k1_context* GetVerifyContext() { + return secp256k1_context_verify; +} diff --git a/src/pubkey.h b/src/pubkey.h index 152a48dd18..eec34a89c2 100644 --- a/src/pubkey.h +++ b/src/pubkey.h @@ -234,6 +234,10 @@ public: * fail. */ bool IsFullyValid() const; + /** Test whether this is the 0 key (the result of default construction). This implies + * !IsFullyValid(). */ + bool IsNull() const { return m_keydata.IsNull(); } + /** Construct an x-only pubkey from exactly 32 bytes. */ explicit XOnlyPubKey(Span<const unsigned char> bytes); @@ -312,4 +316,10 @@ public: ~ECCVerifyHandle(); }; +typedef struct secp256k1_context_struct secp256k1_context; + +/** Access to the internal secp256k1 context used for verification. Only intended to be used + * by key.cpp. */ +const secp256k1_context* GetVerifyContext(); + #endif // BITCOIN_PUBKEY_H diff --git a/src/qt/addressbookpage.cpp b/src/qt/addressbookpage.cpp index 7024fc7654..c31f0aceea 100644 --- a/src/qt/addressbookpage.cpp +++ b/src/qt/addressbookpage.cpp @@ -114,12 +114,12 @@ AddressBookPage::AddressBookPage(const PlatformStyle *platformStyle, Mode _mode, // Build context menu contextMenu = new QMenu(this); - contextMenu->addAction(tr("Copy Address"), this, &AddressBookPage::on_copyAddress_clicked); - contextMenu->addAction(tr("Copy Label"), this, &AddressBookPage::onCopyLabelAction); - contextMenu->addAction(tr("Edit"), this, &AddressBookPage::onEditAction); + contextMenu->addAction(tr("&Copy Address"), this, &AddressBookPage::on_copyAddress_clicked); + contextMenu->addAction(tr("Copy &Label"), this, &AddressBookPage::onCopyLabelAction); + contextMenu->addAction(tr("&Edit"), this, &AddressBookPage::onEditAction); if (tab == SendingTab) { - contextMenu->addAction(tr("Delete"), this, &AddressBookPage::on_deleteAddress_clicked); + contextMenu->addAction(tr("&Delete"), this, &AddressBookPage::on_deleteAddress_clicked); } connect(ui->tableView, &QWidget::customContextMenuRequested, this, &AddressBookPage::contextualMenu); diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 9e6cf56d31..442c813a5a 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -490,7 +490,8 @@ int GuiMain(int argc, char* argv[]) /// 2. Parse command-line options. We do this after qt in order to show an error if there are problems parsing these // Command-line options take precedence: - SetupServerArgs(node_context); + node_context.args = &gArgs; + SetupServerArgs(gArgs); SetupUIArgs(gArgs); std::string error; if (!gArgs.ParseParameters(argc, argv, error)) { diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 50f6e739e8..3d632ec702 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -211,11 +211,6 @@ BitcoinGUI::BitcoinGUI(interfaces::Node& node, const PlatformStyle *_platformSty connect(labelBlocksIcon, &GUIUtil::ClickableLabel::clicked, this, &BitcoinGUI::showModalOverlay); connect(progressBar, &GUIUtil::ClickableProgressBar::clicked, this, &BitcoinGUI::showModalOverlay); -#ifdef ENABLE_WALLET - if(enableWallet) { - connect(walletFrame, &WalletFrame::requestedSyncWarningInfo, this, &BitcoinGUI::showModalOverlay); - } -#endif #ifdef Q_OS_MAC m_app_nap_inhibitor = new CAppNapInhibitor; @@ -690,7 +685,7 @@ void BitcoinGUI::addWallet(WalletModel* walletModel) const QString display_name = walletModel->getDisplayName(); m_wallet_selector->addItem(display_name, QVariant::fromValue(walletModel)); - connect(wallet_view, &WalletView::outOfSyncWarningClicked, walletFrame, &WalletFrame::outOfSyncWarningClicked); + connect(wallet_view, &WalletView::outOfSyncWarningClicked, this, &BitcoinGUI::showModalOverlay); connect(wallet_view, &WalletView::transactionClicked, this, &BitcoinGUI::gotoHistoryPage); connect(wallet_view, &WalletView::coinsSent, this, &BitcoinGUI::gotoHistoryPage); connect(wallet_view, &WalletView::message, [this](const QString& title, const QString& message, unsigned int style) { diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp index 8ae0648141..2360fa9b37 100644 --- a/src/qt/coincontroldialog.cpp +++ b/src/qt/coincontroldialog.cpp @@ -52,13 +52,13 @@ CoinControlDialog::CoinControlDialog(CCoinControl& coin_control, WalletModel* _m // context menu contextMenu = new QMenu(this); - contextMenu->addAction(tr("Copy address"), this, &CoinControlDialog::copyAddress); - contextMenu->addAction(tr("Copy label"), this, &CoinControlDialog::copyLabel); - contextMenu->addAction(tr("Copy amount"), this, &CoinControlDialog::copyAmount); - copyTransactionHashAction = contextMenu->addAction(tr("Copy transaction ID"), this, &CoinControlDialog::copyTransactionHash); + contextMenu->addAction(tr("&Copy address"), this, &CoinControlDialog::copyAddress); + contextMenu->addAction(tr("Copy &label"), this, &CoinControlDialog::copyLabel); + contextMenu->addAction(tr("Copy &amount"), this, &CoinControlDialog::copyAmount); + copyTransactionHashAction = contextMenu->addAction(tr("Copy transaction &ID"), this, &CoinControlDialog::copyTransactionHash); contextMenu->addSeparator(); - lockAction = contextMenu->addAction(tr("Lock unspent"), this, &CoinControlDialog::lockCoin); - unlockAction = contextMenu->addAction(tr("Unlock unspent"), this, &CoinControlDialog::unlockCoin); + lockAction = contextMenu->addAction(tr("L&ock unspent"), this, &CoinControlDialog::lockCoin); + unlockAction = contextMenu->addAction(tr("&Unlock unspent"), this, &CoinControlDialog::unlockCoin); connect(ui->treeWidget, &QWidget::customContextMenuRequested, this, &CoinControlDialog::showMenu); // clipboard actions diff --git a/src/qt/createwalletdialog.cpp b/src/qt/createwalletdialog.cpp index e593697b46..dc24bbc6a6 100644 --- a/src/qt/createwalletdialog.cpp +++ b/src/qt/createwalletdialog.cpp @@ -31,8 +31,9 @@ CreateWalletDialog::CreateWalletDialog(QWidget* parent) : // Disable the disable_privkeys_checkbox and external_signer_checkbox when isEncryptWalletChecked is // set to true, enable it when isEncryptWalletChecked is false. ui->disable_privkeys_checkbox->setEnabled(!checked); +#ifdef ENABLE_EXTERNAL_SIGNER ui->external_signer_checkbox->setEnabled(!checked); - +#endif // When the disable_privkeys_checkbox is disabled, uncheck it. if (!ui->disable_privkeys_checkbox->isEnabled()) { ui->disable_privkeys_checkbox->setChecked(false); @@ -112,8 +113,7 @@ CreateWalletDialog::~CreateWalletDialog() delete ui; } -#ifdef ENABLE_EXTERNAL_SIGNER -void CreateWalletDialog::setSigners(std::vector<ExternalSigner>& signers) +void CreateWalletDialog::setSigners(const std::vector<ExternalSigner>& signers) { if (!signers.empty()) { ui->external_signer_checkbox->setEnabled(true); @@ -132,7 +132,6 @@ void CreateWalletDialog::setSigners(std::vector<ExternalSigner>& signers) ui->external_signer_checkbox->setEnabled(false); } } -#endif QString CreateWalletDialog::walletName() const { diff --git a/src/qt/createwalletdialog.h b/src/qt/createwalletdialog.h index 585b1461f7..25ddf97585 100644 --- a/src/qt/createwalletdialog.h +++ b/src/qt/createwalletdialog.h @@ -7,11 +7,8 @@ #include <QDialog> -class WalletModel; - -#ifdef ENABLE_EXTERNAL_SIGNER class ExternalSigner; -#endif +class WalletModel; namespace Ui { class CreateWalletDialog; @@ -27,9 +24,7 @@ public: explicit CreateWalletDialog(QWidget* parent); virtual ~CreateWalletDialog(); -#ifdef ENABLE_EXTERNAL_SIGNER - void setSigners(std::vector<ExternalSigner>& signers); -#endif + void setSigners(const std::vector<ExternalSigner>& signers); QString walletName() const; bool isEncryptWalletChecked() const; diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index 6ad8db4348..b12fe96567 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -92,6 +92,11 @@ OptionsDialog::OptionsDialog(QWidget *parent, bool enableWallet) : ui->thirdPartyTxUrls->setVisible(false); } +#ifndef ENABLE_EXTERNAL_SIGNER + //: "External signing" means using devices such as hardware wallets. + ui->externalSignerPath->setToolTip(tr("Compiled without external signing support (required for external signing)")); + ui->externalSignerPath->setEnabled(false); +#endif /* Display elements init */ QDir translations(":translations"); diff --git a/src/qt/overviewpage.cpp b/src/qt/overviewpage.cpp index 27783bdf87..26e3dd0d60 100644 --- a/src/qt/overviewpage.cpp +++ b/src/qt/overviewpage.cpp @@ -166,8 +166,8 @@ OverviewPage::OverviewPage(const PlatformStyle *platformStyle, QWidget *parent) // start with displaying the "out of sync" warnings showOutOfSyncWarning(true); - connect(ui->labelWalletStatus, &QPushButton::clicked, this, &OverviewPage::handleOutOfSyncWarningClicks); - connect(ui->labelTransactionsStatus, &QPushButton::clicked, this, &OverviewPage::handleOutOfSyncWarningClicks); + connect(ui->labelWalletStatus, &QPushButton::clicked, this, &OverviewPage::outOfSyncWarningClicked); + connect(ui->labelTransactionsStatus, &QPushButton::clicked, this, &OverviewPage::outOfSyncWarningClicked); } void OverviewPage::handleTransactionClicked(const QModelIndex &index) @@ -176,11 +176,6 @@ void OverviewPage::handleTransactionClicked(const QModelIndex &index) Q_EMIT transactionClicked(filter->mapToSource(index)); } -void OverviewPage::handleOutOfSyncWarningClicks() -{ - Q_EMIT outOfSyncWarningClicked(); -} - void OverviewPage::setPrivacy(bool privacy) { m_privacy = privacy; diff --git a/src/qt/overviewpage.h b/src/qt/overviewpage.h index 755a107a00..5270741c0d 100644 --- a/src/qt/overviewpage.h +++ b/src/qt/overviewpage.h @@ -65,7 +65,6 @@ private Q_SLOTS: void handleTransactionClicked(const QModelIndex &index); void updateAlerts(const QString &warnings); void updateWatchOnlyLabels(bool showWatchOnly); - void handleOutOfSyncWarningClicks(); void setMonospacedFont(bool use_embedded_font); }; diff --git a/src/qt/psbtoperationsdialog.cpp b/src/qt/psbtoperationsdialog.cpp index 99318c3bc0..2adfeeaaf0 100644 --- a/src/qt/psbtoperationsdialog.cpp +++ b/src/qt/psbtoperationsdialog.cpp @@ -50,7 +50,7 @@ void PSBTOperationsDialog::openWithPSBT(PartiallySignedTransaction psbtx) bool complete; size_t n_could_sign; FinalizePSBT(psbtx); // Make sure all existing signatures are fully combined before checking for completeness. - TransactionError err = m_wallet_model->wallet().fillPSBT(SIGHASH_ALL, false /* sign */, true /* bip32derivs */, m_transaction_data, complete, &n_could_sign); + TransactionError err = m_wallet_model->wallet().fillPSBT(SIGHASH_ALL, false /* sign */, true /* bip32derivs */, &n_could_sign, m_transaction_data, complete); if (err != TransactionError::OK) { showStatus(tr("Failed to load transaction: %1") .arg(QString::fromStdString(TransactionErrorString(err).translated)), StatusLevel::ERR); @@ -67,7 +67,7 @@ void PSBTOperationsDialog::signTransaction() { bool complete; size_t n_signed; - TransactionError err = m_wallet_model->wallet().fillPSBT(SIGHASH_ALL, true /* sign */, true /* bip32derivs */, m_transaction_data, complete, &n_signed); + TransactionError err = m_wallet_model->wallet().fillPSBT(SIGHASH_ALL, true /* sign */, true /* bip32derivs */, &n_signed, m_transaction_data, complete); if (err != TransactionError::OK) { showStatus(tr("Failed to sign transaction: %1") @@ -226,7 +226,7 @@ void PSBTOperationsDialog::showStatus(const QString &msg, StatusLevel level) { size_t PSBTOperationsDialog::couldSignInputs(const PartiallySignedTransaction &psbtx) { size_t n_signed; bool complete; - TransactionError err = m_wallet_model->wallet().fillPSBT(SIGHASH_ALL, false /* sign */, false /* bip32derivs */, m_transaction_data, complete, &n_signed); + TransactionError err = m_wallet_model->wallet().fillPSBT(SIGHASH_ALL, false /* sign */, false /* bip32derivs */, &n_signed, m_transaction_data, complete); if (err != TransactionError::OK) { return 0; diff --git a/src/qt/qrimagewidget.cpp b/src/qt/qrimagewidget.cpp index f5200bb5c0..7cdd568644 100644 --- a/src/qt/qrimagewidget.cpp +++ b/src/qt/qrimagewidget.cpp @@ -27,8 +27,8 @@ QRImageWidget::QRImageWidget(QWidget *parent): QLabel(parent), contextMenu(nullptr) { contextMenu = new QMenu(this); - contextMenu->addAction(tr("Save Image…"), this, &QRImageWidget::saveImage); - contextMenu->addAction(tr("Copy Image"), this, &QRImageWidget::copyImage); + contextMenu->addAction(tr("&Save Image…"), this, &QRImageWidget::saveImage); + contextMenu->addAction(tr("&Copy Image"), this, &QRImageWidget::copyImage); } bool QRImageWidget::setQR(const QString& data, const QString& text) diff --git a/src/qt/receivecoinsdialog.cpp b/src/qt/receivecoinsdialog.cpp index 3f4d7f85e6..d47ee95826 100644 --- a/src/qt/receivecoinsdialog.cpp +++ b/src/qt/receivecoinsdialog.cpp @@ -44,11 +44,11 @@ ReceiveCoinsDialog::ReceiveCoinsDialog(const PlatformStyle *_platformStyle, QWid // context menu contextMenu = new QMenu(this); - contextMenu->addAction(tr("Copy URI"), this, &ReceiveCoinsDialog::copyURI); - contextMenu->addAction(tr("Copy address"), this, &ReceiveCoinsDialog::copyAddress); - copyLabelAction = contextMenu->addAction(tr("Copy label"), this, &ReceiveCoinsDialog::copyLabel); - copyMessageAction = contextMenu->addAction(tr("Copy message"), this, &ReceiveCoinsDialog::copyMessage); - copyAmountAction = contextMenu->addAction(tr("Copy amount"), this, &ReceiveCoinsDialog::copyAmount); + contextMenu->addAction(tr("Copy &URI"), this, &ReceiveCoinsDialog::copyURI); + contextMenu->addAction(tr("&Copy address"), this, &ReceiveCoinsDialog::copyAddress); + copyLabelAction = contextMenu->addAction(tr("Copy &label"), this, &ReceiveCoinsDialog::copyLabel); + copyMessageAction = contextMenu->addAction(tr("Copy &message"), this, &ReceiveCoinsDialog::copyMessage); + copyAmountAction = contextMenu->addAction(tr("Copy &amount"), this, &ReceiveCoinsDialog::copyAmount); connect(ui->recentRequestsView, &QWidget::customContextMenuRequested, this, &ReceiveCoinsDialog::showMenu); connect(ui->clearButton, &QPushButton::clicked, this, &ReceiveCoinsDialog::clear); diff --git a/src/qt/receiverequestdialog.cpp b/src/qt/receiverequestdialog.cpp index abe7de8f89..41f22e9c34 100644 --- a/src/qt/receiverequestdialog.cpp +++ b/src/qt/receiverequestdialog.cpp @@ -90,7 +90,7 @@ void ReceiveRequestDialog::setInfo(const SendCoinsRecipient &_info) ui->wallet_content->hide(); } - ui->btnVerify->setVisible(this->model->wallet().hasExternalSigner()); + ui->btnVerify->setVisible(model->wallet().hasExternalSigner()); connect(ui->btnVerify, &QPushButton::clicked, [this] { model->displayAddress(info.address.toStdString()); diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index 9579e6dc24..ff4bfb16f6 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -675,11 +675,11 @@ void RPCConsole::setClientModel(ClientModel *model, int bestblock_height, int64_ // create peer table context menu peersTableContextMenu = new QMenu(this); - peersTableContextMenu->addAction(tr("Disconnect"), this, &RPCConsole::disconnectSelectedNode); - peersTableContextMenu->addAction(ts.ban_for + " " + tr("1 hour"), [this] { banSelectedNode(60 * 60); }); - peersTableContextMenu->addAction(ts.ban_for + " " + tr("1 day"), [this] { banSelectedNode(60 * 60 * 24); }); - peersTableContextMenu->addAction(ts.ban_for + " " + tr("1 week"), [this] { banSelectedNode(60 * 60 * 24 * 7); }); - peersTableContextMenu->addAction(ts.ban_for + " " + tr("1 year"), [this] { banSelectedNode(60 * 60 * 24 * 365); }); + peersTableContextMenu->addAction(tr("&Disconnect"), this, &RPCConsole::disconnectSelectedNode); + peersTableContextMenu->addAction(ts.ban_for + " " + tr("1 &hour"), [this] { banSelectedNode(60 * 60); }); + peersTableContextMenu->addAction(ts.ban_for + " " + tr("1 d&ay"), [this] { banSelectedNode(60 * 60 * 24); }); + peersTableContextMenu->addAction(ts.ban_for + " " + tr("1 &week"), [this] { banSelectedNode(60 * 60 * 24 * 7); }); + peersTableContextMenu->addAction(ts.ban_for + " " + tr("1 &year"), [this] { banSelectedNode(60 * 60 * 24 * 365); }); connect(ui->peerWidget, &QTableView::customContextMenuRequested, this, &RPCConsole::showPeersTableContextMenu); // peer table signal handling - update peer details when selecting new node @@ -701,7 +701,7 @@ void RPCConsole::setClientModel(ClientModel *model, int bestblock_height, int64_ // create ban table context menu banTableContextMenu = new QMenu(this); - banTableContextMenu->addAction(tr("Unban"), this, &RPCConsole::unbanSelectedNode); + banTableContextMenu->addAction(tr("&Unban"), this, &RPCConsole::unbanSelectedNode); connect(ui->banlistWidget, &QTableView::customContextMenuRequested, this, &RPCConsole::showBanTableContextMenu); // ban table signal handling - clear peer details when clicking a peer in the ban table diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index e87a2b97bc..c9bf757dfc 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -200,12 +200,14 @@ void SendCoinsDialog::setModel(WalletModel *_model) ui->optInRBF->setCheckState(Qt::Checked); if (model->wallet().hasExternalSigner()) { + //: "device" usually means a hardware wallet ui->sendButton->setText(tr("Sign on device")); if (gArgs.GetArg("-signer", "") != "") { ui->sendButton->setEnabled(true); ui->sendButton->setToolTip(tr("Connect your hardware wallet first.")); } else { ui->sendButton->setEnabled(false); + //: "External signer" means using devices such as hardware wallets. ui->sendButton->setToolTip(tr("Set external signer script path in Options -> Wallet")); } } else if (model->wallet().privateKeysDisabled()) { @@ -414,23 +416,25 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked) bool complete = false; // Always fill without signing first. This prevents an external signer // from being called prematurely and is not expensive. - TransactionError err = model->wallet().fillPSBT(SIGHASH_ALL, false /* sign */, true /* bip32derivs */, psbtx, complete, nullptr); + TransactionError err = model->wallet().fillPSBT(SIGHASH_ALL, false /* sign */, true /* bip32derivs */, nullptr, psbtx, complete); assert(!complete); assert(err == TransactionError::OK); if (model->wallet().hasExternalSigner()) { try { - err = model->wallet().fillPSBT(SIGHASH_ALL, true /* sign */, true /* bip32derivs */, psbtx, complete, nullptr); + err = model->wallet().fillPSBT(SIGHASH_ALL, true /* sign */, true /* bip32derivs */, nullptr, psbtx, complete); } catch (const std::runtime_error& e) { QMessageBox::critical(nullptr, tr("Sign failed"), e.what()); send_failure = true; return; } if (err == TransactionError::EXTERNAL_SIGNER_NOT_FOUND) { + //: "External signer" means using devices such as hardware wallets. QMessageBox::critical(nullptr, tr("External signer not found"), "External signer not found"); send_failure = true; return; } if (err == TransactionError::EXTERNAL_SIGNER_FAILED) { + //: "External signer" means using devices such as hardware wallets. QMessageBox::critical(nullptr, tr("External signer failure"), "External signer failure"); send_failure = true; return; diff --git a/src/qt/test/apptests.cpp b/src/qt/test/apptests.cpp index cb3dbd2267..9c31cd50df 100644 --- a/src/qt/test/apptests.cpp +++ b/src/qt/test/apptests.cpp @@ -85,11 +85,6 @@ void AppTests::appTests() // Reset global state to avoid interfering with later tests. LogInstance().DisconnectTestLogger(); AbortShutdown(); - { - LOCK(cs_main); - UnloadBlockIndex(/* mempool */ nullptr, g_chainman); - g_chainman.Reset(); - } } //! Entry point for BitcoinGUI tests. diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp index ea35f80cf5..3e1a0e0fa9 100644 --- a/src/qt/test/wallettests.cpp +++ b/src/qt/test/wallettests.cpp @@ -146,14 +146,14 @@ void TestGUI(interfaces::Node& node) LOCK2(wallet->cs_wallet, spk_man->cs_KeyStore); wallet->SetAddressBook(GetDestinationForKey(test.coinbaseKey.GetPubKey(), wallet->m_default_address_type), "", "receive"); spk_man->AddKeyPubKey(test.coinbaseKey, test.coinbaseKey.GetPubKey()); - wallet->SetLastBlockProcessed(105, ::ChainActive().Tip()->GetBlockHash()); + wallet->SetLastBlockProcessed(105, node.context()->chainman->ActiveChain().Tip()->GetBlockHash()); } { WalletRescanReserver reserver(*wallet); reserver.reserve(); CWallet::ScanResult result = wallet->ScanForWalletTransactions(Params().GetConsensus().hashGenesisBlock, 0 /* block height */, {} /* max height */, reserver, true /* fUpdate */); QCOMPARE(result.status, CWallet::ScanResult::SUCCESS); - QCOMPARE(result.last_scanned_block, ::ChainActive().Tip()->GetBlockHash()); + QCOMPARE(result.last_scanned_block, node.context()->chainman->ActiveChain().Tip()->GetBlockHash()); QVERIFY(result.last_failed_block.IsNull()); } wallet->SetBroadcastTransactions(true); diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp index 1e8e012dcf..4b1a546c7c 100644 --- a/src/qt/transactionview.cpp +++ b/src/qt/transactionview.cpp @@ -163,19 +163,19 @@ TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *pa contextMenu = new QMenu(this); contextMenu->setObjectName("contextMenu"); - copyAddressAction = contextMenu->addAction(tr("Copy address"), this, &TransactionView::copyAddress); - copyLabelAction = contextMenu->addAction(tr("Copy label"), this, &TransactionView::copyLabel); - contextMenu->addAction(tr("Copy amount"), this, &TransactionView::copyAmount); - contextMenu->addAction(tr("Copy transaction ID"), this, &TransactionView::copyTxID); - contextMenu->addAction(tr("Copy raw transaction"), this, &TransactionView::copyTxHex); - contextMenu->addAction(tr("Copy full transaction details"), this, &TransactionView::copyTxPlainText); - contextMenu->addAction(tr("Show transaction details"), this, &TransactionView::showDetails); + copyAddressAction = contextMenu->addAction(tr("&Copy address"), this, &TransactionView::copyAddress); + copyLabelAction = contextMenu->addAction(tr("Copy &label"), this, &TransactionView::copyLabel); + contextMenu->addAction(tr("Copy &amount"), this, &TransactionView::copyAmount); + contextMenu->addAction(tr("Copy transaction &ID"), this, &TransactionView::copyTxID); + contextMenu->addAction(tr("Copy &raw transaction"), this, &TransactionView::copyTxHex); + contextMenu->addAction(tr("Copy full transaction &details"), this, &TransactionView::copyTxPlainText); + contextMenu->addAction(tr("&Show transaction details"), this, &TransactionView::showDetails); contextMenu->addSeparator(); - bumpFeeAction = contextMenu->addAction(tr("Increase transaction fee")); + bumpFeeAction = contextMenu->addAction(tr("Increase transaction &fee")); GUIUtil::ExceptionSafeConnect(bumpFeeAction, &QAction::triggered, this, &TransactionView::bumpFee); bumpFeeAction->setObjectName("bumpFeeAction"); - abandonAction = contextMenu->addAction(tr("Abandon transaction"), this, &TransactionView::abandonTx); - contextMenu->addAction(tr("Edit address label"), this, &TransactionView::editLabel); + abandonAction = contextMenu->addAction(tr("A&bandon transaction"), this, &TransactionView::abandonTx); + contextMenu->addAction(tr("&Edit address label"), this, &TransactionView::editLabel); connect(dateWidget, qOverload<int>(&QComboBox::activated), this, &TransactionView::chooseDate); connect(typeWidget, qOverload<int>(&QComboBox::activated), this, &TransactionView::chooseType); diff --git a/src/qt/walletcontroller.cpp b/src/qt/walletcontroller.cpp index 7e5790fd87..3cceb5ca5a 100644 --- a/src/qt/walletcontroller.cpp +++ b/src/qt/walletcontroller.cpp @@ -11,6 +11,7 @@ #include <qt/guiutil.h> #include <qt/walletmodel.h> +#include <external_signer.h> #include <interfaces/handler.h> #include <interfaces/node.h> #include <util/string.h> @@ -295,7 +296,6 @@ void CreateWalletActivity::create() { m_create_wallet_dialog = new CreateWalletDialog(m_parent_widget); -#ifdef ENABLE_EXTERNAL_SIGNER std::vector<ExternalSigner> signers; try { signers = node().externalSigners(); @@ -303,7 +303,6 @@ void CreateWalletActivity::create() QMessageBox::critical(nullptr, tr("Can't list signers"), e.what()); } m_create_wallet_dialog->setSigners(signers); -#endif m_create_wallet_dialog->setWindowModality(Qt::ApplicationModal); m_create_wallet_dialog->show(); diff --git a/src/qt/walletframe.cpp b/src/qt/walletframe.cpp index 0f2ea99685..a1f357e0db 100644 --- a/src/qt/walletframe.cpp +++ b/src/qt/walletframe.cpp @@ -242,8 +242,3 @@ WalletModel* WalletFrame::currentWalletModel() const WalletView* wallet_view = currentWalletView(); return wallet_view ? wallet_view->getWalletModel() : nullptr; } - -void WalletFrame::outOfSyncWarningClicked() -{ - Q_EMIT requestedSyncWarningInfo(); -} diff --git a/src/qt/walletframe.h b/src/qt/walletframe.h index 844ed121a0..4f77bd716f 100644 --- a/src/qt/walletframe.h +++ b/src/qt/walletframe.h @@ -47,9 +47,6 @@ public: QSize sizeHint() const override { return m_size_hint; } Q_SIGNALS: - /** Notify that the user has requested more information about the out-of-sync warning */ - void requestedSyncWarningInfo(); - void createWalletButtonClicked(); private: @@ -98,8 +95,6 @@ public Q_SLOTS: void usedSendingAddresses(); /** Show used receiving addresses */ void usedReceivingAddresses(); - /** Pass on signal over requested out-of-sync-warning information */ - void outOfSyncWarningClicked(); }; #endif // BITCOIN_QT_WALLETFRAME_H diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index e32b7c2807..967dd588b4 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -525,7 +525,7 @@ bool WalletModel::bumpFee(uint256 hash, uint256& new_hash) if (create_psbt) { PartiallySignedTransaction psbtx(mtx); bool complete = false; - const TransactionError err = wallet().fillPSBT(SIGHASH_ALL, false /* sign */, true /* bip32derivs */, psbtx, complete, nullptr); + const TransactionError err = wallet().fillPSBT(SIGHASH_ALL, false /* sign */, true /* bip32derivs */, nullptr, psbtx, complete); if (err != TransactionError::OK || complete) { QMessageBox::critical(nullptr, tr("Fee bump error"), tr("Can't draft transaction.")); return false; diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp index cc9e1502f0..3b8cf4c7ed 100644 --- a/src/qt/walletview.cpp +++ b/src/qt/walletview.cpp @@ -73,7 +73,7 @@ WalletView::WalletView(const PlatformStyle *_platformStyle, QWidget *parent): // Clicking on a transaction on the overview pre-selects the transaction on the transaction history page connect(overviewPage, &OverviewPage::transactionClicked, transactionView, qOverload<const QModelIndex&>(&TransactionView::focusTransaction)); - connect(overviewPage, &OverviewPage::outOfSyncWarningClicked, this, &WalletView::requestedSyncWarningInfo); + connect(overviewPage, &OverviewPage::outOfSyncWarningClicked, this, &WalletView::outOfSyncWarningClicked); connect(sendCoinsPage, &SendCoinsDialog::coinsSent, this, &WalletView::coinsSent); // Highlight transaction after send @@ -347,8 +347,3 @@ void WalletView::showProgress(const QString &title, int nProgress) } } } - -void WalletView::requestedSyncWarningInfo() -{ - Q_EMIT outOfSyncWarningClicked(); -} diff --git a/src/qt/walletview.h b/src/qt/walletview.h index 68f8a5e95b..fedf06b710 100644 --- a/src/qt/walletview.h +++ b/src/qt/walletview.h @@ -111,9 +111,6 @@ public Q_SLOTS: /** Show progress dialog e.g. for rescan */ void showProgress(const QString &title, int nProgress); - /** User has requested more information about the out of sync state */ - void requestedSyncWarningInfo(); - Q_SIGNALS: void setPrivacy(bool privacy); void transactionClicked(); diff --git a/src/rest.cpp b/src/rest.cpp index 747c7aea19..d599f381e3 100644 --- a/src/rest.cpp +++ b/src/rest.cpp @@ -125,7 +125,7 @@ static ChainstateManager* GetChainman(const std::any& context, HTTPRequest* req) __FILE__, __LINE__, __func__, PACKAGE_BUGREPORT)); return nullptr; } - return node_context->chainman; + return node_context->chainman.get(); } static RetFormat ParseDataFormat(std::string& param, const std::string& strReq) diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 83c1975d38..63897e0e05 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -84,7 +84,6 @@ ChainstateManager& EnsureChainman(const NodeContext& node) if (!node.chainman) { throw JSONRPCError(RPC_INTERNAL_ERROR, "Node chainman not found"); } - WITH_LOCK(::cs_main, CHECK_NONFATAL(std::addressof(g_chainman) == std::addressof(*node.chainman))); return *node.chainman; } diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 6826e6fd07..327f961196 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -114,7 +114,6 @@ static bool GenerateBlock(ChainstateManager& chainman, CBlock& block, uint64_t& { LOCK(cs_main); - CHECK_NONFATAL(std::addressof(::ChainActive()) == std::addressof(chainman.ActiveChain())); IncrementExtraNonce(&block, chainman.ActiveChain().Tip(), extra_nonce); } @@ -147,7 +146,6 @@ static UniValue generateBlocks(ChainstateManager& chainman, const CTxMemPool& me { // Don't keep cs_main locked LOCK(cs_main); - CHECK_NONFATAL(std::addressof(::ChainActive()) == std::addressof(chainman.ActiveChain())); nHeight = chainman.ActiveChain().Height(); nHeightEnd = nHeight+nGenerate; } diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 339d711ac9..ccb3123714 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -753,7 +753,8 @@ static RPCHelpMan signrawtransactionwithkey() }, }, }, - {"sighashtype", RPCArg::Type::STR, RPCArg::Default{"ALL"}, "The signature hash type. Must be one of:\n" + {"sighashtype", RPCArg::Type::STR, RPCArg::Default{"DEFAULT"}, "The signature hash type. Must be one of:\n" + " \"DEFAULT\"\n" " \"ALL\"\n" " \"NONE\"\n" " \"SINGLE\"\n" @@ -889,7 +890,7 @@ static RPCHelpMan testmempoolaccept() "\nReturns result of mempool acceptance tests indicating if raw transaction(s) (serialized, hex-encoded) would be accepted by mempool.\n" "\nIf multiple transactions are passed in, parents must come before children and package policies apply: the transactions cannot conflict with any mempool transactions or each other.\n" "\nIf one transaction fails, other transactions may not be fully validated (the 'allowed' key will be blank).\n" - "\nThe maximum number of transactions allowed is 25 (MAX_PACKAGE_COUNT)\n" + "\nThe maximum number of transactions allowed is " + ToString(MAX_PACKAGE_COUNT) + ".\n" "\nThis checks if transactions violate the consensus or policy rules.\n" "\nSee sendrawtransaction call.\n", { @@ -905,7 +906,7 @@ static RPCHelpMan testmempoolaccept() RPCResult{ RPCResult::Type::ARR, "", "The result of the mempool acceptance test for each raw transaction in the input array.\n" "Returns results for each transaction in the same order they were passed in.\n" - "It is possible for transactions to not be fully validated ('allowed' unset) if an earlier transaction failed.\n", + "It is possible for transactions to not be fully validated ('allowed' unset) if another transaction failed.\n", { {RPCResult::Type::OBJ, "", "", { @@ -939,7 +940,6 @@ static RPCHelpMan testmempoolaccept() UniValue::VARR, UniValueType(), // VNUM or VSTR, checked inside AmountFromValue() }); - const UniValue raw_transactions = request.params[0].get_array(); if (raw_transactions.size() < 1 || raw_transactions.size() > MAX_PACKAGE_COUNT) { throw JSONRPCError(RPC_INVALID_PARAMETER, @@ -951,6 +951,7 @@ static RPCHelpMan testmempoolaccept() CFeeRate(AmountFromValue(request.params[1])); std::vector<CTransactionRef> txns; + txns.reserve(raw_transactions.size()); for (const auto& rawtx : raw_transactions.getValues()) { CMutableTransaction mtx; if (!DecodeHexTx(mtx, rawtx.get_str())) { @@ -971,8 +972,8 @@ static RPCHelpMan testmempoolaccept() }(); UniValue rpc_result(UniValue::VARR); - // We will check transaction fees we iterate through txns in order. If any transaction fee - // exceeds maxfeerate, we will keave the rest of the validation results blank, because it + // We will check transaction fees while we iterate through txns in order. If any transaction fee + // exceeds maxfeerate, we will leave the rest of the validation results blank, because it // doesn't make sense to return a validation result for a transaction if its ancestor(s) would // not be submitted. bool exit_early{false}; @@ -1655,6 +1656,7 @@ static RPCHelpMan utxoupdatepsbt() } // Fill the inputs + const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx); for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { PSBTInput& input = psbtx.inputs.at(i); @@ -1671,7 +1673,7 @@ static RPCHelpMan utxoupdatepsbt() // Update script/keypath information using descriptor data. // Note that SignPSBTInput does a lot more than just constructing ECDSA signatures // we don't actually care about those here, in fact. - SignPSBTInput(public_provider, psbtx, i, /* sighash_type */ 1); + SignPSBTInput(public_provider, psbtx, i, &txdata, /* sighash_type */ 1); } // Update script/keypath information using descriptor data. diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp index 51cf8a7d62..84a8b06c5c 100644 --- a/src/script/descriptor.cpp +++ b/src/script/descriptor.cpp @@ -843,7 +843,9 @@ protected: XOnlyPubKey xpk(keys[0]); if (!xpk.IsFullyValid()) return {}; builder.Finalize(xpk); - return Vector(GetScriptForDestination(builder.GetOutput())); + WitnessV1Taproot output = builder.GetOutput(); + out.tr_spenddata[output].Merge(builder.GetSpendData()); + return Vector(GetScriptForDestination(output)); } bool ToStringSubScriptHelper(const SigningProvider* arg, std::string& ret, bool priv, bool normalized) const override { diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 3c3c3ac1a8..2dd173ee20 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -1420,7 +1420,7 @@ uint256 GetSpentScriptsSHA256(const std::vector<CTxOut>& outputs_spent) } // namespace template <class T> -void PrecomputedTransactionData::Init(const T& txTo, std::vector<CTxOut>&& spent_outputs) +void PrecomputedTransactionData::Init(const T& txTo, std::vector<CTxOut>&& spent_outputs, bool force) { assert(!m_spent_outputs_ready); @@ -1431,9 +1431,9 @@ void PrecomputedTransactionData::Init(const T& txTo, std::vector<CTxOut>&& spent } // Determine which precomputation-impacting features this transaction uses. - bool uses_bip143_segwit = false; - bool uses_bip341_taproot = false; - for (size_t inpos = 0; inpos < txTo.vin.size(); ++inpos) { + bool uses_bip143_segwit = force; + bool uses_bip341_taproot = force; + for (size_t inpos = 0; inpos < txTo.vin.size() && !(uses_bip143_segwit && uses_bip341_taproot); ++inpos) { if (!txTo.vin[inpos].scriptWitness.IsNull()) { if (m_spent_outputs_ready && m_spent_outputs[inpos].scriptPubKey.size() == 2 + WITNESS_V1_TAPROOT_SIZE && m_spent_outputs[inpos].scriptPubKey[0] == OP_1) { @@ -1478,8 +1478,8 @@ PrecomputedTransactionData::PrecomputedTransactionData(const T& txTo) } // explicit instantiation -template void PrecomputedTransactionData::Init(const CTransaction& txTo, std::vector<CTxOut>&& spent_outputs); -template void PrecomputedTransactionData::Init(const CMutableTransaction& txTo, std::vector<CTxOut>&& spent_outputs); +template void PrecomputedTransactionData::Init(const CTransaction& txTo, std::vector<CTxOut>&& spent_outputs, bool force); +template void PrecomputedTransactionData::Init(const CMutableTransaction& txTo, std::vector<CTxOut>&& spent_outputs, bool force); template PrecomputedTransactionData::PrecomputedTransactionData(const CTransaction& txTo); template PrecomputedTransactionData::PrecomputedTransactionData(const CMutableTransaction& txTo); @@ -1711,7 +1711,7 @@ bool GenericTransactionSignatureChecker<T>::CheckSchnorrSignature(Span<const uns if (hashtype == SIGHASH_DEFAULT) return set_error(serror, SCRIPT_ERR_SCHNORR_SIG_HASHTYPE); } uint256 sighash; - assert(this->txdata); + if (!this->txdata) return HandleMissingData(m_mdb); if (!SignatureHashSchnorr(sighash, execdata, *txTo, nIn, hashtype, sigversion, *this->txdata, m_mdb)) { return set_error(serror, SCRIPT_ERR_SCHNORR_SIG_HASHTYPE); } diff --git a/src/script/interpreter.h b/src/script/interpreter.h index fa4ee83e04..ced5c28bc1 100644 --- a/src/script/interpreter.h +++ b/src/script/interpreter.h @@ -168,7 +168,7 @@ struct PrecomputedTransactionData PrecomputedTransactionData() = default; template <class T> - void Init(const T& tx, std::vector<CTxOut>&& spent_outputs); + void Init(const T& tx, std::vector<CTxOut>&& spent_outputs, bool force = false); template <class T> explicit PrecomputedTransactionData(const T& tx); @@ -260,6 +260,9 @@ enum class MissingDataBehavior FAIL, //!< Just act as if the signature was invalid }; +template<typename T> +bool SignatureHashSchnorr(uint256& hash_out, const ScriptExecutionData& execdata, const T& tx_to, uint32_t in_pos, uint8_t hash_type, SigVersion sigversion, const PrecomputedTransactionData& cache, MissingDataBehavior mdb); + template <class T> class GenericTransactionSignatureChecker : public BaseSignatureChecker { diff --git a/src/script/sign.cpp b/src/script/sign.cpp index da0092f9e3..65276f641f 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -11,13 +11,28 @@ #include <script/signingprovider.h> #include <script/standard.h> #include <uint256.h> +#include <util/vector.h> typedef std::vector<unsigned char> valtype; -MutableTransactionSignatureCreator::MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, int nHashTypeIn) : txTo(txToIn), nIn(nInIn), nHashType(nHashTypeIn), amount(amountIn), checker(txTo, nIn, amountIn, MissingDataBehavior::FAIL) {} +MutableTransactionSignatureCreator::MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, int nHashTypeIn) + : txTo(txToIn), nIn(nInIn), nHashType(nHashTypeIn), amount(amountIn), checker(txTo, nIn, amountIn, MissingDataBehavior::FAIL), + m_txdata(nullptr) +{ +} + +MutableTransactionSignatureCreator::MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, const PrecomputedTransactionData* txdata, int nHashTypeIn) + : txTo(txToIn), nIn(nInIn), nHashType(nHashTypeIn), amount(amountIn), + checker(txdata ? MutableTransactionSignatureChecker(txTo, nIn, amount, *txdata, MissingDataBehavior::FAIL) : + MutableTransactionSignatureChecker(txTo, nIn, amount, MissingDataBehavior::FAIL)), + m_txdata(txdata) +{ +} bool MutableTransactionSignatureCreator::CreateSig(const SigningProvider& provider, std::vector<unsigned char>& vchSig, const CKeyID& address, const CScript& scriptCode, SigVersion sigversion) const { + assert(sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0); + CKey key; if (!provider.GetKey(address, key)) return false; @@ -26,13 +41,61 @@ bool MutableTransactionSignatureCreator::CreateSig(const SigningProvider& provid if (sigversion == SigVersion::WITNESS_V0 && !key.IsCompressed()) return false; - // Signing for witness scripts needs the amount. - if (sigversion == SigVersion::WITNESS_V0 && amount < 0) return false; + // Signing without known amount does not work in witness scripts. + if (sigversion == SigVersion::WITNESS_V0 && !MoneyRange(amount)) return false; + + // BASE/WITNESS_V0 signatures don't support explicit SIGHASH_DEFAULT, use SIGHASH_ALL instead. + const int hashtype = nHashType == SIGHASH_DEFAULT ? SIGHASH_ALL : nHashType; - uint256 hash = SignatureHash(scriptCode, *txTo, nIn, nHashType, amount, sigversion); + uint256 hash = SignatureHash(scriptCode, *txTo, nIn, hashtype, amount, sigversion, m_txdata); if (!key.Sign(hash, vchSig)) return false; - vchSig.push_back((unsigned char)nHashType); + vchSig.push_back((unsigned char)hashtype); + return true; +} + +bool MutableTransactionSignatureCreator::CreateSchnorrSig(const SigningProvider& provider, std::vector<unsigned char>& sig, const XOnlyPubKey& pubkey, const uint256* leaf_hash, const uint256* merkle_root, SigVersion sigversion) const +{ + assert(sigversion == SigVersion::TAPROOT || sigversion == SigVersion::TAPSCRIPT); + + CKey key; + { + // For now, use the old full pubkey-based key derivation logic. As it indexed by + // Hash160(full pubkey), we need to try both a version prefixed with 0x02, and one + // with 0x03. + unsigned char b[33] = {0x02}; + std::copy(pubkey.begin(), pubkey.end(), b + 1); + CPubKey fullpubkey; + fullpubkey.Set(b, b + 33); + CKeyID keyid = fullpubkey.GetID(); + if (!provider.GetKey(keyid, key)) { + b[0] = 0x03; + fullpubkey.Set(b, b + 33); + CKeyID keyid = fullpubkey.GetID(); + if (!provider.GetKey(keyid, key)) return false; + } + } + + // BIP341/BIP342 signing needs lots of precomputed transaction data. While some + // (non-SIGHASH_DEFAULT) sighash modes exist that can work with just some subset + // of data present, for now, only support signing when everything is provided. + if (!m_txdata || !m_txdata->m_bip341_taproot_ready || !m_txdata->m_spent_outputs_ready) return false; + + ScriptExecutionData execdata; + execdata.m_annex_init = true; + execdata.m_annex_present = false; // Only support annex-less signing for now. + if (sigversion == SigVersion::TAPSCRIPT) { + execdata.m_codeseparator_pos_init = true; + execdata.m_codeseparator_pos = 0xFFFFFFFF; // Only support non-OP_CODESEPARATOR BIP342 signing for now. + if (!leaf_hash) return false; // BIP342 signing needs leaf hash. + execdata.m_tapleaf_hash_init = true; + execdata.m_tapleaf_hash = *leaf_hash; + } + uint256 hash; + if (!SignatureHashSchnorr(hash, execdata, *txTo, nIn, nHashType, sigversion, *m_txdata, MissingDataBehavior::FAIL)) return false; + sig.resize(64); + if (!key.SignSchnorr(hash, sig, merkle_root, nullptr)) return false; + if (nHashType) sig.push_back(nHashType); return true; } @@ -92,6 +155,86 @@ static bool CreateSig(const BaseSignatureCreator& creator, SignatureData& sigdat return false; } +static bool CreateTaprootScriptSig(const BaseSignatureCreator& creator, SignatureData& sigdata, const SigningProvider& provider, std::vector<unsigned char>& sig_out, const XOnlyPubKey& pubkey, const uint256& leaf_hash, SigVersion sigversion) +{ + auto lookup_key = std::make_pair(pubkey, leaf_hash); + auto it = sigdata.taproot_script_sigs.find(lookup_key); + if (it != sigdata.taproot_script_sigs.end()) { + sig_out = it->second; + } + if (creator.CreateSchnorrSig(provider, sig_out, pubkey, &leaf_hash, nullptr, sigversion)) { + sigdata.taproot_script_sigs[lookup_key] = sig_out; + return true; + } + return false; +} + +static bool SignTaprootScript(const SigningProvider& provider, const BaseSignatureCreator& creator, SignatureData& sigdata, int leaf_version, const CScript& script, std::vector<valtype>& result) +{ + // Only BIP342 tapscript signing is supported for now. + if (leaf_version != TAPROOT_LEAF_TAPSCRIPT) return false; + SigVersion sigversion = SigVersion::TAPSCRIPT; + + uint256 leaf_hash = (CHashWriter(HASHER_TAPLEAF) << uint8_t(leaf_version) << script).GetSHA256(); + + // <xonly pubkey> OP_CHECKSIG + if (script.size() == 34 && script[33] == OP_CHECKSIG && script[0] == 0x20) { + XOnlyPubKey pubkey(MakeSpan(script).subspan(1, 32)); + std::vector<unsigned char> sig; + if (CreateTaprootScriptSig(creator, sigdata, provider, sig, pubkey, leaf_hash, sigversion)) { + result = Vector(std::move(sig)); + return true; + } + } + + return false; +} + +static bool SignTaproot(const SigningProvider& provider, const BaseSignatureCreator& creator, const WitnessV1Taproot& output, SignatureData& sigdata, std::vector<valtype>& result) +{ + TaprootSpendData spenddata; + + // Gather information about this output. + if (provider.GetTaprootSpendData(output, spenddata)) { + sigdata.tr_spenddata.Merge(spenddata); + } + + // Try key path spending. + { + std::vector<unsigned char> sig; + if (sigdata.taproot_key_path_sig.size() == 0) { + if (creator.CreateSchnorrSig(provider, sig, spenddata.internal_key, nullptr, &spenddata.merkle_root, SigVersion::TAPROOT)) { + sigdata.taproot_key_path_sig = sig; + } + } + if (sigdata.taproot_key_path_sig.size()) { + result = Vector(sigdata.taproot_key_path_sig); + return true; + } + } + + // Try script path spending. + std::vector<std::vector<unsigned char>> smallest_result_stack; + for (const auto& [key, control_blocks] : sigdata.tr_spenddata.scripts) { + const auto& [script, leaf_ver] = key; + std::vector<std::vector<unsigned char>> result_stack; + if (SignTaprootScript(provider, creator, sigdata, leaf_ver, script, result_stack)) { + result_stack.emplace_back(std::begin(script), std::end(script)); // Push the script + result_stack.push_back(*control_blocks.begin()); // Push the smallest control block + if (smallest_result_stack.size() == 0 || + GetSerializeSize(result_stack, PROTOCOL_VERSION) < GetSerializeSize(smallest_result_stack, PROTOCOL_VERSION)) { + smallest_result_stack = std::move(result_stack); + } + } + } + if (smallest_result_stack.size() != 0) { + result = std::move(smallest_result_stack); + return true; + } + + return false; +} + /** * Sign scriptPubKey using signature made with creator. * Signatures are returned in scriptSigRet (or returns false if scriptPubKey can't be signed), @@ -113,7 +256,6 @@ static bool SignStep(const SigningProvider& provider, const BaseSignatureCreator case TxoutType::NONSTANDARD: case TxoutType::NULL_DATA: case TxoutType::WITNESS_UNKNOWN: - case TxoutType::WITNESS_V1_TAPROOT: return false; case TxoutType::PUBKEY: if (!CreateSig(creator, sigdata, provider, sig, CPubKey(vSolutions[0]), scriptPubKey, sigversion)) return false; @@ -175,6 +317,9 @@ static bool SignStep(const SigningProvider& provider, const BaseSignatureCreator // Could not find witnessScript, add to missing sigdata.missing_witness_script = uint256(vSolutions[0]); return false; + + case TxoutType::WITNESS_V1_TAPROOT: + return SignTaproot(provider, creator, WitnessV1Taproot(XOnlyPubKey{vSolutions[0]}), sigdata, ret); } // no default case, so the compiler can warn about missing cases assert(false); } @@ -205,7 +350,6 @@ bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreato bool solved = SignStep(provider, creator, fromPubKey, result, whichType, SigVersion::BASE, sigdata); bool P2SH = false; CScript subscript; - sigdata.scriptWitness.stack.clear(); if (solved && whichType == TxoutType::SCRIPTHASH) { @@ -238,10 +382,17 @@ bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreato sigdata.scriptWitness.stack = result; sigdata.witness = true; result.clear(); + } else if (whichType == TxoutType::WITNESS_V1_TAPROOT && !P2SH) { + sigdata.witness = true; + if (solved) { + sigdata.scriptWitness.stack = std::move(result); + } + result.clear(); } else if (solved && whichType == TxoutType::WITNESS_UNKNOWN) { sigdata.witness = true; } + if (!sigdata.witness) sigdata.scriptWitness.stack.clear(); if (P2SH) { result.push_back(std::vector<unsigned char>(subscript.begin(), subscript.end())); } @@ -402,6 +553,7 @@ class DummySignatureChecker final : public BaseSignatureChecker public: DummySignatureChecker() {} bool CheckECDSASignature(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override { return true; } + bool CheckSchnorrSignature(Span<const unsigned char> sig, Span<const unsigned char> pubkey, SigVersion sigversion, const ScriptExecutionData& execdata, ScriptError* serror) const override { return true; } }; const DummySignatureChecker DUMMY_CHECKER; @@ -427,6 +579,11 @@ public: vchSig[6 + m_r_len + m_s_len] = SIGHASH_ALL; return true; } + bool CreateSchnorrSig(const SigningProvider& provider, std::vector<unsigned char>& sig, const XOnlyPubKey& pubkey, const uint256* leaf_hash, const uint256* tweak, SigVersion sigversion) const override + { + sig.assign(64, '\000'); + return true; + } }; } @@ -476,6 +633,26 @@ bool SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, // Use CTransaction for the constant parts of the // transaction to avoid rehashing. const CTransaction txConst(mtx); + + PrecomputedTransactionData txdata; + std::vector<CTxOut> spent_outputs; + spent_outputs.resize(mtx.vin.size()); + bool have_all_spent_outputs = true; + for (unsigned int i = 0; i < mtx.vin.size(); i++) { + CTxIn& txin = mtx.vin[i]; + auto coin = coins.find(txin.prevout); + if (coin == coins.end() || coin->second.IsSpent()) { + have_all_spent_outputs = false; + } else { + spent_outputs[i] = CTxOut(coin->second.out.nValue, coin->second.out.scriptPubKey); + } + } + if (have_all_spent_outputs) { + txdata.Init(txConst, std::move(spent_outputs), true); + } else { + txdata.Init(txConst, {}, true); + } + // Sign what we can: for (unsigned int i = 0; i < mtx.vin.size(); i++) { CTxIn& txin = mtx.vin[i]; @@ -490,7 +667,7 @@ bool SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, SignatureData sigdata = DataFromTransaction(mtx, i, coin->second.out); // Only sign SIGHASH_SINGLE if there's a corresponding output: if (!fHashSingle || (i < mtx.vout.size())) { - ProduceSignature(*keystore, MutableTransactionSignatureCreator(&mtx, i, amount, nHashType), prevPubKey, sigdata); + ProduceSignature(*keystore, MutableTransactionSignatureCreator(&mtx, i, amount, &txdata, nHashType), prevPubKey, sigdata); } UpdateInput(txin, sigdata); @@ -502,7 +679,7 @@ bool SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, } ScriptError serror = SCRIPT_ERR_OK; - if (!VerifyScript(txin.scriptSig, prevPubKey, &txin.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionSignatureChecker(&txConst, i, amount, MissingDataBehavior::FAIL), &serror)) { + if (!VerifyScript(txin.scriptSig, prevPubKey, &txin.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionSignatureChecker(&txConst, i, amount, txdata, MissingDataBehavior::FAIL), &serror)) { if (serror == SCRIPT_ERR_INVALID_STACK_OPERATION) { // Unable to sign input and verification failed (possible attempt to partially sign). input_errors[i] = "Unable to sign input, invalid stack size (possibly missing key)"; diff --git a/src/script/sign.h b/src/script/sign.h index a1cfe1574d..b4e7318892 100644 --- a/src/script/sign.h +++ b/src/script/sign.h @@ -11,13 +11,13 @@ #include <pubkey.h> #include <script/interpreter.h> #include <script/keyorigin.h> +#include <script/standard.h> #include <span.h> #include <streams.h> class CKey; class CKeyID; class CScript; -class CScriptID; class CTransaction; class SigningProvider; @@ -31,6 +31,7 @@ public: /** Create a singular (non-script) signature. */ virtual bool CreateSig(const SigningProvider& provider, std::vector<unsigned char>& vchSig, const CKeyID& keyid, const CScript& scriptCode, SigVersion sigversion) const =0; + virtual bool CreateSchnorrSig(const SigningProvider& provider, std::vector<unsigned char>& sig, const XOnlyPubKey& pubkey, const uint256* leaf_hash, const uint256* merkle_root, SigVersion sigversion) const =0; }; /** A signature creator for transactions. */ @@ -40,11 +41,14 @@ class MutableTransactionSignatureCreator : public BaseSignatureCreator { int nHashType; CAmount amount; const MutableTransactionSignatureChecker checker; + const PrecomputedTransactionData* m_txdata; public: MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, int nHashTypeIn = SIGHASH_ALL); + MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, const PrecomputedTransactionData* txdata, int nHashTypeIn = SIGHASH_ALL); const BaseSignatureChecker& Checker() const override { return checker; } bool CreateSig(const SigningProvider& provider, std::vector<unsigned char>& vchSig, const CKeyID& keyid, const CScript& scriptCode, SigVersion sigversion) const override; + bool CreateSchnorrSig(const SigningProvider& provider, std::vector<unsigned char>& sig, const XOnlyPubKey& pubkey, const uint256* leaf_hash, const uint256* merkle_root, SigVersion sigversion) const override; }; /** A signature creator that just produces 71-byte empty signatures. */ @@ -64,8 +68,11 @@ struct SignatureData { CScript redeem_script; ///< The redeemScript (if any) for the input CScript witness_script; ///< The witnessScript (if any) for the input. witnessScripts are used in P2WSH outputs. CScriptWitness scriptWitness; ///< The scriptWitness of an input. Contains complete signatures or the traditional partial signatures format. scriptWitness is part of a transaction input per BIP 144. + TaprootSpendData tr_spenddata; ///< Taproot spending data. std::map<CKeyID, SigPair> signatures; ///< BIP 174 style partial signatures for the input. May contain all signatures necessary for producing a final scriptSig or scriptWitness. std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>> misc_pubkeys; + std::vector<unsigned char> taproot_key_path_sig; /// Schnorr signature for key path spending + std::map<std::pair<XOnlyPubKey, uint256>, std::vector<unsigned char>> taproot_script_sigs; ///< (Partial) schnorr signatures, indexed by XOnlyPubKey and leaf_hash. std::vector<CKeyID> missing_pubkeys; ///< KeyIDs of pubkeys which could not be found std::vector<CKeyID> missing_sigs; ///< KeyIDs of pubkeys for signatures which could not be found uint160 missing_redeem_script; ///< ScriptID of the missing redeemScript (if any) diff --git a/src/script/signingprovider.cpp b/src/script/signingprovider.cpp index 9781ec32af..b80fbe22ce 100644 --- a/src/script/signingprovider.cpp +++ b/src/script/signingprovider.cpp @@ -44,6 +44,11 @@ bool HidingSigningProvider::GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& inf return m_provider->GetKeyOrigin(keyid, info); } +bool HidingSigningProvider::GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const +{ + return m_provider->GetTaprootSpendData(output_key, spenddata); +} + bool FlatSigningProvider::GetCScript(const CScriptID& scriptid, CScript& script) const { return LookupHelper(scripts, scriptid, script); } bool FlatSigningProvider::GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const { return LookupHelper(pubkeys, keyid, pubkey); } bool FlatSigningProvider::GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const @@ -54,6 +59,10 @@ bool FlatSigningProvider::GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) return ret; } bool FlatSigningProvider::GetKey(const CKeyID& keyid, CKey& key) const { return LookupHelper(keys, keyid, key); } +bool FlatSigningProvider::GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const +{ + return LookupHelper(tr_spenddata, output_key, spenddata); +} FlatSigningProvider Merge(const FlatSigningProvider& a, const FlatSigningProvider& b) { @@ -66,6 +75,10 @@ FlatSigningProvider Merge(const FlatSigningProvider& a, const FlatSigningProvide ret.keys.insert(b.keys.begin(), b.keys.end()); ret.origins = a.origins; ret.origins.insert(b.origins.begin(), b.origins.end()); + ret.tr_spenddata = a.tr_spenddata; + for (const auto& [output_key, spenddata] : b.tr_spenddata) { + ret.tr_spenddata[output_key].Merge(spenddata); + } return ret; } diff --git a/src/script/signingprovider.h b/src/script/signingprovider.h index 76f31d2f6f..939ae10622 100644 --- a/src/script/signingprovider.h +++ b/src/script/signingprovider.h @@ -25,6 +25,7 @@ public: virtual bool GetKey(const CKeyID &address, CKey& key) const { return false; } virtual bool HaveKey(const CKeyID &address) const { return false; } virtual bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const { return false; } + virtual bool GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const { return false; } }; extern const SigningProvider& DUMMY_SIGNING_PROVIDER; @@ -42,6 +43,7 @@ public: bool GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const override; bool GetKey(const CKeyID& keyid, CKey& key) const override; bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override; + bool GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const override; }; struct FlatSigningProvider final : public SigningProvider @@ -50,11 +52,13 @@ struct FlatSigningProvider final : public SigningProvider std::map<CKeyID, CPubKey> pubkeys; std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>> origins; std::map<CKeyID, CKey> keys; + std::map<XOnlyPubKey, TaprootSpendData> tr_spenddata; /** Map from output key to spend data. */ bool GetCScript(const CScriptID& scriptid, CScript& script) const override; bool GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const override; bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override; bool GetKey(const CKeyID& keyid, CKey& key) const override; + bool GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const override; }; FlatSigningProvider Merge(const FlatSigningProvider& a, const FlatSigningProvider& b); diff --git a/src/script/standard.cpp b/src/script/standard.cpp index a4b11cc0a9..748f00dda5 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -377,6 +377,16 @@ bool IsValidDestination(const CTxDestination& dest) { /*static*/ TaprootBuilder::NodeInfo TaprootBuilder::Combine(NodeInfo&& a, NodeInfo&& b) { NodeInfo ret; + /* Iterate over all tracked leaves in a, add b's hash to their Merkle branch, and move them to ret. */ + for (auto& leaf : a.leaves) { + leaf.merkle_branch.push_back(b.hash); + ret.leaves.emplace_back(std::move(leaf)); + } + /* Iterate over all tracked leaves in b, add a's hash to their Merkle branch, and move them to ret. */ + for (auto& leaf : b.leaves) { + leaf.merkle_branch.push_back(a.hash); + ret.leaves.emplace_back(std::move(leaf)); + } /* Lexicographically sort a and b's hash, and compute parent hash. */ if (a.hash < b.hash) { ret.hash = (CHashWriter(HASHER_TAPBRANCH) << a.hash << b.hash).GetSHA256(); @@ -386,6 +396,21 @@ bool IsValidDestination(const CTxDestination& dest) { return ret; } +void TaprootSpendData::Merge(TaprootSpendData other) +{ + // TODO: figure out how to better deal with conflicting information + // being merged. + if (internal_key.IsNull() && !other.internal_key.IsNull()) { + internal_key = other.internal_key; + } + if (merkle_root.IsNull() && !other.merkle_root.IsNull()) { + merkle_root = other.merkle_root; + } + for (auto& [key, control_blocks] : other.scripts) { + scripts[key].merge(std::move(control_blocks)); + } +} + void TaprootBuilder::Insert(TaprootBuilder::NodeInfo&& node, int depth) { assert(depth >= 0 && (size_t)depth <= TAPROOT_CONTROL_MAX_NODE_COUNT); @@ -435,13 +460,14 @@ void TaprootBuilder::Insert(TaprootBuilder::NodeInfo&& node, int depth) return branch.size() == 0 || (branch.size() == 1 && branch[0]); } -TaprootBuilder& TaprootBuilder::Add(int depth, const CScript& script, int leaf_version) +TaprootBuilder& TaprootBuilder::Add(int depth, const CScript& script, int leaf_version, bool track) { assert((leaf_version & ~TAPROOT_LEAF_MASK) == 0); if (!IsValid()) return *this; - /* Construct NodeInfo object with leaf hash. */ + /* Construct NodeInfo object with leaf hash and (if track is true) also leaf information. */ NodeInfo node; node.hash = (CHashWriter{HASHER_TAPLEAF} << uint8_t(leaf_version) << script).GetSHA256(); + if (track) node.leaves.emplace_back(LeafInfo{script, leaf_version, {}}); /* Insert into the branch. */ Insert(std::move(node), depth); return *this; @@ -464,8 +490,33 @@ TaprootBuilder& TaprootBuilder::Finalize(const XOnlyPubKey& internal_key) m_internal_key = internal_key; auto ret = m_internal_key.CreateTapTweak(m_branch.size() == 0 ? nullptr : &m_branch[0]->hash); assert(ret.has_value()); - std::tie(m_output_key, std::ignore) = *ret; + std::tie(m_output_key, m_parity) = *ret; return *this; } WitnessV1Taproot TaprootBuilder::GetOutput() { return WitnessV1Taproot{m_output_key}; } + +TaprootSpendData TaprootBuilder::GetSpendData() const +{ + TaprootSpendData spd; + spd.merkle_root = m_branch.size() == 0 ? uint256() : m_branch[0]->hash; + spd.internal_key = m_internal_key; + if (m_branch.size()) { + // If any script paths exist, they have been combined into the root m_branch[0] + // by now. Compute the control block for each of its tracked leaves, and put them in + // spd.scripts. + for (const auto& leaf : m_branch[0]->leaves) { + std::vector<unsigned char> control_block; + control_block.resize(TAPROOT_CONTROL_BASE_SIZE + TAPROOT_CONTROL_NODE_SIZE * leaf.merkle_branch.size()); + control_block[0] = leaf.leaf_version | (m_parity ? 1 : 0); + std::copy(m_internal_key.begin(), m_internal_key.end(), control_block.begin() + 1); + if (leaf.merkle_branch.size()) { + std::copy(leaf.merkle_branch[0].begin(), + leaf.merkle_branch[0].begin() + TAPROOT_CONTROL_NODE_SIZE * leaf.merkle_branch.size(), + control_block.begin() + TAPROOT_CONTROL_BASE_SIZE); + } + spd.scripts[{leaf.script, leaf.leaf_version}].insert(std::move(control_block)); + } + } + return spd; +} diff --git a/src/script/standard.h b/src/script/standard.h index d7ea5cef27..285dd4c116 100644 --- a/src/script/standard.h +++ b/src/script/standard.h @@ -11,6 +11,7 @@ #include <uint256.h> #include <util/hash_type.h> +#include <map> #include <string> #include <variant> @@ -209,15 +210,50 @@ CScript GetScriptForRawPubKey(const CPubKey& pubkey); /** Generate a multisig script. */ CScript GetScriptForMultisig(int nRequired, const std::vector<CPubKey>& keys); +struct ShortestVectorFirstComparator +{ + bool operator()(const std::vector<unsigned char>& a, const std::vector<unsigned char>& b) const + { + if (a.size() < b.size()) return true; + if (a.size() > b.size()) return false; + return a < b; + } +}; + +struct TaprootSpendData +{ + /** The BIP341 internal key. */ + XOnlyPubKey internal_key; + /** The Merkle root of the script tree (0 if no scripts). */ + uint256 merkle_root; + /** Map from (script, leaf_version) to (sets of) control blocks. + * The control blocks are sorted by size, so that the signing logic can + * easily prefer the cheapest one. */ + std::map<std::pair<CScript, int>, std::set<std::vector<unsigned char>, ShortestVectorFirstComparator>> scripts; + /** Merge other TaprootSpendData (for the same scriptPubKey) into this. */ + void Merge(TaprootSpendData other); +}; + /** Utility class to construct Taproot outputs from internal key and script tree. */ class TaprootBuilder { private: + /** Information about a tracked leaf in the Merkle tree. */ + struct LeafInfo + { + CScript script; //!< The script. + int leaf_version; //!< The leaf version for that script. + std::vector<uint256> merkle_branch; //!< The hashing partners above this leaf. + }; + /** Information associated with a node in the Merkle tree. */ struct NodeInfo { /** Merkle hash of this node. */ uint256 hash; + /** Tracked leaves underneath this node (either from the node itself, or its children). + * The merkle_branch field for each is the partners to get to *this* node. */ + std::vector<LeafInfo> leaves; }; /** Whether the builder is in a valid state so far. */ bool m_valid = true; @@ -260,7 +296,8 @@ private: std::vector<std::optional<NodeInfo>> m_branch; XOnlyPubKey m_internal_key; //!< The internal key, set when finalizing. - XOnlyPubKey m_output_key; //!< The output key, computed when finalizing. */ + XOnlyPubKey m_output_key; //!< The output key, computed when finalizing. + bool m_parity; //!< The tweak parity, computed when finalizing. /** Combine information about a parent Merkle tree node from its child nodes. */ static NodeInfo Combine(NodeInfo&& a, NodeInfo&& b); @@ -269,8 +306,9 @@ private: public: /** Add a new script at a certain depth in the tree. Add() operations must be called - * in depth-first traversal order of binary tree. */ - TaprootBuilder& Add(int depth, const CScript& script, int leaf_version); + * in depth-first traversal order of binary tree. If track is true, it will be included in + * the GetSpendData() output. */ + TaprootBuilder& Add(int depth, const CScript& script, int leaf_version, bool track = true); /** Like Add(), but for a Merkle node with a given hash to the tree. */ TaprootBuilder& AddOmitted(int depth, const uint256& hash); /** Finalize the construction. Can only be called when IsComplete() is true. @@ -285,6 +323,8 @@ public: WitnessV1Taproot GetOutput(); /** Check if a list of depths is legal (will lead to IsComplete()). */ static bool ValidDepths(const std::vector<int>& depths); + /** Compute spending data (after Finalize()). */ + TaprootSpendData GetSpendData() const; }; #endif // BITCOIN_SCRIPT_STANDARD_H diff --git a/src/test/addrman_tests.cpp b/src/test/addrman_tests.cpp index 49b40924e0..eb5c37b34d 100644 --- a/src/test/addrman_tests.cpp +++ b/src/test/addrman_tests.cpp @@ -74,9 +74,9 @@ public: // Simulates connection failure so that we can test eviction of offline nodes void SimConnFail(const CService& addr) { - LOCK(cs); int64_t nLastSuccess = 1; - Good_(addr, true, nLastSuccess); // Set last good connection in the deep past. + // Set last good connection in the deep past. + Good(addr, true, nLastSuccess); bool count_failure = false; int64_t nLastTry = GetAdjustedTime()-61; diff --git a/src/test/blockfilter_index_tests.cpp b/src/test/blockfilter_index_tests.cpp index 2f532ef598..2eb653e9ec 100644 --- a/src/test/blockfilter_index_tests.cpp +++ b/src/test/blockfilter_index_tests.cpp @@ -62,7 +62,7 @@ CBlock BuildChainTestingSetup::CreateBlock(const CBlockIndex* prev, const CScript& scriptPubKey) { const CChainParams& chainparams = Params(); - std::unique_ptr<CBlockTemplate> pblocktemplate = BlockAssembler(::ChainstateActive(), *m_node.mempool, chainparams).CreateNewBlock(scriptPubKey); + std::unique_ptr<CBlockTemplate> pblocktemplate = BlockAssembler(m_node.chainman->ActiveChainstate(), *m_node.mempool, chainparams).CreateNewBlock(scriptPubKey); CBlock& block = pblocktemplate->block; block.hashPrevBlock = prev->GetBlockHash(); block.nTime = prev->nTime + 1; @@ -117,9 +117,9 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup) std::vector<BlockFilter> filters; std::vector<uint256> filter_hashes; - for (const CBlockIndex* block_index = ::ChainActive().Genesis(); + for (const CBlockIndex* block_index = m_node.chainman->ActiveChain().Genesis(); block_index != nullptr; - block_index = ::ChainActive().Next(block_index)) { + block_index = m_node.chainman->ActiveChain().Next(block_index)) { BOOST_CHECK(!filter_index.LookupFilter(block_index, filter)); BOOST_CHECK(!filter_index.LookupFilterHeader(block_index, filter_header)); BOOST_CHECK(!filter_index.LookupFilterRange(block_index->nHeight, block_index, filters)); @@ -131,7 +131,7 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup) // BlockUntilSyncedToCurrentChain should return false before index is started. BOOST_CHECK(!filter_index.BlockUntilSyncedToCurrentChain()); - BOOST_REQUIRE(filter_index.Start(::ChainstateActive())); + BOOST_REQUIRE(filter_index.Start(m_node.chainman->ActiveChainstate())); // Allow filter index to catch up with the block index. constexpr int64_t timeout_ms = 10 * 1000; @@ -145,9 +145,9 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup) { LOCK(cs_main); const CBlockIndex* block_index; - for (block_index = ::ChainActive().Genesis(); + for (block_index = m_node.chainman->ActiveChain().Genesis(); block_index != nullptr; - block_index = ::ChainActive().Next(block_index)) { + block_index = m_node.chainman->ActiveChain().Next(block_index)) { CheckFilterLookups(filter_index, block_index, last_header); } } @@ -156,7 +156,7 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup) const CBlockIndex* tip; { LOCK(cs_main); - tip = ::ChainActive().Tip(); + tip = m_node.chainman->ActiveChain().Tip(); } CKey coinbase_key_A, coinbase_key_B; coinbase_key_A.MakeNewKey(true); @@ -178,7 +178,7 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup) const CBlockIndex* block_index; { LOCK(cs_main); - block_index = g_chainman.m_blockman.LookupBlockIndex(block->GetHash()); + block_index = m_node.chainman->m_blockman.LookupBlockIndex(block->GetHash()); } BOOST_CHECK(filter_index.BlockUntilSyncedToCurrentChain()); @@ -196,7 +196,7 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup) const CBlockIndex* block_index; { LOCK(cs_main); - block_index = g_chainman.m_blockman.LookupBlockIndex(block->GetHash()); + block_index = m_node.chainman->m_blockman.LookupBlockIndex(block->GetHash()); } BOOST_CHECK(filter_index.BlockUntilSyncedToCurrentChain()); @@ -210,7 +210,7 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup) const CBlockIndex* block_index; { LOCK(cs_main); - block_index = g_chainman.m_blockman.LookupBlockIndex(block->GetHash()); + block_index = m_node.chainman->m_blockman.LookupBlockIndex(block->GetHash()); } BOOST_CHECK(filter_index.BlockUntilSyncedToCurrentChain()); @@ -231,14 +231,14 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup) { LOCK(cs_main); - block_index = g_chainman.m_blockman.LookupBlockIndex(chainA[i]->GetHash()); + block_index = m_node.chainman->m_blockman.LookupBlockIndex(chainA[i]->GetHash()); } BOOST_CHECK(filter_index.BlockUntilSyncedToCurrentChain()); CheckFilterLookups(filter_index, block_index, chainA_last_header); { LOCK(cs_main); - block_index = g_chainman.m_blockman.LookupBlockIndex(chainB[i]->GetHash()); + block_index = m_node.chainman->m_blockman.LookupBlockIndex(chainB[i]->GetHash()); } BOOST_CHECK(filter_index.BlockUntilSyncedToCurrentChain()); CheckFilterLookups(filter_index, block_index, chainB_last_header); @@ -250,7 +250,7 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup) { LOCK(cs_main); - tip = ::ChainActive().Tip(); + tip = m_node.chainman->ActiveChain().Tip(); } BOOST_CHECK(filter_index.LookupFilterRange(0, tip, filters)); BOOST_CHECK(filter_index.LookupFilterHashRange(0, tip, filter_hashes)); diff --git a/src/test/coinstatsindex_tests.cpp b/src/test/coinstatsindex_tests.cpp index 106fcd2a33..597d7a7340 100644 --- a/src/test/coinstatsindex_tests.cpp +++ b/src/test/coinstatsindex_tests.cpp @@ -22,7 +22,7 @@ BOOST_FIXTURE_TEST_CASE(coinstatsindex_initial_sync, TestChain100Setup) const CBlockIndex* block_index; { LOCK(cs_main); - block_index = ChainActive().Tip(); + block_index = m_node.chainman->ActiveChain().Tip(); } // CoinStatsIndex should not be found before it is started. @@ -32,7 +32,7 @@ BOOST_FIXTURE_TEST_CASE(coinstatsindex_initial_sync, TestChain100Setup) // is started. BOOST_CHECK(!coin_stats_index.BlockUntilSyncedToCurrentChain()); - BOOST_REQUIRE(coin_stats_index.Start(::ChainstateActive())); + BOOST_REQUIRE(coin_stats_index.Start(m_node.chainman->ActiveChainstate())); // Allow the CoinStatsIndex to catch up with the block index that is syncing // in a background thread. @@ -46,7 +46,7 @@ BOOST_FIXTURE_TEST_CASE(coinstatsindex_initial_sync, TestChain100Setup) const CBlockIndex* genesis_block_index; { LOCK(cs_main); - genesis_block_index = ChainActive().Genesis(); + genesis_block_index = m_node.chainman->ActiveChain().Genesis(); } BOOST_CHECK(coin_stats_index.LookUpStats(genesis_block_index, coin_stats)); @@ -64,7 +64,7 @@ BOOST_FIXTURE_TEST_CASE(coinstatsindex_initial_sync, TestChain100Setup) const CBlockIndex* new_block_index; { LOCK(cs_main); - new_block_index = ChainActive().Tip(); + new_block_index = m_node.chainman->ActiveChain().Tip(); } coin_stats_index.LookUpStats(new_block_index, new_coin_stats); diff --git a/src/test/denialofservice_tests.cpp b/src/test/denialofservice_tests.cpp index a56ce51acb..57178d015d 100644 --- a/src/test/denialofservice_tests.cpp +++ b/src/test/denialofservice_tests.cpp @@ -14,36 +14,19 @@ #include <script/signingprovider.h> #include <script/standard.h> #include <serialize.h> +#include <test/util/net.h> +#include <test/util/setup_common.h> #include <txorphanage.h> #include <util/string.h> #include <util/system.h> #include <util/time.h> #include <validation.h> -#include <test/util/setup_common.h> - #include <array> #include <stdint.h> #include <boost/test/unit_test.hpp> -struct CConnmanTest : public CConnman { - using CConnman::CConnman; - void AddNode(CNode& node) - { - LOCK(cs_vNodes); - vNodes.push_back(&node); - } - void ClearNodes() - { - LOCK(cs_vNodes); - for (CNode* node : vNodes) { - delete node; - } - vNodes.clear(); - } -}; - static CService ip(uint32_t i) { struct in_addr s; @@ -83,8 +66,8 @@ BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction) // This test requires that we have a chain with non-zero work. { LOCK(cs_main); - BOOST_CHECK(::ChainActive().Tip() != nullptr); - BOOST_CHECK(::ChainActive().Tip()->nChainWork > 0); + BOOST_CHECK(m_node.chainman->ActiveChain().Tip() != nullptr); + BOOST_CHECK(m_node.chainman->ActiveChain().Tip()->nChainWork > 0); } // Test starts here @@ -120,7 +103,7 @@ BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction) peerLogic->FinalizeNode(dummyNode1); } -static void AddRandomOutboundPeer(std::vector<CNode *> &vNodes, PeerManager &peerLogic, CConnmanTest* connman) +static void AddRandomOutboundPeer(std::vector<CNode*>& vNodes, PeerManager& peerLogic, ConnmanTestMsg& connman) { CAddress addr(ip(g_insecure_rand_ctx.randbits(32)), NODE_NONE); vNodes.emplace_back(new CNode(id++, ServiceFlags(NODE_NETWORK | NODE_WITNESS), INVALID_SOCKET, addr, /* nKeyedNetGroupIn */ 0, /* nLocalHostNonceIn */ 0, CAddress(), /* pszDest */ "", ConnectionType::OUTBOUND_FULL_RELAY, /* inbound_onion */ false)); @@ -130,13 +113,13 @@ static void AddRandomOutboundPeer(std::vector<CNode *> &vNodes, PeerManager &pee peerLogic.InitializeNode(&node); node.fSuccessfullyConnected = true; - connman->AddNode(node); + connman.AddTestNode(node); } BOOST_AUTO_TEST_CASE(stale_tip_peer_management) { const CChainParams& chainparams = Params(); - auto connman = std::make_unique<CConnmanTest>(0x1337, 0x1337, *m_node.addrman); + auto connman = std::make_unique<ConnmanTestMsg>(0x1337, 0x1337, *m_node.addrman); auto peerLogic = PeerManager::make(chainparams, *connman, *m_node.addrman, nullptr, *m_node.scheduler, *m_node.chainman, *m_node.mempool, false); @@ -150,8 +133,8 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management) std::vector<CNode *> vNodes; // Mock some outbound peers - for (int i=0; i<max_outbound_full_relay; ++i) { - AddRandomOutboundPeer(vNodes, *peerLogic, connman.get()); + for (int i = 0; i < max_outbound_full_relay; ++i) { + AddRandomOutboundPeer(vNodes, *peerLogic, *connman); } peerLogic->CheckForStaleTipAndEvictPeers(); @@ -176,7 +159,7 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management) // If we add one more peer, something should get marked for eviction // on the next check (since we're mocking the time to be in the future, the // required time connected check should be satisfied). - AddRandomOutboundPeer(vNodes, *peerLogic, connman.get()); + AddRandomOutboundPeer(vNodes, *peerLogic, *connman); peerLogic->CheckForStaleTipAndEvictPeers(); for (int i = 0; i < max_outbound_full_relay; ++i) { @@ -202,14 +185,14 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management) peerLogic->FinalizeNode(*node); } - connman->ClearNodes(); + connman->ClearTestNodes(); } BOOST_AUTO_TEST_CASE(peer_discouragement) { const CChainParams& chainparams = Params(); auto banman = std::make_unique<BanMan>(m_args.GetDataDirBase() / "banlist.dat", nullptr, DEFAULT_MISBEHAVING_BANTIME); - auto connman = std::make_unique<CConnmanTest>(0x1337, 0x1337, *m_node.addrman); + auto connman = std::make_unique<ConnmanTestMsg>(0x1337, 0x1337, *m_node.addrman); auto peerLogic = PeerManager::make(chainparams, *connman, *m_node.addrman, banman.get(), *m_node.scheduler, *m_node.chainman, *m_node.mempool, false); @@ -233,7 +216,7 @@ BOOST_AUTO_TEST_CASE(peer_discouragement) nodes[0]->SetCommonVersion(PROTOCOL_VERSION); peerLogic->InitializeNode(nodes[0]); nodes[0]->fSuccessfullyConnected = true; - connman->AddNode(*nodes[0]); + connman->AddTestNode(*nodes[0]); peerLogic->Misbehaving(nodes[0]->GetId(), DISCOURAGEMENT_THRESHOLD, /* message */ ""); // Should be discouraged { LOCK(nodes[0]->cs_sendProcessing); @@ -249,7 +232,7 @@ BOOST_AUTO_TEST_CASE(peer_discouragement) nodes[1]->SetCommonVersion(PROTOCOL_VERSION); peerLogic->InitializeNode(nodes[1]); nodes[1]->fSuccessfullyConnected = true; - connman->AddNode(*nodes[1]); + connman->AddTestNode(*nodes[1]); peerLogic->Misbehaving(nodes[1]->GetId(), DISCOURAGEMENT_THRESHOLD - 1, /* message */ ""); { LOCK(nodes[1]->cs_sendProcessing); @@ -280,7 +263,7 @@ BOOST_AUTO_TEST_CASE(peer_discouragement) nodes[2]->SetCommonVersion(PROTOCOL_VERSION); peerLogic->InitializeNode(nodes[2]); nodes[2]->fSuccessfullyConnected = true; - connman->AddNode(*nodes[2]); + connman->AddTestNode(*nodes[2]); peerLogic->Misbehaving(nodes[2]->GetId(), DISCOURAGEMENT_THRESHOLD, /* message */ ""); { LOCK(nodes[2]->cs_sendProcessing); @@ -296,7 +279,7 @@ BOOST_AUTO_TEST_CASE(peer_discouragement) for (CNode* node : nodes) { peerLogic->FinalizeNode(*node); } - connman->ClearNodes(); + connman->ClearTestNodes(); } BOOST_AUTO_TEST_CASE(DoS_bantime) diff --git a/src/test/fuzz/addrman.cpp b/src/test/fuzz/addrman.cpp index 98ae32a8d0..db0b461873 100644 --- a/src/test/fuzz/addrman.cpp +++ b/src/test/fuzz/addrman.cpp @@ -57,15 +57,6 @@ FUZZ_TARGET_INIT(addrman, initialize_addrman) (void)addr_man.SelectTriedCollision(); }, [&] { - (void)addr_man.Select(fuzzed_data_provider.ConsumeBool()); - }, - [&] { - (void)addr_man.GetAddr( - /* max_addresses */ fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096), - /* max_pct */ fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096), - /* network */ std::nullopt); - }, - [&] { const std::optional<CAddress> opt_address = ConsumeDeserializable<CAddress>(fuzzed_data_provider); const std::optional<CNetAddr> opt_net_addr = ConsumeDeserializable<CNetAddr>(fuzzed_data_provider); if (opt_address && opt_net_addr) { @@ -109,12 +100,15 @@ FUZZ_TARGET_INIT(addrman, initialize_addrman) if (opt_service) { addr_man.SetServices(*opt_service, ConsumeWeakEnum(fuzzed_data_provider, ALL_SERVICE_FLAGS)); } - }, - [&] { - (void)addr_man.Check(); }); } - (void)addr_man.size(); + const CAddrMan& const_addr_man{addr_man}; + (void)/*const_*/addr_man.GetAddr( + /* max_addresses */ fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096), + /* max_pct */ fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096), + /* network */ std::nullopt); + (void)/*const_*/addr_man.Select(fuzzed_data_provider.ConsumeBool()); + (void)const_addr_man.size(); CDataStream data_stream(SER_NETWORK, PROTOCOL_VERSION); - data_stream << addr_man; + data_stream << const_addr_man; } diff --git a/src/test/fuzz/coins_view.cpp b/src/test/fuzz/coins_view.cpp index b509ee0b26..42f19d16c6 100644 --- a/src/test/fuzz/coins_view.cpp +++ b/src/test/fuzz/coins_view.cpp @@ -27,6 +27,7 @@ #include <vector> namespace { +const TestingSetup* g_setup; const Coin EMPTY_COIN{}; bool operator==(const Coin& a, const Coin& b) @@ -39,6 +40,7 @@ bool operator==(const Coin& a, const Coin& b) void initialize_coins_view() { static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(); + g_setup = testing_setup.get(); } FUZZ_TARGET_INIT(coins_view, initialize_coins_view) @@ -268,7 +270,7 @@ FUZZ_TARGET_INIT(coins_view, initialize_coins_view) CCoinsStats stats{CoinStatsHashType::HASH_SERIALIZED}; bool expected_code_path = false; try { - (void)GetUTXOStats(&coins_view_cache, WITH_LOCK(::cs_main, return std::ref(g_chainman.m_blockman)), stats); + (void)GetUTXOStats(&coins_view_cache, WITH_LOCK(::cs_main, return std::ref(g_setup->m_node.chainman->m_blockman)), stats); } catch (const std::logic_error&) { expected_code_path = true; } diff --git a/src/test/fuzz/crypto.cpp b/src/test/fuzz/crypto.cpp index eeeac18968..f83747e424 100644 --- a/src/test/fuzz/crypto.cpp +++ b/src/test/fuzz/crypto.cpp @@ -19,6 +19,10 @@ FUZZ_TARGET(crypto) { + // Hashing is expensive with sanitizers enabled, so limit the number of + // calls + int limit_max_ops{30}; + FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; std::vector<uint8_t> data = ConsumeRandomLengthByteVector(fuzzed_data_provider); if (data.empty()) { @@ -36,7 +40,7 @@ FUZZ_TARGET(crypto) SHA3_256 sha3; CSipHasher sip_hasher{fuzzed_data_provider.ConsumeIntegral<uint64_t>(), fuzzed_data_provider.ConsumeIntegral<uint64_t>()}; - while (fuzzed_data_provider.ConsumeBool()) { + while (--limit_max_ops >= 0 && fuzzed_data_provider.ConsumeBool()) { CallOneOf( fuzzed_data_provider, [&] { diff --git a/src/test/fuzz/deserialize.cpp b/src/test/fuzz/deserialize.cpp index 1290c78712..721e4360d0 100644 --- a/src/test/fuzz/deserialize.cpp +++ b/src/test/fuzz/deserialize.cpp @@ -53,9 +53,9 @@ struct invalid_fuzzing_input_exception : public std::exception { }; template <typename T> -CDataStream Serialize(const T& obj, const int version = INIT_PROTO_VERSION) +CDataStream Serialize(const T& obj, const int version = INIT_PROTO_VERSION, const int ser_type = SER_NETWORK) { - CDataStream ds(SER_NETWORK, version); + CDataStream ds(ser_type, version); ds << obj; return ds; } @@ -69,9 +69,9 @@ T Deserialize(CDataStream ds) } template <typename T> -void DeserializeFromFuzzingInput(FuzzBufferType buffer, T& obj, const std::optional<int> protocol_version = std::nullopt) +void DeserializeFromFuzzingInput(FuzzBufferType buffer, T& obj, const std::optional<int> protocol_version = std::nullopt, const int ser_type = SER_NETWORK) { - CDataStream ds(buffer, SER_NETWORK, INIT_PROTO_VERSION); + CDataStream ds(buffer, ser_type, INIT_PROTO_VERSION); if (protocol_version) { ds.SetVersion(*protocol_version); } else { @@ -92,9 +92,9 @@ void DeserializeFromFuzzingInput(FuzzBufferType buffer, T& obj, const std::optio } template <typename T> -void AssertEqualAfterSerializeDeserialize(const T& obj, const int version = INIT_PROTO_VERSION) +void AssertEqualAfterSerializeDeserialize(const T& obj, const int version = INIT_PROTO_VERSION, const int ser_type = SER_NETWORK) { - assert(Deserialize<T>(Serialize(obj, version)) == obj); + assert(Deserialize<T>(Serialize(obj, version, ser_type)) == obj); } } // namespace @@ -136,8 +136,7 @@ FUZZ_TARGET_DESERIALIZE(partial_merkle_tree_deserialize, { FUZZ_TARGET_DESERIALIZE(pub_key_deserialize, { CPubKey pub_key; DeserializeFromFuzzingInput(buffer, pub_key); - // TODO: The following equivalence should hold for CPubKey? Fix. - // AssertEqualAfterSerializeDeserialize(pub_key); + AssertEqualAfterSerializeDeserialize(pub_key); }) FUZZ_TARGET_DESERIALIZE(script_deserialize, { CScript script; @@ -251,9 +250,37 @@ FUZZ_TARGET_DESERIALIZE(messageheader_deserialize, { DeserializeFromFuzzingInput(buffer, mh); (void)mh.IsCommandValid(); }) -FUZZ_TARGET_DESERIALIZE(address_deserialize, { +FUZZ_TARGET_DESERIALIZE(address_deserialize_v1_notime, { CAddress a; - DeserializeFromFuzzingInput(buffer, a); + DeserializeFromFuzzingInput(buffer, a, INIT_PROTO_VERSION); + // A CAddress without nTime (as is expected under INIT_PROTO_VERSION) will roundtrip + // in all 5 formats (with/without nTime, v1/v2, network/disk) + AssertEqualAfterSerializeDeserialize(a, INIT_PROTO_VERSION); + AssertEqualAfterSerializeDeserialize(a, PROTOCOL_VERSION); + AssertEqualAfterSerializeDeserialize(a, 0, SER_DISK); + AssertEqualAfterSerializeDeserialize(a, PROTOCOL_VERSION | ADDRV2_FORMAT); + AssertEqualAfterSerializeDeserialize(a, ADDRV2_FORMAT, SER_DISK); +}) +FUZZ_TARGET_DESERIALIZE(address_deserialize_v1_withtime, { + CAddress a; + DeserializeFromFuzzingInput(buffer, a, PROTOCOL_VERSION); + // A CAddress in V1 mode will roundtrip in all 4 formats that have nTime. + AssertEqualAfterSerializeDeserialize(a, PROTOCOL_VERSION); + AssertEqualAfterSerializeDeserialize(a, 0, SER_DISK); + AssertEqualAfterSerializeDeserialize(a, PROTOCOL_VERSION | ADDRV2_FORMAT); + AssertEqualAfterSerializeDeserialize(a, ADDRV2_FORMAT, SER_DISK); +}) +FUZZ_TARGET_DESERIALIZE(address_deserialize_v2, { + CAddress a; + DeserializeFromFuzzingInput(buffer, a, PROTOCOL_VERSION | ADDRV2_FORMAT); + // A CAddress in V2 mode will roundtrip in both V2 formats, and also in the V1 formats + // with time if it's V1 compatible. + if (a.IsAddrV1Compatible()) { + AssertEqualAfterSerializeDeserialize(a, PROTOCOL_VERSION); + AssertEqualAfterSerializeDeserialize(a, 0, SER_DISK); + } + AssertEqualAfterSerializeDeserialize(a, PROTOCOL_VERSION | ADDRV2_FORMAT); + AssertEqualAfterSerializeDeserialize(a, ADDRV2_FORMAT, SER_DISK); }) FUZZ_TARGET_DESERIALIZE(inv_deserialize, { CInv i; diff --git a/src/test/fuzz/load_external_block_file.cpp b/src/test/fuzz/load_external_block_file.cpp index dbd0c76d42..a7770c90e8 100644 --- a/src/test/fuzz/load_external_block_file.cpp +++ b/src/test/fuzz/load_external_block_file.cpp @@ -13,9 +13,14 @@ #include <cstdint> #include <vector> +namespace { +const TestingSetup* g_setup; +} // namespace + void initialize_load_external_block_file() { static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(); + g_setup = testing_setup.get(); } FUZZ_TARGET_INIT(load_external_block_file, initialize_load_external_block_file) @@ -27,5 +32,5 @@ FUZZ_TARGET_INIT(load_external_block_file, initialize_load_external_block_file) return; } FlatFilePos flat_file_pos; - ::ChainstateActive().LoadExternalBlockFile(Params(), fuzzed_block_file, fuzzed_data_provider.ConsumeBool() ? &flat_file_pos : nullptr); + g_setup->m_node.chainman->ActiveChainstate().LoadExternalBlockFile(Params(), fuzzed_block_file, fuzzed_data_provider.ConsumeBool() ? &flat_file_pos : nullptr); } diff --git a/src/test/fuzz/node_eviction.cpp b/src/test/fuzz/node_eviction.cpp index 70ffc6bf37..a3f71426fa 100644 --- a/src/test/fuzz/node_eviction.cpp +++ b/src/test/fuzz/node_eviction.cpp @@ -31,7 +31,7 @@ FUZZ_TARGET(node_eviction) /* nKeyedNetGroup */ fuzzed_data_provider.ConsumeIntegral<uint64_t>(), /* prefer_evict */ fuzzed_data_provider.ConsumeBool(), /* m_is_local */ fuzzed_data_provider.ConsumeBool(), - /* m_is_onion */ fuzzed_data_provider.ConsumeBool(), + /* m_network */ fuzzed_data_provider.PickValueInArray(ALL_NETWORKS), }); } // Make a copy since eviction_candidates may be in some valid but otherwise diff --git a/src/test/fuzz/process_message.cpp b/src/test/fuzz/process_message.cpp index 7b99193ad0..c4e4d4c785 100644 --- a/src/test/fuzz/process_message.cpp +++ b/src/test/fuzz/process_message.cpp @@ -58,7 +58,19 @@ void initialize_process_message() static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(); g_setup = testing_setup.get(); + + // Temporary debug for https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=35027 + { + LOCK(::cs_main); + assert(CheckDiskSpace(gArgs.GetDataDirNet())); + assert(CheckDiskSpace(gArgs.GetDataDirNet(), 48 * 2 * 2 * g_setup->m_node.chainman->ActiveChainstate().CoinsTip().GetCacheSize())); + } for (int i = 0; i < 2 * COINBASE_MATURITY; i++) { + { + LOCK(::cs_main); + assert(CheckDiskSpace(gArgs.GetDataDirNet())); + assert(CheckDiskSpace(gArgs.GetDataDirNet(), 48 * 2 * 2 * g_setup->m_node.chainman->ActiveChainstate().CoinsTip().GetCacheSize())); + } MineBlock(g_setup->m_node, CScript() << OP_TRUE); } SyncWithValidationInterfaceQueue(); diff --git a/src/test/fuzz/validation_load_mempool.cpp b/src/test/fuzz/validation_load_mempool.cpp index e1a21b6c53..c2aaf486c5 100644 --- a/src/test/fuzz/validation_load_mempool.cpp +++ b/src/test/fuzz/validation_load_mempool.cpp @@ -14,9 +14,14 @@ #include <cstdint> #include <vector> +namespace { +const TestingSetup* g_setup; +} // namespace + void initialize_validation_load_mempool() { static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(); + g_setup = testing_setup.get(); } FUZZ_TARGET_INIT(validation_load_mempool, initialize_validation_load_mempool) @@ -29,6 +34,6 @@ FUZZ_TARGET_INIT(validation_load_mempool, initialize_validation_load_mempool) auto fuzzed_fopen = [&](const fs::path&, const char*) { return fuzzed_file_provider.open(); }; - (void)LoadMempool(pool, ::ChainstateActive(), fuzzed_fopen); + (void)LoadMempool(pool, g_setup->m_node.chainman->ActiveChainstate(), fuzzed_fopen); (void)DumpMempool(pool, fuzzed_fopen, true); } diff --git a/src/test/interfaces_tests.cpp b/src/test/interfaces_tests.cpp index 73463b071e..42a7c7798c 100644 --- a/src/test/interfaces_tests.cpp +++ b/src/test/interfaces_tests.cpp @@ -98,7 +98,7 @@ BOOST_AUTO_TEST_CASE(findCommonAncestor) auto* orig_tip = active.Tip(); for (int i = 0; i < 10; ++i) { BlockValidationState state; - ChainstateActive().InvalidateBlock(state, Params(), active.Tip()); + m_node.chainman->ActiveChainstate().InvalidateBlock(state, Params(), active.Tip()); } BOOST_CHECK_EQUAL(active.Height(), orig_tip->nHeight - 10); coinbaseKey.MakeNewKey(true); diff --git a/src/test/key_tests.cpp b/src/test/key_tests.cpp index cb66d5164e..b915982d98 100644 --- a/src/test/key_tests.cpp +++ b/src/test/key_tests.cpp @@ -300,6 +300,48 @@ BOOST_AUTO_TEST_CASE(bip340_test_vectors) auto sig = ParseHex(test.first[2]); BOOST_CHECK_EQUAL(XOnlyPubKey(pubkey).VerifySchnorr(uint256(msg), sig), test.second); } + + static const std::vector<std::array<std::string, 5>> SIGN_VECTORS = { + {{"0000000000000000000000000000000000000000000000000000000000000003", "F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9", "0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000000", "E907831F80848D1069A5371B402410364BDF1C5F8307B0084C55F1CE2DCA821525F66A4A85EA8B71E482A74F382D2CE5EBEEE8FDB2172F477DF4900D310536C0"}}, + {{"B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF", "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", "0000000000000000000000000000000000000000000000000000000000000001", "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", "6896BD60EEAE296DB48A229FF71DFE071BDE413E6D43F917DC8DCF8C78DE33418906D11AC976ABCCB20B091292BFF4EA897EFCB639EA871CFA95F6DE339E4B0A"}}, + {{"C90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B14E5C9", "DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8", "C87AA53824B4D7AE2EB035A2B5BBBCCC080E76CDC6D1692C4B0B62D798E6D906", "7E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C", "5831AAEED7B44BB74E5EAB94BA9D4294C49BCF2A60728D8B4C200F50DD313C1BAB745879A5AD954A72C45A91C3A51D3C7ADEA98D82F8481E0E1E03674A6F3FB7"}}, + {{"0B432B2677937381AEF05BB02A66ECD012773062CF3FA2549E44F58ED2401710", "25D1DFF95105F5253C4022F628A996AD3A0D95FBF21D468A1B33F8C160D8F517", "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", "7EB0509757E246F19449885651611CB965ECC1A187DD51B64FDA1EDC9637D5EC97582B9CB13DB3933705B32BA982AF5AF25FD78881EBB32771FC5922EFC66EA3"}}, + }; + + for (const auto& [sec_hex, pub_hex, aux_hex, msg_hex, sig_hex] : SIGN_VECTORS) { + auto sec = ParseHex(sec_hex); + auto pub = ParseHex(pub_hex); + uint256 aux256(ParseHex(aux_hex)); + uint256 msg256(ParseHex(msg_hex)); + auto sig = ParseHex(sig_hex); + unsigned char sig64[64]; + + // Run the untweaked test vectors above, comparing with exact expected signature. + CKey key; + key.Set(sec.begin(), sec.end(), true); + XOnlyPubKey pubkey(key.GetPubKey()); + BOOST_CHECK(std::equal(pubkey.begin(), pubkey.end(), pub.begin(), pub.end())); + bool ok = key.SignSchnorr(msg256, sig64, nullptr, &aux256); + BOOST_CHECK(ok); + BOOST_CHECK(std::vector<unsigned char>(sig64, sig64 + 64) == sig); + // Verify those signatures for good measure. + BOOST_CHECK(pubkey.VerifySchnorr(msg256, sig64)); + + // Do 10 iterations where we sign with a random Merkle root to tweak, + // and compare against the resulting tweaked keys, with random aux. + // In iteration i=0 we tweak with empty Merkle tree. + for (int i = 0; i < 10; ++i) { + uint256 merkle_root; + if (i) merkle_root = InsecureRand256(); + auto tweaked = pubkey.CreateTapTweak(i ? &merkle_root : nullptr); + BOOST_CHECK(tweaked); + XOnlyPubKey tweaked_key = tweaked->first; + aux256 = InsecureRand256(); + bool ok = key.SignSchnorr(msg256, sig64, &merkle_root, &aux256); + BOOST_CHECK(ok); + BOOST_CHECK(tweaked_key.VerifySchnorr(msg256, sig64)); + } + } } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index c47d0eae1e..e20c5e4e8f 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -28,8 +28,8 @@ struct MinerTestingSetup : public TestingSetup { void TestPackageSelection(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_node.mempool->cs); bool TestSequenceLocks(const CTransaction& tx, int flags) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_node.mempool->cs) { - CCoinsViewMemPool viewMempool(&m_node.chainman->ActiveChainstate().CoinsTip(), *m_node.mempool); - return CheckSequenceLocks(m_node.chainman->ActiveChain().Tip(), viewMempool, tx, flags); + CCoinsViewMemPool view_mempool(&m_node.chainman->ActiveChainstate().CoinsTip(), *m_node.mempool); + return CheckSequenceLocks(m_node.chainman->ActiveChain().Tip(), view_mempool, tx, flags); } BlockAssembler AssemblerForTest(const CChainParams& params); }; @@ -45,7 +45,7 @@ BlockAssembler MinerTestingSetup::AssemblerForTest(const CChainParams& params) options.nBlockMaxWeight = MAX_BLOCK_WEIGHT; options.blockMinFeeRate = blockMinFeeRate; - return BlockAssembler(::ChainstateActive(), *m_node.mempool, params, options); + return BlockAssembler(m_node.chainman->ActiveChainstate(), *m_node.mempool, params, options); } constexpr static struct { @@ -82,11 +82,11 @@ constexpr static struct { {2, 0xbbbeb305}, {2, 0xfe1c810a}, }; -static CBlockIndex CreateBlockIndex(int nHeight) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +static CBlockIndex CreateBlockIndex(int nHeight, CBlockIndex* active_chain_tip) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { CBlockIndex index; index.nHeight = nHeight; - index.pprev = ::ChainActive().Tip(); + index.pprev = active_chain_tip; return index; } @@ -228,17 +228,17 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) { LOCK(cs_main); pblock->nVersion = 1; - pblock->nTime = ::ChainActive().Tip()->GetMedianTimePast()+1; + pblock->nTime = m_node.chainman->ActiveChain().Tip()->GetMedianTimePast()+1; CMutableTransaction txCoinbase(*pblock->vtx[0]); txCoinbase.nVersion = 1; txCoinbase.vin[0].scriptSig = CScript(); txCoinbase.vin[0].scriptSig.push_back(bi.extranonce); - txCoinbase.vin[0].scriptSig.push_back(::ChainActive().Height()); + txCoinbase.vin[0].scriptSig.push_back(m_node.chainman->ActiveChain().Height()); txCoinbase.vout.resize(1); // Ignore the (optional) segwit commitment added by CreateNewBlock (as the hardcoded nonces don't account for this) txCoinbase.vout[0].scriptPubKey = CScript(); pblock->vtx[0] = MakeTransactionRef(std::move(txCoinbase)); if (txFirst.size() == 0) - baseheight = ::ChainActive().Height(); + baseheight = m_node.chainman->ActiveChain().Height(); if (txFirst.size() < 4) txFirst.push_back(pblock->vtx[0]); pblock->hashMerkleRoot = BlockMerkleRoot(*pblock); @@ -364,29 +364,29 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) m_node.mempool->clear(); // subsidy changing - int nHeight = ::ChainActive().Height(); + int nHeight = m_node.chainman->ActiveChain().Height(); // Create an actual 209999-long block chain (without valid blocks). - while (::ChainActive().Tip()->nHeight < 209999) { - CBlockIndex* prev = ::ChainActive().Tip(); + while (m_node.chainman->ActiveChain().Tip()->nHeight < 209999) { + CBlockIndex* prev = m_node.chainman->ActiveChain().Tip(); CBlockIndex* next = new CBlockIndex(); next->phashBlock = new uint256(InsecureRand256()); - ::ChainstateActive().CoinsTip().SetBestBlock(next->GetBlockHash()); + m_node.chainman->ActiveChainstate().CoinsTip().SetBestBlock(next->GetBlockHash()); next->pprev = prev; next->nHeight = prev->nHeight + 1; next->BuildSkip(); - ::ChainActive().SetTip(next); + m_node.chainman->ActiveChain().SetTip(next); } BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); // Extend to a 210000-long block chain. - while (::ChainActive().Tip()->nHeight < 210000) { - CBlockIndex* prev = ::ChainActive().Tip(); + while (m_node.chainman->ActiveChain().Tip()->nHeight < 210000) { + CBlockIndex* prev = m_node.chainman->ActiveChain().Tip(); CBlockIndex* next = new CBlockIndex(); next->phashBlock = new uint256(InsecureRand256()); - ::ChainstateActive().CoinsTip().SetBestBlock(next->GetBlockHash()); + m_node.chainman->ActiveChainstate().CoinsTip().SetBestBlock(next->GetBlockHash()); next->pprev = prev; next->nHeight = prev->nHeight + 1; next->BuildSkip(); - ::ChainActive().SetTip(next); + m_node.chainman->ActiveChain().SetTip(next); } BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); @@ -409,16 +409,16 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) m_node.mempool->clear(); // Delete the dummy blocks again. - while (::ChainActive().Tip()->nHeight > nHeight) { - CBlockIndex* del = ::ChainActive().Tip(); - ::ChainActive().SetTip(del->pprev); - ::ChainstateActive().CoinsTip().SetBestBlock(del->pprev->GetBlockHash()); + while (m_node.chainman->ActiveChain().Tip()->nHeight > nHeight) { + CBlockIndex* del = m_node.chainman->ActiveChain().Tip(); + m_node.chainman->ActiveChain().SetTip(del->pprev); + m_node.chainman->ActiveChainstate().CoinsTip().SetBestBlock(del->pprev->GetBlockHash()); delete del->phashBlock; delete del; } // non-final txs in mempool - SetMockTime(::ChainActive().Tip()->GetMedianTimePast()+1); + SetMockTime(m_node.chainman->ActiveChain().Tip()->GetMedianTimePast()+1); int flags = LOCKTIME_VERIFY_SEQUENCE|LOCKTIME_MEDIAN_TIME_PAST; // height map std::vector<int> prevheights; @@ -430,7 +430,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) tx.vin[0].prevout.hash = txFirst[0]->GetHash(); // only 1 transaction tx.vin[0].prevout.n = 0; tx.vin[0].scriptSig = CScript() << OP_1; - tx.vin[0].nSequence = ::ChainActive().Tip()->nHeight + 1; // txFirst[0] is the 2nd block + tx.vin[0].nSequence = m_node.chainman->ActiveChain().Tip()->nHeight + 1; // txFirst[0] is the 2nd block prevheights[0] = baseheight + 1; tx.vout.resize(1); tx.vout[0].nValue = BLOCKSUBSIDY-HIGHFEE; @@ -438,53 +438,62 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) tx.nLockTime = 0; hash = tx.GetHash(); m_node.mempool->addUnchecked(entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); - BOOST_CHECK(CheckFinalTx(::ChainActive().Tip(), CTransaction(tx), flags)); // Locktime passes + BOOST_CHECK(CheckFinalTx(m_node.chainman->ActiveChain().Tip(), CTransaction(tx), flags)); // Locktime passes BOOST_CHECK(!TestSequenceLocks(CTransaction(tx), flags)); // Sequence locks fail - BOOST_CHECK(SequenceLocks(CTransaction(tx), flags, prevheights, CreateBlockIndex(::ChainActive().Tip()->nHeight + 2))); // Sequence locks pass on 2nd block + + { + CBlockIndex* active_chain_tip = m_node.chainman->ActiveChain().Tip(); + BOOST_CHECK(SequenceLocks(CTransaction(tx), flags, prevheights, CreateBlockIndex(active_chain_tip->nHeight + 2, active_chain_tip))); // Sequence locks pass on 2nd block + } // relative time locked tx.vin[0].prevout.hash = txFirst[1]->GetHash(); - tx.vin[0].nSequence = CTxIn::SEQUENCE_LOCKTIME_TYPE_FLAG | (((::ChainActive().Tip()->GetMedianTimePast()+1-::ChainActive()[1]->GetMedianTimePast()) >> CTxIn::SEQUENCE_LOCKTIME_GRANULARITY) + 1); // txFirst[1] is the 3rd block + tx.vin[0].nSequence = CTxIn::SEQUENCE_LOCKTIME_TYPE_FLAG | (((m_node.chainman->ActiveChain().Tip()->GetMedianTimePast()+1-m_node.chainman->ActiveChain()[1]->GetMedianTimePast()) >> CTxIn::SEQUENCE_LOCKTIME_GRANULARITY) + 1); // txFirst[1] is the 3rd block prevheights[0] = baseheight + 2; hash = tx.GetHash(); m_node.mempool->addUnchecked(entry.Time(GetTime()).FromTx(tx)); - BOOST_CHECK(CheckFinalTx(::ChainActive().Tip(), CTransaction(tx), flags)); // Locktime passes + BOOST_CHECK(CheckFinalTx(m_node.chainman->ActiveChain().Tip(), CTransaction(tx), flags)); // Locktime passes BOOST_CHECK(!TestSequenceLocks(CTransaction(tx), flags)); // Sequence locks fail for (int i = 0; i < CBlockIndex::nMedianTimeSpan; i++) - ::ChainActive().Tip()->GetAncestor(::ChainActive().Tip()->nHeight - i)->nTime += 512; //Trick the MedianTimePast - BOOST_CHECK(SequenceLocks(CTransaction(tx), flags, prevheights, CreateBlockIndex(::ChainActive().Tip()->nHeight + 1))); // Sequence locks pass 512 seconds later + m_node.chainman->ActiveChain().Tip()->GetAncestor(m_node.chainman->ActiveChain().Tip()->nHeight - i)->nTime += 512; //Trick the MedianTimePast + + { + CBlockIndex* active_chain_tip = m_node.chainman->ActiveChain().Tip(); + BOOST_CHECK(SequenceLocks(CTransaction(tx), flags, prevheights, CreateBlockIndex(active_chain_tip->nHeight + 1, active_chain_tip))); // Sequence locks pass 512 seconds later + } + for (int i = 0; i < CBlockIndex::nMedianTimeSpan; i++) - ::ChainActive().Tip()->GetAncestor(::ChainActive().Tip()->nHeight - i)->nTime -= 512; //undo tricked MTP + m_node.chainman->ActiveChain().Tip()->GetAncestor(m_node.chainman->ActiveChain().Tip()->nHeight - i)->nTime -= 512; //undo tricked MTP // absolute height locked tx.vin[0].prevout.hash = txFirst[2]->GetHash(); tx.vin[0].nSequence = CTxIn::SEQUENCE_FINAL - 1; prevheights[0] = baseheight + 3; - tx.nLockTime = ::ChainActive().Tip()->nHeight + 1; + tx.nLockTime = m_node.chainman->ActiveChain().Tip()->nHeight + 1; hash = tx.GetHash(); m_node.mempool->addUnchecked(entry.Time(GetTime()).FromTx(tx)); - BOOST_CHECK(!CheckFinalTx(::ChainActive().Tip(), CTransaction(tx), flags)); // Locktime fails + BOOST_CHECK(!CheckFinalTx(m_node.chainman->ActiveChain().Tip(), CTransaction(tx), flags)); // Locktime fails BOOST_CHECK(TestSequenceLocks(CTransaction(tx), flags)); // Sequence locks pass - BOOST_CHECK(IsFinalTx(CTransaction(tx), ::ChainActive().Tip()->nHeight + 2, ::ChainActive().Tip()->GetMedianTimePast())); // Locktime passes on 2nd block + BOOST_CHECK(IsFinalTx(CTransaction(tx), m_node.chainman->ActiveChain().Tip()->nHeight + 2, m_node.chainman->ActiveChain().Tip()->GetMedianTimePast())); // Locktime passes on 2nd block // absolute time locked tx.vin[0].prevout.hash = txFirst[3]->GetHash(); - tx.nLockTime = ::ChainActive().Tip()->GetMedianTimePast(); + tx.nLockTime = m_node.chainman->ActiveChain().Tip()->GetMedianTimePast(); prevheights.resize(1); prevheights[0] = baseheight + 4; hash = tx.GetHash(); m_node.mempool->addUnchecked(entry.Time(GetTime()).FromTx(tx)); - BOOST_CHECK(!CheckFinalTx(::ChainActive().Tip(), CTransaction(tx), flags)); // Locktime fails + BOOST_CHECK(!CheckFinalTx(m_node.chainman->ActiveChain().Tip(), CTransaction(tx), flags)); // Locktime fails BOOST_CHECK(TestSequenceLocks(CTransaction(tx), flags)); // Sequence locks pass - BOOST_CHECK(IsFinalTx(CTransaction(tx), ::ChainActive().Tip()->nHeight + 2, ::ChainActive().Tip()->GetMedianTimePast() + 1)); // Locktime passes 1 second later + BOOST_CHECK(IsFinalTx(CTransaction(tx), m_node.chainman->ActiveChain().Tip()->nHeight + 2, m_node.chainman->ActiveChain().Tip()->GetMedianTimePast() + 1)); // Locktime passes 1 second later // mempool-dependent transactions (not added) tx.vin[0].prevout.hash = hash; - prevheights[0] = ::ChainActive().Tip()->nHeight + 1; + prevheights[0] = m_node.chainman->ActiveChain().Tip()->nHeight + 1; tx.nLockTime = 0; tx.vin[0].nSequence = 0; - BOOST_CHECK(CheckFinalTx(::ChainActive().Tip(), CTransaction(tx), flags)); // Locktime passes + BOOST_CHECK(CheckFinalTx(m_node.chainman->ActiveChain().Tip(), CTransaction(tx), flags)); // Locktime passes BOOST_CHECK(TestSequenceLocks(CTransaction(tx), flags)); // Sequence locks pass tx.vin[0].nSequence = 1; BOOST_CHECK(!TestSequenceLocks(CTransaction(tx), flags)); // Sequence locks fail @@ -502,14 +511,14 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) BOOST_CHECK_EQUAL(pblocktemplate->block.vtx.size(), 3U); // However if we advance height by 1 and time by 512, all of them should be mined for (int i = 0; i < CBlockIndex::nMedianTimeSpan; i++) - ::ChainActive().Tip()->GetAncestor(::ChainActive().Tip()->nHeight - i)->nTime += 512; //Trick the MedianTimePast - ::ChainActive().Tip()->nHeight++; - SetMockTime(::ChainActive().Tip()->GetMedianTimePast() + 1); + m_node.chainman->ActiveChain().Tip()->GetAncestor(m_node.chainman->ActiveChain().Tip()->nHeight - i)->nTime += 512; //Trick the MedianTimePast + m_node.chainman->ActiveChain().Tip()->nHeight++; + SetMockTime(m_node.chainman->ActiveChain().Tip()->GetMedianTimePast() + 1); BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); BOOST_CHECK_EQUAL(pblocktemplate->block.vtx.size(), 5U); - ::ChainActive().Tip()->nHeight--; + m_node.chainman->ActiveChain().Tip()->nHeight--; SetMockTime(0); m_node.mempool->clear(); diff --git a/src/test/net_peer_eviction_tests.cpp b/src/test/net_peer_eviction_tests.cpp index 31d391bf7d..4bfd487b86 100644 --- a/src/test/net_peer_eviction_tests.cpp +++ b/src/test/net_peer_eviction_tests.cpp @@ -2,7 +2,9 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <netaddress.h> #include <net.h> +#include <test/util/net.h> #include <test/util/setup_common.h> #include <boost/test/unit_test.hpp> @@ -15,11 +17,6 @@ BOOST_FIXTURE_TEST_SUITE(net_peer_eviction_tests, BasicTestingSetup) -namespace { -constexpr int NODE_EVICTION_TEST_ROUNDS{10}; -constexpr int NODE_EVICTION_TEST_UP_TO_N_NODES{200}; -} // namespace - std::vector<NodeEvictionCandidate> GetRandomNodeEvictionCandidates(const int n_candidates, FastRandomContext& random_context) { std::vector<NodeEvictionCandidate> candidates; @@ -36,7 +33,7 @@ std::vector<NodeEvictionCandidate> GetRandomNodeEvictionCandidates(const int n_c /* nKeyedNetGroup */ random_context.randrange(100), /* prefer_evict */ random_context.randbool(), /* m_is_local */ random_context.randbool(), - /* m_is_onion */ random_context.randbool(), + /* m_network */ ALL_NETWORKS[random_context.randrange(ALL_NETWORKS.size())], }); } return candidates; @@ -94,7 +91,8 @@ BOOST_AUTO_TEST_CASE(peer_protection_test) BOOST_CHECK(IsProtected( num_peers, [](NodeEvictionCandidate& c) { c.nTimeConnected = c.id; - c.m_is_onion = c.m_is_local = false; + c.m_is_local = false; + c.m_network = NET_IPV4; }, /* protected_peer_ids */ {0, 1, 2, 3, 4, 5}, /* unprotected_peer_ids */ {6, 7, 8, 9, 10, 11}, @@ -104,129 +102,359 @@ BOOST_AUTO_TEST_CASE(peer_protection_test) BOOST_CHECK(IsProtected( num_peers, [num_peers](NodeEvictionCandidate& c) { c.nTimeConnected = num_peers - c.id; - c.m_is_onion = c.m_is_local = false; + c.m_is_local = false; + c.m_network = NET_IPV6; }, /* protected_peer_ids */ {6, 7, 8, 9, 10, 11}, /* unprotected_peer_ids */ {0, 1, 2, 3, 4, 5}, random_context)); - // Test protection of onion and localhost peers... + // Test protection of onion, localhost, and I2P peers... // Expect 1/4 onion peers to be protected from eviction, - // independently of other characteristics. + // if no localhost or I2P peers. BOOST_CHECK(IsProtected( num_peers, [](NodeEvictionCandidate& c) { - c.m_is_onion = (c.id == 3 || c.id == 8 || c.id == 9); + c.m_is_local = false; + c.m_network = (c.id == 3 || c.id == 8 || c.id == 9) ? NET_ONION : NET_IPV4; }, /* protected_peer_ids */ {3, 8, 9}, /* unprotected_peer_ids */ {}, random_context)); - // Expect 1/4 onion peers and 1/4 of the others to be protected - // from eviction, sorted by longest uptime (lowest nTimeConnected). + // Expect 1/4 onion peers and 1/4 of the other peers to be protected, + // sorted by longest uptime (lowest nTimeConnected), if no localhost or I2P peers. BOOST_CHECK(IsProtected( num_peers, [](NodeEvictionCandidate& c) { c.nTimeConnected = c.id; c.m_is_local = false; - c.m_is_onion = (c.id == 3 || c.id > 7); + c.m_network = (c.id == 3 || c.id > 7) ? NET_ONION : NET_IPV6; }, /* protected_peer_ids */ {0, 1, 2, 3, 8, 9}, /* unprotected_peer_ids */ {4, 5, 6, 7, 10, 11}, random_context)); // Expect 1/4 localhost peers to be protected from eviction, - // if no onion peers. + // if no onion or I2P peers. BOOST_CHECK(IsProtected( num_peers, [](NodeEvictionCandidate& c) { - c.m_is_onion = false; c.m_is_local = (c.id == 1 || c.id == 9 || c.id == 11); + c.m_network = NET_IPV4; }, /* protected_peer_ids */ {1, 9, 11}, /* unprotected_peer_ids */ {}, random_context)); // Expect 1/4 localhost peers and 1/4 of the other peers to be protected, - // sorted by longest uptime (lowest nTimeConnected), if no onion peers. + // sorted by longest uptime (lowest nTimeConnected), if no onion or I2P peers. BOOST_CHECK(IsProtected( num_peers, [](NodeEvictionCandidate& c) { c.nTimeConnected = c.id; - c.m_is_onion = false; c.m_is_local = (c.id > 6); + c.m_network = NET_IPV6; }, /* protected_peer_ids */ {0, 1, 2, 7, 8, 9}, /* unprotected_peer_ids */ {3, 4, 5, 6, 10, 11}, random_context)); - // Combined test: expect 1/4 onion and 2 localhost peers to be protected - // from eviction, sorted by longest uptime. + // Expect 1/4 I2P peers to be protected from eviction, + // if no onion or localhost peers. + BOOST_CHECK(IsProtected( + num_peers, [](NodeEvictionCandidate& c) { + c.m_is_local = false; + c.m_network = (c.id == 2 || c.id == 7 || c.id == 10) ? NET_I2P : NET_IPV4; + }, + /* protected_peer_ids */ {2, 7, 10}, + /* unprotected_peer_ids */ {}, + random_context)); + + // Expect 1/4 I2P peers and 1/4 of the other peers to be protected, + // sorted by longest uptime (lowest nTimeConnected), if no onion or localhost peers. BOOST_CHECK(IsProtected( num_peers, [](NodeEvictionCandidate& c) { c.nTimeConnected = c.id; - c.m_is_onion = (c.id == 0 || c.id == 5 || c.id == 10); - c.m_is_local = (c.id == 1 || c.id == 9 || c.id == 11); + c.m_is_local = false; + c.m_network = (c.id == 4 || c.id > 8) ? NET_I2P : NET_IPV6; }, - /* protected_peer_ids */ {0, 1, 2, 5, 9, 10}, - /* unprotected_peer_ids */ {3, 4, 6, 7, 8, 11}, + /* protected_peer_ids */ {0, 1, 2, 4, 9, 10}, + /* unprotected_peer_ids */ {3, 5, 6, 7, 8, 11}, random_context)); - // Combined test: expect having only 1 onion to allow allocating the - // remaining 2 of the 1/4 to localhost peers, sorted by longest uptime. + // Tests with 2 networks... + + // Combined test: expect having 1 localhost and 1 onion peer out of 4 to + // protect 1 localhost, 0 onion and 1 other peer, sorted by longest uptime; + // stable sort breaks tie with array order of localhost first. BOOST_CHECK(IsProtected( - num_peers + 4, [](NodeEvictionCandidate& c) { + 4, [](NodeEvictionCandidate& c) { c.nTimeConnected = c.id; - c.m_is_onion = (c.id == 15); - c.m_is_local = (c.id > 6 && c.id < 11); + c.m_is_local = (c.id == 4); + c.m_network = (c.id == 3) ? NET_ONION : NET_IPV4; }, - /* protected_peer_ids */ {0, 1, 2, 3, 7, 8, 9, 15}, - /* unprotected_peer_ids */ {4, 5, 6, 10, 11, 12, 13, 14}, + /* protected_peer_ids */ {0, 4}, + /* unprotected_peer_ids */ {1, 2}, + random_context)); + + // Combined test: expect having 1 localhost and 1 onion peer out of 7 to + // protect 1 localhost, 0 onion, and 2 other peers (3 total), sorted by + // uptime; stable sort breaks tie with array order of localhost first. + BOOST_CHECK(IsProtected( + 7, [](NodeEvictionCandidate& c) { + c.nTimeConnected = c.id; + c.m_is_local = (c.id == 6); + c.m_network = (c.id == 5) ? NET_ONION : NET_IPV4; + }, + /* protected_peer_ids */ {0, 1, 6}, + /* unprotected_peer_ids */ {2, 3, 4, 5}, + random_context)); + + // Combined test: expect having 1 localhost and 1 onion peer out of 8 to + // protect protect 1 localhost, 1 onion and 2 other peers (4 total), sorted + // by uptime; stable sort breaks tie with array order of localhost first. + BOOST_CHECK(IsProtected( + 8, [](NodeEvictionCandidate& c) { + c.nTimeConnected = c.id; + c.m_is_local = (c.id == 6); + c.m_network = (c.id == 5) ? NET_ONION : NET_IPV4; + }, + /* protected_peer_ids */ {0, 1, 5, 6}, + /* unprotected_peer_ids */ {2, 3, 4, 7}, random_context)); - // Combined test: expect 2 onions (< 1/4) to allow allocating the minimum 2 - // localhost peers, sorted by longest uptime. + // Combined test: expect having 3 localhost and 3 onion peers out of 12 to + // protect 2 localhost and 1 onion, plus 3 other peers, sorted by longest + // uptime; stable sort breaks ties with the array order of localhost first. BOOST_CHECK(IsProtected( num_peers, [](NodeEvictionCandidate& c) { c.nTimeConnected = c.id; - c.m_is_onion = (c.id == 7 || c.id == 9); - c.m_is_local = (c.id == 6 || c.id == 11); + c.m_is_local = (c.id == 6 || c.id == 9 || c.id == 11); + c.m_network = (c.id == 7 || c.id == 8 || c.id == 10) ? NET_ONION : NET_IPV6; }, - /* protected_peer_ids */ {0, 1, 6, 7, 9, 11}, - /* unprotected_peer_ids */ {2, 3, 4, 5, 8, 10}, + /* protected_peer_ids */ {0, 1, 2, 6, 7, 9}, + /* unprotected_peer_ids */ {3, 4, 5, 8, 10, 11}, random_context)); - // Combined test: when > 1/4, expect max 1/4 onion and 2 localhost peers - // to be protected from eviction, sorted by longest uptime. + // Combined test: expect having 4 localhost and 1 onion peer out of 12 to + // protect 2 localhost and 1 onion, plus 3 other peers, sorted by longest uptime. BOOST_CHECK(IsProtected( num_peers, [](NodeEvictionCandidate& c) { c.nTimeConnected = c.id; - c.m_is_onion = (c.id > 3 && c.id < 8); - c.m_is_local = (c.id > 7); + c.m_is_local = (c.id > 4 && c.id < 9); + c.m_network = (c.id == 10) ? NET_ONION : NET_IPV4; }, - /* protected_peer_ids */ {0, 4, 5, 6, 8, 9}, - /* unprotected_peer_ids */ {1, 2, 3, 7, 10, 11}, + /* protected_peer_ids */ {0, 1, 2, 5, 6, 10}, + /* unprotected_peer_ids */ {3, 4, 7, 8, 9, 11}, random_context)); - // Combined test: idem > 1/4 with only 8 peers: expect 2 onion and 2 - // localhost peers (1/4 + 2) to be protected, sorted by longest uptime. + // Combined test: expect having 4 localhost and 2 onion peers out of 16 to + // protect 2 localhost and 2 onions, plus 4 other peers, sorted by longest uptime. BOOST_CHECK(IsProtected( - 8, [](NodeEvictionCandidate& c) { + 16, [](NodeEvictionCandidate& c) { + c.nTimeConnected = c.id; + c.m_is_local = (c.id == 6 || c.id == 9 || c.id == 11 || c.id == 12); + c.m_network = (c.id == 8 || c.id == 10) ? NET_ONION : NET_IPV6; + }, + /* protected_peer_ids */ {0, 1, 2, 3, 6, 8, 9, 10}, + /* unprotected_peer_ids */ {4, 5, 7, 11, 12, 13, 14, 15}, + random_context)); + + // Combined test: expect having 5 localhost and 1 onion peer out of 16 to + // protect 3 localhost (recovering the unused onion slot), 1 onion, and 4 + // others, sorted by longest uptime. + BOOST_CHECK(IsProtected( + 16, [](NodeEvictionCandidate& c) { + c.nTimeConnected = c.id; + c.m_is_local = (c.id > 10); + c.m_network = (c.id == 10) ? NET_ONION : NET_IPV4; + }, + /* protected_peer_ids */ {0, 1, 2, 3, 10, 11, 12, 13}, + /* unprotected_peer_ids */ {4, 5, 6, 7, 8, 9, 14, 15}, + random_context)); + + // Combined test: expect having 1 localhost and 4 onion peers out of 16 to + // protect 1 localhost and 3 onions (recovering the unused localhost slot), + // plus 4 others, sorted by longest uptime. + BOOST_CHECK(IsProtected( + 16, [](NodeEvictionCandidate& c) { + c.nTimeConnected = c.id; + c.m_is_local = (c.id == 15); + c.m_network = (c.id > 6 && c.id < 11) ? NET_ONION : NET_IPV6; + }, + /* protected_peer_ids */ {0, 1, 2, 3, 7, 8, 9, 15}, + /* unprotected_peer_ids */ {5, 6, 10, 11, 12, 13, 14}, + random_context)); + + // Combined test: expect having 2 onion and 4 I2P out of 12 peers to protect + // 2 onion (prioritized for having fewer candidates) and 1 I2P, plus 3 + // others, sorted by longest uptime. + BOOST_CHECK(IsProtected( + num_peers, [](NodeEvictionCandidate& c) { c.nTimeConnected = c.id; - c.m_is_onion = (c.id > 1 && c.id < 5); - c.m_is_local = (c.id > 4); + c.m_is_local = false; + if (c.id == 8 || c.id == 10) { + c.m_network = NET_ONION; + } else if (c.id == 6 || c.id == 9 || c.id == 11 || c.id == 12) { + c.m_network = NET_I2P; + } else { + c.m_network = NET_IPV4; + } }, - /* protected_peer_ids */ {2, 3, 5, 6}, - /* unprotected_peer_ids */ {0, 1, 4, 7}, + /* protected_peer_ids */ {0, 1, 2, 6, 8, 10}, + /* unprotected_peer_ids */ {3, 4, 5, 7, 9, 11}, random_context)); - // Combined test: idem > 1/4 with only 6 peers: expect 1 onion peer and no - // localhost peers (1/4 + 0) to be protected, sorted by longest uptime. + // Tests with 3 networks... + + // Combined test: expect having 1 localhost, 1 I2P and 1 onion peer out of 4 + // to protect 1 I2P, 0 localhost, 0 onion and 1 other peer (2 total), sorted + // by longest uptime; stable sort breaks tie with array order of I2P first. BOOST_CHECK(IsProtected( - 6, [](NodeEvictionCandidate& c) { + 4, [](NodeEvictionCandidate& c) { c.nTimeConnected = c.id; - c.m_is_onion = (c.id == 4 || c.id == 5); c.m_is_local = (c.id == 3); + if (c.id == 4) { + c.m_network = NET_I2P; + } else if (c.id == 2) { + c.m_network = NET_ONION; + } else { + c.m_network = NET_IPV6; + } }, - /* protected_peer_ids */ {0, 1, 4}, - /* unprotected_peer_ids */ {2, 3, 5}, + /* protected_peer_ids */ {0, 4}, + /* unprotected_peer_ids */ {1, 2}, + random_context)); + + // Combined test: expect having 1 localhost, 1 I2P and 1 onion peer out of 7 + // to protect 1 I2P, 0 localhost, 0 onion and 2 other peers (3 total) sorted + // by longest uptime; stable sort breaks tie with array order of I2P first. + BOOST_CHECK(IsProtected( + 7, [](NodeEvictionCandidate& c) { + c.nTimeConnected = c.id; + c.m_is_local = (c.id == 4); + if (c.id == 6) { + c.m_network = NET_I2P; + } else if (c.id == 5) { + c.m_network = NET_ONION; + } else { + c.m_network = NET_IPV6; + } + }, + /* protected_peer_ids */ {0, 1, 6}, + /* unprotected_peer_ids */ {2, 3, 4, 5}, + random_context)); + + // Combined test: expect having 1 localhost, 1 I2P and 1 onion peer out of 8 + // to protect 1 I2P, 1 localhost, 0 onion and 2 other peers (4 total) sorted + // by uptime; stable sort breaks tie with array order of I2P then localhost. + BOOST_CHECK(IsProtected( + 8, [](NodeEvictionCandidate& c) { + c.nTimeConnected = c.id; + c.m_is_local = (c.id == 6); + if (c.id == 5) { + c.m_network = NET_I2P; + } else if (c.id == 4) { + c.m_network = NET_ONION; + } else { + c.m_network = NET_IPV6; + } + }, + /* protected_peer_ids */ {0, 1, 5, 6}, + /* unprotected_peer_ids */ {2, 3, 4, 7}, + random_context)); + + // Combined test: expect having 4 localhost, 2 I2P, and 2 onion peers out of + // 16 to protect 1 localhost, 2 I2P, and 1 onion (4/16 total), plus 4 others + // for 8 total, sorted by longest uptime. + BOOST_CHECK(IsProtected( + 16, [](NodeEvictionCandidate& c) { + c.nTimeConnected = c.id; + c.m_is_local = (c.id == 6 || c.id > 11); + if (c.id == 7 || c.id == 11) { + c.m_network = NET_I2P; + } else if (c.id == 9 || c.id == 10) { + c.m_network = NET_ONION; + } else { + c.m_network = NET_IPV4; + } + }, + /* protected_peer_ids */ {0, 1, 2, 3, 6, 7, 9, 11}, + /* unprotected_peer_ids */ {4, 5, 8, 10, 12, 13, 14, 15}, + random_context)); + + // Combined test: expect having 1 localhost, 8 I2P and 1 onion peer out of + // 24 to protect 1, 4, and 1 (6 total), plus 6 others for 12/24 total, + // sorted by longest uptime. + BOOST_CHECK(IsProtected( + 24, [](NodeEvictionCandidate& c) { + c.nTimeConnected = c.id; + c.m_is_local = (c.id == 12); + if (c.id > 14 && c.id < 23) { // 4 protected instead of usual 2 + c.m_network = NET_I2P; + } else if (c.id == 23) { + c.m_network = NET_ONION; + } else { + c.m_network = NET_IPV6; + } + }, + /* protected_peer_ids */ {0, 1, 2, 3, 4, 5, 12, 15, 16, 17, 18, 23}, + /* unprotected_peer_ids */ {6, 7, 8, 9, 10, 11, 13, 14, 19, 20, 21, 22}, + random_context)); + + // Combined test: expect having 1 localhost, 3 I2P and 6 onion peers out of + // 24 to protect 1, 3, and 2 (6 total, I2P has fewer candidates and so gets the + // unused localhost slot), plus 6 others for 12/24 total, sorted by longest uptime. + BOOST_CHECK(IsProtected( + 24, [](NodeEvictionCandidate& c) { + c.nTimeConnected = c.id; + c.m_is_local = (c.id == 15); + if (c.id == 12 || c.id == 14 || c.id == 17) { + c.m_network = NET_I2P; + } else if (c.id > 17) { // 4 protected instead of usual 2 + c.m_network = NET_ONION; + } else { + c.m_network = NET_IPV4; + } + }, + /* protected_peer_ids */ {0, 1, 2, 3, 4, 5, 12, 14, 15, 17, 18, 19}, + /* unprotected_peer_ids */ {6, 7, 8, 9, 10, 11, 13, 16, 20, 21, 22, 23}, + random_context)); + + // Combined test: expect having 1 localhost, 7 I2P and 4 onion peers out of + // 24 to protect 1 localhost, 2 I2P, and 3 onions (6 total), plus 6 others + // for 12/24 total, sorted by longest uptime. + BOOST_CHECK(IsProtected( + 24, [](NodeEvictionCandidate& c) { + c.nTimeConnected = c.id; + c.m_is_local = (c.id == 13); + if (c.id > 16) { + c.m_network = NET_I2P; + } else if (c.id == 12 || c.id == 14 || c.id == 15 || c.id == 16) { + c.m_network = NET_ONION; + } else { + c.m_network = NET_IPV6; + } + }, + /* protected_peer_ids */ {0, 1, 2, 3, 4, 5, 12, 13, 14, 15, 17, 18}, + /* unprotected_peer_ids */ {6, 7, 8, 9, 10, 11, 16, 19, 20, 21, 22, 23}, + random_context)); + + // Combined test: expect having 8 localhost, 4 I2P, and 3 onion peers out of + // 24 to protect 2 of each (6 total), plus 6 others for 12/24 total, sorted + // by longest uptime. + BOOST_CHECK(IsProtected( + 24, [](NodeEvictionCandidate& c) { + c.nTimeConnected = c.id; + c.m_is_local = (c.id > 15); + if (c.id > 10 && c.id < 15) { + c.m_network = NET_I2P; + } else if (c.id > 6 && c.id < 10) { + c.m_network = NET_ONION; + } else { + c.m_network = NET_IPV4; + } + }, + /* protected_peer_ids */ {0, 1, 2, 3, 4, 5, 7, 8, 11, 12, 16, 17}, + /* unprotected_peer_ids */ {6, 9, 10, 13, 14, 15, 18, 19, 20, 21, 22, 23}, random_context)); } @@ -257,91 +485,89 @@ BOOST_AUTO_TEST_CASE(peer_eviction_test) { FastRandomContext random_context{true}; - for (int i = 0; i < NODE_EVICTION_TEST_ROUNDS; ++i) { - for (int number_of_nodes = 0; number_of_nodes < NODE_EVICTION_TEST_UP_TO_N_NODES; ++number_of_nodes) { - // Four nodes with the highest keyed netgroup values should be - // protected from eviction. - BOOST_CHECK(!IsEvicted( - number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) { - candidate.nKeyedNetGroup = number_of_nodes - candidate.id; - }, - {0, 1, 2, 3}, random_context)); - - // Eight nodes with the lowest minimum ping time should be protected - // from eviction. - BOOST_CHECK(!IsEvicted( - number_of_nodes, [](NodeEvictionCandidate& candidate) { - candidate.m_min_ping_time = std::chrono::microseconds{candidate.id}; - }, - {0, 1, 2, 3, 4, 5, 6, 7}, random_context)); - - // Four nodes that most recently sent us novel transactions accepted - // into our mempool should be protected from eviction. - BOOST_CHECK(!IsEvicted( - number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) { - candidate.nLastTXTime = number_of_nodes - candidate.id; - }, - {0, 1, 2, 3}, random_context)); - - // Up to eight non-tx-relay peers that most recently sent us novel - // blocks should be protected from eviction. - BOOST_CHECK(!IsEvicted( - number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) { - candidate.nLastBlockTime = number_of_nodes - candidate.id; - if (candidate.id <= 7) { - candidate.fRelayTxes = false; - candidate.fRelevantServices = true; - } - }, - {0, 1, 2, 3, 4, 5, 6, 7}, random_context)); - - // Four peers that most recently sent us novel blocks should be - // protected from eviction. - BOOST_CHECK(!IsEvicted( - number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) { - candidate.nLastBlockTime = number_of_nodes - candidate.id; - }, - {0, 1, 2, 3}, random_context)); - - // Combination of the previous two tests. - BOOST_CHECK(!IsEvicted( - number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) { - candidate.nLastBlockTime = number_of_nodes - candidate.id; - if (candidate.id <= 7) { - candidate.fRelayTxes = false; - candidate.fRelevantServices = true; - } - }, - {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, random_context)); - - // Combination of all tests above. - BOOST_CHECK(!IsEvicted( - number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) { - candidate.nKeyedNetGroup = number_of_nodes - candidate.id; // 4 protected - candidate.m_min_ping_time = std::chrono::microseconds{candidate.id}; // 8 protected - candidate.nLastTXTime = number_of_nodes - candidate.id; // 4 protected - candidate.nLastBlockTime = number_of_nodes - candidate.id; // 4 protected - }, - {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}, random_context)); - - // An eviction is expected given >= 29 random eviction candidates. The eviction logic protects at most - // four peers by net group, eight by lowest ping time, four by last time of novel tx, up to eight non-tx-relay - // peers by last novel block time, and four more peers by last novel block time. - if (number_of_nodes >= 29) { - BOOST_CHECK(SelectNodeToEvict(GetRandomNodeEvictionCandidates(number_of_nodes, random_context))); - } - - // No eviction is expected given <= 20 random eviction candidates. The eviction logic protects at least - // four peers by net group, eight by lowest ping time, four by last time of novel tx and four peers by last - // novel block time. - if (number_of_nodes <= 20) { - BOOST_CHECK(!SelectNodeToEvict(GetRandomNodeEvictionCandidates(number_of_nodes, random_context))); - } + for (int number_of_nodes = 0; number_of_nodes < 200; ++number_of_nodes) { + // Four nodes with the highest keyed netgroup values should be + // protected from eviction. + BOOST_CHECK(!IsEvicted( + number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) { + candidate.nKeyedNetGroup = number_of_nodes - candidate.id; + }, + {0, 1, 2, 3}, random_context)); + + // Eight nodes with the lowest minimum ping time should be protected + // from eviction. + BOOST_CHECK(!IsEvicted( + number_of_nodes, [](NodeEvictionCandidate& candidate) { + candidate.m_min_ping_time = std::chrono::microseconds{candidate.id}; + }, + {0, 1, 2, 3, 4, 5, 6, 7}, random_context)); + + // Four nodes that most recently sent us novel transactions accepted + // into our mempool should be protected from eviction. + BOOST_CHECK(!IsEvicted( + number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) { + candidate.nLastTXTime = number_of_nodes - candidate.id; + }, + {0, 1, 2, 3}, random_context)); + + // Up to eight non-tx-relay peers that most recently sent us novel + // blocks should be protected from eviction. + BOOST_CHECK(!IsEvicted( + number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) { + candidate.nLastBlockTime = number_of_nodes - candidate.id; + if (candidate.id <= 7) { + candidate.fRelayTxes = false; + candidate.fRelevantServices = true; + } + }, + {0, 1, 2, 3, 4, 5, 6, 7}, random_context)); + + // Four peers that most recently sent us novel blocks should be + // protected from eviction. + BOOST_CHECK(!IsEvicted( + number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) { + candidate.nLastBlockTime = number_of_nodes - candidate.id; + }, + {0, 1, 2, 3}, random_context)); + + // Combination of the previous two tests. + BOOST_CHECK(!IsEvicted( + number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) { + candidate.nLastBlockTime = number_of_nodes - candidate.id; + if (candidate.id <= 7) { + candidate.fRelayTxes = false; + candidate.fRelevantServices = true; + } + }, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, random_context)); + + // Combination of all tests above. + BOOST_CHECK(!IsEvicted( + number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) { + candidate.nKeyedNetGroup = number_of_nodes - candidate.id; // 4 protected + candidate.m_min_ping_time = std::chrono::microseconds{candidate.id}; // 8 protected + candidate.nLastTXTime = number_of_nodes - candidate.id; // 4 protected + candidate.nLastBlockTime = number_of_nodes - candidate.id; // 4 protected + }, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}, random_context)); + + // An eviction is expected given >= 29 random eviction candidates. The eviction logic protects at most + // four peers by net group, eight by lowest ping time, four by last time of novel tx, up to eight non-tx-relay + // peers by last novel block time, and four more peers by last novel block time. + if (number_of_nodes >= 29) { + BOOST_CHECK(SelectNodeToEvict(GetRandomNodeEvictionCandidates(number_of_nodes, random_context))); + } - // Cases left to test: - // * "If any remaining peers are preferred for eviction consider only them. [...]" - // * "Identify the network group with the most connections and youngest member. [...]" + // No eviction is expected given <= 20 random eviction candidates. The eviction logic protects at least + // four peers by net group, eight by lowest ping time, four by last time of novel tx and four peers by last + // novel block time. + if (number_of_nodes <= 20) { + BOOST_CHECK(!SelectNodeToEvict(GetRandomNodeEvictionCandidates(number_of_nodes, random_context))); } + + // Cases left to test: + // * "If any remaining peers are preferred for eviction consider only them. [...]" + // * "Identify the network group with the most connections and youngest member. [...]" } } diff --git a/src/test/txindex_tests.cpp b/src/test/txindex_tests.cpp index d47c54fd6e..3ce7ecb5f2 100644 --- a/src/test/txindex_tests.cpp +++ b/src/test/txindex_tests.cpp @@ -28,7 +28,7 @@ BOOST_FIXTURE_TEST_CASE(txindex_initial_sync, TestChain100Setup) // BlockUntilSyncedToCurrentChain should return false before txindex is started. BOOST_CHECK(!txindex.BlockUntilSyncedToCurrentChain()); - BOOST_REQUIRE(txindex.Start(::ChainstateActive())); + BOOST_REQUIRE(txindex.Start(m_node.chainman->ActiveChainstate())); // Allow tx index to catch up with the block index. constexpr int64_t timeout_ms = 10 * 1000; diff --git a/src/test/txvalidation_tests.cpp b/src/test/txvalidation_tests.cpp index 95ad85d0f8..ade9e210f2 100644 --- a/src/test/txvalidation_tests.cpp +++ b/src/test/txvalidation_tests.cpp @@ -37,7 +37,7 @@ BOOST_FIXTURE_TEST_CASE(tx_mempool_reject_coinbase, TestChain100Setup) LOCK(cs_main); unsigned int initialPoolSize = m_node.mempool->size(); - const MempoolAcceptResult result = AcceptToMemoryPool(::ChainstateActive(), *m_node.mempool, MakeTransactionRef(coinbaseTx), + const MempoolAcceptResult result = AcceptToMemoryPool(m_node.chainman->ActiveChainstate(), *m_node.mempool, MakeTransactionRef(coinbaseTx), true /* bypass_limits */); BOOST_CHECK(result.m_result_type == MempoolAcceptResult::ResultType::INVALID); diff --git a/src/test/txvalidationcache_tests.cpp b/src/test/txvalidationcache_tests.cpp index 3244b58082..23195c0a26 100644 --- a/src/test/txvalidationcache_tests.cpp +++ b/src/test/txvalidationcache_tests.cpp @@ -31,7 +31,7 @@ BOOST_FIXTURE_TEST_CASE(tx_mempool_block_doublespend, TestChain100Setup) const auto ToMemPool = [this](const CMutableTransaction& tx) { LOCK(cs_main); - const MempoolAcceptResult result = AcceptToMemoryPool(::ChainstateActive(), *m_node.mempool, MakeTransactionRef(tx), + const MempoolAcceptResult result = AcceptToMemoryPool(m_node.chainman->ActiveChainstate(), *m_node.mempool, MakeTransactionRef(tx), true /* bypass_limits */); return result.m_result_type == MempoolAcceptResult::ResultType::VALID; }; @@ -63,7 +63,7 @@ BOOST_FIXTURE_TEST_CASE(tx_mempool_block_doublespend, TestChain100Setup) block = CreateAndProcessBlock(spends, scriptPubKey); { LOCK(cs_main); - BOOST_CHECK(::ChainActive().Tip()->GetBlockHash() != block.GetHash()); + BOOST_CHECK(m_node.chainman->ActiveChain().Tip()->GetBlockHash() != block.GetHash()); } // Test 2: ... and should be rejected if spend1 is in the memory pool @@ -71,7 +71,7 @@ BOOST_FIXTURE_TEST_CASE(tx_mempool_block_doublespend, TestChain100Setup) block = CreateAndProcessBlock(spends, scriptPubKey); { LOCK(cs_main); - BOOST_CHECK(::ChainActive().Tip()->GetBlockHash() != block.GetHash()); + BOOST_CHECK(m_node.chainman->ActiveChain().Tip()->GetBlockHash() != block.GetHash()); } m_node.mempool->clear(); @@ -80,7 +80,7 @@ BOOST_FIXTURE_TEST_CASE(tx_mempool_block_doublespend, TestChain100Setup) block = CreateAndProcessBlock(spends, scriptPubKey); { LOCK(cs_main); - BOOST_CHECK(::ChainActive().Tip()->GetBlockHash() != block.GetHash()); + BOOST_CHECK(m_node.chainman->ActiveChain().Tip()->GetBlockHash() != block.GetHash()); } m_node.mempool->clear(); @@ -91,7 +91,7 @@ BOOST_FIXTURE_TEST_CASE(tx_mempool_block_doublespend, TestChain100Setup) block = CreateAndProcessBlock(oneSpend, scriptPubKey); { LOCK(cs_main); - BOOST_CHECK(::ChainActive().Tip()->GetBlockHash() == block.GetHash()); + BOOST_CHECK(m_node.chainman->ActiveChain().Tip()->GetBlockHash() == block.GetHash()); } // spends[1] should have been removed from the mempool when the // block with spends[0] is accepted: @@ -109,7 +109,7 @@ BOOST_FIXTURE_TEST_CASE(tx_mempool_block_doublespend, TestChain100Setup) // should fail. // Capture this interaction with the upgraded_nop argument: set it when evaluating // any script flag that is implemented as an upgraded NOP code. -static void ValidateCheckInputsForAllFlags(const CTransaction &tx, uint32_t failing_flags, bool add_to_cache) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +static void ValidateCheckInputsForAllFlags(const CTransaction &tx, uint32_t failing_flags, bool add_to_cache, CCoinsViewCache& active_coins_tip) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { PrecomputedTransactionData txdata; // If we add many more flags, this loop can get too expensive, but we can @@ -126,7 +126,7 @@ static void ValidateCheckInputsForAllFlags(const CTransaction &tx, uint32_t fail // WITNESS requires P2SH test_flags |= SCRIPT_VERIFY_P2SH; } - bool ret = CheckInputScripts(tx, state, &::ChainstateActive().CoinsTip(), test_flags, true, add_to_cache, txdata, nullptr); + bool ret = CheckInputScripts(tx, state, &active_coins_tip, test_flags, true, add_to_cache, txdata, nullptr); // CheckInputScripts should succeed iff test_flags doesn't intersect with // failing_flags bool expected_return_value = !(test_flags & failing_flags); @@ -136,13 +136,13 @@ static void ValidateCheckInputsForAllFlags(const CTransaction &tx, uint32_t fail if (ret && add_to_cache) { // Check that we get a cache hit if the tx was valid std::vector<CScriptCheck> scriptchecks; - BOOST_CHECK(CheckInputScripts(tx, state, &::ChainstateActive().CoinsTip(), test_flags, true, add_to_cache, txdata, &scriptchecks)); + BOOST_CHECK(CheckInputScripts(tx, state, &active_coins_tip, test_flags, true, add_to_cache, txdata, &scriptchecks)); BOOST_CHECK(scriptchecks.empty()); } else { // Check that we get script executions to check, if the transaction // was invalid, or we didn't add to cache. std::vector<CScriptCheck> scriptchecks; - BOOST_CHECK(CheckInputScripts(tx, state, &::ChainstateActive().CoinsTip(), test_flags, true, add_to_cache, txdata, &scriptchecks)); + BOOST_CHECK(CheckInputScripts(tx, state, &active_coins_tip, test_flags, true, add_to_cache, txdata, &scriptchecks)); BOOST_CHECK_EQUAL(scriptchecks.size(), tx.vin.size()); } } @@ -205,20 +205,20 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup) TxValidationState state; PrecomputedTransactionData ptd_spend_tx; - BOOST_CHECK(!CheckInputScripts(CTransaction(spend_tx), state, &::ChainstateActive().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_DERSIG, true, true, ptd_spend_tx, nullptr)); + BOOST_CHECK(!CheckInputScripts(CTransaction(spend_tx), state, &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_DERSIG, true, true, ptd_spend_tx, nullptr)); // If we call again asking for scriptchecks (as happens in // ConnectBlock), we should add a script check object for this -- we're // not caching invalidity (if that changes, delete this test case). std::vector<CScriptCheck> scriptchecks; - BOOST_CHECK(CheckInputScripts(CTransaction(spend_tx), state, &::ChainstateActive().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_DERSIG, true, true, ptd_spend_tx, &scriptchecks)); + BOOST_CHECK(CheckInputScripts(CTransaction(spend_tx), state, &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_DERSIG, true, true, ptd_spend_tx, &scriptchecks)); BOOST_CHECK_EQUAL(scriptchecks.size(), 1U); // Test that CheckInputScripts returns true iff DERSIG-enforcing flags are // not present. Don't add these checks to the cache, so that we can // test later that block validation works fine in the absence of cached // successes. - ValidateCheckInputsForAllFlags(CTransaction(spend_tx), SCRIPT_VERIFY_DERSIG | SCRIPT_VERIFY_LOW_S | SCRIPT_VERIFY_STRICTENC, false); + ValidateCheckInputsForAllFlags(CTransaction(spend_tx), SCRIPT_VERIFY_DERSIG | SCRIPT_VERIFY_LOW_S | SCRIPT_VERIFY_STRICTENC, false, m_node.chainman->ActiveChainstate().CoinsTip()); } // And if we produce a block with this tx, it should be valid (DERSIG not @@ -227,8 +227,8 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup) block = CreateAndProcessBlock({spend_tx}, p2pk_scriptPubKey); LOCK(cs_main); - BOOST_CHECK(::ChainActive().Tip()->GetBlockHash() == block.GetHash()); - BOOST_CHECK(::ChainstateActive().CoinsTip().GetBestBlock() == block.GetHash()); + BOOST_CHECK(m_node.chainman->ActiveChain().Tip()->GetBlockHash() == block.GetHash()); + BOOST_CHECK(m_node.chainman->ActiveChainstate().CoinsTip().GetBestBlock() == block.GetHash()); // Test P2SH: construct a transaction that is valid without P2SH, and // then test validity with P2SH. @@ -244,7 +244,7 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup) std::vector<unsigned char> vchSig2(p2pk_scriptPubKey.begin(), p2pk_scriptPubKey.end()); invalid_under_p2sh_tx.vin[0].scriptSig << vchSig2; - ValidateCheckInputsForAllFlags(CTransaction(invalid_under_p2sh_tx), SCRIPT_VERIFY_P2SH, true); + ValidateCheckInputsForAllFlags(CTransaction(invalid_under_p2sh_tx), SCRIPT_VERIFY_P2SH, true, m_node.chainman->ActiveChainstate().CoinsTip()); } // Test CHECKLOCKTIMEVERIFY @@ -267,13 +267,13 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup) vchSig.push_back((unsigned char)SIGHASH_ALL); invalid_with_cltv_tx.vin[0].scriptSig = CScript() << vchSig << 101; - ValidateCheckInputsForAllFlags(CTransaction(invalid_with_cltv_tx), SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY, true); + ValidateCheckInputsForAllFlags(CTransaction(invalid_with_cltv_tx), SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY, true, m_node.chainman->ActiveChainstate().CoinsTip()); // Make it valid, and check again invalid_with_cltv_tx.vin[0].scriptSig = CScript() << vchSig << 100; TxValidationState state; PrecomputedTransactionData txdata; - BOOST_CHECK(CheckInputScripts(CTransaction(invalid_with_cltv_tx), state, ::ChainstateActive().CoinsTip(), SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY, true, true, txdata, nullptr)); + BOOST_CHECK(CheckInputScripts(CTransaction(invalid_with_cltv_tx), state, m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY, true, true, txdata, nullptr)); } // TEST CHECKSEQUENCEVERIFY @@ -295,13 +295,13 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup) vchSig.push_back((unsigned char)SIGHASH_ALL); invalid_with_csv_tx.vin[0].scriptSig = CScript() << vchSig << 101; - ValidateCheckInputsForAllFlags(CTransaction(invalid_with_csv_tx), SCRIPT_VERIFY_CHECKSEQUENCEVERIFY, true); + ValidateCheckInputsForAllFlags(CTransaction(invalid_with_csv_tx), SCRIPT_VERIFY_CHECKSEQUENCEVERIFY, true, m_node.chainman->ActiveChainstate().CoinsTip()); // Make it valid, and check again invalid_with_csv_tx.vin[0].scriptSig = CScript() << vchSig << 100; TxValidationState state; PrecomputedTransactionData txdata; - BOOST_CHECK(CheckInputScripts(CTransaction(invalid_with_csv_tx), state, &::ChainstateActive().CoinsTip(), SCRIPT_VERIFY_CHECKSEQUENCEVERIFY, true, true, txdata, nullptr)); + BOOST_CHECK(CheckInputScripts(CTransaction(invalid_with_csv_tx), state, &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_CHECKSEQUENCEVERIFY, true, true, txdata, nullptr)); } // TODO: add tests for remaining script flags @@ -324,11 +324,11 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup) UpdateInput(valid_with_witness_tx.vin[0], sigdata); // This should be valid under all script flags. - ValidateCheckInputsForAllFlags(CTransaction(valid_with_witness_tx), 0, true); + ValidateCheckInputsForAllFlags(CTransaction(valid_with_witness_tx), 0, true, m_node.chainman->ActiveChainstate().CoinsTip()); // Remove the witness, and check that it is now invalid. valid_with_witness_tx.vin[0].scriptWitness.SetNull(); - ValidateCheckInputsForAllFlags(CTransaction(valid_with_witness_tx), SCRIPT_VERIFY_WITNESS, true); + ValidateCheckInputsForAllFlags(CTransaction(valid_with_witness_tx), SCRIPT_VERIFY_WITNESS, true, m_node.chainman->ActiveChainstate().CoinsTip()); } { @@ -353,7 +353,7 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup) } // This should be valid under all script flags - ValidateCheckInputsForAllFlags(CTransaction(tx), 0, true); + ValidateCheckInputsForAllFlags(CTransaction(tx), 0, true, m_node.chainman->ActiveChainstate().CoinsTip()); // Check that if the second input is invalid, but the first input is // valid, the transaction is not cached. @@ -363,12 +363,12 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup) TxValidationState state; PrecomputedTransactionData txdata; // This transaction is now invalid under segwit, because of the second input. - BOOST_CHECK(!CheckInputScripts(CTransaction(tx), state, &::ChainstateActive().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, true, true, txdata, nullptr)); + BOOST_CHECK(!CheckInputScripts(CTransaction(tx), state, &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, true, true, txdata, nullptr)); std::vector<CScriptCheck> scriptchecks; // Make sure this transaction was not cached (ie because the first // input was valid) - BOOST_CHECK(CheckInputScripts(CTransaction(tx), state, &::ChainstateActive().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, true, true, txdata, &scriptchecks)); + BOOST_CHECK(CheckInputScripts(CTransaction(tx), state, &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, true, true, txdata, &scriptchecks)); // Should get 2 script checks back -- caching is on a whole-transaction basis. BOOST_CHECK_EQUAL(scriptchecks.size(), 2U); } diff --git a/src/test/util/mining.cpp b/src/test/util/mining.cpp index 1204873828..f6a11bc02e 100644 --- a/src/test/util/mining.cpp +++ b/src/test/util/mining.cpp @@ -74,12 +74,12 @@ CTxIn MineBlock(const NodeContext& node, const CScript& coinbase_scriptPubKey) std::shared_ptr<CBlock> PrepareBlock(const NodeContext& node, const CScript& coinbase_scriptPubKey) { auto block = std::make_shared<CBlock>( - BlockAssembler{::ChainstateActive(), *Assert(node.mempool), Params()} + BlockAssembler{Assert(node.chainman)->ActiveChainstate(), *Assert(node.mempool), Params()} .CreateNewBlock(coinbase_scriptPubKey) ->block); LOCK(cs_main); - block->nTime = ::ChainActive().Tip()->GetMedianTimePast() + 1; + block->nTime = Assert(node.chainman)->ActiveChain().Tip()->GetMedianTimePast() + 1; block->hashMerkleRoot = BlockMerkleRoot(*block); return block; diff --git a/src/test/util/net.h b/src/test/util/net.h index 71685d437a..1b49a671bd 100644 --- a/src/test/util/net.h +++ b/src/test/util/net.h @@ -6,9 +6,11 @@ #define BITCOIN_TEST_UTIL_NET_H #include <compat.h> +#include <netaddress.h> #include <net.h> #include <util/sock.h> +#include <array> #include <cassert> #include <cstring> #include <string> @@ -67,6 +69,16 @@ constexpr ConnectionType ALL_CONNECTION_TYPES[]{ ConnectionType::ADDR_FETCH, }; +constexpr auto ALL_NETWORKS = std::array{ + Network::NET_UNROUTABLE, + Network::NET_IPV4, + Network::NET_IPV6, + Network::NET_ONION, + Network::NET_I2P, + Network::NET_CJDNS, + Network::NET_INTERNAL, +}; + /** * A mocked Sock alternative that returns a statically contained data upon read and succeeds * and ignores all writes. The data to be returned is given to the constructor and when it is diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index 863c3ab565..e105e85e47 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -76,6 +76,7 @@ BasicTestingSetup::BasicTestingSetup(const std::string& chainName, const std::ve : m_path_root{fs::temp_directory_path() / "test_common_" PACKAGE_NAME / g_insecure_rand_ctx_temp_path.rand256().ToString()}, m_args{} { + m_node.args = &gArgs; const std::vector<const char*> arguments = Cat( { "dummy", @@ -94,7 +95,7 @@ BasicTestingSetup::BasicTestingSetup(const std::string& chainName, const std::ve gArgs.ForceSetArg("-datadir", m_path_root.string()); gArgs.ClearPathCache(); { - SetupServerArgs(m_node); + SetupServerArgs(*m_node.args); std::string error; const bool success{m_node.args->ParseParameters(arguments.size(), arguments.data(), error)}; assert(success); @@ -145,7 +146,7 @@ ChainTestingSetup::ChainTestingSetup(const std::string& chainName, const std::ve m_node.fee_estimator = std::make_unique<CBlockPolicyEstimator>(); m_node.mempool = std::make_unique<CTxMemPool>(m_node.fee_estimator.get(), 1); - m_node.chainman = &::g_chainman; + m_node.chainman = std::make_unique<ChainstateManager>(); // Start script-checking threads. Set g_parallel_script_checks to true so they are used. constexpr int script_check_threads = 2; @@ -167,7 +168,7 @@ ChainTestingSetup::~ChainTestingSetup() m_node.mempool.reset(); m_node.scheduler.reset(); m_node.chainman->Reset(); - m_node.chainman = nullptr; + m_node.chainman.reset(); pblocktree.reset(); } @@ -180,17 +181,17 @@ TestingSetup::TestingSetup(const std::string& chainName, const std::vector<const RegisterAllCoreRPCCommands(tableRPC); m_node.chainman->InitializeChainstate(*m_node.mempool); - ::ChainstateActive().InitCoinsDB( + m_node.chainman->ActiveChainstate().InitCoinsDB( /* cache_size_bytes */ 1 << 23, /* in_memory */ true, /* should_wipe */ false); - assert(!::ChainstateActive().CanFlushToDisk()); - ::ChainstateActive().InitCoinsCache(1 << 23); - assert(::ChainstateActive().CanFlushToDisk()); - if (!::ChainstateActive().LoadGenesisBlock(chainparams)) { + assert(!m_node.chainman->ActiveChainstate().CanFlushToDisk()); + m_node.chainman->ActiveChainstate().InitCoinsCache(1 << 23); + assert(m_node.chainman->ActiveChainstate().CanFlushToDisk()); + if (!m_node.chainman->ActiveChainstate().LoadGenesisBlock(chainparams)) { throw std::runtime_error("LoadGenesisBlock failed."); } BlockValidationState state; - if (!::ChainstateActive().ActivateBestChain(state, chainparams)) { + if (!m_node.chainman->ActiveChainstate().ActivateBestChain(state, chainparams)) { throw std::runtime_error(strprintf("ActivateBestChain failed. (%s)", state.ToString())); } @@ -240,7 +241,7 @@ CBlock TestChain100Setup::CreateAndProcessBlock(const std::vector<CMutableTransa { const CChainParams& chainparams = Params(); CTxMemPool empty_pool; - CBlock block = BlockAssembler(::ChainstateActive(), empty_pool, chainparams).CreateNewBlock(scriptPubKey)->block; + CBlock block = BlockAssembler(m_node.chainman->ActiveChainstate(), empty_pool, chainparams).CreateNewBlock(scriptPubKey)->block; Assert(block.vtx.size() == 1); for (const CMutableTransaction& tx : txns) { @@ -299,7 +300,7 @@ CMutableTransaction TestChain100Setup::CreateValidMempoolTransaction(CTransactio // If submit=true, add transaction to the mempool. if (submit) { LOCK(cs_main); - const MempoolAcceptResult result = AcceptToMemoryPool(::ChainstateActive(), *m_node.mempool.get(), MakeTransactionRef(mempool_txn), /* bypass_limits */ false); + const MempoolAcceptResult result = AcceptToMemoryPool(m_node.chainman->ActiveChainstate(), *m_node.mempool.get(), MakeTransactionRef(mempool_txn), /* bypass_limits */ false); assert(result.m_result_type == MempoolAcceptResult::ResultType::VALID); } diff --git a/src/test/validation_block_tests.cpp b/src/test/validation_block_tests.cpp index 552be0a2da..e0bc10d660 100644 --- a/src/test/validation_block_tests.cpp +++ b/src/test/validation_block_tests.cpp @@ -84,8 +84,8 @@ std::shared_ptr<CBlock> MinerTestingSetup::Block(const uint256& prev_hash) std::shared_ptr<CBlock> MinerTestingSetup::FinalizeBlock(std::shared_ptr<CBlock> pblock) { - LOCK(cs_main); // For g_chainman.m_blockman.LookupBlockIndex - GenerateCoinbaseCommitment(*pblock, g_chainman.m_blockman.LookupBlockIndex(pblock->hashPrevBlock), Params().GetConsensus()); + LOCK(cs_main); // For m_node.chainman->m_blockman.LookupBlockIndex + GenerateCoinbaseCommitment(*pblock, m_node.chainman->m_blockman.LookupBlockIndex(pblock->hashPrevBlock), Params().GetConsensus()); pblock->hashMerkleRoot = BlockMerkleRoot(*pblock); @@ -162,7 +162,7 @@ BOOST_AUTO_TEST_CASE(processnewblock_signals_ordering) const CBlockIndex* initial_tip = nullptr; { LOCK(cs_main); - initial_tip = ::ChainActive().Tip(); + initial_tip = m_node.chainman->ActiveChain().Tip(); } auto sub = std::make_shared<TestSubscriber>(initial_tip->GetBlockHash()); RegisterSharedValidationInterface(sub); @@ -198,7 +198,7 @@ BOOST_AUTO_TEST_CASE(processnewblock_signals_ordering) UnregisterSharedValidationInterface(sub); LOCK(cs_main); - BOOST_CHECK_EQUAL(sub->m_expected_tip, ::ChainActive().Tip()->GetBlockHash()); + BOOST_CHECK_EQUAL(sub->m_expected_tip, m_node.chainman->ActiveChain().Tip()->GetBlockHash()); } /** @@ -232,7 +232,7 @@ BOOST_AUTO_TEST_CASE(mempool_locks_reorg) // Run the test multiple times for (int test_runs = 3; test_runs > 0; --test_runs) { - BOOST_CHECK_EQUAL(last_mined->GetHash(), ::ChainActive().Tip()->GetBlockHash()); + BOOST_CHECK_EQUAL(last_mined->GetHash(), m_node.chainman->ActiveChain().Tip()->GetBlockHash()); // Later on split from here const uint256 split_hash{last_mined->hashPrevBlock}; @@ -273,7 +273,7 @@ BOOST_AUTO_TEST_CASE(mempool_locks_reorg) { LOCK(cs_main); for (const auto& tx : txs) { - const MempoolAcceptResult result = AcceptToMemoryPool(::ChainstateActive(), *m_node.mempool, tx, false /* bypass_limits */); + const MempoolAcceptResult result = AcceptToMemoryPool(m_node.chainman->ActiveChainstate(), *m_node.mempool, tx, false /* bypass_limits */); BOOST_REQUIRE(result.m_result_type == MempoolAcceptResult::ResultType::VALID); } } @@ -306,7 +306,7 @@ BOOST_AUTO_TEST_CASE(mempool_locks_reorg) } LOCK(cs_main); // We are done with the reorg, so the tip must have changed - assert(tip_init != ::ChainActive().Tip()->GetBlockHash()); + assert(tip_init != m_node.chainman->ActiveChain().Tip()->GetBlockHash()); }}; // Submit the reorg in this thread to invalidate and remove the txs from the tx pool @@ -314,7 +314,7 @@ BOOST_AUTO_TEST_CASE(mempool_locks_reorg) ProcessBlock(b); } // Check that the reorg was eventually successful - BOOST_CHECK_EQUAL(last_mined->GetHash(), ::ChainActive().Tip()->GetBlockHash()); + BOOST_CHECK_EQUAL(last_mined->GetHash(), m_node.chainman->ActiveChain().Tip()->GetBlockHash()); // We can join the other thread, which returns when the reorg was successful rpc_thread.join(); diff --git a/src/txmempool.cpp b/src/txmempool.cpp index 4413da7ea7..c5a4bbf1b0 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -513,11 +513,10 @@ void CTxMemPool::removeForReorg(CChainState& active_chainstate, int flags) for (indexed_transaction_set::const_iterator it = mapTx.begin(); it != mapTx.end(); it++) { const CTransaction& tx = it->GetTx(); LockPoints lp = it->GetLockPoints(); - assert(std::addressof(::ChainstateActive()) == std::addressof(active_chainstate)); bool validLP = TestLockPointValidity(active_chainstate.m_chain, &lp); - CCoinsViewMemPool viewMempool(&active_chainstate.CoinsTip(), *this); + CCoinsViewMemPool view_mempool(&active_chainstate.CoinsTip(), *this); if (!CheckFinalTx(active_chainstate.m_chain.Tip(), tx, flags) - || !CheckSequenceLocks(active_chainstate.m_chain.Tip(), viewMempool, tx, flags, &lp, validLP)) { + || !CheckSequenceLocks(active_chainstate.m_chain.Tip(), view_mempool, tx, flags, &lp, validLP)) { // Note if CheckSequenceLocks fails the LockPoints may still be invalid // So it's critical that we remove the tx and not depend on the LockPoints. txToRemove.insert(it); @@ -638,10 +637,8 @@ void CTxMemPool::check(CChainState& active_chainstate) const uint64_t innerUsage = 0; CCoinsViewCache& active_coins_tip = active_chainstate.CoinsTip(); - assert(std::addressof(::ChainstateActive().CoinsTip()) == std::addressof(active_coins_tip)); // TODO: REVIEW-ONLY, REMOVE IN FUTURE COMMIT CCoinsViewCache mempoolDuplicate(const_cast<CCoinsViewCache*>(&active_coins_tip)); const int64_t spendheight = active_chainstate.m_chain.Height() + 1; - assert(g_chainman.m_blockman.GetSpendHeight(mempoolDuplicate) == spendheight); // TODO: REVIEW-ONLY, REMOVE IN FUTURE COMMIT std::list<const CTxMemPoolEntry*> waitingOnDependants; for (indexed_transaction_set::const_iterator it = mapTx.begin(); it != mapTx.end(); it++) { diff --git a/src/txmempool.h b/src/txmempool.h index 46b89049bb..ae4b16d377 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -874,7 +874,8 @@ protected: public: CCoinsViewMemPool(CCoinsView* baseIn, const CTxMemPool& mempoolIn); bool GetCoin(const COutPoint &outpoint, Coin &coin) const override; - /** Add the coins created by this transaction. */ + /** Add the coins created by this transaction. These coins are only temporarily stored in + * m_temp_added and cannot be flushed to the back end. Only used for package validation. */ void PackageAddTransaction(const CTransactionRef& tx); }; diff --git a/src/util/system.cpp b/src/util/system.cpp index 13ccf7463e..5ea0139275 100644 --- a/src/util/system.cpp +++ b/src/util/system.cpp @@ -1243,9 +1243,9 @@ void runCommand(const std::string& strCommand) } #endif -#ifdef ENABLE_EXTERNAL_SIGNER UniValue RunCommandParseJSON(const std::string& str_command, const std::string& str_std_in) { +#ifdef ENABLE_EXTERNAL_SIGNER namespace bp = boost::process; UniValue result_json; @@ -1277,8 +1277,10 @@ UniValue RunCommandParseJSON(const std::string& str_command, const std::string& if (!result_json.read(result)) throw std::runtime_error("Unable to parse JSON: " + result); return result_json; -} +#else + throw std::runtime_error("Compiled without external signing support (required for external signing)."); #endif // ENABLE_EXTERNAL_SIGNER +} void SetupEnvironment() { diff --git a/src/util/system.h b/src/util/system.h index c4317c62d0..ea9870a343 100644 --- a/src/util/system.h +++ b/src/util/system.h @@ -102,7 +102,6 @@ std::string ShellEscape(const std::string& arg); #if HAVE_SYSTEM void runCommand(const std::string& strCommand); #endif -#ifdef ENABLE_EXTERNAL_SIGNER /** * Execute a command which returns JSON, and parse the result. * @@ -111,7 +110,6 @@ void runCommand(const std::string& strCommand); * @return parsed JSON */ UniValue RunCommandParseJSON(const std::string& str_command, const std::string& str_std_in=""); -#endif // ENABLE_EXTERNAL_SIGNER /** * Most paths passed as configuration arguments are treated as relative to diff --git a/src/validation.cpp b/src/validation.cpp index 5e3d429c2e..b48e49a10b 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -101,21 +101,6 @@ bool CBlockIndexWorkComparator::operator()(const CBlockIndex *pa, const CBlockIn return false; } -ChainstateManager g_chainman; - -CChainState& ChainstateActive() -{ - LOCK(::cs_main); - assert(g_chainman.m_active_chainstate); - return *g_chainman.m_active_chainstate; -} - -CChain& ChainActive() -{ - LOCK(::cs_main); - return ::ChainstateActive().m_chain; -} - /** * Mutex to guard access to validation specific variables, such as reading * or changing the chainstate. @@ -161,7 +146,6 @@ void FlushBlockFile(bool fFinalize = false, bool finalize_undo = false); CBlockIndex* BlockManager::LookupBlockIndex(const uint256& hash) const { AssertLockHeld(cs_main); - assert(std::addressof(g_chainman.BlockIndex()) == std::addressof(m_block_index)); BlockMap::const_iterator it = m_block_index.find(hash); return it == m_block_index.end() ? nullptr : it->second; } @@ -170,7 +154,6 @@ CBlockIndex* BlockManager::FindForkInGlobalIndex(const CChain& chain, const CBlo { AssertLockHeld(cs_main); - assert(std::addressof(g_chainman.m_blockman) == std::addressof(*this)); // Find the latest block common to locator and chain - we expect that // locator.vHave is sorted descending by height. for (const uint256& hash : locator.vHave) { @@ -198,7 +181,6 @@ bool CheckFinalTx(const CBlockIndex* active_chain_tip, const CTransaction &tx, i { AssertLockHeld(cs_main); assert(active_chain_tip); // TODO: Make active_chain_tip a reference - assert(std::addressof(*::ChainActive().Tip()) == std::addressof(*active_chain_tip)); // By convention a negative value for flags indicates that the // current network-enforced consensus rules should be used. In @@ -237,7 +219,6 @@ bool TestLockPointValidity(CChain& active_chain, const LockPoints* lp) if (lp->maxInputBlock) { // Check whether ::ChainActive() is an extension of the block at which the LockPoints // calculation was valid. If not LockPoints are no longer valid - assert(std::addressof(::ChainActive()) == std::addressof(active_chain)); if (!active_chain.Contains(lp->maxInputBlock)) { return false; } @@ -331,7 +312,6 @@ static void LimitMempoolSize(CTxMemPool& pool, CCoinsViewCache& coins_cache, siz std::vector<COutPoint> vNoSpendsRemaining; pool.TrimToSize(limit, &vNoSpendsRemaining); - assert(std::addressof(::ChainstateActive().CoinsTip()) == std::addressof(coins_cache)); for (const COutPoint& removed : vNoSpendsRemaining) coins_cache.Uncache(removed); } @@ -339,7 +319,6 @@ static void LimitMempoolSize(CTxMemPool& pool, CCoinsViewCache& coins_cache, siz static bool IsCurrentForFeeEstimation(CChainState& active_chainstate) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { AssertLockHeld(cs_main); - assert(std::addressof(::ChainstateActive()) == std::addressof(active_chainstate)); if (active_chainstate.IsInitialBlockDownload()) return false; if (active_chainstate.m_chain.Tip()->GetBlockTime() < count_seconds(GetTime<std::chrono::seconds>() - MAX_FEE_ESTIMATION_TIP_AGE)) @@ -366,7 +345,6 @@ static void UpdateMempoolForReorg(CChainState& active_chainstate, CTxMemPool& me { AssertLockHeld(cs_main); AssertLockHeld(mempool.cs); - assert(std::addressof(::ChainstateActive()) == std::addressof(active_chainstate)); std::vector<uint256> vHashUpdate; // disconnectpool's insertion_order index sorts the entries from // oldest to newest, but the oldest entry will be the last tx from the @@ -433,7 +411,6 @@ static bool CheckInputsFromMempoolAndCache(const CTransaction& tx, TxValidationS assert(txFrom->vout.size() > txin.prevout.n); assert(txFrom->vout[txin.prevout.n] == coin.out); } else { - assert(std::addressof(::ChainstateActive().CoinsTip()) == std::addressof(coins_tip)); const Coin& coinFromUTXOSet = coins_tip.AccessCoin(txin.prevout); assert(!coinFromUTXOSet.IsSpent()); assert(coinFromUTXOSet.out == coin.out); @@ -454,7 +431,6 @@ public: m_limit_ancestor_size(gArgs.GetArg("-limitancestorsize", DEFAULT_ANCESTOR_SIZE_LIMIT)*1000), m_limit_descendants(gArgs.GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT)), m_limit_descendant_size(gArgs.GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT)*1000) { - assert(std::addressof(::ChainstateActive()) == std::addressof(m_active_chainstate)); } // We put the arguments we're handed into a struct, so we can pass them @@ -472,8 +448,10 @@ public: */ std::vector<COutPoint>& m_coins_to_uncache; const bool m_test_accept; - /** Disable BIP125 RBFing; disallow all conflicts with mempool transactions. */ - const bool disallow_mempool_conflicts; + /** Whether we allow transactions to replace mempool transactions by BIP125 rules. If false, + * any transaction spending the same inputs as a transaction in the mempool is considered + * a conflict. */ + const bool m_allow_bip125_replacement{true}; }; // Single transaction acceptance @@ -482,7 +460,7 @@ public: /** * Multiple transaction acceptance. Transactions may or may not be interdependent, * but must not conflict with each other. Parents must come before children if any - * dependencies exist, otherwise a TX_MISSING_INPUTS error will be returned. + * dependencies exist. */ PackageMempoolAcceptResult AcceptMultipleTransactions(const std::vector<CTransactionRef>& txns, ATMPArgs& args) EXCLUSIVE_LOCKS_REQUIRED(cs_main); @@ -605,7 +583,6 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) // Only accept nLockTime-using transactions that can be mined in the next // block; we don't want our mempool filled up with transactions that can't // be mined yet. - assert(std::addressof(::ChainActive()) == std::addressof(m_active_chainstate.m_chain)); if (!CheckFinalTx(m_active_chainstate.m_chain.Tip(), tx, STANDARD_LOCKTIME_VERIFY_FLAGS)) return state.Invalid(TxValidationResult::TX_PREMATURE_SPEND, "non-final"); @@ -619,6 +596,10 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) { const CTransaction* ptxConflicting = m_pool.GetConflictTx(txin.prevout); if (ptxConflicting) { + if (!args.m_allow_bip125_replacement) { + // Transaction conflicts with a mempool tx, but we're not allowing replacements. + return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "bip125-replacement-disallowed"); + } if (!setConflicts.count(ptxConflicting->GetHash())) { // Allow opt-out of transaction replacement by setting @@ -645,7 +626,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) break; } } - if (fReplacementOptOut || args.disallow_mempool_conflicts) { + if (fReplacementOptOut) { return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "txn-mempool-conflict"); } @@ -657,7 +638,6 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) LockPoints lp; m_view.SetBackend(m_viewmempool); - assert(std::addressof(::ChainstateActive().CoinsTip()) == std::addressof(m_active_chainstate.CoinsTip())); const CCoinsViewCache& coins_cache = m_active_chainstate.CoinsTip(); // do all inputs exist? for (const CTxIn& txin : tx.vin) { @@ -695,18 +675,15 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) // be mined yet. // Pass in m_view which has all of the relevant inputs cached. Note that, since m_view's // backend was removed, it no longer pulls coins from the mempool. - assert(std::addressof(::ChainstateActive()) == std::addressof(m_active_chainstate)); if (!CheckSequenceLocks(m_active_chainstate.m_chain.Tip(), m_view, tx, STANDARD_LOCKTIME_VERIFY_FLAGS, &lp)) return state.Invalid(TxValidationResult::TX_PREMATURE_SPEND, "non-BIP68-final"); - assert(std::addressof(g_chainman.m_blockman) == std::addressof(m_active_chainstate.m_blockman)); if (!Consensus::CheckTxInputs(tx, state, m_view, m_active_chainstate.m_blockman.GetSpendHeight(m_view), ws.m_base_fees)) { return false; // state filled in by CheckTxInputs } // Check for non-standard pay-to-script-hash in inputs const auto& params = args.m_chainparams.GetConsensus(); - assert(std::addressof(::ChainActive()) == std::addressof(m_active_chainstate.m_chain)); auto taproot_state = VersionBitsState(m_active_chainstate.m_chain.Tip(), params, Consensus::DEPLOYMENT_TAPROOT, versionbitscache); if (fRequireStandard && !AreInputsStandard(tx, m_view, taproot_state == ThresholdState::ACTIVE)) { return state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, "bad-txns-nonstandard-inputs"); @@ -733,7 +710,6 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) } } - assert(std::addressof(::ChainActive()) == std::addressof(m_active_chainstate.m_chain)); entry.reset(new CTxMemPoolEntry(ptx, ws.m_base_fees, nAcceptTime, m_active_chainstate.m_chain.Height(), fSpendsCoinbase, nSigOpsCost, lp)); unsigned int nSize = entry->GetTxSize(); @@ -986,9 +962,7 @@ bool MemPoolAccept::ConsensusScriptChecks(const ATMPArgs& args, Workspace& ws, P // There is a similar check in CreateNewBlock() to prevent creating // invalid blocks (using TestBlockValidity), however allowing such // transactions into the mempool can be exploited as a DoS attack. - assert(std::addressof(::ChainActive()) == std::addressof(m_active_chainstate.m_chain)); unsigned int currentBlockScriptVerifyFlags = GetBlockScriptFlags(m_active_chainstate.m_chain.Tip(), chainparams.GetConsensus()); - assert(std::addressof(::ChainstateActive().CoinsTip()) == std::addressof(m_active_chainstate.CoinsTip())); if (!CheckInputsFromMempoolAndCache(tx, state, m_view, m_pool, currentBlockScriptVerifyFlags, txdata, m_active_chainstate.CoinsTip())) { return error("%s: BUG! PLEASE REPORT THIS! CheckInputScripts failed against latest-block but not STANDARD flags %s, %s", __func__, hash.ToString(), state.ToString()); @@ -1029,7 +1003,6 @@ bool MemPoolAccept::Finalize(const ATMPArgs& args, Workspace& ws) // - it's not being re-added during a reorg which bypasses typical mempool fee limits // - the node is not behind // - the transaction is not dependent on any other transactions in the mempool - assert(std::addressof(::ChainstateActive()) == std::addressof(m_active_chainstate)); bool validForFeeEstimation = !fReplacementTransaction && !bypass_limits && IsCurrentForFeeEstimation(m_active_chainstate) && m_pool.HasNoInputsOf(tx); // Store transaction in memory @@ -1037,7 +1010,6 @@ bool MemPoolAccept::Finalize(const ATMPArgs& args, Workspace& ws) // trim mempool and check if tx was trimmed if (!bypass_limits) { - assert(std::addressof(::ChainstateActive().CoinsTip()) == std::addressof(m_active_chainstate.CoinsTip())); LimitMempoolSize(m_pool, m_active_chainstate.CoinsTip(), gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000, std::chrono::hours{gArgs.GetArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY)}); if (!m_pool.exists(hash)) return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "mempool full"); @@ -1080,65 +1052,15 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std:: { AssertLockHeld(cs_main); + // These context-free package limits can be done before taking the mempool lock. PackageValidationState package_state; - const unsigned int package_count = txns.size(); - - // These context-free package limits can be checked before taking the mempool lock. - if (package_count > MAX_PACKAGE_COUNT) { - package_state.Invalid(PackageValidationResult::PCKG_POLICY, "package-too-many-transactions"); - return PackageMempoolAcceptResult(package_state, {}); - } + if (!CheckPackage(txns, package_state)) return PackageMempoolAcceptResult(package_state, {}); - const int64_t total_size = std::accumulate(txns.cbegin(), txns.cend(), 0, - [](int64_t sum, const auto& tx) { return sum + GetVirtualTransactionSize(*tx); }); - // If the package only contains 1 tx, it's better to report the policy violation on individual tx size. - if (package_count > 1 && total_size > MAX_PACKAGE_SIZE * 1000) { - package_state.Invalid(PackageValidationResult::PCKG_POLICY, "package-too-large"); - return PackageMempoolAcceptResult(package_state, {}); - } - - // Construct workspaces and check package policies. std::vector<Workspace> workspaces{}; - workspaces.reserve(package_count); - { - std::unordered_set<uint256, SaltedTxidHasher> later_txids; - std::transform(txns.cbegin(), txns.cend(), std::inserter(later_txids, later_txids.end()), - [](const auto& tx) { return tx->GetHash(); }); - // Require the package to be sorted in order of dependency, i.e. parents appear before children. - // An unsorted package will fail anyway on missing-inputs, but it's better to quit earlier and - // fail on something less ambiguous (missing-inputs could also be an orphan or trying to - // spend nonexistent coins). - for (const auto& tx : txns) { - for (const auto& input : tx->vin) { - if (later_txids.find(input.prevout.hash) != later_txids.end()) { - // The parent is a subsequent transaction in the package. - package_state.Invalid(PackageValidationResult::PCKG_POLICY, "package-not-sorted"); - return PackageMempoolAcceptResult(package_state, {}); - } - } - later_txids.erase(tx->GetHash()); - workspaces.emplace_back(Workspace(tx)); - } - } + workspaces.reserve(txns.size()); + std::transform(txns.cbegin(), txns.cend(), std::back_inserter(workspaces), + [](const auto& tx) { return Workspace(tx); }); std::map<const uint256, const MempoolAcceptResult> results; - { - // Don't allow any conflicting transactions, i.e. spending the same inputs, in a package. - std::unordered_set<COutPoint, SaltedOutpointHasher> inputs_seen; - for (const auto& tx : txns) { - for (const auto& input : tx->vin) { - if (inputs_seen.find(input.prevout) != inputs_seen.end()) { - // This input is also present in another tx in the package. - package_state.Invalid(PackageValidationResult::PCKG_POLICY, "conflict-in-package"); - return PackageMempoolAcceptResult(package_state, {}); - } - } - // Batch-add all the inputs for a tx at a time. If we added them 1 at a time, we could - // catch duplicate inputs within a single tx. This is a more severe, consensus error, - // and we want to report that from CheckTransaction instead. - std::transform(tx->vin.cbegin(), tx->vin.cend(), std::inserter(inputs_seen, inputs_seen.end()), - [](const auto& input) { return input.prevout; }); - } - } LOCK(m_pool.cs); @@ -1151,10 +1073,10 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std:: return PackageMempoolAcceptResult(package_state, std::move(results)); } // Make the coins created by this transaction available for subsequent transactions in the - // package to spend. Since we already checked conflicts in the package and RBFs are - // impossible, we don't need to track the coins spent. Note that this logic will need to be - // updated if RBFs in packages are allowed in the future. - assert(args.disallow_mempool_conflicts); + // package to spend. Since we already checked conflicts in the package and we don't allow + // replacements, we don't need to track the coins spent. Note that this logic will need to be + // updated if package replace-by-fee is allowed in the future. + assert(!args.m_allow_bip125_replacement); m_viewmempool.PackageAddTransaction(ws.m_ptx); } @@ -1188,9 +1110,8 @@ static MempoolAcceptResult AcceptToMemoryPoolWithTime(const CChainParams& chainp { std::vector<COutPoint> coins_to_uncache; MemPoolAccept::ATMPArgs args { chainparams, nAcceptTime, bypass_limits, coins_to_uncache, - test_accept, /* disallow_mempool_conflicts */ false }; + test_accept, /* m_allow_bip125_replacement */ true }; - assert(std::addressof(::ChainstateActive()) == std::addressof(active_chainstate)); const MempoolAcceptResult result = MemPoolAccept(pool, active_chainstate).AcceptSingleTransaction(tx, args); if (result.m_result_type != MempoolAcceptResult::ResultType::VALID) { // Remove coins that were not present in the coins cache before calling @@ -1210,7 +1131,6 @@ static MempoolAcceptResult AcceptToMemoryPoolWithTime(const CChainParams& chainp MempoolAcceptResult AcceptToMemoryPool(CChainState& active_chainstate, CTxMemPool& pool, const CTransactionRef& tx, bool bypass_limits, bool test_accept) { - assert(std::addressof(::ChainstateActive()) == std::addressof(active_chainstate)); return AcceptToMemoryPoolWithTime(Params(), pool, active_chainstate, tx, GetTime(), bypass_limits, test_accept); } @@ -1225,12 +1145,10 @@ PackageMempoolAcceptResult ProcessNewPackage(CChainState& active_chainstate, CTx std::vector<COutPoint> coins_to_uncache; const CChainParams& chainparams = Params(); MemPoolAccept::ATMPArgs args { chainparams, GetTime(), /* bypass_limits */ false, coins_to_uncache, - test_accept, /* disallow_mempool_conflicts */ true }; - assert(std::addressof(::ChainstateActive()) == std::addressof(active_chainstate)); + test_accept, /* m_allow_bip125_replacement */ false }; const PackageMempoolAcceptResult result = MemPoolAccept(pool, active_chainstate).AcceptMultipleTransactions(package, args); // Uncache coins pertaining to transactions that were not submitted to the mempool. - // Ensure the cache is still within its size limits. for (const COutPoint& hashTx : coins_to_uncache) { active_chainstate.CoinsTip().Uncache(hashTx); } @@ -1366,7 +1284,6 @@ static void AlertNotify(const std::string& strMessage) void CChainState::CheckForkWarningConditions() { AssertLockHeld(cs_main); - assert(std::addressof(::ChainstateActive()) == std::addressof(*this)); // Before we get past initial download, we cannot reliably alert about forks // (we assume we don't get stuck on a fork before finishing our initial sync) @@ -1385,7 +1302,6 @@ void CChainState::CheckForkWarningConditions() // Called both upon regular invalid block discovery *and* InvalidateBlock void CChainState::InvalidChainFound(CBlockIndex* pindexNew) { - assert(std::addressof(::ChainstateActive()) == std::addressof(*this)); if (!pindexBestInvalid || pindexNew->nChainWork > pindexBestInvalid->nChainWork) pindexBestInvalid = pindexNew; if (pindexBestHeader != nullptr && pindexBestHeader->GetAncestor(pindexNew->nHeight) == pindexNew) { @@ -1446,7 +1362,6 @@ bool CScriptCheck::operator()() { int BlockManager::GetSpendHeight(const CCoinsViewCache& inputs) { AssertLockHeld(cs_main); - assert(std::addressof(g_chainman.m_blockman) == std::addressof(*this)); CBlockIndex* pindexPrev = LookupBlockIndex(inputs.GetBestBlock()); return pindexPrev->nHeight + 1; } @@ -2330,14 +2245,13 @@ static void UpdateTip(CTxMemPool& mempool, const CBlockIndex* pindexNew, const C } bilingual_str warning_messages; - assert(std::addressof(::ChainstateActive()) == std::addressof(active_chainstate)); if (!active_chainstate.IsInitialBlockDownload()) { const CBlockIndex* pindex = pindexNew; for (int bit = 0; bit < VERSIONBITS_NUM_BITS; bit++) { WarningBitsConditionChecker checker(bit); ThresholdState state = checker.GetStateFor(pindex, chainParams.GetConsensus(), warningcache[bit]); if (state == ThresholdState::ACTIVE || state == ThresholdState::LOCKED_IN) { - const bilingual_str warning = strprintf(_("Warning: unknown new rules activated (versionbit %i)"), bit); + const bilingual_str warning = strprintf(_("Unknown new rules activated (versionbit %i)"), bit); if (state == ThresholdState::ACTIVE) { DoWarning(warning); } else { @@ -2346,7 +2260,6 @@ static void UpdateTip(CTxMemPool& mempool, const CBlockIndex* pindexNew, const C } } } - assert(std::addressof(::ChainstateActive()) == std::addressof(active_chainstate)); LogPrintf("%s: new best=%s height=%d version=0x%08x log2_work=%f tx=%lu date='%s' progress=%f cache=%.1fMiB(%utxo)%s\n", __func__, pindexNew->GetBlockHash().ToString(), pindexNew->nHeight, pindexNew->nVersion, log(pindexNew->nChainWork.getdouble())/log(2.0), (unsigned long)pindexNew->nChainTx, @@ -2606,7 +2519,6 @@ bool CChainState::ActivateBestChainStep(BlockValidationState& state, const CChai { AssertLockHeld(cs_main); AssertLockHeld(m_mempool.cs); - assert(std::addressof(::ChainstateActive()) == std::addressof(*this)); const CBlockIndex* pindexOldTip = m_chain.Tip(); const CBlockIndex* pindexFork = m_chain.FindFork(pindexMostWork); @@ -2706,7 +2618,6 @@ static bool NotifyHeaderTip(CChainState& chainstate) LOCKS_EXCLUDED(cs_main) { if (pindexHeader != pindexHeaderOld) { fNotify = true; - assert(std::addressof(::ChainstateActive()) == std::addressof(chainstate)); fInitialBlockDownload = chainstate.IsInitialBlockDownload(); pindexHeaderOld = pindexHeader; } @@ -2917,7 +2828,6 @@ bool CChainState::InvalidateBlock(BlockValidationState& state, const CChainParam // transactions back to the mempool if disconnecting was successful, // and we're not doing a very deep invalidation (in which case // keeping the mempool up to date is probably futile anyway). - assert(std::addressof(::ChainstateActive()) == std::addressof(*this)); UpdateMempoolForReorg(*this, m_mempool, disconnectpool, /* fAddToMempool = */ (++disconnected <= 10) && ret); if (!ret) return false; assert(invalid_walk_tip->pprev == m_chain.Tip()); @@ -3248,7 +3158,6 @@ CBlockIndex* BlockManager::GetLastCheckpoint(const CCheckpointData& data) for (const MapCheckpoints::value_type& i : reverse_iterate(checkpoints)) { const uint256& hash = i.second; - assert(std::addressof(g_chainman.m_blockman) == std::addressof(*this)); CBlockIndex* pindex = LookupBlockIndex(hash); if (pindex) { return pindex; @@ -3281,7 +3190,6 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidatio // Don't accept any forks from the main chain prior to last checkpoint. // GetLastCheckpoint finds the last checkpoint in MapCheckpoints that's in our // BlockIndex(). - assert(std::addressof(g_chainman.m_blockman) == std::addressof(blockman)); CBlockIndex* pcheckpoint = blockman.GetLastCheckpoint(params.Checkpoints()); if (pcheckpoint && nHeight < pcheckpoint->nHeight) { LogPrintf("ERROR: %s: forked chain older than last checkpoint (height %d)\n", __func__, nHeight); @@ -3485,7 +3393,6 @@ bool BlockManager::AcceptBlockHeader(const CBlockHeader& block, BlockValidationS // Exposed wrapper for AcceptBlockHeader bool ChainstateManager::ProcessNewBlockHeaders(const std::vector<CBlockHeader>& headers, BlockValidationState& state, const CChainParams& chainparams, const CBlockIndex** ppindex) { - assert(std::addressof(::ChainstateActive()) == std::addressof(ActiveChainstate())); AssertLockNotHeld(cs_main); { LOCK(cs_main); @@ -3576,7 +3483,6 @@ bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, Block // Write block to history file if (fNewBlock) *fNewBlock = true; - assert(std::addressof(::ChainActive()) == std::addressof(m_chain)); try { FlatFilePos blockPos = SaveBlockToDisk(block, pindex->nHeight, m_chain, chainparams, dbp); if (blockPos.IsNull()) { @@ -3598,7 +3504,6 @@ bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, Block bool ChainstateManager::ProcessNewBlock(const CChainParams& chainparams, const std::shared_ptr<const CBlock>& block, bool force_processing, bool* new_block) { AssertLockNotHeld(cs_main); - assert(std::addressof(::ChainstateActive()) == std::addressof(ActiveChainstate())); { CBlockIndex *pindex = nullptr; @@ -3643,7 +3548,6 @@ bool TestBlockValidity(BlockValidationState& state, bool fCheckMerkleRoot) { AssertLockHeld(cs_main); - assert(std::addressof(::ChainstateActive()) == std::addressof(chainstate)); assert(pindexPrev && pindexPrev == chainstate.m_chain.Tip()); CCoinsViewCache viewNew(&chainstate.CoinsTip()); uint256 block_hash(block.GetHash()); @@ -3653,7 +3557,6 @@ bool TestBlockValidity(BlockValidationState& state, indexDummy.phashBlock = &block_hash; // NOTE: CheckBlockHeader is called by CheckBlock - assert(std::addressof(g_chainman.m_blockman) == std::addressof(chainstate.m_blockman)); if (!ContextualCheckBlockHeader(block, state, chainstate.m_blockman, chainparams, pindexPrev, GetAdjustedTime())) return error("%s: Consensus::ContextualCheckBlockHeader: %s", __func__, state.ToString()); if (!CheckBlock(block, state, chainparams.GetConsensus(), fCheckPOW, fCheckMerkleRoot)) @@ -3733,7 +3636,6 @@ void PruneBlockFilesManual(CChainState& active_chainstate, int nManualPruneHeigh { BlockValidationState state; const CChainParams& chainparams = Params(); - assert(std::addressof(::ChainstateActive()) == std::addressof(active_chainstate)); if (!active_chainstate.FlushStateToDisk( chainparams, state, FlushStateMode::NONE, nManualPruneHeight)) { LogPrintf("%s: failed to flush state (%s)\n", __func__, state.ToString()); @@ -3887,7 +3789,6 @@ void BlockManager::Unload() { bool CChainState::LoadBlockIndexDB(const CChainParams& chainparams) { - assert(std::addressof(::ChainstateActive()) == std::addressof(*this)); if (!m_blockman.LoadBlockIndex( chainparams.GetConsensus(), *pblocktree, setBlockIndexCandidates)) { @@ -3944,7 +3845,6 @@ bool CChainState::LoadBlockIndexDB(const CChainParams& chainparams) void CChainState::LoadMempool(const ArgsManager& args) { if (args.GetArg("-persistmempool", DEFAULT_PERSIST_MEMPOOL)) { - assert(std::addressof(::ChainstateActive()) == std::addressof(*this)); ::LoadMempool(m_mempool, *this); } m_mempool.SetIsLoaded(!ShutdownRequested()); @@ -3996,7 +3896,6 @@ bool CVerifyDB::VerifyDB( { AssertLockHeld(cs_main); - assert(std::addressof(::ChainstateActive()) == std::addressof(chainstate)); if (chainstate.m_chain.Tip() == nullptr || chainstate.m_chain.Tip()->pprev == nullptr) return true; @@ -4265,7 +4164,6 @@ bool CChainState::LoadGenesisBlock(const CChainParams& chainparams) if (m_blockman.m_block_index.count(chainparams.GenesisBlock().GetHash())) return true; - assert(std::addressof(::ChainActive()) == std::addressof(m_chain)); try { const CBlock& block = chainparams.GenesisBlock(); FlatFilePos blockPos = SaveBlockToDisk(block, 0, m_chain, chainparams, nullptr); @@ -4329,7 +4227,6 @@ void CChainState::LoadExternalBlockFile(const CChainParams& chainparams, FILE* f { LOCK(cs_main); // detect out of order blocks, and store them for later - assert(std::addressof(g_chainman.m_blockman) == std::addressof(m_blockman)); if (hash != chainparams.GetConsensus().hashGenesisBlock && !m_blockman.LookupBlockIndex(block.hashPrevBlock)) { LogPrint(BCLog::REINDEX, "%s: Out of order block %s, parent %s not known\n", __func__, hash.ToString(), block.hashPrevBlock.ToString()); @@ -4339,11 +4236,9 @@ void CChainState::LoadExternalBlockFile(const CChainParams& chainparams, FILE* f } // process in case the block isn't known yet - assert(std::addressof(g_chainman.m_blockman) == std::addressof(m_blockman)); CBlockIndex* pindex = m_blockman.LookupBlockIndex(hash); if (!pindex || (pindex->nStatus & BLOCK_HAVE_DATA) == 0) { BlockValidationState state; - assert(std::addressof(::ChainstateActive()) == std::addressof(*this)); if (AcceptBlock(pblock, state, chainparams, nullptr, true, dbp, nullptr)) { nLoaded++; } @@ -4358,13 +4253,11 @@ void CChainState::LoadExternalBlockFile(const CChainParams& chainparams, FILE* f // Activate the genesis block so normal node progress can continue if (hash == chainparams.GetConsensus().hashGenesisBlock) { BlockValidationState state; - assert(std::addressof(::ChainstateActive()) == std::addressof(*this)); if (!ActivateBestChain(state, chainparams, nullptr)) { break; } } - assert(std::addressof(::ChainstateActive()) == std::addressof(*this)); NotifyHeaderTip(*this); // Recursively process earlier encountered successors of this block @@ -4383,7 +4276,6 @@ void CChainState::LoadExternalBlockFile(const CChainParams& chainparams, FILE* f head.ToString()); LOCK(cs_main); BlockValidationState dummy; - assert(std::addressof(::ChainstateActive()) == std::addressof(*this)); if (AcceptBlock(pblockrecursive, dummy, chainparams, nullptr, true, &it->second, nullptr)) { nLoaded++; @@ -4392,7 +4284,6 @@ void CChainState::LoadExternalBlockFile(const CChainParams& chainparams, FILE* f } range.first++; mapBlocksUnknownParent.erase(it); - assert(std::addressof(::ChainstateActive()) == std::addressof(*this)); NotifyHeaderTip(*this); } } @@ -4673,7 +4564,6 @@ bool LoadMempool(CTxMemPool& pool, CChainState& active_chainstate, FopenFn mocka } if (nTime > nNow - nExpiryTimeout) { LOCK(cs_main); - assert(std::addressof(::ChainstateActive()) == std::addressof(active_chainstate)); if (AcceptToMemoryPoolWithTime(chainparams, pool, active_chainstate, tx, nTime, false /* bypass_limits */, false /* test_accept */).m_result_type == MempoolAcceptResult::ResultType::VALID) { ++count; diff --git a/src/validation.h b/src/validation.h index 359a6c779f..5720ba8071 100644 --- a/src/validation.h +++ b/src/validation.h @@ -234,11 +234,13 @@ MempoolAcceptResult AcceptToMemoryPool(CChainState& active_chainstate, CTxMemPoo bool bypass_limits, bool test_accept=false) EXCLUSIVE_LOCKS_REQUIRED(cs_main); /** -* Atomically test acceptance of a package. If the package only contains one tx, package rules still apply. +* Atomically test acceptance of a package. If the package only contains one tx, package rules still +* apply. Package validation does not allow BIP125 replacements, so the transaction(s) cannot spend +* the same inputs as any transaction in the mempool. * @param[in] txns Group of transactions which may be independent or contain -* parent-child dependencies. The transactions must not conflict, i.e. -* must not spend the same inputs, even if it would be a valid BIP125 -* replace-by-fee. Parents must appear before children. +* parent-child dependencies. The transactions must not conflict +* with each other, i.e., must not spend the same inputs. If any +* dependencies exist, parents must appear before children. * @returns a PackageMempoolAcceptResult which includes a MempoolAcceptResult for each transaction. * If a transaction fails, validation will exit early and some results may be missing. */ @@ -269,9 +271,13 @@ bool TestLockPointValidity(CChain& active_chain, const LockPoints* lp) EXCLUSIVE * Check if transaction will be BIP68 final in the next block to be created on top of tip. * @param[in] tip Chain tip to check tx sequence locks against. For example, * the tip of the current active chain. - * @param[in] coins_view Any CCoinsView that provides access to the relevant coins - * for checking sequence locks. Any CCoinsView can be passed in; - * it is assumed to be consistent with the tip. + * @param[in] coins_view Any CCoinsView that provides access to the relevant coins for + * checking sequence locks. For example, it can be a CCoinsViewCache + * that isn't connected to anything but contains all the relevant + * coins, or a CCoinsViewMemPool that is connected to the + * mempool and chainstate UTXO set. In the latter case, the caller is + * responsible for holding the appropriate locks to ensure that + * calls to GetCoin() return correct coins. * Simulates calling SequenceLocks() with data from the tip passed in. * Optionally stores in LockPoints the resulting height and time calculated and the hash * of the block needed for calculation or skips the calculation and uses the LockPoints @@ -885,10 +891,6 @@ private: CAutoFile& coins_file, const SnapshotMetadata& metadata); - // For access to m_active_chainstate. - friend CChainState& ChainstateActive(); - friend CChain& ChainActive(); - public: std::thread m_load_block; //! A single BlockManager instance is shared across each constructed @@ -1011,16 +1013,13 @@ public: //! Check to see if caches are out of balance and if so, call //! ResizeCoinsCaches() as needed. void MaybeRebalanceCaches() EXCLUSIVE_LOCKS_REQUIRED(::cs_main); -}; - -/** DEPRECATED! Please use node.chainman instead. May only be used in validation.cpp internally */ -extern ChainstateManager g_chainman GUARDED_BY(::cs_main); -/** Please prefer the identical ChainstateManager::ActiveChainstate */ -CChainState& ChainstateActive(); - -/** Please prefer the identical ChainstateManager::ActiveChain */ -CChain& ChainActive(); + ~ChainstateManager() { + LOCK(::cs_main); + UnloadBlockIndex(/* mempool */ nullptr, *this); + Reset(); + } +}; /** Global variable that points to the active block tree (protected by cs_main) */ extern std::unique_ptr<CBlockTreeDB> pblocktree; diff --git a/src/wallet/external_signer_scriptpubkeyman.cpp b/src/wallet/external_signer_scriptpubkeyman.cpp index fe2c810afa..efef1ec754 100644 --- a/src/wallet/external_signer_scriptpubkeyman.cpp +++ b/src/wallet/external_signer_scriptpubkeyman.cpp @@ -13,8 +13,6 @@ #include <utility> #include <vector> -#ifdef ENABLE_EXTERNAL_SIGNER - bool ExternalSignerScriptPubKeyMan::SetupDescriptor(std::unique_ptr<Descriptor> desc) { LOCK(cs_desc_man); @@ -62,10 +60,10 @@ bool ExternalSignerScriptPubKeyMan::DisplayAddress(const CScript scriptPubKey, c } // If sign is true, transaction must previously have been filled -TransactionError ExternalSignerScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbt, int sighash_type, bool sign, bool bip32derivs, int* n_signed) const +TransactionError ExternalSignerScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type, bool sign, bool bip32derivs, int* n_signed) const { if (!sign) { - return DescriptorScriptPubKeyMan::FillPSBT(psbt, sighash_type, false, bip32derivs, n_signed); + return DescriptorScriptPubKeyMan::FillPSBT(psbt, txdata, sighash_type, false, bip32derivs, n_signed); } // Already complete if every input is now signed @@ -84,5 +82,3 @@ TransactionError ExternalSignerScriptPubKeyMan::FillPSBT(PartiallySignedTransact FinalizePSBT(psbt); // This won't work in a multisig setup return TransactionError::OK; } - -#endif diff --git a/src/wallet/external_signer_scriptpubkeyman.h b/src/wallet/external_signer_scriptpubkeyman.h index 1786958912..8eed947b7b 100644 --- a/src/wallet/external_signer_scriptpubkeyman.h +++ b/src/wallet/external_signer_scriptpubkeyman.h @@ -5,7 +5,6 @@ #ifndef BITCOIN_WALLET_EXTERNAL_SIGNER_SCRIPTPUBKEYMAN_H #define BITCOIN_WALLET_EXTERNAL_SIGNER_SCRIPTPUBKEYMAN_H -#ifdef ENABLE_EXTERNAL_SIGNER #include <wallet/scriptpubkeyman.h> #include <memory> @@ -29,8 +28,6 @@ class ExternalSignerScriptPubKeyMan : public DescriptorScriptPubKeyMan bool DisplayAddress(const CScript scriptPubKey, const ExternalSigner &signer) const; - TransactionError FillPSBT(PartiallySignedTransaction& psbt, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const override; + TransactionError FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const override; }; -#endif - #endif // BITCOIN_WALLET_EXTERNAL_SIGNER_SCRIPTPUBKEYMAN_H diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp index ee92316b89..5a832d020b 100644 --- a/src/wallet/interfaces.cpp +++ b/src/wallet/interfaces.cpp @@ -349,9 +349,9 @@ public: TransactionError fillPSBT(int sighash_type, bool sign, bool bip32derivs, + size_t* n_signed, PartiallySignedTransaction& psbtx, - bool& complete, - size_t* n_signed) override + bool& complete) override { return m_wallet->FillPSBT(psbtx, complete, sighash_type, sign, bip32derivs, n_signed); } diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 726b13beac..4e9ba83ead 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -1530,6 +1530,18 @@ static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, c } } + // Taproot descriptors cannot be imported if Taproot is not yet active. + // Check if this is a Taproot descriptor + CTxDestination dest; + ExtractDestination(scripts[0], dest); + if (std::holds_alternative<WitnessV1Taproot>(dest)) { + // Check if Taproot is active + if (!wallet.chain().isTaprootActive()) { + // Taproot is not active, raise an error + throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import tr() descriptor when Taproot is not active"); + } + } + // If private keys are enabled, check some things. if (!wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { if (keys.keys.empty()) { diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 534c974178..ab34af2329 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3320,7 +3320,8 @@ RPCHelpMan signrawtransactionwithwallet() }, }, }, - {"sighashtype", RPCArg::Type::STR, RPCArg::Default{"ALL"}, "The signature hash type. Must be one of\n" + {"sighashtype", RPCArg::Type::STR, RPCArg::Default{"DEFAULT"}, "The signature hash type. Must be one of\n" + " \"DEFAULT\"\n" " \"ALL\"\n" " \"NONE\"\n" " \"SINGLE\"\n" @@ -3542,7 +3543,7 @@ static RPCHelpMan bumpfee_helper(std::string method_name) } else { PartiallySignedTransaction psbtx(mtx); bool complete = false; - const TransactionError err = pwallet->FillPSBT(psbtx, complete, SIGHASH_ALL, false /* sign */, true /* bip32derivs */); + const TransactionError err = pwallet->FillPSBT(psbtx, complete, SIGHASH_DEFAULT, false /* sign */, true /* bip32derivs */); CHECK_NONFATAL(err == TransactionError::OK); CHECK_NONFATAL(!complete); CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); @@ -4175,8 +4176,8 @@ static RPCHelpMan send() // First fill transaction with our data without signing, // so external signers are not asked sign more than once. bool complete; - pwallet->FillPSBT(psbtx, complete, SIGHASH_ALL, false, true); - const TransactionError err = pwallet->FillPSBT(psbtx, complete, SIGHASH_ALL, true, false); + pwallet->FillPSBT(psbtx, complete, SIGHASH_DEFAULT, false, true); + const TransactionError err = pwallet->FillPSBT(psbtx, complete, SIGHASH_DEFAULT, true, false); if (err != TransactionError::OK) { throw JSONRPCTransactionError(err); } @@ -4291,7 +4292,8 @@ static RPCHelpMan walletprocesspsbt() { {"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "The transaction base64 string"}, {"sign", RPCArg::Type::BOOL, RPCArg::Default{true}, "Also sign the transaction when updating"}, - {"sighashtype", RPCArg::Type::STR, RPCArg::Default{"ALL"}, "The signature hash type to sign with if not specified by the PSBT. Must be one of\n" + {"sighashtype", RPCArg::Type::STR, RPCArg::Default{"DEFAULT"}, "The signature hash type to sign with if not specified by the PSBT. Must be one of\n" + " \"DEFAULT\"\n" " \"ALL\"\n" " \"NONE\"\n" " \"SINGLE\"\n" diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp index 2eb9ca5c6d..c8baa0665e 100644 --- a/src/wallet/scriptpubkeyman.cpp +++ b/src/wallet/scriptpubkeyman.cpp @@ -13,7 +13,6 @@ #include <util/system.h> #include <util/time.h> #include <util/translation.h> -#include <external_signer.h> #include <wallet/scriptpubkeyman.h> #include <optional> @@ -597,7 +596,7 @@ SigningResult LegacyScriptPubKeyMan::SignMessage(const std::string& message, con return SigningResult::SIGNING_FAILED; } -TransactionError LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, int sighash_type, bool sign, bool bip32derivs, int* n_signed) const +TransactionError LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, const PrecomputedTransactionData& txdata, int sighash_type, bool sign, bool bip32derivs, int* n_signed) const { if (n_signed) { *n_signed = 0; @@ -626,7 +625,7 @@ TransactionError LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psb } SignatureData sigdata; input.FillSignatureData(sigdata); - SignPSBTInput(HidingSigningProvider(this, !sign, !bip32derivs), psbtx, i, sighash_type); + SignPSBTInput(HidingSigningProvider(this, !sign, !bip32derivs), psbtx, i, &txdata, sighash_type); bool signed_one = PSBTInputSigned(input); if (n_signed && (signed_one || !sign)) { @@ -2083,7 +2082,7 @@ SigningResult DescriptorScriptPubKeyMan::SignMessage(const std::string& message, return SigningResult::OK; } -TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, int sighash_type, bool sign, bool bip32derivs, int* n_signed) const +TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, const PrecomputedTransactionData& txdata, int sighash_type, bool sign, bool bip32derivs, int* n_signed) const { if (n_signed) { *n_signed = 0; @@ -2133,7 +2132,7 @@ TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& } } - SignPSBTInput(HidingSigningProvider(keys.get(), !sign, !bip32derivs), psbtx, i, sighash_type); + SignPSBTInput(HidingSigningProvider(keys.get(), !sign, !bip32derivs), psbtx, i, &txdata, sighash_type); bool signed_one = PSBTInputSigned(input); if (n_signed && (signed_one || !sign)) { diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h index b8e34fbac3..3c4603608c 100644 --- a/src/wallet/scriptpubkeyman.h +++ b/src/wallet/scriptpubkeyman.h @@ -235,7 +235,7 @@ public: /** Sign a message with the given script */ virtual SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const { return SigningResult::SIGNING_FAILED; }; /** Adds script and derivation path information to a PSBT, and optionally signs it. */ - virtual TransactionError FillPSBT(PartiallySignedTransaction& psbt, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const { return TransactionError::INVALID_PSBT; } + virtual TransactionError FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const { return TransactionError::INVALID_PSBT; } virtual uint256 GetID() const { return uint256(); } @@ -394,7 +394,7 @@ public: bool SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, std::string>& input_errors) const override; SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const override; - TransactionError FillPSBT(PartiallySignedTransaction& psbt, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const override; + TransactionError FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const override; uint256 GetID() const override; @@ -605,7 +605,7 @@ public: bool SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, std::string>& input_errors) const override; SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const override; - TransactionError FillPSBT(PartiallySignedTransaction& psbt, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const override; + TransactionError FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const override; uint256 GetID() const override; diff --git a/src/wallet/test/psbt_wallet_tests.cpp b/src/wallet/test/psbt_wallet_tests.cpp index ce7e661b67..1cefa386b7 100644 --- a/src/wallet/test/psbt_wallet_tests.cpp +++ b/src/wallet/test/psbt_wallet_tests.cpp @@ -71,7 +71,7 @@ BOOST_AUTO_TEST_CASE(psbt_updater_test) // Try to sign the mutated input SignatureData sigdata; - BOOST_CHECK(spk_man->FillPSBT(psbtx, SIGHASH_ALL, true, true) != TransactionError::OK); + BOOST_CHECK(spk_man->FillPSBT(psbtx, PrecomputePSBTData(psbtx), SIGHASH_ALL, true, true) != TransactionError::OK); } BOOST_AUTO_TEST_CASE(parse_hd_keypath) diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index 6a791748b4..a0070b8dd3 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -83,17 +83,17 @@ static void AddKey(CWallet& wallet, const CKey& key) BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) { // Cap last block file size, and mine new block in a new block file. - CBlockIndex* oldTip = ::ChainActive().Tip(); + CBlockIndex* oldTip = m_node.chainman->ActiveChain().Tip(); GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE; CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); - CBlockIndex* newTip = ::ChainActive().Tip(); + CBlockIndex* newTip = m_node.chainman->ActiveChain().Tip(); // Verify ScanForWalletTransactions fails to read an unknown start block. { CWallet wallet(m_node.chain.get(), "", CreateDummyWalletDatabase()); { LOCK(wallet.cs_wallet); - wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash()); + wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash()); } AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(wallet); @@ -112,7 +112,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) CWallet wallet(m_node.chain.get(), "", CreateDummyWalletDatabase()); { LOCK(wallet.cs_wallet); - wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash()); + wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash()); } AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(wallet); @@ -138,7 +138,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) CWallet wallet(m_node.chain.get(), "", CreateDummyWalletDatabase()); { LOCK(wallet.cs_wallet); - wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash()); + wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash()); } AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(wallet); @@ -163,7 +163,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) CWallet wallet(m_node.chain.get(), "", CreateDummyWalletDatabase()); { LOCK(wallet.cs_wallet); - wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash()); + wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash()); } AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(wallet); @@ -180,10 +180,10 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup) { // Cap last block file size, and mine new block in a new block file. - CBlockIndex* oldTip = ::ChainActive().Tip(); + CBlockIndex* oldTip = m_node.chainman->ActiveChain().Tip(); GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE; CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); - CBlockIndex* newTip = ::ChainActive().Tip(); + CBlockIndex* newTip = m_node.chainman->ActiveChain().Tip(); // Prune the older block file. { @@ -242,7 +242,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) { // Create two blocks with same timestamp to verify that importwallet rescan // will pick up both blocks, not just the first. - const int64_t BLOCK_TIME = ::ChainActive().Tip()->GetBlockTimeMax() + 5; + const int64_t BLOCK_TIME = m_node.chainman->ActiveChain().Tip()->GetBlockTimeMax() + 5; SetMockTime(BLOCK_TIME); m_coinbase_txns.emplace_back(CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]); m_coinbase_txns.emplace_back(CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]); @@ -265,7 +265,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) spk_man->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()); AddWallet(wallet); - wallet->SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash()); + wallet->SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash()); } JSONRPCRequest request; request.params.setArray(); @@ -286,7 +286,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) request.params.setArray(); request.params.push_back(backup_file); AddWallet(wallet); - wallet->SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash()); + wallet->SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash()); ::importwallet().HandleRequest(request); RemoveWallet(wallet, std::nullopt); @@ -313,9 +313,9 @@ BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup) CWalletTx wtx(&wallet, m_coinbase_txns.back()); LOCK2(wallet.cs_wallet, spk_man->cs_KeyStore); - wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash()); + wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash()); - CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, ::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash(), 0); + CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash(), 0); wtx.m_confirm = confirm; // Call GetImmatureCredit() once before adding the key to the wallet to @@ -483,16 +483,16 @@ public: wallet = std::make_unique<CWallet>(m_node.chain.get(), "", CreateMockWalletDatabase()); { LOCK2(wallet->cs_wallet, ::cs_main); - wallet->SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash()); + wallet->SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash()); } wallet->LoadWallet(); AddKey(*wallet, coinbaseKey); WalletRescanReserver reserver(*wallet); reserver.reserve(); - CWallet::ScanResult result = wallet->ScanForWalletTransactions(::ChainActive().Genesis()->GetBlockHash(), 0 /* start_height */, {} /* max_height */, reserver, false /* update */); + CWallet::ScanResult result = wallet->ScanForWalletTransactions(m_node.chainman->ActiveChain().Genesis()->GetBlockHash(), 0 /* start_height */, {} /* max_height */, reserver, false /* update */); BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS); - BOOST_CHECK_EQUAL(result.last_scanned_block, ::ChainActive().Tip()->GetBlockHash()); - BOOST_CHECK_EQUAL(*result.last_scanned_height, ::ChainActive().Height()); + BOOST_CHECK_EQUAL(result.last_scanned_block, m_node.chainman->ActiveChain().Tip()->GetBlockHash()); + BOOST_CHECK_EQUAL(*result.last_scanned_height, m_node.chainman->ActiveChain().Height()); BOOST_CHECK(result.last_failed_block.IsNull()); } @@ -521,10 +521,10 @@ public: CreateAndProcessBlock({CMutableTransaction(blocktx)}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); LOCK(wallet->cs_wallet); - wallet->SetLastBlockProcessed(wallet->GetLastBlockHeight() + 1, ::ChainActive().Tip()->GetBlockHash()); + wallet->SetLastBlockProcessed(wallet->GetLastBlockHeight() + 1, m_node.chainman->ActiveChain().Tip()->GetBlockHash()); auto it = wallet->mapWallet.find(tx->GetHash()); BOOST_CHECK(it != wallet->mapWallet.end()); - CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, ::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash(), 1); + CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash(), 1); it->second.m_confirm = confirm; return it->second; } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 4b6630de3c..256faf2b23 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -8,6 +8,7 @@ #include <chain.h> #include <consensus/consensus.h> #include <consensus/validation.h> +#include <external_signer.h> #include <fs.h> #include <interfaces/chain.h> #include <interfaces/wallet.h> @@ -1807,7 +1808,7 @@ bool CWallet::SignTransaction(CMutableTransaction& tx) const coins[input.prevout] = Coin(wtx.tx->vout[input.prevout.n], wtx.m_confirm.block_height, wtx.IsCoinBase()); } std::map<int, std::string> input_errors; - return SignTransaction(tx, coins, SIGHASH_ALL, input_errors); + return SignTransaction(tx, coins, SIGHASH_DEFAULT, input_errors); } bool CWallet::SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, std::string>& input_errors) const @@ -1830,6 +1831,7 @@ TransactionError CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bool& comp if (n_signed) { *n_signed = 0; } + const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx); LOCK(cs_wallet); // Get all of the previous transactions for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { @@ -1856,7 +1858,7 @@ TransactionError CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bool& comp // Fill in information from ScriptPubKeyMans for (ScriptPubKeyMan* spk_man : GetAllScriptPubKeyMans()) { int n_signed_this_spkm = 0; - TransactionError res = spk_man->FillPSBT(psbtx, sighash_type, sign, bip32derivs, &n_signed_this_spkm); + TransactionError res = spk_man->FillPSBT(psbtx, txdata, sighash_type, sign, bip32derivs, &n_signed_this_spkm); if (res != TransactionError::OK) { return res; } @@ -2215,7 +2217,6 @@ void ReserveDestination::ReturnDestination() bool CWallet::DisplayAddress(const CTxDestination& dest) { -#ifdef ENABLE_EXTERNAL_SIGNER CScript scriptPubKey = GetScriptForDestination(dest); const auto spk_man = GetScriptPubKeyMan(scriptPubKey); if (spk_man == nullptr) { @@ -2227,9 +2228,6 @@ bool CWallet::DisplayAddress(const CTxDestination& dest) } ExternalSigner signer = ExternalSignerScriptPubKeyMan::GetExternalSigner(); return signer_spk_man->DisplayAddress(scriptPubKey, signer); -#else - return false; -#endif } void CWallet::LockCoin(const COutPoint& output) @@ -3063,12 +3061,8 @@ void CWallet::ConnectScriptPubKeyManNotifiers() void CWallet::LoadDescriptorScriptPubKeyMan(uint256 id, WalletDescriptor& desc) { if (IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER)) { -#ifdef ENABLE_EXTERNAL_SIGNER auto spk_manager = std::unique_ptr<ScriptPubKeyMan>(new ExternalSignerScriptPubKeyMan(*this, desc)); m_spk_managers[id] = std::move(spk_manager); -#else - throw std::runtime_error(std::string(__func__) + ": Compiled without external signing support (required for external signing)"); -#endif } else { auto spk_manager = std::unique_ptr<ScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, desc)); m_spk_managers[id] = std::move(spk_manager); @@ -3108,7 +3102,6 @@ void CWallet::SetupDescriptorScriptPubKeyMans() } } } else { -#ifdef ENABLE_EXTERNAL_SIGNER ExternalSigner signer = ExternalSignerScriptPubKeyMan::GetExternalSigner(); // TODO: add account parameter @@ -3135,9 +3128,6 @@ void CWallet::SetupDescriptorScriptPubKeyMans() AddActiveScriptPubKeyMan(id, t, internal); } } -#else - throw std::runtime_error(std::string(__func__) + ": Compiled without external signing support (required for external signing)"); -#endif // ENABLE_EXTERNAL_SIGNER } } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index d0e26c416c..66f39edb4d 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -21,7 +21,6 @@ #include <validationinterface.h> #include <wallet/coinselection.h> #include <wallet/crypter.h> -#include <external_signer.h> #include <wallet/receive.h> #include <wallet/scriptpubkeyman.h> #include <wallet/spend.h> |