p2p: reject empty getblocktxn requests
What changed, and why it matters
This change makes Bitcoin Core disconnect any peer that sends a 'getblocktxn' message asking for zero transactions. Such a request is pointless because if no transactions are missing, the receiver already has everything it needs to reconstruct the block. The patch prevents the node from wastefully reading a block from disk and helps identify misbehaving or buggy peers. It is a hardening fix rather than a clear-cut exploit patch, and the commit message frames it as performance/robustness, not a security vulnerability.
Treat as a low-severity hardening improvement. Operators and downstream maintainers should include it in routine updates, but it does not appear to require an emergency security release. Reviewers may want to confirm that the disconnect does not interfere with any legitimate high-bandwidth compact-block recovery path.
Security signals we found
Denial-of-service hardening: prevents a peer from forcing the node to read a block from disk for no legitimate purpose
Protocol invariant enforcement: empty getblocktxn requests are semantically invalid under BIP 152 compact block reconstruction
Peer misbehavior response: disconnects rather than servicing the request
No memory corruption, authentication bypass, or consensus change
Evidence from the diff
In src/net_processing.cpp, after deserializing a GETBLOCKTXN message, the code now checks req.indexes.empty(). If true, it logs a debug message and sets pfrom.fDisconnect = true, terminating the connection. Previously, an empty request would proceed into the normal handler, which would look up the requested block hash and read it from disk to build a (useless) empty response. A functional test is added to p2p_compactblocks.py to verify the disconnect behavior. The change is defensive: it closes a protocol-inefficient edge case that could be abused to trigger unnecessary disk I/O on demand from any peer.
Changed components
src/net_processing.cpp GETBLOCKTXN message handlerBitcoin Core P2P compact block (BIP 152) protocol implementationtest/functional/p2p_compactblocks.py functional test suiteInspect captured patch +20 / −0
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index 7387f78b..f5f81a65 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -4345,6 +4345,14 @@ void PeerManagerImpl::ProcessMessage(Peer& peer, CNode& pfrom, const std::string
if (msg_type == NetMsgType::GETBLOCKTXN) {
BlockTransactionsRequest req;
vRecv >> req;
+
+ // No legitimate reason to send indexes empty
+ if (req.indexes.empty()) {
+ LogDebug(BCLog::NET, "getblocktxn received with no transaction indexes, %s", pfrom.DisconnectMsg());
+ pfrom.fDisconnect = true;
+ return;
+ }
+
// Verify differential encoding invariant: indexes must be strictly increasing
// DifferenceFormatter should guarantee this property during deserialization
for (size_t i = 1; i < req.indexes.size(); ++i) {
diff --git a/test/functional/p2p_compactblocks.py b/test/functional/p2p_compactblocks.py
index 8db0fab7..7466b833 100755
--- a/test/functional/p2p_compactblocks.py
+++ b/test/functional/p2p_compactblocks.py
@@ -843,6 +843,17 @@ class CompactBlocksTest(BitcoinTestFramework):
msg = msg_cmpctblock(comp_block.to_p2p())
test_node.send_await_disconnect(msg)
+ def test_empty_getblocktxn_disconnects(self):
+ self.log.info("Testing empty getblocktxn disconnects the peer...")
+ node = self.nodes[0]
+ block_hash = int(node.getbestblockhash(), 16)
+ peer = node.add_p2p_connection(P2PInterface())
+ msg = msg_getblocktxn()
+ msg.block_txn_request = BlockTransactionsRequest(blockhash=block_hash, indexes=[])
+ with node.assert_debug_log(['getblocktxn received with no transaction indexes']):
+ peer.send_without_ping(msg)
+ peer.wait_for_disconnect()
+
# peer generates a block and sends it to node, which makes the peer a
# candidate for high-bandwidth 'to' (up to 3 peers according to BIP 152)
def make_peer_hb_to_candidate(self, node, peer):
@@ -1097,6 +1108,7 @@ class CompactBlocksTest(BitcoinTestFramework):
self.log.info("Testing handling of invalid compact blocks...")
self.test_invalid_tx_in_compactblock(self.segwit_node)
+ self.test_empty_getblocktxn_disconnects()
# The previous test will lead to a disconnection. Reconnect before continuing.
self.segwit_node = self.nodes[0].add_p2p_connection(TestP2PConn())
Why this scored 31/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.