Skip to content

Commit 0b76874

Browse files
committed
Merge bitcoin/bitcoin#28170: p2p: adaptive connections services flags
27f260a net: remove now unused global 'g_initial_block_download_completed' (furszy) aff7d92 test: add coverage for peerman adaptive connections service flags (furszy) 6ed5360 net: peer manager, dynamically adjust desirable services flag (furszy) 9f36e59 net: move state dependent peer services flags (furszy) f9ac96b net: decouple state independent service flags from desirable ones (furszy) 97df4e3 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 27f260a vasild: ACK 27f260a naumenkogs: ACK 27f260a mzumsande: Light Code Review ACK 27f260a andrewtoth: ACK 27f260a Tree-SHA512: 07befb9bcd0b60a4e7c45e4429c02e7b6c66244f0910f4b2ad97c9b98258b6f46c914660a717b5ed4ef4814d0dbfae6e18e6559fe9bec7d0fbc2034109200953
2 parents 11b436a + 27f260a commit 0b76874

File tree

11 files changed

+163
-64
lines changed

11 files changed

+163
-64
lines changed

contrib/seeds/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Utility to generate the seeds.txt list that is compiled into the client
44
(see [src/chainparamsseeds.h](/src/chainparamsseeds.h) and other utilities in [contrib/seeds](/contrib/seeds)).
55

