mempool_package_limits.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 logic for limiting mempool and package ancestors/descendants."""
   6  from test_framework.blocktools import COINBASE_MATURITY
   7  from test_framework.test_framework import BitcoinTestFramework
   8  from test_framework.util import (
   9      assert_equal,
  10  )
  11  from test_framework.wallet import MiniWallet
  12  
  13  # Decorator to
  14  # 1) check that mempool is empty at the start of a subtest
  15  # 2) run the subtest, which may submit some transaction(s) to the mempool and
  16  #    create a list of hex transactions
  17  # 3) testmempoolaccept the package hex and check that it fails with the error
  18  #    "too-large-cluster" for each tx
  19  # 4) after mining a block, clearing the pre-submitted transactions from mempool,
  20  #    check that submitting the created package succeeds
  21  def check_package_limits(func):
  22      def func_wrapper(self, *args, **kwargs):
  23          node = self.nodes[0]
  24          assert_equal(0, node.getmempoolinfo()["size"])
  25          package_hex = func(self, *args, **kwargs)
  26          testres_error_expected = node.testmempoolaccept(rawtxs=package_hex)
  27          assert_equal(len(testres_error_expected), len(package_hex))
  28          for txres in testres_error_expected:
  29              assert "too-large-cluster" in txres["package-error"]
  30  
  31          # Clear mempool and check that the package passes now
  32          self.generate(node, 1)
  33          assert all([res["allowed"] for res in node.testmempoolaccept(rawtxs=package_hex)])
  34  
  35      return func_wrapper
  36  
  37  
  38  class MempoolPackageLimitsTest(BitcoinTestFramework):
  39      def set_test_params(self):
  40          self.num_nodes = 1
  41          self.setup_clean_chain = True
  42          self.extra_args = [["-limitclustercount=25"]]
  43  
  44      def run_test(self):
  45          self.wallet = MiniWallet(self.nodes[0])
  46          # Add enough mature utxos to the wallet so that all txs spend confirmed coins.
  47          self.generate(self.wallet, COINBASE_MATURITY + 35)
  48  
  49          self.test_chain_limits()
  50          self.test_desc_count_limits()
  51          self.test_desc_count_limits_2()
  52          self.test_desc_size_limits()
  53  
  54      @check_package_limits
  55      def test_chain_limits_helper(self, mempool_count, package_count):
  56          node = self.nodes[0]
  57          chain_hex = []
  58  
  59          chaintip_utxo = self.wallet.send_self_transfer_chain(from_node=node, chain_length=mempool_count)[-1]["new_utxo"]
  60          # in-package transactions
  61          for _ in range(package_count):
  62              tx = self.wallet.create_self_transfer(utxo_to_spend=chaintip_utxo)
  63              chaintip_utxo = tx["new_utxo"]
  64              chain_hex.append(tx["hex"])
  65          return chain_hex
  66  
  67      def test_chain_limits(self):
  68          """Create chains from mempool and package transactions that are longer than 25,
  69          but only if both in-mempool and in-package transactions are considered together.
  70          This checks that both mempool and in-package transactions are taken into account when
  71          calculating ancestors/descendant limits.
  72          """
  73          self.log.info("Check that in-package ancestors count for mempool ancestor limits")
  74  
  75          # 24 transactions in the mempool and 2 in the package. The parent in the package has
  76          # 24 in-mempool ancestors and 1 in-package descendant. The child has 0 direct parents
  77          # in the mempool, but 25 in-mempool and in-package ancestors in total.
  78          self.test_chain_limits_helper(24, 2)
  79          # 2 transactions in the mempool and 24 in the package.
  80          self.test_chain_limits_helper(2, 24)
  81          # 13 transactions in the mempool and 13 in the package.
  82          self.test_chain_limits_helper(13, 13)
  83  
  84      @check_package_limits
  85      def test_desc_count_limits(self):
  86          """Create an 'A' shaped package with 24 transactions in the mempool and 2 in the package:
  87                      M1
  88                     ^  ^
  89                   M2a  M2b
  90                  .       .
  91                 .         .
  92                .           .
  93               M12a          ^
  94              ^              M13b
  95             ^                 ^
  96            Pa                  Pb
  97          The top ancestor in the package exceeds descendant limits but only if the in-mempool and in-package
  98          descendants are all considered together (24 including in-mempool descendants and 26 including both
  99          package transactions).
 100          """
 101          node = self.nodes[0]
 102          self.log.info("Check that in-mempool and in-package descendants are calculated properly in packages")
 103          # Top parent in mempool, M1
 104          m1_utxos = self.wallet.send_self_transfer_multi(from_node=node, num_outputs=2)['new_utxos']
 105  
 106          package_hex = []
 107          # Chain A (M2a... M12a)
 108          chain_a_tip_utxo = self.wallet.send_self_transfer_chain(from_node=node, chain_length=11, utxo_to_spend=m1_utxos[0])[-1]["new_utxo"]
 109          # Pa
 110          pa_hex = self.wallet.create_self_transfer(utxo_to_spend=chain_a_tip_utxo)["hex"]
 111          package_hex.append(pa_hex)
 112  
 113          # Chain B (M2b... M13b)
 114          chain_b_tip_utxo = self.wallet.send_self_transfer_chain(from_node=node, chain_length=12, utxo_to_spend=m1_utxos[1])[-1]["new_utxo"]
 115          # Pb
 116          pb_hex = self.wallet.create_self_transfer(utxo_to_spend=chain_b_tip_utxo)["hex"]
 117          package_hex.append(pb_hex)
 118  
 119          assert_equal(24, node.getmempoolinfo()["size"])
 120          assert_equal(2, len(package_hex))
 121          return package_hex
 122  
 123      @check_package_limits
 124      def test_desc_count_limits_2(self):
 125          """Create a Package with 24 transaction in mempool and 2 transaction in package:
 126                        M1
 127                       ^  ^
 128                     M2    ^
 129                     .      ^
 130                    .        ^
 131                   .          ^
 132                  M24          ^
 133                                ^
 134                                P1
 135                                ^
 136                                P2
 137          P1 has M1 as a mempool ancestor, P2 has no in-mempool ancestors, but when
 138          combined P2 has M1 as an ancestor and M1 exceeds descendant_limits(23 in-mempool
 139          descendants + 2 in-package descendants, a total of 26 including itself).
 140          """
 141  
 142          node = self.nodes[0]
 143          package_hex = []
 144          # M1
 145          m1_utxos = self.wallet.send_self_transfer_multi(from_node=node, num_outputs=2)['new_utxos']
 146  
 147          # Chain M2...M24
 148          self.wallet.send_self_transfer_chain(from_node=node, chain_length=23, utxo_to_spend=m1_utxos[0])[-1]["new_utxo"]
 149  
 150          # P1
 151          p1_tx = self.wallet.create_self_transfer(utxo_to_spend=m1_utxos[1])
 152          package_hex.append(p1_tx["hex"])
 153  
 154          # P2
 155          p2_tx = self.wallet.create_self_transfer(utxo_to_spend=p1_tx["new_utxo"])
 156          package_hex.append(p2_tx["hex"])
 157  
 158          assert_equal(24, node.getmempoolinfo()["size"])
 159          assert_equal(2, len(package_hex))
 160          return package_hex
 161  
 162      @check_package_limits
 163      def test_desc_size_limits(self):
 164          """Create 3 mempool transactions and 2 package transactions (21KvB each):
 165                Ma
 166               ^ ^
 167              Mb  Mc
 168             ^     ^
 169            Pd      Pe
 170          The top ancestor in the package exceeds descendant size limits but only if the in-mempool
 171          and in-package descendants are all considered together.
 172          """
 173          node = self.nodes[0]
 174          target_vsize = 21_000
 175          high_fee = 10 * target_vsize  # 10 sats/vB
 176          self.log.info("Check that in-mempool and in-package descendant sizes are calculated properly in packages")
 177          # Top parent in mempool, Ma
 178          ma_tx = self.wallet.create_self_transfer_multi(num_outputs=2, fee_per_output=high_fee // 2, target_vsize=target_vsize)
 179          self.wallet.sendrawtransaction(from_node=node, tx_hex=ma_tx["hex"])
 180  
 181          package_hex = []
 182          for j in range(2): # Two legs (left and right)
 183              # Mempool transaction (Mb and Mc)
 184              mempool_tx = self.wallet.create_self_transfer(utxo_to_spend=ma_tx["new_utxos"][j], target_vsize=target_vsize)
 185              self.wallet.sendrawtransaction(from_node=node, tx_hex=mempool_tx["hex"])
 186  
 187              # Package transaction (Pd and Pe)
 188              package_tx = self.wallet.create_self_transfer(utxo_to_spend=mempool_tx["new_utxo"], target_vsize=target_vsize)
 189              package_hex.append(package_tx["hex"])
 190  
 191          assert_equal(3, node.getmempoolinfo()["size"])
 192          assert_equal(2, len(package_hex))
 193          return package_hex
 194  
 195  
 196  if __name__ == "__main__":
 197      MempoolPackageLimitsTest(__file__).main()
 198