From aa7b390a84bea5387afc73d7a878a52352f09dce Mon Sep 17 00:00:00 2001 From: DCG-Claude Date: Mon, 2 Nov 2026 02:09:57 -0600 Subject: [PATCH 1/6] Merge bitcoin/bitcoin#28170: p2p: adaptive connections services flags 27f260aa6e04f82dad78e9a06d58927546143a27 net: remove now unused global 'g_initial_block_download_completed' (furszy) aff7d92b1500e2478ce36a7e86ae47df47dda178 test: add coverage for peerman adaptive connections service flags (furszy) 6ed53602ac7c565273b5722de167cb2569a0e381 net: peer manager, dynamically adjust desirable services flag (furszy) 9f36e591c551ec2e58a6496334541bfdae8fdfe5 net: move state dependent peer services flags (furszy) f9ac96b8d6f4eba23c88f302b22a2c676e351263 net: decouple state independent service flags from desirable ones (furszy) 97df4e38879d2644aeec34c1eef241fed627333e net: store best block tip time inside PeerManager (furszy) Pull request description: Derived from #28120 discussion. By relocating the peer desirable services flags into the peer manager, we allow the connections acceptance process to handle post-IBD potential stalling scenarios. The peer manager will be able to dynamically adjust the services flags based on the node's proximity to the tip (back and forth). Allowing the node to recover from the following post-IBD scenario: Suppose the node has successfully synced the chain, but later experienced dropped connections and remained inactive for a duration longer than the limited peers threshold (the timeframe within which limited peers can provide blocks). In such cases, upon reconnecting to the network, the node might only establish connections with limited peers, filling up all available outbound slots. Resulting in an inability to synchronize the chain (because limited peers will not provide blocks older than the `NODE_NETWORK_LIMITED_MIN_BLOCKS` threshold). ACKs for top commit: achow101: ACK 27f260aa6e04f82dad78e9a06d58927546143a27 vasild: ACK 27f260aa6e04f82dad78e9a06d58927546143a27 naumenkogs: ACK 27f260aa6e04f82dad78e9a06d58927546143a27 mzumsande: Light Code Review ACK 27f260aa6e04f82dad78e9a06d58927546143a27 andrewtoth: ACK 27f260aa6e04f82dad78e9a06d58927546143a27 Tree-SHA512: 07befb9bcd0b60a4e7c45e4429c02e7b6c66244f0910f4b2ad97c9b98258b6f46c914660a717b5ed4ef4814d0dbfae6e18e6559fe9bec7d0fbc2034109200953 Dash adaptations: - src/protocol.h: SeedsServiceFlags() returns NODE_NETWORK only, since Dash has no NODE_WITNESS (matching Dash's old GetDesirableServiceFlags). - src/net_processing.cpp: GetDesirableServiceFlags() returns NODE_NETWORK_LIMITED / NODE_NETWORK without NODE_WITNESS, which Dash lacks. - src/net_processing.cpp: ApproximateBestBlockDepth() declared next to Dash's CanDirectFetch(), which sits earlier in PeerManagerImpl than upstream's; Dash's m_handlers kept. - src/net_processing.cpp: SetBestBlock/HasAllDesirableServiceFlags/GetDesirableServiceFlags added beside Dash's relay, object-request and handler overrides, all kept. - src/net_processing.cpp: UpdatedBlockTip's direct m_best_height assignment and SetServiceFlagsIBDCache call replaced by SetBestBlock(height, block time). - src/init.cpp: tip_info->block_time uses best_block_time; Dash's extra tip_info->block_hash line kept. - src/protocol.cpp: removed the IBD-cache global and the two functions; Dash's MAKE_MSG macro and Dash message types untouched. - src/test/peerman_tests.cpp: builds peerman with Dash's MakePeerManager() test helper, since PeerManager::make needs Dash's masternode/LLMQ/CoinJoin managers. - src/test/peerman_tests.cpp: uses Dash's BlockAssembler(chainstate, node, mempool) and 3-arg ProcessNewBlock (no min_pow_checked); NODE_WITNESS removed from flags. - src/test/peerman_tests.cpp: comment says 2.5 minutes, not 10, for Dash's block spacing; the test math already uses nPowTargetSpacing. Not applicable to Dash (intentionally omitted): - contrib/seeds/README.md: Dash's README builds seeds from the protx list and has no PATTERN_AGENT / GetDesirableServiceFlags paragraph to rename. --- src/Makefile.test.include | 1 + src/init.cpp | 6 ++- src/net.cpp | 8 ++-- src/net.h | 6 +++ src/net_processing.cpp | 47 ++++++++++++++++++++--- src/net_processing.h | 27 +++++++++++++- src/protocol.cpp | 14 ------- src/protocol.h | 43 +++------------------ src/test/fuzz/integer.cpp | 1 - src/test/peerman_tests.cpp | 76 ++++++++++++++++++++++++++++++++++++++ 10 files changed, 164 insertions(+), 65 deletions(-) create mode 100644 src/test/peerman_tests.cpp diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 7a180f80daa4..c66e1bae5897 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -173,6 +173,7 @@ BITCOIN_TESTS =\ test/net_tests.cpp \ test/netbase_tests.cpp \ test/orphanage_tests.cpp \ + test/peerman_tests.cpp \ test/pmt_tests.cpp \ test/policyestimator_tests.cpp \ test/pool_tests.cpp \ diff --git a/src/init.cpp b/src/init.cpp index 38bda8f4212a..2ce62e4dd17f 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -2598,13 +2598,15 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) // ********************************************************* Step 12: start node //// debug print + int64_t best_block_time{}; { LOCK(cs_main); LogPrintf("block tree size = %u\n", chainman.BlockIndex().size()); chain_active_height = chainman.ActiveChain().Height(); + best_block_time = chainman.ActiveChain().Tip() ? chainman.ActiveChain().Tip()->GetBlockTime() : chainman.GetParams().GenesisBlock().GetBlockTime(); if (tip_info) { tip_info->block_height = chain_active_height; - tip_info->block_time = chainman.ActiveChain().Tip() ? chainman.ActiveChain().Tip()->GetBlockTime() : chainman.GetParams().GenesisBlock().GetBlockTime(); + tip_info->block_time = best_block_time; tip_info->block_hash = chainman.ActiveChain().Tip() ? chainman.ActiveChain().Tip()->GetBlockHash() : chainman.GetParams().GenesisBlock().GetHash(); tip_info->verification_progress = GuessVerificationProgress(chainman.GetParams().TxData(), chainman.ActiveChain().Tip()); } @@ -2614,7 +2616,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) } } LogPrintf("nBestHeight = %d\n", chain_active_height); - if (node.peerman) node.peerman->SetBestHeight(chain_active_height); + if (node.peerman) node.peerman->SetBestBlock(chain_active_height, std::chrono::seconds{best_block_time}); // Map ports with UPnP or NAT-PMP. StartMapPort(args.GetBoolArg("-upnp", DEFAULT_UPNP), args.GetBoolArg("-natpmp", DEFAULT_NATPMP)); diff --git a/src/net.cpp b/src/net.cpp index 131d8083899c..fa027c41717c 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -219,7 +219,7 @@ static std::vector ConvertSeeds(const std::vector &vSeedsIn) while (!s.eof()) { CService endpoint; s >> endpoint; - CAddress addr{endpoint, GetDesirableServiceFlags(NODE_NONE)}; + CAddress addr{endpoint, SeedsServiceFlags()}; addr.nTime = rng.rand_uniform_delay(Now() - one_week, -one_week); LogPrint(BCLog::NET, "Added hardcoded seed: %s\n", addr.ToStringAddrPort()); vSeedsOut.push_back(addr); @@ -2780,7 +2780,7 @@ void CConnman::ThreadDNSAddressSeed() AddAddrFetch(seed); } else { std::vector vAdd; - ServiceFlags requiredServiceBits = GetDesirableServiceFlags(NODE_NONE); + constexpr ServiceFlags requiredServiceBits{SeedsServiceFlags()}; std::string host = strprintf("x%x.%s", requiredServiceBits, seed); CNetAddr resolveSource; if (!resolveSource.SetInternal(host)) { @@ -3178,7 +3178,7 @@ void CConnman::ThreadOpenConnections(const std::vector connect, CDe const CAddress addr = m_anchors.back(); m_anchors.pop_back(); if (!addr.IsValid() || IsLocal(addr) || !g_reachable_nets.Contains(addr) || - !HasAllDesirableServiceFlags(addr.nServices) || + !m_msgproc->HasAllDesirableServiceFlags(addr.nServices) || outbound_ipv46_peer_netgroups.count(m_netgroupman.GetGroup(addr))) continue; addrConnect = addr; LogPrint(BCLog::NET, "Trying to make an anchor connection to %s\n", addrConnect.ToStringAddrPort()); @@ -3253,7 +3253,7 @@ void CConnman::ThreadOpenConnections(const std::vector connect, CDe // for non-feelers, require all the services we'll want, // for feelers, only require they be a full node (only because most // SPV clients don't have a good address DB available) - if (!fFeeler && !HasAllDesirableServiceFlags(addr.nServices)) { + if (!fFeeler && !m_msgproc->HasAllDesirableServiceFlags(addr.nServices)) { continue; } else if (fFeeler && !MayHaveUsefulAddressDB(addr.nServices)) { continue; diff --git a/src/net.h b/src/net.h index bef696cc76d5..3998a6e4ac4e 100644 --- a/src/net.h +++ b/src/net.h @@ -1177,6 +1177,12 @@ class NetEventsInterface /** Handle removal of a peer (clear state) */ virtual void FinalizeNode(const CNode& node) = 0; + /** + * Callback to determine whether the given set of service flags are sufficient + * for a peer to be "relevant". + */ + virtual bool HasAllDesirableServiceFlags(ServiceFlags services) const = 0; + /** * Process protocol messages received from a given node * diff --git a/src/net_processing.cpp b/src/net_processing.cpp index ef6825d814ea..d4004d3112bc 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -177,6 +177,8 @@ static const unsigned int MAX_BLOCKS_TO_ANNOUNCE = 8; static const int MAX_NUM_UNCONNECTING_HEADERS_MSGS = 10; /** Minimum blocks required to signal NODE_NETWORK_LIMITED */ static const unsigned int NODE_NETWORK_LIMITED_MIN_BLOCKS = 288; +/** Window, in blocks, for connecting to NODE_NETWORK_LIMITED peers */ +static const unsigned int NODE_NETWORK_LIMITED_ALLOW_CONN_BLOCKS = 144; /** Average delay between local address broadcasts */ static constexpr auto AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL{24h}; /** Average delay between peer address broadcasts */ @@ -611,6 +613,7 @@ class PeerManagerImpl final : public PeerManager void InitializeNode(CNode& node, ServiceFlags our_services) override EXCLUSIVE_LOCKS_REQUIRED(!m_object_request_mutex, !m_peer_mutex); void FinalizeNode(const CNode& node) override EXCLUSIVE_LOCKS_REQUIRED(!m_object_request_mutex, !m_peer_mutex); + bool HasAllDesirableServiceFlags(ServiceFlags services) const override; bool ProcessMessages(CNode* pfrom, std::atomic& interrupt) override EXCLUSIVE_LOCKS_REQUIRED(!m_object_request_mutex, !m_peer_mutex, !m_recent_confirmed_transactions_mutex, !m_most_recent_block_mutex, g_msgproc_mutex); @@ -632,13 +635,18 @@ class PeerManagerImpl final : public PeerManager void RelayTransaction(const uint256& txid) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); void RelayRecoveredSig(const llmq::CRecoveredSig& sig, bool proactive_relay) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); void RelayDSQ(const CCoinJoinQueue& queue) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); - void SetBestHeight(int height) override { m_best_height = height; }; + void SetBestBlock(int height, std::chrono::seconds time) override + { + m_best_height = height; + m_best_block_time = time; + }; void UnitTestMisbehaving(NodeId peer_id, int howmuch) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex) { Misbehaving(*Assert(GetPeerRef(peer_id)), howmuch, ""); }; void ProcessMessage(CNode& pfrom, const std::string& msg_type, CDataStream& vRecv, const std::chrono::microseconds time_received, const std::atomic& interruptMsgProc) override EXCLUSIVE_LOCKS_REQUIRED(!m_object_request_mutex, !m_peer_mutex, !m_recent_confirmed_transactions_mutex, !m_most_recent_block_mutex, g_msgproc_mutex); void UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds) override; + ServiceFlags GetDesirableServiceFlags(ServiceFlags services) const override; size_t GetRequestedObjectCount(NodeId nodeid) const override EXCLUSIVE_LOCKS_REQUIRED(!m_object_request_mutex); /** Implements external handlers logic */ @@ -863,6 +871,8 @@ class PeerManagerImpl final : public PeerManager /** The height of the best chain */ std::atomic m_best_height{-1}; + /** The time of the best chain tip block */ + std::atomic m_best_block_time{0s}; /** Next time to check for stale tip */ std::chrono::seconds m_stale_tip_check_time GUARDED_BY(cs_main){0s}; @@ -902,6 +912,12 @@ class PeerManagerImpl final : public PeerManager void UpdateBlockAvailability(NodeId nodeid, const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main); bool CanDirectFetch() EXCLUSIVE_LOCKS_REQUIRED(cs_main); + /** + * Estimates the distance, in blocks, between the best-known block and the network chain tip. + * Utilizes the best-block time and the chainparams blocks spacing to approximate it. + */ + int64_t ApproximateBestBlockDepth() const; + /** * To prevent fingerprinting attacks, only send blocks/headers outside of the * active chain if they are no more than a month older (both in time, and in @@ -1450,6 +1466,11 @@ bool PeerManagerImpl::TipMayBeStale() return m_last_tip_update.load() < GetTime() - std::chrono::seconds{consensusParams.nPowTargetSpacing * 3} && mapBlocksInFlight.empty(); } +int64_t PeerManagerImpl::ApproximateBestBlockDepth() const +{ + return (GetTime() - m_best_block_time.load()).count() / m_chainparams.GetConsensus().nPowTargetSpacing; +} + bool PeerManagerImpl::CanDirectFetch() { return m_chainman.ActiveChain().Tip()->Time() > NodeClock::now() - m_chainparams.GetConsensus().PowTargetSpacing() * 20; @@ -1886,6 +1907,23 @@ void PeerManagerImpl::FinalizeNode(const CNode& node) { LogPrint(BCLog::NET, "Cleared nodestate for peer=%d\n", nodeid); } +bool PeerManagerImpl::HasAllDesirableServiceFlags(ServiceFlags services) const +{ + // Shortcut for (services & GetDesirableServiceFlags(services)) == GetDesirableServiceFlags(services) + return !(GetDesirableServiceFlags(services) & (~services)); +} + +ServiceFlags PeerManagerImpl::GetDesirableServiceFlags(ServiceFlags services) const +{ + if (services & NODE_NETWORK_LIMITED) { + // Limited peers are desirable when we are close to the tip. + if (ApproximateBestBlockDepth() < NODE_NETWORK_LIMITED_ALLOW_CONN_BLOCKS) { + return ServiceFlags(NODE_NETWORK_LIMITED); + } + } + return ServiceFlags(NODE_NETWORK); +} + PeerRef PeerManagerImpl::GetPeerRef(NodeId id) const { READ_LOCK(m_peer_mutex); @@ -2300,10 +2338,9 @@ void PeerManagerImpl::NewPoWValidBlock(const CBlockIndex *pindex, const std::sha * Update our best height and announce any block hashes which weren't previously * in m_chainman.ActiveChain() to our peers. */ -void PeerManagerImpl::UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload){ - m_best_height = pindexNew->nHeight; - - SetServiceFlagsIBDCache(!fInitialDownload); +void PeerManagerImpl::UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) +{ + SetBestBlock(pindexNew->nHeight, std::chrono::seconds{pindexNew->GetBlockTime()}); // Don't relay inventory during initial block download. if (fInitialDownload) return; diff --git a/src/net_processing.h b/src/net_processing.h index 20b30fbddf33..d83a30d0f493 100644 --- a/src/net_processing.h +++ b/src/net_processing.h @@ -230,8 +230,8 @@ class PeerManager : public CValidationInterface, public NetEventsInterface, publ /** Relay recovered sigs to all interested peers */ virtual void RelayRecoveredSig(const llmq::CRecoveredSig& sig, bool proactive_relay) = 0; - /** Set the best height */ - virtual void SetBestHeight(int height) = 0; + /** Set the height of the best block and its time (seconds since epoch). */ + virtual void SetBestBlock(int height, std::chrono::seconds time) = 0; /* Public for unit testing. */ virtual void UnitTestMisbehaving(NodeId peer_id, int howmuch) = 0; @@ -252,6 +252,29 @@ class PeerManager : public CValidationInterface, public NetEventsInterface, publ /** This function is used for testing the stale tip eviction logic, see denialofservice_tests.cpp */ virtual void UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds) = 0; + /** + * Gets the set of service flags which are "desirable" for a given peer. + * + * These are the flags which are required for a peer to support for them + * to be "interesting" to us, ie for us to wish to use one of our few + * outbound connection slots for or for us to wish to prioritize keeping + * their connection around. + * + * Relevant service flags may be peer- and state-specific in that the + * version of the peer may determine which flags are required (eg in the + * case of NODE_NETWORK_LIMITED where we seek out NODE_NETWORK peers + * unless they set NODE_NETWORK_LIMITED and we are out of IBD, in which + * case NODE_NETWORK_LIMITED suffices). + * + * Thus, generally, avoid calling with 'services' == NODE_NONE, unless + * state-specific flags must absolutely be avoided. When called with + * 'services' == NODE_NONE, the returned desirable service flags are + * guaranteed to not change dependent on state - ie they are suitable for + * use when describing peers which we know to be desirable, but for which + * we do not have a confirmed set of service flags. + */ + virtual ServiceFlags GetDesirableServiceFlags(ServiceFlags services) const = 0; + virtual size_t GetRequestedObjectCount(NodeId nodeid) const = 0; virtual void AddExtraHandler(std::unique_ptr&& handler) = 0; diff --git a/src/protocol.cpp b/src/protocol.cpp index 5b225e3627d6..f27f00bf835b 100644 --- a/src/protocol.cpp +++ b/src/protocol.cpp @@ -9,8 +9,6 @@ #include -static std::atomic g_initial_block_download_completed(false); - #define MAKE_MSG(var_name, p2p_name_str) \ const char* var_name=p2p_name_str; \ static_assert(std::size(p2p_name_str) <= CMessageHeader::COMMAND_SIZE + 1, "p2p_name_str cannot be greater than COMMAND_SIZE"); // Includes +1 for null termination character. @@ -247,18 +245,6 @@ bool CMessageHeader::IsCommandValid() const return true; } - -ServiceFlags GetDesirableServiceFlags(ServiceFlags services) { - if ((services & NODE_NETWORK_LIMITED) && g_initial_block_download_completed) { - return ServiceFlags(NODE_NETWORK_LIMITED); - } - return ServiceFlags(NODE_NETWORK); -} - -void SetServiceFlagsIBDCache(bool state) { - g_initial_block_download_completed = state; -} - CInv::CInv() { type = 0; diff --git a/src/protocol.h b/src/protocol.h index 754abb35b1a5..c9c8337d9965 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -347,43 +347,12 @@ enum ServiceFlags : uint64_t { std::vector serviceFlagsToStr(uint64_t flags); /** - * Gets the set of service flags which are "desirable" for a given peer. - * - * These are the flags which are required for a peer to support for them - * to be "interesting" to us, ie for us to wish to use one of our few - * outbound connection slots for or for us to wish to prioritize keeping - * their connection around. - * - * Relevant service flags may be peer- and state-specific in that the - * version of the peer may determine which flags are required (eg in the - * case of NODE_NETWORK_LIMITED where we seek out NODE_NETWORK peers - * unless they set NODE_NETWORK_LIMITED and we are out of IBD, in which - * case NODE_NETWORK_LIMITED suffices). - * - * Thus, generally, avoid calling with peerServices == NODE_NONE, unless - * state-specific flags must absolutely be avoided. When called with - * peerServices == NODE_NONE, the returned desirable service flags are - * guaranteed to not change dependent on state - ie they are suitable for - * use when describing peers which we know to be desirable, but for which - * we do not have a confirmed set of service flags. - * - * If the NODE_NONE return value is changed, contrib/seeds/makeseeds.py - * should be updated appropriately to filter for the same nodes. - */ -ServiceFlags GetDesirableServiceFlags(ServiceFlags services); - -/** Set the current IBD status in order to figure out the desirable service flags */ -void SetServiceFlagsIBDCache(bool status); - -/** - * A shortcut for (services & GetDesirableServiceFlags(services)) - * == GetDesirableServiceFlags(services), ie determines whether the given - * set of service flags are sufficient for a peer to be "relevant". - */ -static inline bool HasAllDesirableServiceFlags(ServiceFlags services) -{ - return !(GetDesirableServiceFlags(services) & (~services)); -} + * State independent service flags. + * If the return value is changed, contrib/seeds/makeseeds.py + * should be updated appropriately to filter for nodes with + * desired service flags (compatible with our new flags). + */ +constexpr ServiceFlags SeedsServiceFlags() { return ServiceFlags(NODE_NETWORK); } /** * Checks if a peer with the given service flags may be capable of having a diff --git a/src/test/fuzz/integer.cpp b/src/test/fuzz/integer.cpp index 6a163b206e45..bc1bec691241 100644 --- a/src/test/fuzz/integer.cpp +++ b/src/test/fuzz/integer.cpp @@ -209,7 +209,6 @@ FUZZ_TARGET(integer, .init = initialize_integer) { const ServiceFlags service_flags = (ServiceFlags)u64; - (void)HasAllDesirableServiceFlags(service_flags); (void)MayHaveUsefulAddressDB(service_flags); } diff --git a/src/test/peerman_tests.cpp b/src/test/peerman_tests.cpp new file mode 100644 index 000000000000..dff3f654df83 --- /dev/null +++ b/src/test/peerman_tests.cpp @@ -0,0 +1,76 @@ +// Copyright (c) 2024-present The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php. + +#include +#include +#include +#include +#include +#include + +#include + +BOOST_FIXTURE_TEST_SUITE(peerman_tests, RegTestingSetup) + +/** Window, in blocks, for connecting to NODE_NETWORK_LIMITED peers */ +static constexpr int64_t NODE_NETWORK_LIMITED_ALLOW_CONN_BLOCKS = 144; + +static void mineBlock(const node::NodeContext& node, std::chrono::seconds block_time) +{ + auto curr_time = GetTime(); + SetMockTime(block_time); // update time so the block is created with it + CBlock block = node::BlockAssembler{node.chainman->ActiveChainstate(), node, nullptr}.CreateNewBlock(CScript() << OP_TRUE)->block; + while (!CheckProofOfWork(block.GetHash(), block.nBits, node.chainman->GetConsensus())) ++block.nNonce; + block.fChecked = true; // little speedup + SetMockTime(curr_time); // process block at current time + Assert(node.chainman->ProcessNewBlock(std::make_shared(block), /*force_processing=*/true, /*new_block=*/nullptr)); + SyncWithValidationInterfaceQueue(); // drain events queue +} + +// Verifying when network-limited peer connections are desirable based on the node's proximity to the tip +BOOST_AUTO_TEST_CASE(connections_desirable_service_flags) +{ + std::unique_ptr peerman = MakePeerManager(*m_node.connman, m_node, /*banman=*/nullptr, {}); + auto consensus = m_node.chainman->GetParams().GetConsensus(); + + // Check we start connecting to full nodes + ServiceFlags peer_flags{NODE_NETWORK_LIMITED}; + BOOST_CHECK(peerman->GetDesirableServiceFlags(peer_flags) == ServiceFlags(NODE_NETWORK)); + + // Make peerman aware of the initial best block and verify we accept limited peers when we start close to the tip time. + auto tip = WITH_LOCK(::cs_main, return m_node.chainman->ActiveChain().Tip()); + uint64_t tip_block_time = tip->GetBlockTime(); + int tip_block_height = tip->nHeight; + peerman->SetBestBlock(tip_block_height, std::chrono::seconds{tip_block_time}); + + SetMockTime(tip_block_time + 1); // Set node time to tip time + BOOST_CHECK(peerman->GetDesirableServiceFlags(peer_flags) == ServiceFlags(NODE_NETWORK_LIMITED)); + + // Check we don't disallow limited peers connections when we are behind but still recoverable (below the connection safety window) + SetMockTime(GetTime() + std::chrono::seconds{consensus.nPowTargetSpacing * (NODE_NETWORK_LIMITED_ALLOW_CONN_BLOCKS - 1)}); + BOOST_CHECK(peerman->GetDesirableServiceFlags(peer_flags) == ServiceFlags(NODE_NETWORK_LIMITED)); + + // Check we disallow limited peers connections when we are further than the limited peers safety window + SetMockTime(GetTime() + std::chrono::seconds{consensus.nPowTargetSpacing * 2}); + BOOST_CHECK(peerman->GetDesirableServiceFlags(peer_flags) == ServiceFlags(NODE_NETWORK)); + + // By now, we tested that the connections desirable services flags change based on the node's time proximity to the tip. + // Now, perform the same tests for when the node receives a block. + RegisterValidationInterface(peerman.get()); + + // First, verify a block in the past doesn't enable limited peers connections + // At this point, our time is (NODE_NETWORK_LIMITED_ALLOW_CONN_BLOCKS + 1) * 2.5 minutes ahead the tip's time. + mineBlock(m_node, /*block_time=*/std::chrono::seconds{tip_block_time + 1}); + BOOST_CHECK(peerman->GetDesirableServiceFlags(peer_flags) == ServiceFlags(NODE_NETWORK)); + + // Verify a block close to the tip enables limited peers connections + mineBlock(m_node, /*block_time=*/GetTime()); + BOOST_CHECK(peerman->GetDesirableServiceFlags(peer_flags) == ServiceFlags(NODE_NETWORK_LIMITED)); + + // Lastly, verify the stale tip checks can disallow limited peers connections after not receiving blocks for a prolonged period. + SetMockTime(GetTime() + std::chrono::seconds{consensus.nPowTargetSpacing * NODE_NETWORK_LIMITED_ALLOW_CONN_BLOCKS + 1}); + BOOST_CHECK(peerman->GetDesirableServiceFlags(peer_flags) == ServiceFlags(NODE_NETWORK)); +} + +BOOST_AUTO_TEST_SUITE_END() From b04467647069d65a486935c3b2c386b67ff8f9dc Mon Sep 17 00:00:00 2001 From: DCG-Claude Date: Mon, 2 Nov 2026 02:12:30 -0600 Subject: [PATCH 2/6] Merge bitcoin/bitcoin#29524: p2p: Don't consider blocks mutated if they don't connect to known prev block a1fbde0ef7cf6c94d4a5181f8ceb327096713160 p2p: Don't consider blocks mutated if they don't connect to known prev block (Greg Sanders) Pull request description: Followup to https://github.com/bitcoin/bitcoin/pull/29412 to revert some of the behavior change that was likely unintentional. Based on comments from https://github.com/bitcoin/bitcoin/pull/29412#discussion_r1507499192 ACKs for top commit: 0xB10C: utACK a1fbde0ef7cf6c94d4a5181f8ceb327096713160 dergoegge: Code review ACK a1fbde0ef7cf6c94d4a5181f8ceb327096713160 Sjors: ACK a1fbde0ef7cf6c94d4a5181f8ceb327096713160 sr-gi: tACK https://github.com/bitcoin/bitcoin/commit/a1fbde0ef7cf6c94d4a5181f8ceb327096713160 Tree-SHA512: be6204c8cc57b271d55c1d02a5c77d03a37738d91cb5ac164f483b0bab3991c24679c5ff02fbaa52bf37ee625874b63f4c9f7b39ad6fd5f3a25386567a0942e4 Dash adaptations: - test/functional/p2p_mutated_blocks.py: drops extra_args '-testactivationheight=segwit@1'; Dash has no segwit deployment and rejects that name - test/functional/p2p_mutated_blocks.py: new check's comment says 'mutation check', not 'unexpected-witness mutation check'; Dash has no witness check Not applicable to Dash (intentionally omitted): - src/net_processing.cpp: Upstream gates the mutation check on a known prev block only to evaluate DEPLOYMENT_SEGWIT; Dash's IsBlockMutated(block) needs no prev block, so the check stays unconditional --- test/functional/p2p_mutated_blocks.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/functional/p2p_mutated_blocks.py b/test/functional/p2p_mutated_blocks.py index bf55131f613c..f17879f259a5 100755 --- a/test/functional/p2p_mutated_blocks.py +++ b/test/functional/p2p_mutated_blocks.py @@ -95,5 +95,20 @@ def self_transfer_requested(): assert_equal(int(self.nodes[0].getbestblockhash(), 16), block.sha256) + # Check that the mutation check doesn't trigger on a header that doesn't connect to anything + assert_equal(len(self.nodes[0].getpeerinfo()), 1) + attacker = self.nodes[0].add_p2p_connection(P2PInterface()) + block_missing_prev = copy.deepcopy(block) + block_missing_prev.hashPrevBlock = 123 + block_missing_prev.solve() + + # Attacker gets a DoS score of 10, not immediately disconnected, so we do it 10 times to get to 100 + for _ in range(10): + assert_equal(len(self.nodes[0].getpeerinfo()), 2) + with self.nodes[0].assert_debug_log(expected_msgs=["AcceptBlock FAILED (prev-blk-not-found)"]): + attacker.send_message(msg_block(block_missing_prev)) + attacker.wait_for_disconnect(timeout=5) + + if __name__ == '__main__': MutatedBlocksTest().main() From 432a93bc0b0af6726ca018f20d2b5f37b020ffd1 Mon Sep 17 00:00:00 2001 From: DCG-Claude Date: Mon, 2 Nov 2026 02:13:35 -0600 Subject: [PATCH 3/6] Merge bitcoin/bitcoin#28464: net: improve max-connection limits code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit df69b22f2e3cc03764a582f29a16a36114f67e17 doc: improve documentation around connection limit maximums (Amiti Uttarwar) adc171edf45ec90857d990b8ec570f3c8c2242b7 scripted-diff: Rename connection limit variables (Amiti Uttarwar) e9fd9c0225527ec7727d2a7ccbdf028784aadc6c net: add m_max_inbound to connman (Amiti Uttarwar) c25e0e05550426f29d79571368d90f63fb472b02 net, refactor: move calculations for connection type limits into connman (Amiti Uttarwar) Pull request description: This is joint work with amitiuttarwar. This has the first few commits of #28463. It is not strictly a prerequisite for that, but has changes that in our opinion make sense on their own. It improves the handling of maximum numbers for different connection types (that are set during init and don’t change after) by: * moving all calculations into one place, `CConnMan::Init()`. Before, they were dispersed between `Init`, `CConnman::Init` and other parts of `CConnman`, resulting in some duplicated test code. * removing the possibility of having a negative maximum of inbound connections, which is hard to argue about * renaming of variables and doc improvements ACKs for top commit: amitiuttarwar: co-author review ACK df69b22f2e3cc03764a582f29a16a36114f67e17 naumenkogs: ACK df69b22f2e3cc03764a582f29a16a36114f67e17 achow101: ACK df69b22f2e3cc03764a582f29a16a36114f67e17 Tree-SHA512: 913d56136bc1df739978de50db67302f88bac2a9d34748ae96763288d97093e998fc0f94f9b6eff12867712d7e86225af6128f4170bf2b5b8ab76f024870a22c Dash adaptations: - src/init.cpp: -maxconnections help says 'automatic connections' and keeps Dash's '(temporary service connections excluded)' wording - src/init.cpp: still sets connOptions.m_max_outbound_onion, now from the local nMaxConnections because Options::nMaxConnections is gone - src/net.h: keeps Dash-only Options::m_max_outbound_onion and copies it in Init(); its comment now says m_max_automatic_outbound - src/net.cpp: Dash's masternode-aware inbound eviction loop (while nInbound - nVerifiedInboundMasternodes >= ...) now uses m_max_inbound instead of the removed local nMaxInbound - src/net.cpp: Dash-only GetMaxOutboundNodeCount() returns m_max_automatic_outbound because m_max_outbound was renamed --- src/init.cpp | 10 +++----- src/net.cpp | 19 +++++++------- src/net.h | 40 ++++++++++++++++++------------ src/test/denialofservice_tests.cpp | 8 ++---- 4 files changed, 38 insertions(+), 39 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index 2ce62e4dd17f..2e4d1d0ec355 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -675,7 +675,7 @@ void SetupServerArgs(ArgsManager& argsman) argsman.AddArg("-forcednsseed", strprintf("Always query for peer addresses via DNS lookup (default: %u)", DEFAULT_FORCEDNSSEED), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-listen", strprintf("Accept connections from outside (default: %u if no -proxy, -connect or -maxconnections=0)", DEFAULT_LISTEN), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-listenonion", strprintf("Automatically create Tor onion service if Tor is available and the onion bind could be bound (see -bind). If set to 1 explicitly, fail to start if the onion bind cannot be bound (default: %d)", DEFAULT_LISTEN_ONION), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); - argsman.AddArg("-maxconnections=", strprintf("Maintain at most connections to peers (temporary service connections excluded) (default: %u). This limit does not apply to connections manually added via -addnode or the addnode RPC, which have a separate limit of %u.", DEFAULT_MAX_PEER_CONNECTIONS, MAX_ADDNODE_CONNECTIONS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + argsman.AddArg("-maxconnections=", strprintf("Maintain at most automatic connections to peers (temporary service connections excluded) (default: %u). This limit does not apply to connections manually added via -addnode or the addnode RPC, which have a separate limit of %u.", DEFAULT_MAX_PEER_CONNECTIONS, MAX_ADDNODE_CONNECTIONS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-maxreceivebuffer=", strprintf("Maximum per-connection receive buffer, *1000 bytes (default: %u)", DEFAULT_MAXRECEIVEBUFFER), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-maxsendbuffer=", strprintf("Maximum per-connection memory usage for the send buffer, *1000 bytes (default: %u)", DEFAULT_MAXSENDBUFFER), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-maxtimeadjustment", strprintf("Maximum allowed median peer time offset adjustment. Local perspective of time may be influenced by outbound peers forward or backward by this amount (default: %u seconds).", DEFAULT_MAX_TIME_ADJUSTMENT), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); @@ -2623,12 +2623,8 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) CConnman::Options connOptions; connOptions.nLocalServices = nLocalServices; - connOptions.nMaxConnections = nMaxConnections; - connOptions.m_max_outbound_full_relay = std::min(MAX_OUTBOUND_FULL_RELAY_CONNECTIONS, connOptions.nMaxConnections); - connOptions.m_max_outbound_block_relay = std::min(MAX_BLOCK_RELAY_ONLY_CONNECTIONS, connOptions.nMaxConnections-connOptions.m_max_outbound_full_relay); - connOptions.m_max_outbound_onion = std::min(MAX_DESIRED_ONION_CONNECTIONS, connOptions.nMaxConnections / 2); - connOptions.nMaxAddnode = MAX_ADDNODE_CONNECTIONS; - connOptions.nMaxFeeler = MAX_FEELER_CONNECTIONS; + connOptions.m_max_automatic_connections = nMaxConnections; + connOptions.m_max_outbound_onion = std::min(MAX_DESIRED_ONION_CONNECTIONS, nMaxConnections / 2); connOptions.uiInterface = &uiInterface; connOptions.m_banman = node.banman.get(); connOptions.m_msgproc = node.peerman.get(); diff --git a/src/net.cpp b/src/net.cpp index fa027c41717c..012f8de5d207 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -1900,7 +1900,6 @@ void CConnman::CreateNodeFromAcceptedSocket(std::unique_ptr&& sock, { int nInbound = 0; int nVerifiedInboundMasternodes = 0; - int nMaxInbound = nMaxConnections - m_max_outbound; const bool inbound_onion = std::find(m_onion_binds.begin(), m_onion_binds.end(), addr_bind) != m_onion_binds.end(); @@ -1956,18 +1955,18 @@ void CConnman::CreateNodeFromAcceptedSocket(std::unique_ptr&& sock, // Only accept connections from discouraged peers if our inbound slots aren't (almost) full. bool discouraged = m_banman && m_banman->IsDiscouraged(addr); - if (!NetPermissions::HasFlag(permission_flags, NetPermissionFlags::NoBan) && nInbound + 1 >= nMaxInbound && discouraged) + if (!NetPermissions::HasFlag(permission_flags, NetPermissionFlags::NoBan) && nInbound + 1 >= m_max_inbound && discouraged) { LogPrint(BCLog::NET_NETCONN, "connection from %s dropped (discouraged)\n", addr.ToStringAddrPort()); return; } - // Evict connections until we are below nMaxInbound. In case eviction protection resulted in nodes to not be evicted + // Evict connections until we are below m_max_inbound. In case eviction protection resulted in nodes to not be evicted // before, they might get evicted in batches now (after the protection timeout). // We don't evict verified MN connections and also don't take them into account when checking limits. We can do // this because such connections are limited by the total number of MNs times MAX_VERIFIED_INBOUND_PER_PROTX // (enforced in TryMarkVerified), so this is not usable for attacks. - while (nInbound - nVerifiedInboundMasternodes >= nMaxInbound) + while (nInbound - nVerifiedInboundMasternodes >= m_max_inbound) { if (!AttemptToEvictConnection()) { // No connection to evict, disconnect the new connection @@ -3303,7 +3302,7 @@ void CConnman::ThreadOpenConnections(const std::vector connect, CDe // different netgroups in ipv4/ipv6 networks + all peers in Tor/I2P/CJDNS networks. // Don't record addrman failure attempts when node is offline. This can be identified since all local // network connections (if any) belong in the same netgroup, and the size of `outbound_ipv46_peer_netgroups` would only be 1. - const bool count_failures{((int)outbound_ipv46_peer_netgroups.size() + outbound_privacy_network_peers) >= std::min(nMaxConnections - 1, 2)}; + const bool count_failures{((int)outbound_ipv46_peer_netgroups.size() + outbound_privacy_network_peers) >= std::min(m_max_automatic_connections - 1, 2)}; // Use BIP324 transport when both us and them have NODE_V2_P2P set. const bool use_v2transport(addrConnect.nServices & GetLocalServices() & NODE_P2P_V2); OpenNetworkConnection(addrConnect, count_failures, std::move(grant), /*strDest=*/nullptr, conn_type, use_v2transport); @@ -4063,11 +4062,11 @@ bool CConnman::Start(CDeterministicMNManager& dmnman, CMasternodeMetaMan& mn_met if (semOutbound == nullptr) { // initialize semaphore - semOutbound = std::make_unique(std::min(m_max_outbound, nMaxConnections)); + semOutbound = std::make_unique(std::min(m_max_automatic_outbound, m_max_automatic_connections)); } if (semAddnode == nullptr) { // initialize semaphore - semAddnode = std::make_unique(nMaxAddnode); + semAddnode = std::make_unique(m_max_addnode); } // @@ -4177,13 +4176,13 @@ void CConnman::Interrupt() InterruptSocks5(true); if (semOutbound) { - for (int i=0; ipost(); } } if (semAddnode) { - for (int i=0; ipost(); } } @@ -4571,7 +4570,7 @@ std::map CConnman::getNetLocalAddresses() const size_t CConnman::GetMaxOutboundNodeCount() { - return m_max_outbound; + return m_max_automatic_outbound; } size_t CConnman::GetMaxOutboundOnionNodeCount() diff --git a/src/net.h b/src/net.h index 3998a6e4ac4e..bb3146e7edd9 100644 --- a/src/net.h +++ b/src/net.h @@ -1226,12 +1226,8 @@ friend class CNode; struct Options { ServiceFlags nLocalServices = NODE_NONE; - int nMaxConnections = 0; - int m_max_outbound_full_relay = 0; - int m_max_outbound_block_relay = 0; + int m_max_automatic_connections = 0; int m_max_outbound_onion = 0; - int nMaxAddnode = 0; - int nMaxFeeler = 0; CClientUIInterface* uiInterface = nullptr; NetEventsInterface* m_msgproc = nullptr; BanMan* m_banman = nullptr; @@ -1269,14 +1265,13 @@ friend class CNode; AssertLockNotHeld(m_total_bytes_sent_mutex); nLocalServices = connOptions.nLocalServices; - nMaxConnections = connOptions.nMaxConnections; - m_max_outbound_full_relay = std::min(connOptions.m_max_outbound_full_relay, connOptions.nMaxConnections); - m_max_outbound_block_relay = connOptions.m_max_outbound_block_relay; + m_max_automatic_connections = connOptions.m_max_automatic_connections; + m_max_outbound_full_relay = std::min(MAX_OUTBOUND_FULL_RELAY_CONNECTIONS, m_max_automatic_connections); + m_max_outbound_block_relay = std::min(MAX_BLOCK_RELAY_ONLY_CONNECTIONS, m_max_automatic_connections - m_max_outbound_full_relay); m_max_outbound_onion = connOptions.m_max_outbound_onion; + m_max_automatic_outbound = m_max_outbound_full_relay + m_max_outbound_block_relay + m_max_feeler; + m_max_inbound = std::max(0, m_max_automatic_connections - m_max_automatic_outbound); m_use_addrman_outgoing = connOptions.m_use_addrman_outgoing; - nMaxAddnode = connOptions.nMaxAddnode; - nMaxFeeler = connOptions.nMaxFeeler; - m_max_outbound = m_max_outbound_full_relay + m_max_outbound_block_relay + nMaxFeeler; m_client_interface = connOptions.uiInterface; m_banman = connOptions.m_banman; m_msgproc = connOptions.m_msgproc; @@ -1899,7 +1894,18 @@ friend class CNode; std::unique_ptr semOutbound; std::unique_ptr semAddnode; - int nMaxConnections; + + /** + * Maximum number of automatic connections permitted, excluding manual + * connections but including inbounds. May be changed by the user and is + * potentially limited by the operating system (number of file descriptors). + */ + int m_max_automatic_connections; + + /* + * Maximum number of peers by connection type. Might vary from defaults + * based on -maxconnections init value. + */ // How many full-relay (tx, block, addr) outbound peers we want int m_max_outbound_full_relay; @@ -1908,12 +1914,14 @@ friend class CNode; // We do not relay tx or addr messages with these peers int m_max_outbound_block_relay; - // How many onion outbound peers we want; don't care if full or block only; does not increase m_max_outbound + // How many onion outbound peers we want; don't care if full or block only; does not increase m_max_automatic_outbound int m_max_outbound_onion; - int nMaxAddnode; - int nMaxFeeler; - int m_max_outbound; + int m_max_addnode{MAX_ADDNODE_CONNECTIONS}; + int m_max_feeler{MAX_FEELER_CONNECTIONS}; + int m_max_automatic_outbound; + int m_max_inbound; + bool m_use_addrman_outgoing; CClientUIInterface* m_client_interface; NetEventsInterface* m_msgproc; diff --git a/src/test/denialofservice_tests.cpp b/src/test/denialofservice_tests.cpp index 4f6b0f1ed2ff..eb3f1c308556 100644 --- a/src/test/denialofservice_tests.cpp +++ b/src/test/denialofservice_tests.cpp @@ -150,9 +150,7 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management) constexpr int max_outbound_full_relay = MAX_OUTBOUND_FULL_RELAY_CONNECTIONS; CConnman::Options options; - options.nMaxConnections = DEFAULT_MAX_PEER_CONNECTIONS; - options.m_max_outbound_full_relay = max_outbound_full_relay; - options.nMaxFeeler = MAX_FEELER_CONNECTIONS; + options.m_max_automatic_connections = DEFAULT_MAX_PEER_CONNECTIONS; const auto time_init{GetTime()}; SetMockTime(time_init); @@ -251,9 +249,7 @@ BOOST_AUTO_TEST_CASE(block_relay_only_eviction) constexpr int max_outbound_block_relay{MAX_BLOCK_RELAY_ONLY_CONNECTIONS}; constexpr int64_t MINIMUM_CONNECT_TIME{30}; CConnman::Options options; - options.nMaxConnections = DEFAULT_MAX_PEER_CONNECTIONS; - options.m_max_outbound_full_relay = MAX_OUTBOUND_FULL_RELAY_CONNECTIONS; - options.m_max_outbound_block_relay = max_outbound_block_relay; + options.m_max_automatic_connections = DEFAULT_MAX_PEER_CONNECTIONS; connman->Init(options); std::vector vNodes; From 38a4000d7aae6c7f3b2066c4f4986c82a38770ee Mon Sep 17 00:00:00 2001 From: DCG-Claude Date: Mon, 2 Nov 2026 02:16:00 -0600 Subject: [PATCH 4/6] Merge bitcoin/bitcoin#28578: fuzz: add target for `DescriptorScriptPubKeyMan` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 47e5c9994c087d1826ccc0d539e916845b5648fb fuzz: add target for `DescriptorScriptPubKeyMan` (brunoerg) 641dddf01812407d163520def83f5975413691e4 fuzz: create ConsumeCoins (brunoerg) 2e1833ca1341ab4dc92508a59181aa6c7c38db88 fuzz: move `MockedDescriptorConverter` to `fuzz/util` (brunoerg) Pull request description: This PR adds fuzz target for `DescriptorScriptPubKeyMan`. Also, moves `MockedDescriptorConverter` to `fuzz/util/descriptor` to be used here and in `descriptor` target. ACKs for top commit: maflcko: lgtm ACK 47e5c9994c087d1826ccc0d539e916845b5648fb πŸ“ dergoegge: ACK 47e5c9994c087d1826ccc0d539e916845b5648fb Tree-SHA512: 519acca6d7b7a3a0bfc031441b02d5980b12bfb97198bd1958a83cd815ceb9eb1499a48a3f0a7fe20e5d06d83b89335d987376fc0a014e2106b0bc0e9838dd02 Dash adaptations: - src/Makefile.test_fuzz.include: keeps Dash's test/util/mining.{h,cpp} entries in libtest_fuzz next to the new test/fuzz/util/descriptor.{h,cpp} - src/test/fuzz/descriptor_parse.cpp: removes Dash's 5-key-type MockedDescriptorConverter; keeps Dash's includes (script/standard.h, no util/chaintype.h) and adds test/fuzz/util/descriptor.h - src/test/fuzz/util/descriptor.{h,cpp}: moved converter has no x-only pubkey type (KEY_TYPES_COUNT 5, no IdIsXOnlyPubKey/XOnlyPubKey) because Dash has no taproot; this matches Dash's existing converter - src/wallet/test/fuzz/scriptpubkeyman.cpp: SelectParams(CBaseChainParams::MAIN) instead of ChainType::MAIN, because Dash has no util/chaintype.h - src/wallet/test/fuzz/scriptpubkeyman.cpp: uses CreateMockWalletDatabase() because Dash lacks CreateMockableWalletDatabase (bitcoin/bitcoin#26715) - src/wallet/test/fuzz/scriptpubkeyman.cpp: deserializes CMutableTransaction without TX_WITH_WITNESS, because Dash has no witness serialization - src/wallet/test/fuzz/scriptpubkeyman.cpp: calls GetNewDestination() with no argument (Dash's DescriptorScriptPubKeyMan has no OutputType parameter); keeps upstream's IsSingleType/GetOutputType checks --- src/Makefile.test.include | 3 +- src/Makefile.test_fuzz.include | 2 + src/test/fuzz/descriptor_parse.cpp | 92 +------------ src/test/fuzz/script_sign.cpp | 13 +- src/test/fuzz/util.cpp | 18 +++ src/test/fuzz/util.h | 2 + src/test/fuzz/util/descriptor.cpp | 69 ++++++++++ src/test/fuzz/util/descriptor.h | 47 +++++++ src/wallet/test/fuzz/scriptpubkeyman.cpp | 165 +++++++++++++++++++++++ 9 files changed, 307 insertions(+), 104 deletions(-) create mode 100644 src/test/fuzz/util/descriptor.cpp create mode 100644 src/test/fuzz/util/descriptor.h create mode 100644 src/wallet/test/fuzz/scriptpubkeyman.cpp diff --git a/src/Makefile.test.include b/src/Makefile.test.include index c66e1bae5897..a145cbdec341 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -264,7 +264,8 @@ FUZZ_WALLET_SRC = \ if USE_SQLITE FUZZ_WALLET_SRC += \ - wallet/test/fuzz/notifications.cpp + wallet/test/fuzz/notifications.cpp \ + wallet/test/fuzz/scriptpubkeyman.cpp endif # USE_SQLITE BITCOIN_TEST_SUITE += \ diff --git a/src/Makefile.test_fuzz.include b/src/Makefile.test_fuzz.include index e909ab9359c1..b41475894e82 100644 --- a/src/Makefile.test_fuzz.include +++ b/src/Makefile.test_fuzz.include @@ -12,6 +12,7 @@ TEST_FUZZ_H = \ test/fuzz/FuzzedDataProvider.h \ test/fuzz/util.h \ test/util/mining.h \ + test/fuzz/util/descriptor.h \ test/fuzz/util/mempool.h \ test/fuzz/util/net.h @@ -21,6 +22,7 @@ libtest_fuzz_a_SOURCES = \ test/fuzz/fuzz.cpp \ test/util/mining.cpp \ test/fuzz/util.cpp \ + test/fuzz/util/descriptor.cpp \ test/fuzz/util/mempool.cpp \ test/fuzz/util/net.cpp \ $(TEST_FUZZ_H) diff --git a/src/test/fuzz/descriptor_parse.cpp b/src/test/fuzz/descriptor_parse.cpp index 1084c569a703..a8edc83fb409 100644 --- a/src/test/fuzz/descriptor_parse.cpp +++ b/src/test/fuzz/descriptor_parse.cpp @@ -9,97 +9,7 @@ #include