66
Be sure to update `PATTERN_AGENT` in `makeseeds.py` to include the current version,
7-
and remove old versions as necessary (at a minimum when GetDesirableServiceFlags
7+
and remove old versions as necessary (at a minimum when SeedsServiceFlags()
88
changes its default return value, as those are the services which seeds are added
99
to addrman with).
1010

src/Makefile.test.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ BITCOIN_TESTS =\
117117
test/net_tests.cpp \
118118
test/netbase_tests.cpp \
119119
test/orphanage_tests.cpp \
120+
test/peerman_tests.cpp \
120121
test/pmt_tests.cpp \
121122
test/policy_fee_tests.cpp \
122123
test/policyestimator_tests.cpp \

src/init.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1743,13 +1743,15 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
17431743
// ********************************************************* Step 12: start node
17441744

17451745
//// debug print
1746+
int64_t best_block_time{};
17461747
{
17471748
LOCK(cs_main);
17481749
LogPrintf("block tree size = %u\n", chainman.BlockIndex().size());
17491750
chain_active_height = chainman.ActiveChain().Height();
1751+
best_block_time = chainman.ActiveChain().Tip() ? chainman.ActiveChain().Tip()->GetBlockTime() : chainman.GetParams().GenesisBlock().GetBlockTime();
17501752
if (tip_info) {
17511753
tip_info->block_height = chain_active_height;
1752-
tip_info->block_time = chainman.ActiveChain().Tip() ? chainman.ActiveChain().Tip()->GetBlockTime() : chainman.GetParams().GenesisBlock().GetBlockTime();
1754+
tip_info->block_time = best_block_time;
17531755
tip_info->verification_progress = GuessVerificationProgress(chainman.GetParams().TxData(), chainman.ActiveChain().Tip());
17541756
}
17551757
if (tip_info && chainman.m_best_header) {
@@ -1758,7 +1760,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
17581760
}
17591761
}
17601762
LogPrintf("nBestHeight = %d\n", chain_active_height);
1761-
if (node.peerman) node.peerman->SetBestHeight(chain_active_height);
1763+
if (node.peerman) node.peerman->SetBestBlock(chain_active_height, std::chrono::seconds{best_block_time});
17621764

17631765
// Map ports with UPnP or NAT-PMP.
17641766
StartMapPort(args.GetBoolArg("-upnp", DEFAULT_UPNP), args.GetBoolArg("-natpmp", DEFAULT_NATPMP));

src/net.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ static std::vector<CAddress> ConvertSeeds(const std::vector<uint8_t> &vSeedsIn)
203203
while (!s.eof()) {
204204
CService endpoint;
205205
s >> endpoint;
206-
CAddress addr{endpoint, GetDesirableServiceFlags(NODE_NONE)};
206+
CAddress addr{endpoint, SeedsServiceFlags()};
207207
addr.nTime = rng.rand_uniform_delay(Now<NodeSeconds>() - one_week, -one_week);
208208
LogPrint(BCLog::NET, "Added hardcoded seed: %s\n", addr.ToStringAddrPort());
209209
vSeedsOut.push_back(addr);
@@ -2267,7 +2267,7 @@ void CConnman::ThreadDNSAddressSeed()
22672267
AddAddrFetch(seed);
22682268
} else {
22692269
std::vector<CAddress> vAdd;
2270-
ServiceFlags requiredServiceBits = GetDesirableServiceFlags(NODE_NONE);
2270+
constexpr ServiceFlags requiredServiceBits{SeedsServiceFlags()};
22712271
std::string host = strprintf("x%x.%s", requiredServiceBits, seed);
22722272
CNetAddr resolveSource;
22732273
if (!resolveSource.SetInternal(host)) {
@@ -2638,7 +2638,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
26382638
const CAddress addr = m_anchors.back();
26392639
m_anchors.pop_back();
26402640
if (!addr.IsValid() || IsLocal(addr) || !g_reachable_nets.Contains(addr) ||
2641-
!HasAllDesirableServiceFlags(addr.nServices) ||
2641+
!m_msgproc->HasAllDesirableServiceFlags(addr.nServices) ||
26422642
outbound_ipv46_peer_netgroups.count(m_netgroupman.GetGroup(addr))) continue;
26432643
addrConnect = addr;
26442644
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)
27042704
// for non-feelers, require all the services we'll want,
27052705
// for feelers, only require they be a full node (only because most
27062706
// SPV clients don't have a good address DB available)
2707-
if (!fFeeler && !HasAllDesirableServiceFlags(addr.nServices)) {
2707+
if (!fFeeler && !m_msgproc->HasAllDesirableServiceFlags(addr.nServices)) {
27082708
continue;
27092709
} else if (fFeeler && !MayHaveUsefulAddressDB(addr.nServices)) {
27102710
continue;

src/net.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,6 +1005,12 @@ class NetEventsInterface
10051005
/** Handle removal of a peer (clear state) */
10061006
virtual void FinalizeNode(const CNode& node) = 0;
10071007

1008+
/**
1009+
* Callback to determine whether the given set of service flags are sufficient
1010+
* for a peer to be "relevant".
1011+
*/
1012+
virtual bool HasAllDesirableServiceFlags(ServiceFlags services) const = 0;
1013+
10081014
/**
10091015
* Process protocol messages received from a given node
10101016
*

src/net_processing.cpp

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,8 @@ static const unsigned int MAX_BLOCKS_TO_ANNOUNCE = 8;
133133
static const int MAX_NUM_UNCONNECTING_HEADERS_MSGS = 10;
134134
/** Minimum blocks required to signal NODE_NETWORK_LIMITED */
135135
static const unsigned int NODE_NETWORK_LIMITED_MIN_BLOCKS = 288;
136+
/** Window, in blocks, for connecting to NODE_NETWORK_LIMITED peers */
137+
static const unsigned int NODE_NETWORK_LIMITED_ALLOW_CONN_BLOCKS = 144;
136138
/** Average delay between local address broadcasts */
137139
static constexpr auto AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL{24h};
138140
/** Average delay between peer address broadcasts */
@@ -499,6 +501,7 @@ class PeerManagerImpl final : public PeerManager
499501
/** Implement NetEventsInterface */
500502
void InitializeNode(CNode& node, ServiceFlags our_services) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
501503
void FinalizeNode(const CNode& node) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_headers_presync_mutex);
504+
bool HasAllDesirableServiceFlags(ServiceFlags services) const override;
502505
bool ProcessMessages(CNode* pfrom, std::atomic<bool>& interrupt) override
503506
EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_recent_confirmed_transactions_mutex, !m_most_recent_block_mutex, !m_headers_presync_mutex, g_msgproc_mutex);
504507
bool SendMessages(CNode* pto) override
@@ -513,12 +516,17 @@ class PeerManagerImpl final : public PeerManager
513516
bool IgnoresIncomingTxs() override { return m_opts.ignore_incoming_txs; }
514517
void SendPings() override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
515518
void RelayTransaction(const uint256& txid, const uint256& wtxid) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
516-
void SetBestHeight(int height) override { m_best_height = height; };
519+
void SetBestBlock(int height, std::chrono::seconds time) override
520+
{
521+
m_best_height = height;
522+
m_best_block_time = time;
523+
};
517524
void UnitTestMisbehaving(NodeId peer_id, int howmuch) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex) { Misbehaving(*Assert(GetPeerRef(peer_id)), howmuch, ""); };
518525
void ProcessMessage(CNode& pfrom, const std::string& msg_type, DataStream& vRecv,
519526
const std::chrono::microseconds time_received, const std::atomic<bool>& interruptMsgProc) override
520527
EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_recent_confirmed_transactions_mutex, !m_most_recent_block_mutex, !m_headers_presync_mutex, g_msgproc_mutex);
521528
void UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds) override;
529+
ServiceFlags GetDesirableServiceFlags(ServiceFlags services) const override;
522530

