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