From e845e2634430b44f6379e2e49bbcf2e1ac29c5e4 Mon Sep 17 00:00:00 2001 From: David Gumberg Date: Fri, 23 May 2025 14:56:12 -0700 Subject: [PATCH] test: p2p: Nodes ignore unsolicited CMPCTBLOCK's --- test/functional/p2p_compactblocks.py | 67 +++++++++++++++++++++++++--- 1 file changed, 60 insertions(+), 7 deletions(-) diff --git a/test/functional/p2p_compactblocks.py b/test/functional/p2p_compactblocks.py index 243b010f932..82c1a944c0a 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()