523531
private:
524532
/** Consider evicting an outbound peer based on the amount of time they've been behind our tip */
@@ -721,6 +729,8 @@ class PeerManagerImpl final : public PeerManager
721729

722730
/** The height of the best chain */
723731
std::atomic<int> m_best_height{-1};
732+
/** The time of the best chain tip block */
733+
std::atomic<std::chrono::seconds> m_best_block_time{0s};
724734

725735
/** Next time to check for stale tip */
726736
std::chrono::seconds m_stale_tip_check_time GUARDED_BY(cs_main){0s};
@@ -992,6 +1002,12 @@ class PeerManagerImpl final : public PeerManager
9921002
void UpdateBlockAvailability(NodeId nodeid, const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
9931003
bool CanDirectFetch() EXCLUSIVE_LOCKS_REQUIRED(cs_main);
9941004

1005+
/**
1006+
* Estimates the distance, in blocks, between the best-known block and the network chain tip.
1007+
* Utilizes the best-block time and the chainparams blocks spacing to approximate it.
1008+
*/
1009+
int64_t ApproximateBestBlockDepth() const;
1010+
9951011
/**
9961012
* To prevent fingerprinting attacks, only send blocks/headers outside of
9971013
* the active chain if they are no more than a month older (both in time,
@@ -1311,6 +1327,11 @@ bool PeerManagerImpl::TipMayBeStale()
13111327
return m_last_tip_update.load() < GetTime<std::chrono::seconds>() - std::chrono::seconds{consensusParams.nPowTargetSpacing * 3} && mapBlocksInFlight.empty();
13121328
}
13131329

1330+
int64_t PeerManagerImpl::ApproximateBestBlockDepth() const
1331+
{
1332+
return (GetTime<std::chrono::seconds>() - m_best_block_time.load()).count() / m_chainparams.GetConsensus().nPowTargetSpacing;
1333+
}
1334+
13141335
bool PeerManagerImpl::CanDirectFetch()
13151336
{
13161337
return m_chainman.ActiveChain().Tip()->Time() > GetAdjustedTime() - m_chainparams.GetConsensus().PowTargetSpacing() * 20;
@@ -1651,6 +1672,23 @@ void PeerManagerImpl::FinalizeNode(const CNode& node)
16511672
LogPrint(BCLog::NET, "Cleared nodestate for peer=%d\n", nodeid);
16521673
}
16531674

1675+
bool PeerManagerImpl::HasAllDesirableServiceFlags(ServiceFlags services) const
1676+
{
1677+
// Shortcut for (services & GetDesirableServiceFlags(services)) == GetDesirableServiceFlags(services)
1678+
return !(GetDesirableServiceFlags(services) & (~services));
1679+
}
1680+
1681+
ServiceFlags PeerManagerImpl::GetDesirableServiceFlags(ServiceFlags services) const
1682+
{
1683+
if (services & NODE_NETWORK_LIMITED) {
1684+
// Limited peers are desirable when we are close to the tip.
1685+
if (ApproximateBestBlockDepth() < NODE_NETWORK_LIMITED_ALLOW_CONN_BLOCKS) {
1686+
return ServiceFlags(NODE_NETWORK_LIMITED | NODE_WITNESS);
1687+
}
1688+
}
1689+
return ServiceFlags(NODE_NETWORK | NODE_WITNESS);
1690+
}
1691+
16541692
PeerRef PeerManagerImpl::GetPeerRef(NodeId id) const
16551693
{
16561694
LOCK(m_peer_mutex);
@@ -2047,8 +2085,7 @@ void PeerManagerImpl::NewPoWValidBlock(const CBlockIndex *pindex, const std::sha
20472085
*/
20482086
void PeerManagerImpl::UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload)
20492087
{
2050-
SetBestHeight(pindexNew->nHeight);
2051-
SetServiceFlagsIBDCache(!fInitialDownload);
2088+
SetBestBlock(pindexNew->nHeight, std::chrono::seconds{pindexNew->GetBlockTime()});
20522089

20532090
// Don't relay inventory during initial block download.
20542091
if (fInitialDownload) return;

src/net_processing.h

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,8 @@ class PeerManager : public CValidationInterface, public NetEventsInterface
9292
/** Send ping message to all peers */
9393
virtual void SendPings() = 0;
9494

95-
/** Set the best height */
96-
virtual void SetBestHeight(int height) = 0;
95+
/** Set the height of the best block and its time (seconds since epoch). */
96+
virtual void SetBestBlock(int height, std::chrono::seconds time) = 0;
9797

9898
/* Public for unit testing. */
9999
virtual void UnitTestMisbehaving(NodeId peer_id, int howmuch) = 0;
@@ -110,6 +110,29 @@ class PeerManager : public CValidationInterface, public NetEventsInterface
110110

111111
/** This function is used for testing the stale tip eviction logic, see denialofservice_tests.cpp */
112112
virtual void UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds) = 0;
113+
114+
/**
115+
* Gets the set of service flags which are "desirable" for a given peer.
116+
*
117+
* These are the flags which are required for a peer to support for them
118+
* to be "interesting" to us, ie for us to wish to use one of our few
119+
* outbound connection slots for or for us to wish to prioritize keeping
120+
* their connection around.
121+
*
122+
* Relevant service flags may be peer- and state-specific in that the
123+
* version of the peer may determine which flags are required (eg in the
124+
* case of NODE_NETWORK_LIMITED where we seek out NODE_NETWORK peers
125+
* unless they set NODE_NETWORK_LIMITED and we are out of IBD, in which
126+
* case NODE_NETWORK_LIMITED suffices).
127+
*
128+
* Thus, generally, avoid calling with 'services' == NODE_NONE, unless
129+
* state-specific flags must absolutely be avoided. When called with
130+
* 'services' == NODE_NONE, the returned desirable service flags are
131+
* guaranteed to not change dependent on state - ie they are suitable for
132+
* use when describing peers which we know to be desirable, but for which
133+
* we do not have a confirmed set of service flags.
134+
*/
135+
virtual ServiceFlags GetDesirableServiceFlags(ServiceFlags services) const = 0;
113136
};
114137

