/ test / functional / mempool_package_limits.py
mempool_package_limits.py
  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()