Skip to content

Commit 9f66ac7

Browse files
committed
net_processing: do not treat non-connecting headers as response
Since bitcoin/bitcoin#25454 we keep track of the last GETHEADERS request that was sent and wasn't responded to. So far, every incoming HEADERS message is treated as a response to whatever GETHEADERS was last sent, regardless of its contents. This commit makes this tracking more accurate, by only treating HEADERS messages which (1) are empty, (2) connect to our existing block header tree, or (3) are a continuation of a low-work headers sync as responses that clear the "outstanding GETHEADERS" state (m_last_getheaders_timestamp). That means that HEADERS messages which do not satisfy any of the above criteria will be ignored, not triggering a GETHEADERS, and potentially (for now, but see later commit) increase misbehavior score.
1 parent 0a7c650 commit 9f66ac7

File tree

2 files changed

+24
-17
lines changed

2 files changed

+24
-17
lines changed

src/net_processing.cpp

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2768,25 +2768,21 @@ bool PeerManagerImpl::IsContinuationOfLowWorkHeadersSync(Peer& peer, CNode& pfro
27682768
{
27692769
if (peer.m_headers_sync) {
27702770
auto result = peer.m_headers_sync->ProcessNextHeaders(headers, headers.size() == MAX_HEADERS_RESULTS);
2771+
// If it is a valid continuation, we should treat the existing getheaders request as responded to.
2772+
if (result.success) peer.m_last_getheaders_timestamp = {};
27712773
if (result.request_more) {
27722774
auto locator = peer.m_headers_sync->NextHeadersRequestLocator();
27732775
// If we were instructed to ask for a locator, it should not be empty.
27742776
Assume(!locator.vHave.empty());
2777+
// We can only be instructed to request more if processing was successful.
2778+
Assume(result.success);
27752779
if (!locator.vHave.empty()) {
27762780
// It should be impossible for the getheaders request to fail,
2777-
// because we should have cleared the last getheaders timestamp
2778-
// when processing the headers that triggered this call. But
2779-
// it may be possible to bypass this via compactblock
2780-
// processing, so check the result before logging just to be
2781-
// safe.
2781+
// because we just cleared the last getheaders timestamp.
27822782
bool sent_getheaders = MaybeSendGetHeaders(pfrom, locator, peer);
2783-
if (sent_getheaders) {
2784-
LogPrint(BCLog::NET, "more getheaders (from %s) to peer=%d\n",
2785-
locator.vHave.front().ToString(), pfrom.GetId());
2786-
} else {
2787-
LogPrint(BCLog::NET, "error sending next getheaders (from %s) to continue sync with peer=%d\n",
2788-
locator.vHave.front().ToString(), pfrom.GetId());
2789-
}
2783+
Assume(sent_getheaders);
2784+
LogPrint(BCLog::NET, "more getheaders (from %s) to peer=%d\n",
2785+
locator.vHave.front().ToString(), pfrom.GetId());
27902786
}
27912787
}
27922788

@@ -3065,6 +3061,9 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, Peer& peer,
30653061
LOCK(m_headers_presync_mutex);
30663062
m_headers_presync_stats.erase(pfrom.GetId());
30673063
}
3064+
// A headers message with no headers cannot be an announcement, so assume
3065+
// it is a response to our last getheaders request, if there is one.
3066+
peer.m_last_getheaders_timestamp = {};
30683067
return;
30693068
}
30703069

@@ -3129,6 +3128,11 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, Peer& peer,
31293128
return;
31303129
}
31313130

3131+
// If headers connect, assume that this is in response to any outstanding getheaders
3132+
// request we may have sent, and clear out the time of our last request. Non-connecting
3133+
// headers cannot be a response to a getheaders request.
3134+
peer.m_last_getheaders_timestamp = {};
3135+
31323136
// If the headers we received are already in memory and an ancestor of
31333137
// m_best_header or our tip, skip anti-DoS checks. These headers will not
31343138
// use any more memory (and we are not leaking information that could be
@@ -4984,10 +4988,6 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
49844988
return;
49854989
}
49864990

4987-
// Assume that this is in response to any outstanding getheaders
4988-
// request we may have sent, and clear out the time of our last request
4989-
peer->m_last_getheaders_timestamp = {};
4990-
49914991
std::vector<CBlockHeader> headers;
49924992

49934993
// Bypass the normal CBlock deserialization, as we don't want to risk deserializing 2000 full blocks.

test/functional/p2p_sendheaders.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -559,7 +559,13 @@ def test_nonnull_locators(self, test_node, inv_node):
559559
height += 1
560560

561561
for i in range(1, MAX_NUM_UNCONNECTING_HEADERS_MSGS):
562-
# Send a header that doesn't connect, check that we get a getheaders.
562+
with p2p_lock:
563+
test_node.last_message.pop("getheaders", None)
564+
# Send an empty header as a failed response to the received getheaders
565+
# (from the previous iteration). Otherwise, the new headers will be
566+
# treated as a response instead of as an announcement.
567+
test_node.send_header_for_blocks([])
568+
# Send the actual unconnecting header, which should trigger a new getheaders.
563569
test_node.send_header_for_blocks([blocks[i]])
564570
test_node.wait_for_getheaders(block_hash=expected_hash)
565571

@@ -574,6 +580,7 @@ def test_nonnull_locators(self, test_node, inv_node):
574580
# before we get disconnected. Should be 5*MAX_NUM_UNCONNECTING_HEADERS_MSGS
575581
for i in range(5 * MAX_NUM_UNCONNECTING_HEADERS_MSGS - 1):
576582
# Send a header that doesn't connect, check that we get a getheaders.
583+
test_node.send_header_for_blocks([])
577584
test_node.send_header_for_blocks([blocks[i % len(blocks)]])
578585
test_node.wait_for_getheaders(block_hash=expected_hash)
579586

0 commit comments

Comments
 (0)