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