From bf9884f4e55df502b67b2636969cacce62edaee9 Mon Sep 17 00:00:00 2001 From: David Gumberg Date: Thu, 19 Feb 2026 10:51:25 -0800 Subject: [PATCH] p2p: make blocksonly nodes ignore CMPCTBLOCK messages blocksonly nodes don't benefit from compact blocks, since they don't have a mempool to aid in reconstruction, so they should not process CMPCTBLOCK messages. This is not just belt-and-suspenders, as a blocksonly node will trivially reveal exactly which transactions in a block are its own in the GETBLOCKTXN response to a CMPCTBLOCK. Since it will be missing every transaction in the block, except for its own. See discussion: https://github.com/bitcoin/bitcoin/issues/28272 --- src/net_processing.cpp | 5 +- .../p2p_compactblocks_blocksonly.py | 47 +++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/net_processing.cpp b/src/net_processing.cpp index e7afa10fd4a..41d39966f0b 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -4469,7 +4469,10 @@ void PeerManagerImpl::ProcessMessage(Peer& peer, CNode& pfrom, const std::string { // Ignore cmpctblock received while importing if (m_chainman.m_blockman.LoadingBlocks()) { - LogDebug(BCLog::NET, "Unexpected cmpctblock message received from peer %d\n", pfrom.GetId()); + LogDebug(BCLog::CMPCTBLOCK, "%s sent us a compact block even though we are still loading blocks!", pfrom.LogPeer()); + return; + } else if (m_opts.ignore_incoming_txs) { + LogDebug(BCLog::CMPCTBLOCK, "%s sent us a compact block even though we are blocksonly!", pfrom.LogPeer()); return; } diff --git a/test/functional/p2p_compactblocks_blocksonly.py b/test/functional/p2p_compactblocks_blocksonly.py index befc283fff0..3fed634a922 100755 --- a/test/functional/p2p_compactblocks_blocksonly.py +++ b/test/functional/p2p_compactblocks_blocksonly.py @@ -12,7 +12,9 @@ from test_framework.messages import ( CBlockHeader, CInv, from_hex, + HeaderAndShortIDs, msg_block, + msg_cmpctblock, msg_getdata, msg_headers, msg_sendcmpct, @@ -38,6 +40,30 @@ class P2PCompactBlocksBlocksOnly(BitcoinTestFramework): block = from_hex(CBlock(), block_hex) return block + def ignores_cmpctblock(self, node_id, conn, solicited=False): + # Briefly connect to mining node, sync, and disconnect, just to make + # sure the node is on the same tip as the miner so that compact block + # reconstruction works. + self.connect_nodes(2, node_id) + self.sync_blocks([self.nodes[2], self.nodes[node_id]], timeout=10) + self.disconnect_nodes(2, node_id) + + block = self.build_block_on_tip() + if solicited: + conn.send_without_ping(msg_headers([block])) + conn.wait_for_getdata([block.hash_int], timeout=10) + cmpct_block = HeaderAndShortIDs() + cmpct_block.initialize_from_block(block, use_witness=True) + msg = msg_cmpctblock(cmpct_block.to_p2p()) + conn.send_and_ping(msg) + + cmpct_block_received = self.nodes[node_id].getbestblockhash() == cmpct_block.header.hash_hex + if not cmpct_block_received: + # Compact block was ignored, send the full block to keep in sync. + conn.send_and_ping(msg_block(block)) + + return not cmpct_block_received + def run_test(self): # Nodes will only request hb compact blocks mode when they're out of IBD for node in self.nodes: @@ -98,11 +124,19 @@ class P2PCompactBlocksBlocksOnly(BitcoinTestFramework): p2p_conn_high_bw.send_and_ping(msg_headers(headers=[CBlockHeader(block1)])) assert_equal(p2p_conn_high_bw.last_message['getdata'].inv, [CInv(MSG_CMPCT_BLOCK, block1.hash_int)]) + # Send the block to avoid stalling the peer later in the test. + comp_block = HeaderAndShortIDs() + comp_block.initialize_from_block(block1, use_witness=True) + block1_cb_msg = msg_cmpctblock(comp_block.to_p2p()) + p2p_conn_high_bw.send_and_ping(block1_cb_msg) + self.log.info("Test that getdata(CMPCT) is still sent on BIP152 low bandwidth connections" " when no -blocksonly nodes are involved") p2p_conn_low_bw.send_and_ping(msg_headers(headers=[CBlockHeader(block1)])) assert_equal(p2p_conn_low_bw.last_message['getdata'].inv, [CInv(MSG_CMPCT_BLOCK, block1.hash_int)]) + # Send the block to avoid stalling the peer later in the test. + p2p_conn_low_bw.send_and_ping(block1_cb_msg) self.log.info("Test that -blocksonly nodes still serve compact blocks") @@ -122,5 +156,18 @@ class P2PCompactBlocksBlocksOnly(BitcoinTestFramework): self.nodes[0].submitblock(block2.serialize().hex()) p2p_conn_blocksonly.wait_until(lambda: test_for_cmpctblock(block2)) + # This is redundant with other tests, and is here as a test-of-the-test + self.log.info("Test that normal nodes don't ignore CMPCTBLOCK messages from HB peers") + assert not self.ignores_cmpctblock(1, p2p_conn_high_bw, solicited=False) + + self.log.info("Test that -blocksonly nodes ignore CMPCTBLOCK messages") + assert self.ignores_cmpctblock(0, p2p_conn_blocksonly, solicited=False) + + self.log.info("Test that low bandwidth nodes listen to CMPCTBLOCK messages when the block is requested") + assert not self.ignores_cmpctblock(3, p2p_conn_low_bw, solicited=True) + + self.log.info("Test that -blocksonly nodes ignore CMPCTBLOCK messages even when the block is requested") + assert self.ignores_cmpctblock(0, p2p_conn_blocksonly, solicited=True) + if __name__ == '__main__': P2PCompactBlocksBlocksOnly(__file__).main()