115138
#endif // BITCOIN_NET_PROCESSING_H

src/protocol.cpp

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99

1010
#include <atomic>
1111

12-
static std::atomic<bool> g_initial_block_download_completed(false);
13-
1412
namespace NetMsgType {
1513
const char* VERSION = "version";
1614
const char* VERACK = "verack";
@@ -125,18 +123,6 @@ bool CMessageHeader::IsCommandValid() const
125123
return true;
126124
}
127125

128-
129-
ServiceFlags GetDesirableServiceFlags(ServiceFlags services) {
130-
if ((services & NODE_NETWORK_LIMITED) && g_initial_block_download_completed) {
131-
return ServiceFlags(NODE_NETWORK_LIMITED | NODE_WITNESS);
132-
}
133-
return ServiceFlags(NODE_NETWORK | NODE_WITNESS);
134-
}
135-
136-
void SetServiceFlagsIBDCache(bool state) {
137-
g_initial_block_download_completed = state;
138-
}
139-
140126
CInv::CInv()
141127
{
142128
type = 0;

src/protocol.h

Lines changed: 6 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -311,43 +311,12 @@ enum ServiceFlags : uint64_t {
311311
std::vector<std::string> serviceFlagsToStr(uint64_t flags);
312312

313313
/**
314-
* Gets the set of service flags which are "desirable" for a given peer.
315-
*
316-
* These are the flags which are required for a peer to support for them
317-
* to be "interesting" to us, ie for us to wish to use one of our few
318-
* outbound connection slots for or for us to wish to prioritize keeping
319-
* their connection around.
320-
*
321-
* Relevant service flags may be peer- and state-specific in that the
322-
* version of the peer may determine which flags are required (eg in the
323-
* case of NODE_NETWORK_LIMITED where we seek out NODE_NETWORK peers
324-
* unless they set NODE_NETWORK_LIMITED and we are out of IBD, in which
325-
* case NODE_NETWORK_LIMITED suffices).
326-
*
327-
* Thus, generally, avoid calling with peerServices == NODE_NONE, unless
328-
* state-specific flags must absolutely be avoided. When called with
329-
* peerServices == NODE_NONE, the returned desirable service flags are
330-
* guaranteed to not change dependent on state - ie they are suitable for
331-
* use when describing peers which we know to be desirable, but for which
332-
* we do not have a confirmed set of service flags.
333-
*
334-
* If the NODE_NONE return value is changed, contrib/seeds/makeseeds.py
335-
* should be updated appropriately to filter for the same nodes.
336-
*/
337-
ServiceFlags GetDesirableServiceFlags(ServiceFlags services);
338-
339-
/** Set the current IBD status in order to figure out the desirable service flags */
340-
void SetServiceFlagsIBDCache(bool status);
341-
342-
/**
343-
* A shortcut for (services & GetDesirableServiceFlags(services))
344-
* == GetDesirableServiceFlags(services), ie determines whether the given
345-
* set of service flags are sufficient for a peer to be "relevant".
346-
*/
347-
static inline bool HasAllDesirableServiceFlags(ServiceFlags services)
348-
{
349-
return !(GetDesirableServiceFlags(services) & (~services));
350-
}
314+
* State independent service flags.
315+
* If the return value is changed, contrib/seeds/makeseeds.py
316+
* should be updated appropriately to filter for nodes with
317+
* desired service flags (compatible with our new flags).
318+
*/
319+
constexpr ServiceFlags SeedsServiceFlags() { return ServiceFlags(NODE_NETWORK | NODE_WITNESS); }
351320

352321
/**
353322
* Checks if a peer with the given service flags may be capable of having a

src/test/fuzz/integer.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,6 @@ FUZZ_TARGET(integer, .init = initialize_integer)
213213

214214
{
215215
const ServiceFlags service_flags = (ServiceFlags)u64;
216-
(void)HasAllDesirableServiceFlags(service_flags);
217216
(void)MayHaveUsefulAddressDB(service_flags);
218217
}
219218

0 commit comments

Comments
 (0)