@@ -61,6 +61,8 @@ static constexpr auto UNCONDITIONAL_RELAY_DELAY = 2min;
61
61
* Timeout = base + per_header * (expected number of headers) */
62
62
static constexpr auto HEADERS_DOWNLOAD_TIMEOUT_BASE = 15min;
63
63
static 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};
64
66
/* * Protect at least this many outbound peers from disconnection due to slow/
65
67
* behind headers chain.
66
68
*/
@@ -355,6 +357,9 @@ struct Peer {
355
357
/* * Work queue of items requested by this peer **/
356
358
std::deque<CInv> m_getdata_requests GUARDED_BY (m_getdata_requests_mutex);
357
359
360
+ /* * Time of the last getheaders message to this peer */
361
+ std::atomic<std::chrono::seconds> m_last_getheaders_timestamp{0s};
362
+
358
363
Peer (NodeId id)
359
364
: m_id{id}
360
365
{}
@@ -501,7 +506,7 @@ class PeerManagerImpl final : public PeerManager
501
506
502
507
private:
503
508
/* * 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);
505
510
506
511
/* * If we have extra outbound peers, try to disconnect the one with the oldest block announcement */
507
512
void EvictExtraOutboundPeers (std::chrono::seconds now) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
@@ -567,9 +572,12 @@ class PeerManagerImpl final : public PeerManager
567
572
void HandleFewUnconnectingHeaders (CNode& pfrom, Peer& peer, const std::vector<CBlockHeader>& headers);
568
573
/* * Return true if the headers connect to each other, false otherwise */
569
574
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 */
573
581
void HeadersDirectFetchBlocks (CNode& pfrom, const CBlockIndex* pindexLast);
574
582
/* * Update peer state based on received headers message */
575
583
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,
2228
2236
CNodeState *nodestate = State (pfrom.GetId ());
2229
2237
2230
2238
nodestate->nUnconnectingHeaders ++;
2231
-
2232
2239
// 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 " ,
2235
2242
headers[0 ].GetHash ().ToString (),
2236
2243
headers[0 ].hashPrevBlock .ToString (),
2237
2244
m_chainman.m_best_header ->nHeight ,
2238
2245
pfrom.GetId (), nodestate->nUnconnectingHeaders );
2239
-
2246
+ }
2240
2247
// Set hashLastUnknownBlock for this peer, so that if we
2241
2248
// eventually get the headers - even from a different peer -
2242
2249
// we can use this peer to download.
@@ -2261,23 +2268,19 @@ bool PeerManagerImpl::CheckHeadersAreContinuous(const std::vector<CBlockHeader>&
2261
2268
return true ;
2262
2269
}
2263
2270
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)
2275
2272
{
2276
2273
const CNetMsgMaker msgMaker (pfrom.GetCommonVersion ());
2277
2274
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 ;
2281
2284
}
2282
2285
2283
2286
/*
@@ -2458,7 +2461,10 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, Peer& peer,
2458
2461
// Consider fetching more headers.
2459
2462
if (nCount == MAX_HEADERS_RESULTS) {
2460
2463
// 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
+ }
2462
2468
}
2463
2469
2464
2470
UpdatePeerStateForReceivedHeaders (pfrom, pindexLast, received_new_header, nCount == MAX_HEADERS_RESULTS);
@@ -3228,8 +3234,11 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
3228
3234
}
3229
3235
3230
3236
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
+ }
3233
3242
}
3234
3243
3235
3244
return ;
@@ -3402,7 +3411,10 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
3402
3411
// others.
3403
3412
if (m_chainman.ActiveTip () == nullptr ||
3404
3413
(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>()));
3406
3418
return ;
3407
3419
}
3408
3420
@@ -3683,8 +3695,9 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
3683
3695
3684
3696
if (!m_chainman.m_blockman .LookupBlockIndex (cmpctblock.header .hashPrevBlock )) {
3685
3697
// 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
+ }
3688
3701
return ;
3689
3702
}
3690
3703
@@ -3958,6 +3971,10 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
3958
3971
return ;
3959
3972
}
3960
3973
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
+
3961
3978
std::vector<CBlockHeader> headers;
3962
3979
3963
3980
// 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
4386
4403
return fMoreWork ;
4387
4404
}
4388
4405
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)
4390
4407
{
4391
4408
AssertLockHeld (cs_main);
4392
4409
@@ -4424,10 +4441,15 @@ void PeerManagerImpl::ConsiderEviction(CNode& pto, std::chrono::seconds time_in_
4424
4441
pto.fDisconnect = true ;
4425
4442
} else {
4426
4443
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);
4427
4451
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 ()));
4429
4452
state.m_chain_sync .m_sent_getheaders = true ;
4430
- constexpr auto HEADERS_RESPONSE_TIME{2min};
4431
4453
// Bump the timeout to allow a response, which could clear the timeout
4432
4454
// (if the response shows the peer has synced), reset the timeout (if
4433
4455
// the peer syncs to the required work but not to our tip), or result
@@ -4835,15 +4857,6 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
4835
4857
if (!state.fSyncStarted && !pto->fClient && !fImporting && !fReindex ) {
4836
4858
// Only actively request headers from a single peer, unless we're close to today.
4837
4859
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++;
4847
4860
const CBlockIndex* pindexStart = m_chainman.m_best_header ;
4848
4861
/* If possible, start at the block preceding the currently
4849
4862
best known header. This ensures that we always get a
@@ -4854,8 +4867,19 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
4854
4867
got back an empty response. */
4855
4868
if (pindexStart->pprev )
4856
4869
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
+ }
4859
4883
}
4860
4884
}
4861
4885
@@ -5201,7 +5225,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
5201
5225
5202
5226
// Check that outbound peers have reasonable chains
5203
5227
// 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>());
5205
5229
5206
5230
//
5207
5231
// Message: getdata (blocks)
0 commit comments