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()