Merge 0b768746ef into merged_master (Bitcoin PR bitcoin/bitcoin#28170)

This commit is contained in:
Byron Hambly 2025-11-29 13:46:08 +02:00
commit b5859dc083
No known key found for this signature in database
GPG key ID: DE8F6EA20A661697
11 changed files with 162 additions and 63 deletions

View file

@ -4,7 +4,7 @@ Utility to generate the seeds.txt list that is compiled into the client
(see [src/chainparamsseeds.h](/src/chainparamsseeds.h) and other utilities in [contrib/seeds](/contrib/seeds)).
Be sure to update `PATTERN_AGENT` in `makeseeds.py` to include the current version,
and remove old versions as necessary (at a minimum when GetDesirableServiceFlags
and remove old versions as necessary (at a minimum when SeedsServiceFlags()
changes its default return value, as those are the services which seeds are added
to addrman with).

View file

@ -120,6 +120,7 @@ BITCOIN_TESTS =\
test/net_tests.cpp \
test/netbase_tests.cpp \
test/orphanage_tests.cpp \
test/peerman_tests.cpp \
test/pmt_tests.cpp \
test/policy_fee_tests.cpp \
test/policyestimator_tests.cpp \

View file

@ -1886,13 +1886,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->verification_progress = GuessVerificationProgress(chainman.ActiveChain().Tip(), chainman.GetParams().GetConsensus().nPowTargetSpacing);
}
@ -1902,7 +1904,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));

View file

@ -203,7 +203,7 @@ static std::vector<CAddress> ConvertSeeds(const std::vector<uint8_t> &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<NodeSeconds>() - one_week, -one_week);
LogPrint(BCLog::NET, "Added hardcoded seed: %s\n", addr.ToStringAddrPort());
vSeedsOut.push_back(addr);
@ -2267,7 +2267,7 @@ void CConnman::ThreadDNSAddressSeed()
AddAddrFetch(seed);
} else {
std::vector<CAddress> 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)) {
@ -2638,7 +2638,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
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());
@ -2704,7 +2704,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
// 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;

View file

