1 #!/usr/bin/env python3
2 # Copyright (c) 2021-2022 The Limenka 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 LimenkaTestFramework
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 # "package-mempool-limits" 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 "package-mempool-limits" 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(LimenkaTestFramework):
39 def set_test_params(self):
40 self.num_nodes = 1
41 self.setup_clean_chain = True
42 43 def run_test(self):
44 self.wallet = MiniWallet(self.nodes[0])
45 # Add enough mature utxos to the wallet so that all txs spend confirmed coins.
46 self.generate(self.wallet, COINBASE_MATURITY + 35)
47 48 self.test_chain_limits()
49 self.test_desc_count_limits()
50 self.test_desc_count_limits_2()
51 self.test_anc_count_limits()
52 self.test_anc_count_limits_2()
53 self.test_anc_count_limits_bushy()
54 55 # The node will accept (nonstandard) extra large OP_RETURN outputs
56 self.restart_node(0, extra_args=["-datacarriersize=100000"])
57 self.test_anc_size_limits()
58 self.test_desc_size_limits()
59 60 @check_package_limits
61 def test_chain_limits_helper(self, mempool_count, package_count):
62 node = self.nodes[0]
63 chain_hex = []
64 65 chaintip_utxo = self.wallet.send_self_transfer_chain(from_node=node, chain_length=mempool_count)[-1]["new_utxo"]
66 # in-package transactions
67 for _ in range(package_count):
68 tx = self.wallet.create_self_transfer(utxo_to_spend=chaintip_utxo)
69 chaintip_utxo = tx["new_utxo"]
70 chain_hex.append(tx["hex"])
71 return chain_hex
72 73 def test_chain_limits(self):
74 """Create chains from mempool and package transactions that are longer than 25,
75 but only if both in-mempool and in-package transactions are considered together.
76 This checks that both mempool and in-package transactions are taken into account when
77 calculating ancestors/descendant limits.
78 """
79 self.log.info("Check that in-package ancestors count for mempool ancestor limits")
80 81 # 24 transactions in the mempool and 2 in the package. The parent in the package has
82 # 24 in-mempool ancestors and 1 in-package descendant. The child has 0 direct parents
83 # in the mempool, but 25 in-mempool and in-package ancestors in total.
84 self.test_chain_limits_helper(24, 2)
85 # 2 transactions in the mempool and 24 in the package.
86 self.test_chain_limits_helper(2, 24)
87 # 13 transactions in the mempool and 13 in the package.
88 self.test_chain_limits_helper(13, 13)
89 90 @check_package_limits
91 def test_desc_count_limits(self):
92 """Create an 'A' shaped package with 24 transactions in the mempool and 2 in the package:
93 M1
94 ^ ^
95 M2a M2b
96 . .
97 . .
98 . .
99 M12a ^
100 ^ M13b
101 ^ ^
102 Pa Pb
103 The top ancestor in the package exceeds descendant limits but only if the in-mempool and in-package
104 descendants are all considered together (24 including in-mempool descendants and 26 including both
105 package transactions).
106 """
107 node = self.nodes[0]
108 self.log.info("Check that in-mempool and in-package descendants are calculated properly in packages")
109 # Top parent in mempool, M1
110 m1_utxos = self.wallet.send_self_transfer_multi(from_node=node, num_outputs=2)['new_utxos']
111 112 package_hex = []
113 # Chain A (M2a... M12a)
114 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"]
115 # Pa
116 pa_hex = self.wallet.create_self_transfer(utxo_to_spend=chain_a_tip_utxo)["hex"]
117 package_hex.append(pa_hex)
118 119 # Chain B (M2b... M13b)
120 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"]
121 # Pb
122 pb_hex = self.wallet.create_self_transfer(utxo_to_spend=chain_b_tip_utxo)["hex"]
123 package_hex.append(pb_hex)
124 125 assert_equal(24, node.getmempoolinfo()["size"])
126 assert_equal(2, len(package_hex))
127 return package_hex
128 129 @check_package_limits
130 def test_desc_count_limits_2(self):
131 """Create a Package with 24 transaction in mempool and 2 transaction in package:
132 M1
133 ^ ^
134 M2 ^
135 . ^
136 . ^
137 . ^
138 M24 ^
139 ^
140 P1
141 ^
142 P2
143 P1 has M1 as a mempool ancestor, P2 has no in-mempool ancestors, but when
144 combined P2 has M1 as an ancestor and M1 exceeds descendant_limits(23 in-mempool
145 descendants + 2 in-package descendants, a total of 26 including itself).
146 """
147 148 node = self.nodes[0]
149 package_hex = []
150 # M1
151 m1_utxos = self.wallet.send_self_transfer_multi(from_node=node, num_outputs=2)['new_utxos']
152 153 # Chain M2...M24
154 self.wallet.send_self_transfer_chain(from_node=node, chain_length=23, utxo_to_spend=m1_utxos[0])[-1]["new_utxo"]
155 156 # P1
157 p1_tx = self.wallet.create_self_transfer(utxo_to_spend=m1_utxos[1])
158 package_hex.append(p1_tx["hex"])
159 160 # P2
161 p2_tx = self.wallet.create_self_transfer(utxo_to_spend=p1_tx["new_utxo"])
162 package_hex.append(p2_tx["hex"])
163 164 assert_equal(24, node.getmempoolinfo()["size"])
165 assert_equal(2, len(package_hex))
166 return package_hex
167 168 @check_package_limits
169 def test_anc_count_limits(self):
170 """Create a 'V' shaped chain with 24 transactions in the mempool and 3 in the package:
171 M1a M1b
172 ^ ^
173 M2a M2b
174 . .
175 . .
176 . .
177 M12a M12b
178 ^ ^
179 Pa Pb
180 ^ ^
181 Pc
182 The lowest descendant, Pc, exceeds ancestor limits, but only if the in-mempool
183 and in-package ancestors are all considered together.
184 """
185 node = self.nodes[0]
186 package_hex = []
187 pc_parent_utxos = []
188 189 self.log.info("Check that in-mempool and in-package ancestors are calculated properly in packages")
190 191 # Two chains of 13 transactions each
192 for _ in range(2):
193 chain_tip_utxo = self.wallet.send_self_transfer_chain(from_node=node, chain_length=12)[-1]["new_utxo"]
194 # Save the 13th transaction for the package
195 tx = self.wallet.create_self_transfer(utxo_to_spend=chain_tip_utxo)
196 package_hex.append(tx["hex"])
197 pc_parent_utxos.append(tx["new_utxo"])
198 199 # Child Pc
200 pc_hex = self.wallet.create_self_transfer_multi(utxos_to_spend=pc_parent_utxos)["hex"]
201 package_hex.append(pc_hex)
202 203 assert_equal(24, node.getmempoolinfo()["size"])
204 assert_equal(3, len(package_hex))
205 return package_hex
206 207 @check_package_limits
208 def test_anc_count_limits_2(self):
209 """Create a 'Y' shaped chain with 24 transactions in the mempool and 2 in the package:
210 M1a M1b
211 ^ ^
212 M2a M2b
213 . .
214 . .
215 . .
216 M12a M12b
217 ^ ^
218 Pc
219 ^
220 Pd
221 The lowest descendant, Pd, exceeds ancestor limits, but only if the in-mempool
222 and in-package ancestors are all considered together.
223 """
224 node = self.nodes[0]
225 pc_parent_utxos = []
226 227 self.log.info("Check that in-mempool and in-package ancestors are calculated properly in packages")
228 # Two chains of 12 transactions each
229 for _ in range(2):
230 chaintip_utxo = self.wallet.send_self_transfer_chain(from_node=node, chain_length=12)[-1]["new_utxo"]
231 # last 2 transactions will be the parents of Pc
232 pc_parent_utxos.append(chaintip_utxo)
233 234 # Child Pc
235 pc_tx = self.wallet.create_self_transfer_multi(utxos_to_spend=pc_parent_utxos)
236 237 # Child Pd
238 pd_tx = self.wallet.create_self_transfer(utxo_to_spend=pc_tx["new_utxos"][0])
239 240 assert_equal(24, node.getmempoolinfo()["size"])
241 return [pc_tx["hex"], pd_tx["hex"]]
242 243 @check_package_limits
244 def test_anc_count_limits_bushy(self):
245 """Create a tree with 20 transactions in the mempool and 6 in the package:
246 M1...M4 M5...M8 M9...M12 M13...M16 M17...M20
247 ^ ^ ^ ^ ^ (each with 4 parents)
248 P0 P1 P2 P3 P4
249 ^ ^ ^ ^ ^ (5 parents)
250 PC
251 Where M(4i+1)...M+(4i+4) are the parents of Pi and P0, P1, P2, P3, and P4 are the parents of PC.
252 P0... P4 individually only have 4 parents each, and PC has no in-mempool parents. But
253 combined, PC has 25 in-mempool and in-package parents.
254 """
255 node = self.nodes[0]
256 package_hex = []
257 pc_parent_utxos = []
258 for _ in range(5): # Make package transactions P0 ... P4
259 pc_grandparent_utxos = []
260 for _ in range(4): # Make mempool transactions M(4i+1)...M(4i+4)
261 pc_grandparent_utxos.append(self.wallet.send_self_transfer(from_node=node)["new_utxo"])
262 # Package transaction Pi
263 pi_tx = self.wallet.create_self_transfer_multi(utxos_to_spend=pc_grandparent_utxos)
264 package_hex.append(pi_tx["hex"])
265 pc_parent_utxos.append(pi_tx["new_utxos"][0])
266 # Package transaction PC
267 pc_hex = self.wallet.create_self_transfer_multi(utxos_to_spend=pc_parent_utxos)["hex"]
268 package_hex.append(pc_hex)
269 270 assert_equal(20, node.getmempoolinfo()["size"])
271 assert_equal(6, len(package_hex))
272 return package_hex
273 274 @check_package_limits
275 def test_anc_size_limits(self):
276 """Test Case with 2 independent transactions in the mempool and a parent + child in the
277 package, where the package parent is the child of both mempool transactions (30KvB each):
278 A B
279 ^ ^
280 C
281 ^
282 D
283 The lowest descendant, D, exceeds ancestor size limits, but only if the in-mempool
284 and in-package ancestors are all considered together.
285 """
286 node = self.nodes[0]
287 parent_utxos = []
288 target_vsize = 30_000
289 high_fee = 10 * target_vsize # 10 sats/vB
290 self.log.info("Check that in-mempool and in-package ancestor size limits are calculated properly in packages")
291 # Mempool transactions A and B
292 for _ in range(2):
293 bulked_tx = self.wallet.create_self_transfer(target_vsize=target_vsize)
294 self.wallet.sendrawtransaction(from_node=node, tx_hex=bulked_tx["hex"])
295 parent_utxos.append(bulked_tx["new_utxo"])
296 297 # Package transaction C
298 pc_tx = self.wallet.create_self_transfer_multi(utxos_to_spend=parent_utxos, fee_per_output=high_fee, target_vsize=target_vsize)
299 300 # Package transaction D
301 pd_tx = self.wallet.create_self_transfer(utxo_to_spend=pc_tx["new_utxos"][0], target_vsize=target_vsize)
302 303 assert_equal(2, node.getmempoolinfo()["size"])
304 return [pc_tx["hex"], pd_tx["hex"]]
305 306 @check_package_limits
307 def test_desc_size_limits(self):
308 """Create 3 mempool transactions and 2 package transactions (21KvB each):
309 Ma
310 ^ ^
311 Mb Mc
312 ^ ^
313 Pd Pe
314 The top ancestor in the package exceeds descendant size limits but only if the in-mempool
315 and in-package descendants are all considered together.
316 """
317 node = self.nodes[0]
318 target_vsize = 21_000
319 high_fee = 10 * target_vsize # 10 sats/vB
320 self.log.info("Check that in-mempool and in-package descendant sizes are calculated properly in packages")
321 # Top parent in mempool, Ma
322 ma_tx = self.wallet.create_self_transfer_multi(num_outputs=2, fee_per_output=high_fee // 2, target_vsize=target_vsize)
323 self.wallet.sendrawtransaction(from_node=node, tx_hex=ma_tx["hex"])
324 325 package_hex = []
326 for j in range(2): # Two legs (left and right)
327 # Mempool transaction (Mb and Mc)
328 mempool_tx = self.wallet.create_self_transfer(utxo_to_spend=ma_tx["new_utxos"][j], target_vsize=target_vsize)
329 self.wallet.sendrawtransaction(from_node=node, tx_hex=mempool_tx["hex"])
330 331 # Package transaction (Pd and Pe)
332 package_tx = self.wallet.create_self_transfer(utxo_to_spend=mempool_tx["new_utxo"], target_vsize=target_vsize)
333 package_hex.append(package_tx["hex"])
334 335 assert_equal(3, node.getmempoolinfo()["size"])
336 assert_equal(2, len(package_hex))
337 return package_hex
338 339 340 if __name__ == "__main__":
341 MempoolPackageLimitsTest(__file__).main()
342