p2p_compactblocks_blocksonly.py raw
1 #!/usr/bin/env python3
2 # Copyright (c) 2021-present The Bitcoin Core developers
3 # Distributed under the MIT software license, see the accompanying
4 # file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 """Test that a node in blocksonly mode does not request compact blocks."""
6
7 from test_framework.messages import (
8 MSG_BLOCK,
9 MSG_CMPCT_BLOCK,
10 MSG_WITNESS_FLAG,
11 CBlock,
12 CBlockHeader,
13 CInv,
14 from_hex,
15 HeaderAndShortIDs,
16 msg_block,
17 msg_cmpctblock,
18 msg_getdata,
19 msg_headers,
20 msg_sendcmpct,
21 )
22 from test_framework.p2p import P2PInterface
23 from test_framework.test_framework import BitcoinTestFramework
24 from test_framework.util import assert_equal
25
26
27 class P2PCompactBlocksBlocksOnly(BitcoinTestFramework):
28 def set_test_params(self):
29 self.extra_args = [["-blocksonly"], [], [], []]
30 self.num_nodes = 4
31
32 def setup_network(self):
33 self.setup_nodes()
34 # Start network with everyone disconnected
35 self.sync_all()
36
37 def build_block_on_tip(self):
38 blockhash = self.generate(self.nodes[2], 1, sync_fun=self.no_op)[0]
39 block_hex = self.nodes[2].getblock(blockhash=blockhash, verbosity=0)
40 block = from_hex(CBlock(), block_hex)
41 return block
42
43 def ignores_cmpctblock(self, node_id, conn, solicited=False):
44 # Briefly connect to mining node, sync, and disconnect, just to make
45 # sure the node is on the same tip as the miner so that compact block
46 # reconstruction works.
47 self.connect_nodes(2, node_id)
48 self.sync_blocks([self.nodes[2], self.nodes[node_id]], timeout=10)
49 self.disconnect_nodes(2, node_id)
50
51 block = self.build_block_on_tip()
52 if solicited:
53 conn.send_without_ping(msg_headers([block]))
54 conn.wait_for_getdata([block.hash_int], timeout=10)
55 cmpct_block = HeaderAndShortIDs()
56 cmpct_block.initialize_from_block(block, use_witness=True)
57 msg = msg_cmpctblock(cmpct_block.to_p2p())
58 conn.send_and_ping(msg)
59
60 cmpct_block_received = self.nodes[node_id].getbestblockhash() == cmpct_block.header.hash_hex
61 if not cmpct_block_received:
62 # Compact block was ignored, send the full block to keep in sync.
63 conn.send_and_ping(msg_block(block))
64
65 return not cmpct_block_received
66
67 def run_test(self):
68 # Nodes will only request hb compact blocks mode when they're out of IBD
69 for node in self.nodes:
70 assert not node.getblockchaininfo()['initialblockdownload']
71
72 p2p_conn_blocksonly = self.nodes[0].add_p2p_connection(P2PInterface())
73 p2p_conn_high_bw = self.nodes[1].add_p2p_connection(P2PInterface())
74 p2p_conn_low_bw = self.nodes[3].add_p2p_connection(P2PInterface())
75 for conn in [p2p_conn_blocksonly, p2p_conn_high_bw, p2p_conn_low_bw]:
76 assert_equal(conn.message_count['sendcmpct'], 1)
77 conn.send_and_ping(msg_sendcmpct(announce=False, version=2))
78
79 # Nodes:
80 # 0 -> blocksonly
81 # 1 -> high bandwidth
82 # 2 -> miner
83 # 3 -> low bandwidth
84 #
85 # Topology:
86 # p2p_conn_blocksonly ---> node0
87 # p2p_conn_high_bw ---> node1
88 # p2p_conn_low_bw ---> node3
89 # node2 (no connections)
90 #
91 # node2 produces blocks that are passed to the rest of the nodes
92 # through the respective p2p connections.
93
94 self.log.info("Test that -blocksonly nodes do not select peers for BIP152 high bandwidth mode")
95
96 block0 = self.build_block_on_tip()
97
98 # A -blocksonly node should not request BIP152 high bandwidth mode upon
99 # receiving a new valid block at the tip.
100 p2p_conn_blocksonly.send_and_ping(msg_block(block0))
101 assert_equal(self.nodes[0].getbestblockhash(), block0.hash_hex)
102 assert_equal(p2p_conn_blocksonly.message_count['sendcmpct'], 1)
103 assert_equal(p2p_conn_blocksonly.last_message['sendcmpct'].announce, False)
104
105 # A normal node participating in transaction relay should request BIP152
106 # high bandwidth mode upon receiving a new valid block at the tip.
107 p2p_conn_high_bw.send_and_ping(msg_block(block0))
108 assert_equal(self.nodes[1].getbestblockhash(), block0.hash_hex)
109 p2p_conn_high_bw.wait_until(lambda: p2p_conn_high_bw.message_count['sendcmpct'] == 2)
110 assert_equal(p2p_conn_high_bw.last_message['sendcmpct'].announce, True)
111
112 # Don't send a block from the p2p_conn_low_bw so the low bandwidth node
113 # doesn't select it for BIP152 high bandwidth relay.
114 self.nodes[3].submitblock(block0.serialize().hex())
115
116 self.log.info("Test that -blocksonly nodes send getdata(BLOCK) instead"
117 " of getdata(CMPCT) in BIP152 low bandwidth mode")
118
119 block1 = self.build_block_on_tip()
120
121 p2p_conn_blocksonly.send_and_ping(msg_headers(headers=[CBlockHeader(block1)]))
122 assert_equal(p2p_conn_blocksonly.last_message['getdata'].inv, [CInv(MSG_BLOCK | MSG_WITNESS_FLAG, block1.hash_int)])
123
124 p2p_conn_high_bw.send_and_ping(msg_headers(headers=[CBlockHeader(block1)]))
125 assert_equal(p2p_conn_high_bw.last_message['getdata'].inv, [CInv(MSG_CMPCT_BLOCK, block1.hash_int)])
126
127 # Send the block to avoid stalling the peer later in the test.
128 comp_block = HeaderAndShortIDs()
129 comp_block.initialize_from_block(block1, use_witness=True)
130 block1_cb_msg = msg_cmpctblock(comp_block.to_p2p())
131 p2p_conn_high_bw.send_and_ping(block1_cb_msg)
132
133 self.log.info("Test that getdata(CMPCT) is still sent on BIP152 low bandwidth connections"
134 " when no -blocksonly nodes are involved")
135
136 p2p_conn_low_bw.send_and_ping(msg_headers(headers=[CBlockHeader(block1)]))
137 assert_equal(p2p_conn_low_bw.last_message['getdata'].inv, [CInv(MSG_CMPCT_BLOCK, block1.hash_int)])
138 # Send the block to avoid stalling the peer later in the test.
139 p2p_conn_low_bw.send_and_ping(block1_cb_msg)
140
141 self.log.info("Test that -blocksonly nodes still serve compact blocks")
142
143 def test_for_cmpctblock(block):
144 if 'cmpctblock' not in p2p_conn_blocksonly.last_message:
145 return False
146 return p2p_conn_blocksonly.last_message['cmpctblock'].header_and_shortids.header.hash_int == block.hash_int
147
148 p2p_conn_blocksonly.send_without_ping(msg_getdata([CInv(MSG_CMPCT_BLOCK, block0.hash_int)]))
149 p2p_conn_blocksonly.wait_until(lambda: test_for_cmpctblock(block0))
150
151 # Request BIP152 high bandwidth mode from the -blocksonly node.
152 p2p_conn_blocksonly.send_and_ping(msg_sendcmpct(announce=True, version=2))
153
154 block2 = self.build_block_on_tip()
155 self.nodes[0].submitblock(block1.serialize().hex())
156 self.nodes[0].submitblock(block2.serialize().hex())
157 p2p_conn_blocksonly.wait_until(lambda: test_for_cmpctblock(block2))
158
159 # This is redundant with other tests, and is here as a test-of-the-test
160 self.log.info("Test that normal nodes don't ignore CMPCTBLOCK messages from HB peers")
161 assert not self.ignores_cmpctblock(1, p2p_conn_high_bw, solicited=False)
162
163 self.log.info("Test that -blocksonly nodes ignore CMPCTBLOCK messages")
164 assert self.ignores_cmpctblock(0, p2p_conn_blocksonly, solicited=False)
165
166 self.log.info("Test that low bandwidth nodes listen to CMPCTBLOCK messages when the block is requested")
167 assert not self.ignores_cmpctblock(3, p2p_conn_low_bw, solicited=True)
168
169 self.log.info("Test that -blocksonly nodes ignore CMPCTBLOCK messages even when the block is requested")
170 assert self.ignores_cmpctblock(0, p2p_conn_blocksonly, solicited=True)
171
172 if __name__ == '__main__':
173 P2PCompactBlocksBlocksOnly(__file__).main()
174