mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-09-11 21:20:39 +02:00
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
174 lines
7.7 KiB
Python
Executable File
174 lines
7.7 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# Copyright (c) 2021-present The Bitcoin Core developers
|
|
# Distributed under the MIT software license, see the accompanying
|
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
"""Test that a node in blocksonly mode does not request compact blocks."""
|
|
|
|
from test_framework.messages import (
|
|
MSG_BLOCK,
|
|
MSG_CMPCT_BLOCK,
|
|
MSG_WITNESS_FLAG,
|
|
CBlock,
|
|
CBlockHeader,
|
|
CInv,
|
|
from_hex,
|
|
HeaderAndShortIDs,
|
|
msg_block,
|
|
msg_cmpctblock,
|
|
msg_getdata,
|
|
msg_headers,
|
|
msg_sendcmpct,
|
|
)
|
|
from test_framework.p2p import P2PInterface
|
|
from test_framework.test_framework import BitcoinTestFramework
|
|
from test_framework.util import assert_equal
|
|
|
|
|
|
class P2PCompactBlocksBlocksOnly(BitcoinTestFramework):
|
|
def set_test_params(self):
|
|
self.extra_args = [["-blocksonly"], [], [], []]
|
|
self.num_nodes = 4
|
|
|
|
def setup_network(self):
|
|
self.setup_nodes()
|
|
# Start network with everyone disconnected
|
|
self.sync_all()
|
|
|
|
def build_block_on_tip(self):
|
|
blockhash = self.generate(self.nodes[2], 1, sync_fun=self.no_op)[0]
|
|
block_hex = self.nodes[2].getblock(blockhash=blockhash, verbosity=0)
|
|
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:
|
|
assert not node.getblockchaininfo()['initialblockdownload']
|
|
|
|
p2p_conn_blocksonly = self.nodes[0].add_p2p_connection(P2PInterface())
|
|
p2p_conn_high_bw = self.nodes[1].add_p2p_connection(P2PInterface())
|
|
p2p_conn_low_bw = self.nodes[3].add_p2p_connection(P2PInterface())
|
|
for conn in [p2p_conn_blocksonly, p2p_conn_high_bw, p2p_conn_low_bw]:
|
|
assert_equal(conn.message_count['sendcmpct'], 1)
|
|
conn.send_and_ping(msg_sendcmpct(announce=False, version=2))
|
|
|
|
# Nodes:
|
|
# 0 -> blocksonly
|
|
# 1 -> high bandwidth
|
|
# 2 -> miner
|
|
# 3 -> low bandwidth
|
|
#
|
|
# Topology:
|
|
# p2p_conn_blocksonly ---> node0
|
|
# p2p_conn_high_bw ---> node1
|
|
# p2p_conn_low_bw ---> node3
|
|
# node2 (no connections)
|
|
#
|
|
# node2 produces blocks that are passed to the rest of the nodes
|
|
# through the respective p2p connections.
|
|
|
|
self.log.info("Test that -blocksonly nodes do not select peers for BIP152 high bandwidth mode")
|
|
|
|
block0 = self.build_block_on_tip()
|
|
|
|
# A -blocksonly node should not request BIP152 high bandwidth mode upon
|
|
# receiving a new valid block at the tip.
|
|
p2p_conn_blocksonly.send_and_ping(msg_block(block0))
|
|
assert_equal(self.nodes[0].getbestblockhash(), block0.hash_hex)
|
|
assert_equal(p2p_conn_blocksonly.message_count['sendcmpct'], 1)
|
|
assert_equal(p2p_conn_blocksonly.last_message['sendcmpct'].announce, False)
|
|
|
|
# A normal node participating in transaction relay should request BIP152
|
|
# high bandwidth mode upon receiving a new valid block at the tip.
|
|
p2p_conn_high_bw.send_and_ping(msg_block(block0))
|
|
assert_equal(self.nodes[1].getbestblockhash(), block0.hash_hex)
|
|
p2p_conn_high_bw.wait_until(lambda: p2p_conn_high_bw.message_count['sendcmpct'] == 2)
|
|
assert_equal(p2p_conn_high_bw.last_message['sendcmpct'].announce, True)
|
|
|
|
# Don't send a block from the p2p_conn_low_bw so the low bandwidth node
|
|
# doesn't select it for BIP152 high bandwidth relay.
|
|
self.nodes[3].submitblock(block0.serialize().hex())
|
|
|
|
self.log.info("Test that -blocksonly nodes send getdata(BLOCK) instead"
|
|
" of getdata(CMPCT) in BIP152 low bandwidth mode")
|
|
|
|
block1 = self.build_block_on_tip()
|
|
|
|
p2p_conn_blocksonly.send_and_ping(msg_headers(headers=[CBlockHeader(block1)]))
|
|
assert_equal(p2p_conn_blocksonly.last_message['getdata'].inv, [CInv(MSG_BLOCK | MSG_WITNESS_FLAG, block1.hash_int)])
|
|
|
|
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")
|
|
|
|
def test_for_cmpctblock(block):
|
|
if 'cmpctblock' not in p2p_conn_blocksonly.last_message:
|
|
return False
|
|
return p2p_conn_blocksonly.last_message['cmpctblock'].header_and_shortids.header.hash_int == block.hash_int
|
|
|
|
p2p_conn_blocksonly.send_without_ping(msg_getdata([CInv(MSG_CMPCT_BLOCK, block0.hash_int)]))
|
|
p2p_conn_blocksonly.wait_until(lambda: test_for_cmpctblock(block0))
|
|
|
|
# Request BIP152 high bandwidth mode from the -blocksonly node.
|
|
p2p_conn_blocksonly.send_and_ping(msg_sendcmpct(announce=True, version=2))
|
|
|
|
block2 = self.build_block_on_tip()
|
|
self.nodes[0].submitblock(block1.serialize().hex())
|
|
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()
|