/ test / functional / p2p_1p1c_network.py
p2p_1p1c_network.py
  1  #!/usr/bin/env python3
  2  # Copyright (c) 2024-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  """
  6  Test that 1p1c package submission allows a 1p1c package to propagate in a "network" of nodes. Send
  7  various packages from different nodes on a network in which some nodes have already received some of
  8  the transactions (and submitted them to mempool, kept them as orphans or rejected them as
  9  too-low-feerate transactions). The packages should be received and accepted by all nodes.
 10  """
 11  
 12  from decimal import Decimal
 13  from math import ceil
 14  
 15  from test_framework.mempool_util import (
 16      fill_mempool,
 17  )
 18  from test_framework.messages import (
 19      msg_tx,
 20  )
 21  from test_framework.p2p import (
 22      P2PInterface,
 23  )
 24  from test_framework.test_framework import BitcoinTestFramework
 25  from test_framework.util import (
 26      assert_equal,
 27      assert_greater_than,
 28  )
 29  from test_framework.wallet import (
 30      MiniWallet,
 31      MiniWalletMode,
 32  )
 33  
 34  # 1sat/vB feerate denominated in BTC/KvB
 35  FEERATE_1SAT_VB = Decimal("0.00001000")
 36  
 37  class PackageRelayTest(BitcoinTestFramework):
 38      def set_test_params(self):
 39          self.setup_clean_chain = True
 40          self.num_nodes = 4
 41          # hugely speeds up the test, as it involves multiple hops of tx relay.
 42          self.noban_tx_relay = True
 43          self.extra_args = [[
 44              "-datacarriersize=100000",
 45              "-maxmempool=5",
 46          ]] * self.num_nodes
 47          self.supports_cli = False
 48  
 49      def raise_network_minfee(self):
 50          fill_mempool(self, self.nodes[0])
 51  
 52          self.log.debug("Check that all nodes' mempool minimum feerates are above min relay feerate")
 53          for node in self.nodes:
 54              assert_equal(node.getmempoolinfo()['minrelaytxfee'], FEERATE_1SAT_VB)
 55              assert_greater_than(node.getmempoolinfo()['mempoolminfee'], FEERATE_1SAT_VB)
 56  
 57      def create_basic_1p1c(self, wallet):
 58          low_fee_parent = wallet.create_self_transfer(fee_rate=FEERATE_1SAT_VB, confirmed_only=True)
 59          high_fee_child = wallet.create_self_transfer(utxo_to_spend=low_fee_parent["new_utxo"], fee_rate=999*FEERATE_1SAT_VB)
 60          package_hex_basic = [low_fee_parent["hex"], high_fee_child["hex"]]
 61          return package_hex_basic, low_fee_parent["tx"], high_fee_child["tx"]
 62  
 63      def create_package_2outs(self, wallet):
 64          # First create a tester tx to see the vsize, and then adjust the fees
 65          utxo_for_2outs = wallet.get_utxo(confirmed_only=True)
 66  
 67          low_fee_parent_2outs_tester = wallet.create_self_transfer_multi(
 68              utxos_to_spend=[utxo_for_2outs],
 69              num_outputs=2,
 70          )
 71  
 72          # Target 1sat/vB so the number of satoshis is equal to the vsize.
 73          # Round up. The goal is to be between min relay feerate and mempool min feerate.
 74          fee_2outs = ceil(low_fee_parent_2outs_tester["tx"].get_vsize() / 2)
 75  
 76          low_fee_parent_2outs = wallet.create_self_transfer_multi(
 77              utxos_to_spend=[utxo_for_2outs],
 78              num_outputs=2,
 79              fee_per_output=fee_2outs,
 80          )
 81  
 82          # Now create the child
 83          high_fee_child_2outs = wallet.create_self_transfer_multi(
 84              utxos_to_spend=low_fee_parent_2outs["new_utxos"][::-1],
 85              fee_per_output=fee_2outs*100,
 86          )
 87          return [low_fee_parent_2outs["hex"], high_fee_child_2outs["hex"]], low_fee_parent_2outs["tx"], high_fee_child_2outs["tx"]
 88  
 89      def create_package_2p1c(self, wallet):
 90          parent1 = wallet.create_self_transfer(fee_rate=FEERATE_1SAT_VB*10, confirmed_only=True)
 91          parent2 = wallet.create_self_transfer(fee_rate=FEERATE_1SAT_VB*20, confirmed_only=True)
 92          child = wallet.create_self_transfer_multi(
 93              utxos_to_spend=[parent1["new_utxo"], parent2["new_utxo"]],
 94              fee_per_output=999*parent1["tx"].get_vsize(),
 95          )
 96          return [parent1["hex"], parent2["hex"], child["hex"]], parent1["tx"], parent2["tx"], child["tx"]
 97  
 98      def create_packages(self):
 99          # 1: Basic 1-parent-1-child package, parent 1sat/vB, child 999sat/vB
100          package_hex_1, parent_1, child_1 = self.create_basic_1p1c(self.wallet)
101  
102          # 2: same as 1, parent's txid is the same as its wtxid.
103          package_hex_2, parent_2, child_2 = self.create_basic_1p1c(self.wallet_nonsegwit)
104  
105          # 3: 2-parent-1-child package. Both parents are above mempool min feerate. No package submission happens.
106          # We require packages to be child-with-unconfirmed-parents and only allow 1-parent-1-child packages.
107          package_hex_3, parent_31, _parent_32, child_3 = self.create_package_2p1c(self.wallet)
108  
109          # 4: parent + child package where the child spends 2 different outputs from the parent.
110          package_hex_4, parent_4, child_4 = self.create_package_2outs(self.wallet)
111  
112          # Assemble return results
113          packages_to_submit = [package_hex_1, package_hex_2, package_hex_3, package_hex_4]
114          # node0: sender
115          # node1: pre-received the children (orphan)
116          # node3: pre-received the parents (too low fee)
117          # All nodes receive parent_31 ahead of time.
118          txns_to_send = [
119              [],
120              [child_1, child_2, parent_31, child_3, child_4],
121              [parent_31],
122              [parent_1, parent_2, parent_31, parent_4]
123          ]
124  
125          return packages_to_submit, txns_to_send
126  
127      def run_test(self):
128          self.wallet = MiniWallet(self.nodes[1])
129          self.wallet_nonsegwit = MiniWallet(self.nodes[2], mode=MiniWalletMode.RAW_P2PK)
130          self.generate(self.wallet_nonsegwit, 10)
131          self.generate(self.wallet, 120)
132  
133          self.log.info("Fill mempools with large transactions to raise mempool minimum feerates")
134          self.raise_network_minfee()
135  
136          # Create the transactions.
137          self.wallet.rescan_utxos(include_mempool=True)
138          packages_to_submit, transactions_to_presend = self.create_packages()
139  
140          self.peers = [self.nodes[i].add_p2p_connection(P2PInterface()) for i in range(self.num_nodes)]
141  
142          self.log.info("Pre-send some transactions to nodes")
143          for (i, peer) in enumerate(self.peers):
144              for tx in transactions_to_presend[i]:
145                  peer.send_and_ping(msg_tx(tx))
146  
147          # Disconnect python peers to clear outstanding orphan requests with them, avoiding timeouts.
148          # We are only interested in the syncing behavior between real nodes.
149          for i in range(self.num_nodes):
150              self.nodes[i].disconnect_p2ps()
151  
152          self.log.info("Submit full packages to node0")
153          for package_hex in packages_to_submit:
154              submitpackage_result = self.nodes[0].submitpackage(package_hex)
155              assert_equal(submitpackage_result["package_msg"], "success")
156  
157          self.log.info("Wait for mempools to sync")
158          self.sync_mempools()
159  
160  
161  if __name__ == '__main__':
162      PackageRelayTest(__file__).main()