p2p_1p1c_network.py raw

   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  
  14  from test_framework.mempool_util import (
  15      DEFAULT_MIN_RELAY_TX_FEE,
  16  )
  17  from test_framework.messages import (
  18      COIN,
  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  )
  28  from test_framework.wallet import (
  29      MiniWallet,
  30      MiniWalletMode,
  31  )
  32  
  33  class PackageRelayTest(BitcoinTestFramework):
  34      def set_test_params(self):
  35          self.setup_clean_chain = True
  36          self.num_nodes = 4
  37          # hugely speeds up the test, as it involves multiple hops of tx relay.
  38          self.noban_tx_relay = True
  39  
  40      def create_basic_1p1c(self, wallet):
  41          low_fee_parent = wallet.create_self_transfer(fee_rate=0, confirmed_only=True)
  42          high_fee_child = wallet.create_self_transfer(utxo_to_spend=low_fee_parent["new_utxo"], fee_rate=999*Decimal(DEFAULT_MIN_RELAY_TX_FEE)/ COIN)
  43          package_hex_basic = [low_fee_parent["hex"], high_fee_child["hex"]]
  44          return package_hex_basic, low_fee_parent["tx"], high_fee_child["tx"]
  45  
  46      def create_package_2outs(self, wallet):
  47          # First create a tester tx to see the vsize, and then adjust the fees
  48          utxo_for_2outs = wallet.get_utxo(confirmed_only=True)
  49  
  50          low_fee_parent_2outs = wallet.create_self_transfer_multi(
  51              utxos_to_spend=[utxo_for_2outs],
  52              num_outputs=2,
  53              fee_per_output=0,
  54          )
  55  
  56          # Now create the child
  57          high_fee_child_2outs = wallet.create_self_transfer_multi(
  58              utxos_to_spend=low_fee_parent_2outs["new_utxos"][::-1],
  59              fee_per_output=10_000,
  60          )
  61          return [low_fee_parent_2outs["hex"], high_fee_child_2outs["hex"]], low_fee_parent_2outs["tx"], high_fee_child_2outs["tx"]
  62  
  63      def create_package_2p1c(self, wallet):
  64          parent1 = wallet.create_self_transfer(fee_rate=Decimal(DEFAULT_MIN_RELAY_TX_FEE) / COIN * 10, confirmed_only=True)
  65          parent2 = wallet.create_self_transfer(fee_rate=Decimal(DEFAULT_MIN_RELAY_TX_FEE) / COIN * 20, confirmed_only=True)
  66          child = wallet.create_self_transfer_multi(
  67              utxos_to_spend=[parent1["new_utxo"], parent2["new_utxo"]],
  68              fee_per_output=999*parent1["tx"].get_vsize(),
  69          )
  70          return [parent1["hex"], parent2["hex"], child["hex"]], parent1["tx"], parent2["tx"], child["tx"]
  71  
  72      def create_packages(self):
  73          # 1: Basic 1-parent-1-child package, parent 0sat/vB, child 999sat/vB
  74          package_hex_1, parent_1, child_1 = self.create_basic_1p1c(self.wallet)
  75  
  76          # 2: same as 1, parent's txid is the same as its wtxid.
  77          package_hex_2, parent_2, child_2 = self.create_basic_1p1c(self.wallet_nonsegwit)
  78  
  79          # 3: 2-parent-1-child package. Both parents are above mempool min feerate. No package submission happens.
  80          # We require packages to be child-with-parents and only allow 1-parent-1-child packages.
  81          package_hex_3, parent_31, _parent_32, child_3 = self.create_package_2p1c(self.wallet)
  82  
  83          # 4: parent + child package where the child spends 2 different outputs from the parent.
  84          package_hex_4, parent_4, child_4 = self.create_package_2outs(self.wallet)
  85  
  86          # Assemble return results
  87          packages_to_submit = [package_hex_1, package_hex_2, package_hex_3, package_hex_4]
  88          # node0: sender
  89          # node1: pre-received the children (orphans, will be dropped on peer disconnect)
  90          # node3: pre-received the parents (too low fee)
  91          # All nodes receive parent_31 ahead of time.
  92          txns_to_send = [
  93              [],
  94              [child_1, child_2, parent_31, child_3, child_4],
  95              [parent_31],
  96              [parent_1, parent_2, parent_31, parent_4]
  97          ]
  98  
  99          return packages_to_submit, txns_to_send
 100  
 101      def run_test(self):
 102          self.wallet = MiniWallet(self.nodes[1])
 103          self.wallet_nonsegwit = MiniWallet(self.nodes[2], mode=MiniWalletMode.RAW_P2PK)
 104          self.generate(self.wallet_nonsegwit, 10)
 105          self.generate(self.wallet, 120)
 106  
 107          # Create the transactions.
 108          self.wallet.rescan_utxos(include_mempool=True)
 109          packages_to_submit, transactions_to_presend = self.create_packages()
 110  
 111          self.peers = [self.nodes[i].add_p2p_connection(P2PInterface()) for i in range(self.num_nodes)]
 112  
 113          self.log.info("Pre-send some transactions to nodes")
 114          for (i, peer) in enumerate(self.peers):
 115              for tx in transactions_to_presend[i]:
 116                  peer.send_and_ping(msg_tx(tx))
 117  
 118          # The fee-having parent should be the only thing in mempools
 119          self.sync_mempools()
 120          sufficient_parent = transactions_to_presend[2][0].txid_hex
 121          for i, node in enumerate(self.nodes):
 122              # node1 has non-empty orphanage as well
 123              if i == 1:
 124                  assert_equal(len(self.nodes[i].getorphantxs()), 4)
 125              else:
 126                  assert_equal(self.nodes[i].getorphantxs(), [])
 127  
 128              assert_equal(node.getrawmempool(), [sufficient_parent])
 129  
 130          # Disconnect python peers to clear outstanding orphan requests with them, avoiding timeouts.
 131          # We are only interested in the syncing behavior between real nodes.
 132          for i in range(self.num_nodes):
 133              self.nodes[i].disconnect_p2ps()
 134              self.wait_until(lambda: len(self.nodes[i].getorphantxs()) == 0)
 135  
 136          self.log.info("Submit full packages to node0")
 137          for package_hex in packages_to_submit:
 138              submitpackage_result = self.nodes[0].submitpackage(package_hex)
 139              assert_equal(submitpackage_result["package_msg"], "success")
 140  
 141          self.log.info("Wait for mempools to sync")
 142          self.wait_until(lambda: len(self.nodes[0].getrawmempool()) == 9)
 143          self.sync_mempools()
 144  
 145  
 146  if __name__ == '__main__':
 147      PackageRelayTest(__file__).main()
 148