@@ -61,6 +61,8 @@ static constexpr auto UNCONDITIONAL_RELAY_DELAY = 2min;
6161 * Timeout = base + per_header * (expected number of headers) */
6262static constexpr auto HEADERS_DOWNLOAD_TIMEOUT_BASE = 15min;
6363static constexpr auto HEADERS_DOWNLOAD_TIMEOUT_PER_HEADER = 1ms;
64+ /* * How long to wait for a peer to respond to a getheaders request */
65+ static constexpr auto HEADERS_RESPONSE_TIME{2min};
6466/* * Protect at least this many outbound peers from disconnection due to slow/
6567 * behind headers chain.
6668 */
@@ -355,6 +357,9 @@ struct Peer {
355357 /* * Work queue of items requested by this peer **/
356358 std::deque<CInv> m_getdata_requests GUARDED_BY (m_getdata_requests_mutex);
357359
360+ /* * Time of the last getheaders message to this peer */
361+ std::atomic<std::chrono::seconds> m_last_getheaders_timestamp{0s};
362+
358363 Peer (NodeId id)
359364 : m_id{id}
360365 {}
@@ -501,7 +506,7 @@ class PeerManagerImpl final : public PeerManager
501506
502507private:
503508 /* * Consider evicting an outbound peer based on the amount of time they've been behind our tip */
504- void ConsiderEviction (CNode& pto, std::chrono::seconds time_in_seconds) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
509+ void ConsiderEviction (CNode& pto, Peer& peer, std::chrono::seconds time_in_seconds) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
505510
506511 /* * If we have extra outbound peers, try to disconnect the one with the oldest block announcement */
507512 void EvictExtraOutboundPeers (std::chrono::seconds now) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
@@ -567,9 +572,12 @@ class PeerManagerImpl final : public PeerManager
567572 void HandleFewUnconnectingHeaders (CNode& pfrom, Peer& peer, const std::vector<CBlockHeader>& headers);
568573 /* * Return true if the headers connect to each other, false otherwise */
569574 bool CheckHeadersAreContinuous (const std::vector<CBlockHeader>& headers) const ;
570- /* * Request further headers from this peer from a given block header */
571- void FetchMoreHeaders (CNode& pfrom, const CBlockIndex *pindexLast, const Peer& peer);
572- /* * Potentially fetch blocks from this peer upon receipt of new headers tip */
575+ /* * Request further headers from this peer with a given locator.
576+ * We don't issue a getheaders message if we have a recent one outstanding.
577+ * This returns true if a getheaders is actually sent, and false otherwise.
578+ */
579+ bool MaybeSendGetHeaders (CNode& pfrom, const CBlockLocator& locator, Peer& peer);
580+ /* * Potentially fetch blocks from this peer upon receipt of a new headers tip */
573581 void HeadersDirectFetchBlocks (CNode& pfrom, const CBlockIndex* pindexLast);
574582 /* * Update peer state based on received headers message */
575583 void UpdatePeerStateForReceivedHeaders (CNode& pfrom, const CBlockIndex *pindexLast, bool received_new_header, bool may_have_more_headers);
@@ -2228,15 +2236,14 @@ void PeerManagerImpl::HandleFewUnconnectingHeaders(CNode& pfrom, Peer& peer,
22282236 CNodeState *nodestate = State (pfrom.GetId ());
22292237
22302238 nodestate->nUnconnectingHeaders ++;
2231-
22322239 // Try to fill in the missing headers.
2233- m_connman. PushMessage (& pfrom, msgMaker. Make (NetMsgType::GETHEADERS, m_chainman.ActiveChain ().GetLocator (m_chainman.m_best_header ), uint256 ()));
2234- LogPrint (BCLog::NET, " received header %s: missing prev block %s, sending getheaders (%d) to end (peer=%d, nUnconnectingHeaders=%d)\n " ,
2240+ if ( MaybeSendGetHeaders ( pfrom, m_chainman.ActiveChain ().GetLocator (m_chainman.m_best_header ), peer)) {
2241+ LogPrint (BCLog::NET, " received header %s: missing prev block %s, sending getheaders (%d) to end (peer=%d, nUnconnectingHeaders=%d)\n " ,
22352242 headers[0 ].GetHash ().ToString (),
22362243 headers[0 ].hashPrevBlock .ToString (),
22372244 m_chainman.m_best_header ->nHeight ,
22382245 pfrom.GetId (), nodestate->nUnconnectingHeaders );
2239-
2246+ }
22402247 // Set hashLastUnknownBlock for this peer, so that if we
22412248 // eventually get the headers - even from a different peer -
22422249 // we can use this peer to download.
@@ -2261,23 +2268,19 @@ bool PeerManagerImpl::CheckHeadersAreContinuous(const std::vector<CBlockHeader>&
22612268 return true ;
22622269}
22632270
2264- /*
2265- * Continue fetching headers from a given point.
2266- * pindexLast should be the last header we learned from a peer in their prior
2267- * headers message.
2268- *
2269- * This is used for headers sync with a peer; even if pindexLast is an ancestor
2270- * of a known chain (such as our tip) we don't yet know where the peer's chain
2271- * might fork from what we know, so we continue exactly from where the peer
2272- * left off.
2273- */
2274- void PeerManagerImpl::FetchMoreHeaders (CNode& pfrom, const CBlockIndex *pindexLast, const Peer& peer)
2271+ bool PeerManagerImpl::MaybeSendGetHeaders (CNode& pfrom, const CBlockLocator& locator, Peer& peer)
22752272{
22762273 const CNetMsgMaker msgMaker (pfrom.GetCommonVersion ());
22772274
2278- LogPrint (BCLog::NET, " more getheaders (%d) to end to peer=%d (startheight:%d)\n " ,
2279- pindexLast->nHeight , pfrom.GetId (), peer.m_starting_height );
2280- m_connman.PushMessage (&pfrom, msgMaker.Make (NetMsgType::GETHEADERS, m_chainman.ActiveChain ().GetLocator (pindexLast), uint256 ()));
2275+ const auto current_time = GetTime<std::chrono::seconds>();
2276+ // Only allow a new getheaders message to go out if we don't have a recent
2277+ // one already in-flight
2278+ if (peer.m_last_getheaders_timestamp .load () < current_time - HEADERS_RESPONSE_TIME) {
2279+ m_connman.PushMessage (&pfrom, msgMaker.Make (NetMsgType::GETHEADERS, locator, uint256 ()));
2280+ peer.m_last_getheaders_timestamp = current_time;
2281+ return true ;
2282+ }
2283+ return false ;
22812284}
22822285
22832286/*
@@ -2458,7 +2461,10 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, Peer& peer,
24582461 // Consider fetching more headers.
24592462 if (nCount == MAX_HEADERS_RESULTS) {
24602463 // Headers message had its maximum size; the peer may have more headers.
2461- FetchMoreHeaders (pfrom, pindexLast, peer);
2464+ if (MaybeSendGetHeaders (pfrom, m_chainman.ActiveChain ().GetLocator (pindexLast), peer)) {
2465+ LogPrint (BCLog::NET, " more getheaders (%d) to end to peer=%d (startheight:%d)\n " ,
2466+ pindexLast->nHeight , pfrom.GetId (), peer.m_starting_height );
2467+ }
24622468 }
24632469
24642470 UpdatePeerStateForReceivedHeaders (pfrom, pindexLast, received_new_header, nCount == MAX_HEADERS_RESULTS);
@@ -3228,8 +3234,11 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
32283234 }
32293235
32303236 if (best_block != nullptr ) {
3231- m_connman.PushMessage (&pfrom, msgMaker.Make (NetMsgType::GETHEADERS, m_chainman.ActiveChain ().GetLocator (m_chainman.m_best_header ), *best_block));
3232- LogPrint (BCLog::NET, " getheaders (%d) %s to peer=%d\n " , m_chainman.m_best_header ->nHeight , best_block->ToString (), pfrom.GetId ());
3237+ if (MaybeSendGetHeaders (pfrom, m_chainman.ActiveChain ().GetLocator (m_chainman.m_best_header ), *peer)) {
3238+ LogPrint (BCLog::NET, " getheaders (%d) %s to peer=%d\n " ,
3239+ m_chainman.m_best_header ->nHeight , best_block->ToString (),
3240+ pfrom.GetId ());
3241+ }
32333242 }
32343243
32353244 return ;
@@ -3402,7 +3411,10 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
34023411 // others.
34033412 if (m_chainman.ActiveTip () == nullptr ||
34043413 (m_chainman.ActiveTip ()->nChainWork < nMinimumChainWork && !pfrom.HasPermission (NetPermissionFlags::Download))) {
3405- LogPrint (BCLog::NET, " Ignoring getheaders from peer=%d because active chain has too little work\n " , pfrom.GetId ());
3414+ LogPrint (BCLog::NET, " Ignoring getheaders from peer=%d because active chain has too little work; sending empty response\n " , pfrom.GetId ());
3415+ // Just respond with an empty headers message, to tell the peer to
3416+ // go away but not treat us as unresponsive.
3417+ m_connman.PushMessage (&pfrom, msgMaker.Make (NetMsgType::HEADERS, std::vector<CBlock>()));
34063418 return ;
34073419 }
34083420
@@ -3683,8 +3695,9 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
36833695
36843696 if (!m_chainman.m_blockman .LookupBlockIndex (cmpctblock.header .hashPrevBlock )) {
36853697 // Doesn't connect (or is genesis), instead of DoSing in AcceptBlockHeader, request deeper headers
3686- if (!m_chainman.ActiveChainstate ().IsInitialBlockDownload ())
3687- m_connman.PushMessage (&pfrom, msgMaker.Make (NetMsgType::GETHEADERS, m_chainman.ActiveChain ().GetLocator (m_chainman.m_best_header ), uint256 ()));
3698+ if (!m_chainman.ActiveChainstate ().IsInitialBlockDownload ()) {
3699+ MaybeSendGetHeaders (pfrom, m_chainman.ActiveChain ().GetLocator (m_chainman.m_best_header ), *peer);
3700+ }
36883701 return ;
36893702 }
36903703
@@ -3958,6 +3971,10 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
39583971 return ;
39593972 }
39603973
3974+ // Assume that this is in response to any outstanding getheaders
3975+ // request we may have sent, and clear out the time of our last request
3976+ peer->m_last_getheaders_timestamp = 0s;
3977+
39613978 std::vector<CBlockHeader> headers;
39623979
39633980 // Bypass the normal CBlock deserialization, as we don't want to risk deserializing 2000 full blocks.
@@ -4386,7 +4403,7 @@ bool PeerManagerImpl::ProcessMessages(CNode* pfrom, std::atomic<bool>& interrupt
43864403 return fMoreWork ;
43874404}
43884405
4389- void PeerManagerImpl::ConsiderEviction (CNode& pto, std::chrono::seconds time_in_seconds)
4406+ void PeerManagerImpl::ConsiderEviction (CNode& pto, Peer& peer, std::chrono::seconds time_in_seconds)
43904407{
43914408 AssertLockHeld (cs_main);
43924409
@@ -4424,10 +4441,15 @@ void PeerManagerImpl::ConsiderEviction(CNode& pto, std::chrono::seconds time_in_
44244441 pto.fDisconnect = true ;
44254442 } else {
44264443 assert (state.m_chain_sync .m_work_header );
4444+ // Here, we assume that the getheaders message goes out,
4445+ // because it'll either go out or be skipped because of a
4446+ // getheaders in-flight already, in which case the peer should
4447+ // still respond to us with a sufficiently high work chain tip.
4448+ MaybeSendGetHeaders (pto,
4449+ m_chainman.ActiveChain ().GetLocator (state.m_chain_sync .m_work_header ->pprev ),
4450+ peer);
44274451 LogPrint (BCLog::NET, " sending getheaders to outbound peer=%d to verify chain work (current best known block:%s, benchmark blockhash: %s)\n " , pto.GetId (), state.pindexBestKnownBlock != nullptr ? state.pindexBestKnownBlock ->GetBlockHash ().ToString () : " <none>" , state.m_chain_sync .m_work_header ->GetBlockHash ().ToString ());
4428- m_connman.PushMessage (&pto, msgMaker.Make (NetMsgType::GETHEADERS, m_chainman.ActiveChain ().GetLocator (state.m_chain_sync .m_work_header ->pprev ), uint256 ()));
44294452 state.m_chain_sync .m_sent_getheaders = true ;
4430- constexpr auto HEADERS_RESPONSE_TIME{2min};
44314453 // Bump the timeout to allow a response, which could clear the timeout
44324454 // (if the response shows the peer has synced), reset the timeout (if
44334455 // the peer syncs to the required work but not to our tip), or result
@@ -4835,15 +4857,6 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
48354857 if (!state.fSyncStarted && !pto->fClient && !fImporting && !fReindex ) {
48364858 // Only actively request headers from a single peer, unless we're close to today.
48374859 if ((nSyncStarted == 0 && sync_blocks_and_headers_from_peer) || m_chainman.m_best_header ->GetBlockTime () > GetAdjustedTime () - 24 * 60 * 60 ) {
4838- state.fSyncStarted = true ;
4839- state.m_headers_sync_timeout = current_time + HEADERS_DOWNLOAD_TIMEOUT_BASE +
4840- (
4841- // Convert HEADERS_DOWNLOAD_TIMEOUT_PER_HEADER to microseconds before scaling
4842- // to maintain precision
4843- std::chrono::microseconds{HEADERS_DOWNLOAD_TIMEOUT_PER_HEADER} *
4844- (GetAdjustedTime () - m_chainman.m_best_header ->GetBlockTime ()) / consensusParams.nPowTargetSpacing
4845- );
4846- nSyncStarted++;
48474860 const CBlockIndex* pindexStart = m_chainman.m_best_header ;
48484861 /* If possible, start at the block preceding the currently
48494862 best known header. This ensures that we always get a
@@ -4854,8 +4867,19 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
48544867 got back an empty response. */
48554868 if (pindexStart->pprev )
48564869 pindexStart = pindexStart->pprev ;
4857- LogPrint (BCLog::NET, " initial getheaders (%d) to peer=%d (startheight:%d)\n " , pindexStart->nHeight , pto->GetId (), peer->m_starting_height );
4858- m_connman.PushMessage (pto, msgMaker.Make (NetMsgType::GETHEADERS, m_chainman.ActiveChain ().GetLocator (pindexStart), uint256 ()));
4870+ if (MaybeSendGetHeaders (*pto, m_chainman.ActiveChain ().GetLocator (pindexStart), *peer)) {
4871+ LogPrint (BCLog::NET, " initial getheaders (%d) to peer=%d (startheight:%d)\n " , pindexStart->nHeight , pto->GetId (), peer->m_starting_height );
4872+
4873+ state.fSyncStarted = true ;
4874+ state.m_headers_sync_timeout = current_time + HEADERS_DOWNLOAD_TIMEOUT_BASE +
4875+ (
4876+ // Convert HEADERS_DOWNLOAD_TIMEOUT_PER_HEADER to microseconds before scaling
4877+ // to maintain precision
4878+ std::chrono::microseconds{HEADERS_DOWNLOAD_TIMEOUT_PER_HEADER} *
4879+ (GetAdjustedTime () - m_chainman.m_best_header ->GetBlockTime ()) / consensusParams.nPowTargetSpacing
4880+ );
4881+ nSyncStarted++;
4882+ }
48594883 }
48604884 }
48614885
@@ -5201,7 +5225,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
52015225
52025226 // Check that outbound peers have reasonable chains
52035227 // GetTime() is used by this anti-DoS logic so we can test this using mocktime
5204- ConsiderEviction (*pto, GetTime<std::chrono::seconds>());
5228+ ConsiderEviction (*pto, *peer, GetTime<std::chrono::seconds>());
52055229
52065230 //
52075231 // Message: getdata (blocks)
0 commit comments