@ -1005,6 +1005,12 @@ public:
/** 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
*

View file

@ -133,6 +133,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 */
@ -502,6 +504,7 @@ public:
/** Implement NetEventsInterface */
void InitializeNode(const CNode& node, ServiceFlags our_services) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
void FinalizeNode(const CNode& node) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_headers_presync_mutex);
bool HasAllDesirableServiceFlags(ServiceFlags services) const override;
bool ProcessMessages(CNode* pfrom, std::atomic<bool>& interrupt) override
EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_recent_confirmed_transactions_mutex, !m_most_recent_block_mutex, !m_headers_presync_mutex, g_msgproc_mutex);
bool SendMessages(CNode* pto) override
@ -516,12 +519,17 @@ public:
bool IgnoresIncomingTxs() override { return m_opts.ignore_incoming_txs; }
void SendPings() override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
void RelayTransaction(const uint256& txid, const uint256& wtxid) 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, DataStream& vRecv,
const std::chrono::microseconds time_received, const std::atomic<bool>& interruptMsgProc) override
EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_recent_confirmed_transactions_mutex, !m_most_recent_block_mutex, !m_headers_presync_mutex, g_msgproc_mutex);
void UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds) override;
ServiceFlags GetDesirableServiceFlags(ServiceFlags services) const override;
private:
/** Consider evicting an outbound peer based on the amount of time they've been behind our tip */
@ -724,6 +732,8 @@ private:
/** The height of the best chain */
std::atomic<int> m_best_height{-1};
/** The time of the best chain tip block */
std::atomic<std::chrono::seconds> 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};
@ -995,6 +1005,12 @@ private:
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,
@ -1314,6 +1330,11 @@ bool PeerManagerImpl::TipMayBeStale()
return m_last_tip_update.load() < GetTime<std::chrono::seconds>() - std::chrono::seconds{consensusParams.nPowTargetSpacing * 3} && mapBlocksInFlight.empty();
}
int64_t PeerManagerImpl::ApproximateBestBlockDepth() const
{
return (GetTime<std::chrono::seconds>() - m_best_block_time.load()).count() / m_chainparams.GetConsensus().nPowTargetSpacing;
}
bool PeerManagerImpl::CanDirectFetch()
{
if(!m_chainman.ActiveChain().Tip()) {
@ -1655,6 +1676,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 | NODE_WITNESS);
}
}
return ServiceFlags(NODE_NETWORK | NODE_WITNESS);
}
PeerRef PeerManagerImpl::GetPeerRef(NodeId id) const
{
LOCK(m_peer_mutex);
@ -2051,8 +2089,7 @@ void PeerManagerImpl::NewPoWValidBlock(const CBlockIndex *pindex, const std::sha
*/
void PeerManagerImpl::UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload)
{
SetBestHeight(pindexNew->nHeight);
SetServiceFlagsIBDCache(!fInitialDownload);
SetBestBlock(pindexNew->nHeight, std::chrono::seconds{pindexNew->GetBlockTime()});
// Don't relay inventory during initial block download.
if (fInitialDownload) return;

View file

@ -92,8 +92,8 @@ public:
/** Send ping message to all peers */
virtual void SendPings() = 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;
@ -110,6 +110,29 @@ public:
/** 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;
};
#endif // BITCOIN_NET_PROCESSING_H

View file

@ -9,8 +9,6 @@
#include <atomic>
static std::atomic<bool> g_initial_block_download_completed(false);
namespace NetMsgType {
const char* VERSION = "version";
const char* VERACK = "verack";
@ -125,18 +123,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 | NODE_WITNESS);
}
return ServiceFlags(NODE_NETWORK | NODE_WITNESS);
}
void SetServiceFlagsIBDCache(bool state) {
g_initial_block_download_completed = state;
}
CInv::CInv()
{
type = 0;

View file

@ -313,43 +313,12 @@ enum ServiceFlags : uint64_t {
std::vector<std::string> 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.
* 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).
*/
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));
}
constexpr ServiceFlags SeedsServiceFlags() { return ServiceFlags(NODE_NETWORK | NODE_WITNESS); }
/**
* Checks if a peer with the given service flags may be capable of having a

View file

@ -213,7 +213,6 @@ FUZZ_TARGET(integer, .init = initialize_integer)
{
const ServiceFlags service_flags = (ServiceFlags)u64;
(void)HasAllDesirableServiceFlags(service_flags);
(void)MayHaveUsefulAddressDB(service_flags);
}

View file

@ -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 <chainparams.h>
#include <node/miner.h>
#include <net_processing.h>
#include <pow.h>
#include <test/util/setup_common.h>
#include <validation.h>
#include <boost/test/unit_test.hpp>
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<std::chrono::seconds>();
SetMockTime(block_time); // update time so the block is created with it
CBlock block = node::BlockAssembler{node.chainman->ActiveChainstate(), 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<const CBlock>(block), /*force_processing=*/true, /*min_pow_checked=*/true, 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<PeerManager> peerman = PeerManager::make(*m_node.connman, *m_node.addrman, nullptr, *m_node.chainman, *m_node.mempool, {});
auto consensus = m_node.chainman->GetParams().GetConsensus();
// Check we start connecting to full nodes
ServiceFlags peer_flags{NODE_WITNESS | NODE_NETWORK_LIMITED};
BOOST_CHECK(peerman->GetDesirableServiceFlags(peer_flags) == ServiceFlags(NODE_NETWORK | NODE_WITNESS));
// 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 | NODE_WITNESS));
// 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>() + std::chrono::seconds{consensus.nPowTargetSpacing * (NODE_NETWORK_LIMITED_ALLOW_CONN_BLOCKS - 1)});
BOOST_CHECK(peerman->GetDesirableServiceFlags(peer_flags) == ServiceFlags(NODE_NETWORK_LIMITED | NODE_WITNESS));
// Check we disallow limited peers connections when we are further than the limited peers safety window
SetMockTime(GetTime<std::chrono::seconds>() + std::chrono::seconds{consensus.nPowTargetSpacing * 2});
BOOST_CHECK(peerman->GetDesirableServiceFlags(peer_flags) == ServiceFlags(NODE_NETWORK | NODE_WITNESS));
// 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) * 10 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 | NODE_WITNESS));
// Verify a block close to the tip enables limited peers connections
mineBlock(m_node, /*block_time=*/GetTime<std::chrono::seconds>());
BOOST_CHECK(peerman->GetDesirableServiceFlags(peer_flags) == ServiceFlags(NODE_NETWORK_LIMITED | NODE_WITNESS));
// Lastly, verify the stale tip checks can disallow limited peers connections after not receiving blocks for a prolonged period.
SetMockTime(GetTime<std::chrono::seconds>() + std::chrono::seconds{consensus.nPowTargetSpacing * NODE_NETWORK_LIMITED_ALLOW_CONN_BLOCKS + 1});
BOOST_CHECK(peerman->GetDesirableServiceFlags(peer_flags) == ServiceFlags(NODE_NETWORK | NODE_WITNESS));
}
BOOST_AUTO_TEST_SUITE_END()