Skip to content

Commit abf5d16

Browse files
committed
Don't send getheaders message when another request is outstanding
Change getheaders messages so that we wait up to 2 minutes for a response to a prior getheaders message before issuing a new one. Also change the handling of the getheaders message sent in response to a block INV, so that we no longer use the hashstop variable (including the hash stop will just mean that if our peer's headers chain is longer, then we won't learn it, so there's no benefit to using hashstop). Also, now respond to a getheaders during IBD with an empty headers message (rather than nothing) -- this better conforms to the intent of the new logic that it's better to not ignore a peer's getheaders message, even if you have nothing to give. This also avoids a lot of functional tests breaking. p2p_segwit.py is modified to use this same strategy, as the test logic (of expecting a getheaders after a block inv) would otherwise be broken.
1 parent ffe87db commit abf5d16

File tree

3 files changed

+72
-44
lines changed

3 files changed

+72
-44
lines changed

src/net_processing.cpp

Lines changed: 67 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ static constexpr auto UNCONDITIONAL_RELAY_DELAY = 2min;
6161
* Timeout = base + per_header * (expected number of headers) */
6262
static constexpr auto HEADERS_DOWNLOAD_TIMEOUT_BASE = 15min;
6363
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};
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

502507
private:
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)

test/functional/feature_minchainwork.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ def run_test(self):
8282
msg.hashstop = 0
8383
peer.send_and_ping(msg)
8484
time.sleep(5)
85-
assert "headers" not in peer.last_message
85+
assert ("headers" not in peer.last_message or len(peer.last_message["headers"].headers) == 0)
8686

8787
self.log.info("Generating one more block")
8888
self.generate(self.nodes[0], 1)

test/functional/p2p_segwit.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,10 @@ def test_block_relay(self):
371371
block1 = self.build_next_block()
372372
block1.solve()
373373

374+
# Send an empty headers message, to clear out any prior getheaders
375+
# messages that our peer may be waiting for us on.
376+
self.test_node.send_message(msg_headers())
377+
374378
self.test_node.announce_block_and_wait_for_getdata(block1, use_header=False)
375379
assert self.test_node.last_message["getdata"].inv[0].type == blocktype
376380
test_witness_block(self.nodes[0], self.test_node, block1, True)

0 commit comments

Comments
 (0)