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
This commit is contained in:
David Gumberg
2026-02-19 10:51:25 -08:00
parent 92cea63c71
commit bf9884f4e5
2 changed files with 51 additions and 1 deletions

View File

@@ -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()