test: p2p: Nodes ignore unsolicited CMPCTBLOCK's
What changed, and why it matters
This commit only adds a new functional test to Bitcoin Core. It verifies that nodes ignore unsolicited compact block (CMPCTBLOCK) messages from peers that are not in high-bandwidth mode, while still accepting them from high-bandwidth peers or when explicitly requested. There is no change to production code, so it cannot directly introduce or fix a runtime security vulnerability.
No security action required. Review as normal test-quality change. If the underlying behavior being tested is newly introduced or recently changed, ensure the corresponding production commit was already reviewed for security relevance.
Security signals we found
Behavioral test coverage for P2P compact block acceptance policy
Refactoring of test helper to use explicit peer index instead of last peer
No modification of src/ production code
Evidence from the diff
The diff modifies test/functional/p2p_compactblocks.py. It refactors assert_highbandwidth_states to accept a peer index, then adds test_compact_blocks_ignored() and wires it into run_test(). The new test confirms: (1) unsolicited CMPCTBLOCK from a non-HB peer is ignored and does not flip HB state; (2) solicited CMPCTBLOCK from a non-HB peer is processed; (3) unsolicited CMPCTBLOCK from an existing HB peer is processed. No consensus, networking, or wallet code is changed.
Changed components
test/functional/p2p_compactblocks.pyInspect captured patch +60 / −7
diff --git a/test/functional/p2p_compactblocks.py b/test/functional/p2p_compactblocks.py
index 243b010f..82c1a944 100755
--- a/test/functional/p2p_compactblocks.py
+++ b/test/functional/p2p_compactblocks.py
@@ -870,10 +870,10 @@ class CompactBlocksTest(BitcoinTestFramework):
assert_equal(node.getbestblockhash(), block.hash_hex)
# assert the RPC getpeerinfo boolean fields `bip152_hb_{to, from}`
- # match the given parameters for the last peer of a given node
+ # match the given parameters for the peer at idx of a given node
@staticmethod
- def assert_highbandwidth_states(node, hb_to, hb_from):
- peerinfo = node.getpeerinfo()[-1]
+ def assert_highbandwidth_states(node, idx, hb_to, hb_from):
+ peerinfo = node.getpeerinfo()[idx]
assert_equal(peerinfo['bip152_hb_to'], hb_to)
assert_equal(peerinfo['bip152_hb_from'], hb_from)
@@ -881,22 +881,25 @@ class CompactBlocksTest(BitcoinTestFramework):
# create new p2p connection for a fresh state w/o any prior sendcmpct messages sent
hb_test_node = self.nodes[0].add_p2p_connection(TestP2PConn())
+ # Newly created hb_test_node is the last connection.
+ hb_test_node_idx = -1
+
# initially, neither node has selected the other peer as high-bandwidth yet
- self.assert_highbandwidth_states(self.nodes[0], hb_to=False, hb_from=False)
+ self.assert_highbandwidth_states(self.nodes[0], hb_test_node_idx, hb_to=False, hb_from=False)
# peer requests high-bandwidth mode by sending sendcmpct(1)
hb_test_node.send_and_ping(msg_sendcmpct(announce=True, version=2))
- self.assert_highbandwidth_states(self.nodes[0], hb_to=False, hb_from=True)
+ self.assert_highbandwidth_states(self.nodes[0], hb_test_node_idx, hb_to=False, hb_from=True)
# peer generates a block and sends it to node, which should
# select the peer as high-bandwidth (up to 3 peers according to BIP 152)
block = self.build_block_on_tip(self.nodes[0])
hb_test_node.send_and_ping(msg_block(block))
- self.assert_highbandwidth_states(self.nodes[0], hb_to=True, hb_from=True)
+ self.assert_highbandwidth_states(self.nodes[0], hb_test_node_idx, hb_to=True, hb_from=True)
# peer requests low-bandwidth mode by sending sendcmpct(0)
hb_test_node.send_and_ping(msg_sendcmpct(announce=False, version=2))
- self.assert_highbandwidth_states(self.nodes[0], hb_to=True, hb_from=False)
+ self.assert_highbandwidth_states(self.nodes[0], hb_test_node_idx, hb_to=True, hb_from=False)
def test_compactblock_reconstruction_parallel_reconstruction(self, stalling_peer, delivery_peer, inbound_peer, outbound_peer):
""" All p2p connections are inbound except outbound_peer. We test that ultimate parallel slot
@@ -952,6 +955,53 @@ class CompactBlocksTest(BitcoinTestFramework):
stalling_peer.send_and_ping(msg)
self.utxos.append([block.vtx[-1].txid_int, 0, block.vtx[-1].vout[0].nValue])
+ def test_compact_blocks_ignored(self):
+ node = self.nodes[0]
+
+ def build_compact_block():
+ # generate a compact block to send that will force a GETBLOCKTXN
+ utxo = self.utxos.pop(0)
+ block = self.build_block_with_transactions(node, utxo, 10)
+ cmpct_block = HeaderAndShortIDs()
+ cmpct_block.initialize_from_block(block)
+ msg = msg_cmpctblock(cmpct_block.to_p2p())
+ return block, msg
+
+ # Sends a compact block, uses presence of GETBLOCKTXN as a heuristic
+ # for whether or not the node processed the CMPCTBLOCK.
+ # Note: since we never fulfill the GETBLOCKTXN, this will never change
+ # the HB status of a connection.
+ def ignores_compact_block(conn, solicited=False):
+ block, msg = build_compact_block()
+ conn.clear_getblocktxn()
+ if solicited:
+ conn.send_without_ping(msg_headers([block]))
+ conn.wait_for_getdata([block.hash_int], timeout=30)
+ conn.send_and_ping(msg)
+ with p2p_lock:
+ return "getblocktxn" not in conn.last_message
+
+ # create new p2p connection for a fresh state w/o any prior sendcmpct messages sent
+ unsolicited_peer = self.nodes[0].add_p2p_connection(TestP2PConn())
+ self.assert_highbandwidth_states(node, idx=-1, hb_to=False, hb_from=False)
+
+ self.log.info("Test that a node ignores unsolicited CMPCTBLOCK messages from non-HB peers.")
+ assert ignores_compact_block(unsolicited_peer, solicited=False)
+ self.assert_highbandwidth_states(node, idx=-1, hb_to=False, hb_from=False)
+
+ self.log.info("Test that a node does not ignore solicited CMPCTBLOCK messages from non-HB peers.")
+ assert not ignores_compact_block(unsolicited_peer, solicited=True)
+ self.assert_highbandwidth_states(node, idx=-1, hb_to=False, hb_from=False)
+
+ # The node will ask for transactions from an unsolicited compact block
+ # it receives from a high bandwidth peer, we need to use one set up earlier,
+ # since all the slots are full.
+ self.log.info("Test that a node does not ignore unsolicited CMPCTBLOCK messages from HB peers.")
+
+ hb_peer_idx = -2
+ self.assert_highbandwidth_states(node, idx=hb_peer_idx, hb_to=True, hb_from=False)
+ hb_peer = self.nodes[0].p2ps[hb_peer_idx]
+ assert not ignores_compact_block(hb_peer, solicited=False)
def run_test(self):
self.wallet = MiniWallet(self.nodes[0])
@@ -1027,6 +1077,9 @@ class CompactBlocksTest(BitcoinTestFramework):
self.log.info("Testing high-bandwidth mode states via getpeerinfo...")
self.test_highbandwidth_mode_states_via_getpeerinfo()
+ self.log.info("Testing CMPCTBLOCK messages are ignored when expected...")
+ self.test_compact_blocks_ignored()
+
if __name__ == '__main__':
CompactBlocksTest(__file__).main()
Why this scored 15/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.