/ test / functional / p2p_sendtxrcncl.py
p2p_sendtxrcncl.py
  1  #!/usr/bin/env python3
  2  # Copyright (c) 2022-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 SENDTXRCNCL message
  6  """
  7  
  8  from test_framework.messages import (
  9      msg_sendtxrcncl,
 10      msg_verack,
 11      msg_version,
 12      msg_wtxidrelay,
 13      NODE_BLOOM,
 14  )
 15  from test_framework.p2p import (
 16      P2PInterface,
 17      P2P_SERVICES,
 18      P2P_SUBVERSION,
 19      P2P_VERSION,
 20  )
 21  from test_framework.test_framework import BitcoinTestFramework
 22  from test_framework.util import (
 23      assert_equal,
 24      assert_not_equal,
 25  )
 26  
 27  class PeerNoVerack(P2PInterface):
 28      def __init__(self, wtxidrelay=True):
 29          super().__init__(wtxidrelay=wtxidrelay)
 30  
 31      def on_version(self, message):
 32          # Avoid sending verack in response to version.
 33          # When calling add_p2p_connection, wait_for_verack=False must be set (see
 34          # comment in add_p2p_connection).
 35          self.send_version()
 36          if message.nVersion >= 70016 and self.wtxidrelay:
 37              self.send_without_ping(msg_wtxidrelay())
 38  
 39  class SendTxrcnclReceiver(P2PInterface):
 40      def __init__(self):
 41          super().__init__()
 42          self.sendtxrcncl_msg_received = None
 43  
 44      def on_sendtxrcncl(self, message):
 45          self.sendtxrcncl_msg_received = message
 46  
 47  
 48  class P2PFeelerReceiver(SendTxrcnclReceiver):
 49      def on_version(self, message):
 50          # feeler connections can not send any message other than their own version
 51          self.send_version()
 52  
 53  
 54  class PeerTrackMsgOrder(P2PInterface):
 55      def __init__(self):
 56          super().__init__()
 57          self.messages = []
 58  
 59      def on_message(self, message):
 60          super().on_message(message)
 61          self.messages.append(message)
 62  
 63  def create_sendtxrcncl_msg():
 64      sendtxrcncl_msg = msg_sendtxrcncl()
 65      sendtxrcncl_msg.version = 1
 66      sendtxrcncl_msg.salt = 2
 67      return sendtxrcncl_msg
 68  
 69  class SendTxRcnclTest(BitcoinTestFramework):
 70      def set_test_params(self):
 71          self.num_nodes = 1
 72          self.extra_args = [['-txreconciliation']]
 73  
 74      def run_test(self):
 75          # Check everything concerning *sending* SENDTXRCNCL
 76          # First, *sending* to *inbound*.
 77          self.log.info('SENDTXRCNCL sent to an inbound')
 78          peer = self.nodes[0].add_p2p_connection(SendTxrcnclReceiver(), send_version=True, wait_for_verack=True)
 79          assert peer.sendtxrcncl_msg_received
 80          assert_equal(peer.sendtxrcncl_msg_received.version, 1)
 81          self.nodes[0].disconnect_p2ps()
 82  
 83          self.log.info('SENDTXRCNCL should be sent before VERACK')
 84          peer = self.nodes[0].add_p2p_connection(PeerTrackMsgOrder(), send_version=True, wait_for_verack=True)
 85          peer.wait_for_verack()
 86          verack_index = [i for i, msg in enumerate(peer.messages) if msg.msgtype == b'verack'][0]
 87          sendtxrcncl_index = [i for i, msg in enumerate(peer.messages) if msg.msgtype == b'sendtxrcncl'][0]
 88          assert sendtxrcncl_index < verack_index
 89          self.nodes[0].disconnect_p2ps()
 90  
 91          self.log.info('SENDTXRCNCL on pre-WTXID version should not be sent')
 92          peer = self.nodes[0].add_p2p_connection(SendTxrcnclReceiver(), send_version=False, wait_for_verack=False)
 93          pre_wtxid_version_msg = msg_version()
 94          pre_wtxid_version_msg.nVersion = 70015
 95          pre_wtxid_version_msg.strSubVer = P2P_SUBVERSION
 96          pre_wtxid_version_msg.nServices = P2P_SERVICES
 97          pre_wtxid_version_msg.relay = 1
 98          peer.send_without_ping(pre_wtxid_version_msg)
 99          peer.wait_for_verack()
100          assert not peer.sendtxrcncl_msg_received
101          self.nodes[0].disconnect_p2ps()
102  
103          self.log.info('SENDTXRCNCL for fRelay=false should not be sent')
104          peer = self.nodes[0].add_p2p_connection(SendTxrcnclReceiver(), send_version=False, wait_for_verack=False)
105          no_txrelay_version_msg = msg_version()
106          no_txrelay_version_msg.nVersion = P2P_VERSION
107          no_txrelay_version_msg.strSubVer = P2P_SUBVERSION
108          no_txrelay_version_msg.nServices = P2P_SERVICES
109          no_txrelay_version_msg.relay = 0
110          peer.send_without_ping(no_txrelay_version_msg)
111          peer.wait_for_verack()
112          assert not peer.sendtxrcncl_msg_received
113          self.nodes[0].disconnect_p2ps()
114  
115          self.log.info('SENDTXRCNCL for fRelay=false should not be sent (with NODE_BLOOM offered)')
116          self.restart_node(0, ["-peerbloomfilters", "-txreconciliation"])
117          peer = self.nodes[0].add_p2p_connection(SendTxrcnclReceiver(), send_version=False, wait_for_verack=False)
118          no_txrelay_version_msg = msg_version()
119          no_txrelay_version_msg.nVersion = P2P_VERSION
120          no_txrelay_version_msg.strSubVer = P2P_SUBVERSION
121          no_txrelay_version_msg.nServices = P2P_SERVICES
122          no_txrelay_version_msg.relay = 0
123          peer.send_without_ping(no_txrelay_version_msg)
124          peer.wait_for_verack()
125          assert_not_equal(peer.nServices & NODE_BLOOM, 0)
126          assert not peer.sendtxrcncl_msg_received
127          self.nodes[0].disconnect_p2ps()
128  
129          # Now, *sending* to *outbound*.
130          self.log.info('SENDTXRCNCL sent to an outbound')
131          peer = self.nodes[0].add_outbound_p2p_connection(
132              SendTxrcnclReceiver(), wait_for_verack=True, p2p_idx=0, connection_type="outbound-full-relay")
133          assert peer.sendtxrcncl_msg_received
134          assert_equal(peer.sendtxrcncl_msg_received.version, 1)
135          self.nodes[0].disconnect_p2ps()
136  
137          self.log.info('SENDTXRCNCL should not be sent if block-relay-only')
138          peer = self.nodes[0].add_outbound_p2p_connection(
139              SendTxrcnclReceiver(), wait_for_verack=True, p2p_idx=0, connection_type="block-relay-only")
140          assert not peer.sendtxrcncl_msg_received
141          self.nodes[0].disconnect_p2ps()
142  
143          self.log.info("SENDTXRCNCL should not be sent if feeler")
144          peer = self.nodes[0].add_outbound_p2p_connection(P2PFeelerReceiver(), p2p_idx=0, connection_type="feeler")
145          assert not peer.sendtxrcncl_msg_received
146          self.nodes[0].disconnect_p2ps()
147  
148          self.log.info("SENDTXRCNCL should not be sent if addrfetch")
149          peer = self.nodes[0].add_outbound_p2p_connection(
150              SendTxrcnclReceiver(), wait_for_verack=True, p2p_idx=0, connection_type="addr-fetch")
151          assert not peer.sendtxrcncl_msg_received
152          self.nodes[0].disconnect_p2ps()
153  
154          self.log.info('SENDTXRCNCL not sent if -txreconciliation flag is not set')
155          self.restart_node(0, [])
156          peer = self.nodes[0].add_p2p_connection(SendTxrcnclReceiver(), send_version=True, wait_for_verack=True)
157          assert not peer.sendtxrcncl_msg_received
158          self.nodes[0].disconnect_p2ps()
159  
160          self.log.info('SENDTXRCNCL not sent if blocksonly is set')
161          self.restart_node(0, ["-txreconciliation", "-blocksonly"])
162          peer = self.nodes[0].add_p2p_connection(SendTxrcnclReceiver(), send_version=True, wait_for_verack=True)
163          assert not peer.sendtxrcncl_msg_received
164          self.nodes[0].disconnect_p2ps()
165  
166          # Check everything concerning *receiving* SENDTXRCNCL
167          # First, receiving from *inbound*.
168          self.restart_node(0, ["-txreconciliation"])
169          self.log.info('valid SENDTXRCNCL received')
170          peer = self.nodes[0].add_p2p_connection(PeerNoVerack(), send_version=True, wait_for_verack=False)
171          with self.nodes[0].assert_debug_log(["received: sendtxrcncl"], timeout=2):
172              peer.send_without_ping(create_sendtxrcncl_msg())
173          self.log.info('second SENDTXRCNCL triggers a disconnect')
174          with self.nodes[0].assert_debug_log(["(sendtxrcncl received from already registered peer), disconnecting peer=0"]):
175              peer.send_without_ping(create_sendtxrcncl_msg())
176              peer.wait_for_disconnect()
177  
178          self.restart_node(0, [])
179          self.log.info('SENDTXRCNCL if no txreconciliation supported is ignored')
180          peer = self.nodes[0].add_p2p_connection(PeerNoVerack(), send_version=True, wait_for_verack=False)
181          with self.nodes[0].assert_debug_log(['ignored, as our node does not have txreconciliation enabled'], timeout=2):
182              peer.send_without_ping(create_sendtxrcncl_msg())
183          self.nodes[0].disconnect_p2ps()
184  
185          self.restart_node(0, ["-txreconciliation"])
186  
187          self.log.info('SENDTXRCNCL with version=0 triggers a disconnect')
188          sendtxrcncl_low_version = create_sendtxrcncl_msg()
189          sendtxrcncl_low_version.version = 0
190          peer = self.nodes[0].add_p2p_connection(PeerNoVerack(), send_version=True, wait_for_verack=False)
191          with self.nodes[0].assert_debug_log(["txreconciliation protocol violation"]):
192              peer.send_without_ping(sendtxrcncl_low_version)
193              peer.wait_for_disconnect()
194  
195          self.log.info('SENDTXRCNCL with version=2 is valid')
196          sendtxrcncl_higher_version = create_sendtxrcncl_msg()
197          sendtxrcncl_higher_version.version = 2
198          peer = self.nodes[0].add_p2p_connection(PeerNoVerack(), send_version=True, wait_for_verack=False)
199          with self.nodes[0].assert_debug_log(['Register peer=1'], timeout=2):
200              peer.send_without_ping(sendtxrcncl_higher_version)
201          self.nodes[0].disconnect_p2ps()
202  
203          self.log.info('unexpected SENDTXRCNCL is ignored')
204          peer = self.nodes[0].add_p2p_connection(PeerNoVerack(), send_version=False, wait_for_verack=False)
205          old_version_msg = msg_version()
206          old_version_msg.nVersion = 70015
207          old_version_msg.strSubVer = P2P_SUBVERSION
208          old_version_msg.nServices = P2P_SERVICES
209          old_version_msg.relay = 1
210          peer.send_without_ping(old_version_msg)
211          with self.nodes[0].assert_debug_log(['Ignore unexpected txreconciliation signal'], timeout=2):
212              peer.send_without_ping(create_sendtxrcncl_msg())
213          self.nodes[0].disconnect_p2ps()
214  
215          self.log.info('sending SENDTXRCNCL after sending VERACK triggers a disconnect')
216          peer = self.nodes[0].add_p2p_connection(P2PInterface())
217          with self.nodes[0].assert_debug_log(["sendtxrcncl received after verack"]):
218              peer.send_without_ping(create_sendtxrcncl_msg())
219              peer.wait_for_disconnect()
220  
221          self.log.info('SENDTXRCNCL without WTXIDRELAY is ignored (recon state is erased after VERACK)')
222          peer = self.nodes[0].add_p2p_connection(PeerNoVerack(wtxidrelay=False), send_version=True, wait_for_verack=False)
223          with self.nodes[0].assert_debug_log(['Forget txreconciliation state of peer']):
224              peer.send_without_ping(create_sendtxrcncl_msg())
225              peer.send_and_ping(msg_verack())
226          self.nodes[0].disconnect_p2ps()
227  
228          # Now, *receiving* from *outbound*.
229          self.log.info('SENDTXRCNCL if block-relay-only triggers a disconnect')
230          peer = self.nodes[0].add_outbound_p2p_connection(
231              PeerNoVerack(), wait_for_verack=False, p2p_idx=0, connection_type="block-relay-only")
232          with self.nodes[0].assert_debug_log(["we indicated no tx relay, disconnecting peer=5"]):
233              peer.send_without_ping(create_sendtxrcncl_msg())
234              peer.wait_for_disconnect()
235  
236  
237  if __name__ == '__main__':
238      SendTxRcnclTest(__file__).main()