p2p_blocksonly.py raw

   1  #!/usr/bin/env python3
   2  # Copyright (c) 2019-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 p2p blocksonly mode & block-relay-only connections."""
   6  
   7  import time
   8  
   9  from test_framework.messages import msg_tx, msg_inv, CInv, MSG_WTX
  10  from test_framework.p2p import P2PInterface, P2PTxInvStore
  11  from test_framework.test_framework import BitcoinTestFramework
  12  from test_framework.util import assert_equal
  13  from test_framework.wallet import MiniWallet
  14  
  15  
  16  class P2PBlocksOnly(BitcoinTestFramework):
  17      def set_test_params(self):
  18          self.num_nodes = 1
  19          self.extra_args = [["-blocksonly"]]
  20  
  21      def run_test(self):
  22          self.miniwallet = MiniWallet(self.nodes[0])
  23  
  24          self.blocksonly_mode_tests()
  25          self.blocks_relay_conn_tests()
  26  
  27      def blocksonly_mode_tests(self):
  28          self.log.info("Tests with node running in -blocksonly mode")
  29          assert_equal(self.nodes[0].getnetworkinfo()['localrelay'], False)
  30  
  31          self.nodes[0].add_p2p_connection(P2PInterface())
  32          tx, txid, wtxid, tx_hex = self.check_p2p_tx_violation()
  33  
  34          self.log.info('Check that tx invs also violate the protocol')
  35          self.nodes[0].add_p2p_connection(P2PInterface())
  36          with self.nodes[0].assert_debug_log(['transaction (0000000000000000000000000000000000000000000000000000000000001234) inv sent in violation of protocol, disconnecting peer']):
  37              self.nodes[0].p2ps[0].send_without_ping(msg_inv([CInv(t=MSG_WTX, h=0x1234)]))
  38              self.nodes[0].p2ps[0].wait_for_disconnect()
  39              del self.nodes[0].p2ps[0]
  40  
  41          self.log.info('Check that txs from rpc are not rejected and relayed to other peers')
  42          tx_relay_peer = self.nodes[0].add_p2p_connection(P2PInterface())
  43          assert_equal(self.nodes[0].getpeerinfo()[0]['relaytxes'], True)
  44  
  45          assert_equal(self.nodes[0].testmempoolaccept([tx_hex])[0]['allowed'], True)
  46          with self.nodes[0].assert_debug_log(['received getdata for: wtx {} peer'.format(wtxid)]):
  47              self.nodes[0].sendrawtransaction(tx_hex)
  48              tx_relay_peer.wait_for_tx(txid)
  49              assert_equal(self.nodes[0].getmempoolinfo()['size'], 1)
  50  
  51          self.log.info("Restarting node 0 with relay permission and blocksonly")
  52          self.restart_node(0, ["-persistmempool=0", "-whitelist=relay@127.0.0.1", "-blocksonly"])
  53          assert_equal(self.nodes[0].getrawmempool(), [])
  54          first_peer = self.nodes[0].add_p2p_connection(P2PInterface())
  55          second_peer = self.nodes[0].add_p2p_connection(P2PInterface())
  56          peer_1_info = self.nodes[0].getpeerinfo()[0]
  57          assert_equal(peer_1_info['permissions'], ['relay'])
  58          assert_equal(first_peer.relay, 1)
  59          peer_2_info = self.nodes[0].getpeerinfo()[1]
  60          assert_equal(peer_2_info['permissions'], ['relay'])
  61          assert_equal(self.nodes[0].testmempoolaccept([tx_hex])[0]['allowed'], True)
  62  
  63          self.log.info('Check that the tx from first_peer with relay-permission is relayed to others (ie.second_peer)')
  64          with self.nodes[0].assert_debug_log(["received getdata"]):
  65              # Note that normally, first_peer would never send us transactions since we're a blocksonly node.
  66              # By activating blocksonly, we explicitly tell our peers that they should not send us transactions,
  67              # and Bitcoin Core respects that choice and will not send transactions.
  68              # But if, for some reason, first_peer decides to relay transactions to us anyway, we should relay them to
  69              # second_peer since we gave relay permission to first_peer.
  70              # See https://github.com/bitcoin/bitcoin/issues/19943 for details.
  71              first_peer.send_without_ping(msg_tx(tx))
  72              self.log.info('Check that the peer with relay-permission is still connected after sending the transaction')
  73              assert_equal(first_peer.is_connected, True)
  74              second_peer.wait_for_tx(txid)
  75              assert_equal(self.nodes[0].getmempoolinfo()['size'], 1)
  76          self.log.info("Relay-permission peer's transaction is accepted and relayed")
  77  
  78          self.nodes[0].disconnect_p2ps()
  79          self.generate(self.nodes[0], 1)
  80  
  81      def blocks_relay_conn_tests(self):
  82          self.log.info('Tests with node in normal mode with block-relay-only connections')
  83          self.restart_node(0, ["-noblocksonly"])  # disables blocks only mode
  84          assert_equal(self.nodes[0].getnetworkinfo()['localrelay'], True)
  85  
  86          # Ensure we disconnect if a block-relay-only connection sends us a transaction
  87          self.nodes[0].add_outbound_p2p_connection(P2PInterface(), p2p_idx=0, connection_type="block-relay-only")
  88          assert_equal(self.nodes[0].getpeerinfo()[0]['relaytxes'], False)
  89          _, txid, _, tx_hex = self.check_p2p_tx_violation()
  90  
  91          self.log.info("Tests with node in normal mode with block-relay-only connection, sending an inv")
  92          conn = self.nodes[0].add_outbound_p2p_connection(P2PInterface(), p2p_idx=0, connection_type="block-relay-only")
  93          assert_equal(self.nodes[0].getpeerinfo()[0]['relaytxes'], False)
  94          self.check_p2p_inv_violation(conn)
  95  
  96          self.log.info("Check that txs from RPC are not sent to blockrelay connection")
  97          conn = self.nodes[0].add_outbound_p2p_connection(P2PTxInvStore(), p2p_idx=1, connection_type="block-relay-only")
  98  
  99          self.nodes[0].sendrawtransaction(tx_hex)
 100  
 101          # Bump time forward to ensure m_next_inv_send_time timer pops
 102          self.nodes[0].setmocktime(int(time.time()) + 60)
 103  
 104          conn.sync_with_ping()
 105          assert int(txid, 16) not in conn.get_invs()
 106  
 107      def check_p2p_inv_violation(self, peer):
 108          self.log.info("Check that tx-invs from P2P are rejected and result in disconnect")
 109          with self.nodes[0].assert_debug_log(["inv sent in violation of protocol, disconnecting peer"]):
 110              peer.send_without_ping(msg_inv([CInv(t=MSG_WTX, h=0x12345)]))
 111              peer.wait_for_disconnect()
 112          self.nodes[0].disconnect_p2ps()
 113  
 114      def check_p2p_tx_violation(self):
 115          self.log.info('Check that txs from P2P are rejected and result in disconnect')
 116          spendtx = self.miniwallet.create_self_transfer()
 117  
 118          with self.nodes[0].assert_debug_log(['transaction sent in violation of protocol, disconnecting peer=0']):
 119              self.nodes[0].p2ps[0].send_without_ping(msg_tx(spendtx['tx']))
 120              self.nodes[0].p2ps[0].wait_for_disconnect()
 121              assert_equal(self.nodes[0].getmempoolinfo()['size'], 0)
 122          self.nodes[0].disconnect_p2ps()
 123  
 124          return spendtx['tx'], spendtx['txid'], spendtx['wtxid'], spendtx['hex']
 125  
 126  
 127  if __name__ == '__main__':
 128      P2PBlocksOnly(__file__).main()
 129