mempool_package_rbf.py raw
1 #!/usr/bin/env python3
2 # Copyright (c) 2021 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
6 from decimal import Decimal
7
8 from test_framework.messages import (
9 COIN,
10 MAX_BIP125_RBF_SEQUENCE,
11 )
12 from test_framework.test_framework import LimenkaTestFramework
13 from test_framework.mempool_util import fill_mempool
14 from test_framework.util import (
15 assert_greater_than_or_equal,
16 assert_equal,
17 )
18 from test_framework.wallet import (
19 DEFAULT_FEE,
20 MiniWallet,
21 )
22 from test_framework import mempool_util
23
24 MAX_REPLACEMENT_CANDIDATES = 100
25
26 # Value high enough to cause evictions in each subtest
27 # for typical cases
28 DEFAULT_CHILD_FEE = DEFAULT_FEE * 4
29
30 class PackageRBFTest(LimenkaTestFramework):
31 def set_test_params(self):
32 self.num_nodes = 2
33 self.setup_clean_chain = True
34 # Required for fill_mempool()
35 self.extra_args = [[
36 "-datacarriersize=100000",
37 "-maxmempool=5",
38 ]] * self.num_nodes
39
40 def assert_mempool_contents(self, expected=None):
41 mempool_util.assert_mempool_contents(self, self.nodes[0], expected, sync=False)
42
43 def create_simple_package(self, parent_coin, parent_fee=DEFAULT_FEE, child_fee=DEFAULT_CHILD_FEE, heavy_child=False):
44 """Create a 1 parent 1 child package using the coin passed in as the parent's input. The
45 parent has 1 output, used to fund 1 child transaction.
46 All transactions signal BIP125 replaceability, but nSequence changes based on self.ctr. This
47 prevents identical txids between packages when the parents spend the same coin and have the
48 same fee (i.e. 0sat).
49
50 returns tuple (hex serialized txns, CTransaction objects)
51 """
52 self.ctr += 1
53 # Use fee_rate=0 because create_self_transfer will use the default fee_rate value otherwise.
54 # Passing in fee>0 overrides fee_rate, so this still works for non-zero parent_fee.
55 parent_result = self.wallet.create_self_transfer(
56 fee=parent_fee,
57 utxo_to_spend=parent_coin,
58 sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
59 )
60
61 num_child_outputs = 10 if heavy_child else 1
62 child_result = self.wallet.create_self_transfer_multi(
63 utxos_to_spend=[parent_result["new_utxo"]],
64 num_outputs=num_child_outputs,
65 fee_per_output=int(child_fee * COIN // num_child_outputs),
66 sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
67 )
68 package_hex = [parent_result["hex"], child_result["hex"]]
69 package_txns = [parent_result["tx"], child_result["tx"]]
70 return package_hex, package_txns
71
72 def run_test(self):
73 # Counter used to count the number of times we constructed packages. Since we're constructing parent transactions with the same
74 # coins (to create conflicts), and perhaps giving them the same fee, we might accidentally just create the same transaction again.
75 # To prevent this, set nSequences to MAX_BIP125_RBF_SEQUENCE - self.ctr.
76 self.ctr = 0
77
78 self.log.info("Generate blocks to create UTXOs")
79 self.wallet = MiniWallet(self.nodes[0])
80
81 # Make more than enough coins for the sum of all tests,
82 # otherwise a wallet rescan is needed later
83 self.generate(self.wallet, 300)
84 self.coins = self.wallet.get_utxos(mark_as_spent=False)
85
86 self.test_package_rbf_basic()
87 self.test_package_rbf_singleton()
88 self.test_package_rbf_additional_fees()
89 self.test_package_rbf_max_conflicts()
90 self.test_too_numerous_ancestors()
91 self.test_package_rbf_with_wrong_pkg_size()
92 self.test_insufficient_feerate()
93 self.test_wrong_conflict_cluster_size_linear()
94 self.test_wrong_conflict_cluster_size_parents_child()
95 self.test_wrong_conflict_cluster_size_parent_children()
96 self.test_0fee_package_rbf()
97 self.test_child_conflicts_parent_mempool_ancestor()
98
99 def test_package_rbf_basic(self):
100 self.log.info("Test that a child can pay to replace its parents' conflicts of cluster size 2")
101 node = self.nodes[0]
102 # Reuse the same coins so that the transactions conflict with one another.
103 parent_coin = self.coins.pop()
104 package_hex1, package_txns1 = self.create_simple_package(parent_coin, DEFAULT_FEE, DEFAULT_FEE)
105 package_hex2, package_txns2 = self.create_simple_package(parent_coin, DEFAULT_FEE, DEFAULT_CHILD_FEE)
106 node.submitpackage(package_hex1)
107 self.assert_mempool_contents(expected=package_txns1)
108
109 # Make sure 2nd node gets set up for basic package RBF
110 self.sync_all()
111
112 # Test run rejected because conflicts are not allowed in subpackage evaluation
113 testres = node.testmempoolaccept(package_hex2)
114 assert_equal(testres[0]["reject-reason"], "bip125-replacement-disallowed")
115
116 # But accepted during normal submission
117 submitres = node.submitpackage(package_hex2)
118 assert_equal(set(submitres["replaced-transactions"]), set([tx.rehash() for tx in package_txns1]))
119 self.assert_mempool_contents(expected=package_txns2)
120
121 # Make sure 2nd node gets a basic package RBF over p2p
122 self.sync_all()
123
124 self.generate(node, 1)
125
126 def test_package_rbf_singleton(self):
127 self.log.info("Test child can pay to replace a parent's single conflicted tx")
128 node = self.nodes[0]
129
130 # Make singleton tx to conflict with in next batch
131 singleton_coin = self.coins.pop()
132 singleton_tx = self.wallet.create_self_transfer(utxo_to_spend=singleton_coin)
133 node.sendrawtransaction(singleton_tx["hex"])
134 self.assert_mempool_contents(expected=[singleton_tx["tx"]])
135
136 package_hex, package_txns = self.create_simple_package(singleton_coin, DEFAULT_FEE, singleton_tx["fee"] * 2)
137
138 submitres = node.submitpackage(package_hex)
139 assert_equal(submitres["replaced-transactions"], [singleton_tx["tx"].rehash()])
140 self.assert_mempool_contents(expected=package_txns)
141
142 self.generate(node, 1)
143
144 def test_package_rbf_additional_fees(self):
145 self.log.info("Check Package RBF must increase the absolute fee")
146 node = self.nodes[0]
147 coin = self.coins.pop()
148
149 package_hex1, package_txns1 = self.create_simple_package(coin, parent_fee=DEFAULT_FEE, child_fee=DEFAULT_CHILD_FEE, heavy_child=True)
150 assert_greater_than_or_equal(1000, package_txns1[-1].get_vsize())
151 node.submitpackage(package_hex1)
152 self.assert_mempool_contents(expected=package_txns1)
153
154 PACKAGE_FEE = DEFAULT_FEE + DEFAULT_CHILD_FEE
155 PACKAGE_FEE_MINUS_ONE = PACKAGE_FEE - Decimal("0.00000001")
156
157 # Package 2 has a higher feerate but lower absolute fee
158 package_hex2, package_txns2 = self.create_simple_package(coin, parent_fee=DEFAULT_FEE, child_fee=DEFAULT_CHILD_FEE - Decimal("0.00000001"))
159 pkg_results2 = node.submitpackage(package_hex2)
160 assert_equal(f"package RBF failed: insufficient anti-DoS fees, rejecting replacement {package_txns2[1].rehash()}, less fees than conflicting txs; {PACKAGE_FEE_MINUS_ONE} < {PACKAGE_FEE}", pkg_results2["package_msg"])
161 self.assert_mempool_contents(expected=package_txns1)
162
163 self.log.info("Check replacement pays for incremental bandwidth")
164 _, placeholder_txns3 = self.create_simple_package(coin)
165 package_3_size = sum([tx.get_vsize() for tx in placeholder_txns3])
166 incremental_sats_required = (Decimal(package_3_size * 0.1) / COIN).quantize(Decimal("0.00000001"))
167 incremental_sats_short = incremental_sats_required - Decimal("0.00000005")
168 # Recreate the package with slightly higher fee once we know the size of the new package, but still short of required fee
169 failure_package_hex3, failure_package_txns3 = self.create_simple_package(coin, parent_fee=DEFAULT_FEE, child_fee=DEFAULT_CHILD_FEE + incremental_sats_short)
170 assert_equal(package_3_size, sum([tx.get_vsize() for tx in failure_package_txns3]))
171 pkg_results3 = node.submitpackage(failure_package_hex3)
172 assert_equal(f"package RBF failed: insufficient anti-DoS fees, rejecting replacement {failure_package_txns3[1].rehash()}, not enough additional fees to relay; {incremental_sats_short:.8f} < {incremental_sats_required:.8f}", pkg_results3["package_msg"])
173 self.assert_mempool_contents(expected=package_txns1)
174
175 success_package_hex3, success_package_txns3 = self.create_simple_package(coin, parent_fee=DEFAULT_FEE, child_fee=DEFAULT_CHILD_FEE + incremental_sats_required)
176 node.submitpackage(success_package_hex3)
177 self.assert_mempool_contents(expected=success_package_txns3)
178 self.generate(node, 1)
179
180 self.log.info("Check Package RBF must have strict cpfp structure")
181 coin = self.coins.pop()
182 package_hex4, package_txns4 = self.create_simple_package(coin, parent_fee=DEFAULT_FEE, child_fee=DEFAULT_CHILD_FEE)
183 node.submitpackage(package_hex4)
184 self.assert_mempool_contents(expected=package_txns4)
185 package_hex5, _package_txns5 = self.create_simple_package(coin, parent_fee=DEFAULT_CHILD_FEE, child_fee=DEFAULT_CHILD_FEE)
186 pkg_results5 = node.submitpackage(package_hex5)
187 assert 'package RBF failed: package feerate is less than or equal to parent feerate' in pkg_results5["package_msg"]
188 self.assert_mempool_contents(expected=package_txns4)
189
190 package_hex5_1, package_txns5_1 = self.create_simple_package(coin, parent_fee=DEFAULT_CHILD_FEE, child_fee=DEFAULT_CHILD_FEE + Decimal("0.00000001"))
191 node.submitpackage(package_hex5_1)
192 self.assert_mempool_contents(expected=package_txns5_1)
193 self.generate(node, 1)
194
195 def test_package_rbf_max_conflicts(self):
196 node = self.nodes[0]
197 self.log.info("Check Package RBF cannot replace more than MAX_REPLACEMENT_CANDIDATES transactions")
198 num_coins = 51
199 parent_coins = self.coins[:num_coins]
200 del self.coins[:num_coins]
201
202 # Original transactions: 51 transactions with 1 descendants each -> 102 total transactions
203 size_two_clusters = []
204 for coin in parent_coins:
205 size_two_clusters.append(self.wallet.send_self_transfer_chain(from_node=node, chain_length=2, utxo_to_spend=coin))
206 expected_txns = [txn["tx"] for parent_child_txns in size_two_clusters for txn in parent_child_txns]
207 assert_equal(len(expected_txns), num_coins * 2)
208 self.assert_mempool_contents(expected=expected_txns)
209
210 # parent feeerate needs to be high enough for minrelay
211 # child feerate needs to be large enough to trigger package rbf with a very large parent and
212 # pay for all evicted fees. maxfeerate turned off for all submissions since child feerate
213 # is extremely high
214 parent_fee_per_conflict = 10000
215 child_feerate = 10000 * DEFAULT_FEE
216
217 # Conflict against all transactions by double-spending each parent, causing 102 evictions
218 package_parent = self.wallet.create_self_transfer_multi(utxos_to_spend=parent_coins, fee_per_output=parent_fee_per_conflict)
219 package_child = self.wallet.create_self_transfer(fee_rate=child_feerate, utxo_to_spend=package_parent["new_utxos"][0])
220
221 pkg_results = node.submitpackage([package_parent["hex"], package_child["hex"]], maxfeerate=0)
222 assert_equal(f"package RBF failed: too many potential replacements, rejecting replacement {package_child['tx'].rehash()}; too many potential replacements (102 > 100)", pkg_results["package_msg"])
223 self.assert_mempool_contents(expected=expected_txns)
224
225 # Make singleton tx to conflict with in next batch
226 singleton_coin = self.coins.pop()
227 singleton_tx = self.wallet.create_self_transfer(utxo_to_spend=singleton_coin)
228 node.sendrawtransaction(singleton_tx["hex"])
229 expected_txns.append(singleton_tx["tx"])
230
231 # Double-spend same set minus last, and double-spend singleton. This hits 101 evictions; should still fail.
232 # N.B. we can't RBF just a child tx in the clusters, as that would make resulting cluster of size 3.
233 double_spending_coins = parent_coins[:-1] + [singleton_coin]
234 package_parent = self.wallet.create_self_transfer_multi(utxos_to_spend=double_spending_coins, fee_per_output=parent_fee_per_conflict)
235 package_child = self.wallet.create_self_transfer(fee_rate=child_feerate, utxo_to_spend=package_parent["new_utxos"][0])
236 pkg_results = node.submitpackage([package_parent["hex"], package_child["hex"]], maxfeerate=0)
237 assert_equal(f"package RBF failed: too many potential replacements, rejecting replacement {package_child['tx'].rehash()}; too many potential replacements (101 > 100)", pkg_results["package_msg"])
238 self.assert_mempool_contents(expected=expected_txns)
239
240 # Finally, evict MAX_REPLACEMENT_CANDIDATES
241 package_parent = self.wallet.create_self_transfer_multi(utxos_to_spend=parent_coins[:-1], fee_per_output=parent_fee_per_conflict)
242 package_child = self.wallet.create_self_transfer(fee_rate=child_feerate, utxo_to_spend=package_parent["new_utxos"][0])
243 pkg_results = node.submitpackage([package_parent["hex"], package_child["hex"]], maxfeerate=0)
244 assert_equal(pkg_results["package_msg"], "success")
245 self.assert_mempool_contents(expected=[singleton_tx["tx"], size_two_clusters[-1][0]["tx"], size_two_clusters[-1][1]["tx"], package_parent["tx"], package_child["tx"]] )
246
247 self.generate(node, 1)
248
249 def test_too_numerous_ancestors(self):
250 self.log.info("Test that package RBF doesn't work with packages larger than 2 due to ancestors")
251 node = self.nodes[0]
252 coin = self.coins.pop()
253
254 package_hex1, package_txns1 = self.create_simple_package(coin, DEFAULT_FEE, DEFAULT_CHILD_FEE)
255 node.submitpackage(package_hex1)
256 self.assert_mempool_contents(expected=package_txns1)
257
258 # Double-spends the original package
259 self.ctr += 1
260 parent_result1 = self.wallet.create_self_transfer(
261 fee=DEFAULT_FEE,
262 utxo_to_spend=coin,
263 sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
264 )
265
266 coin2 = self.coins.pop()
267
268 # Added to make package too large for package RBF;
269 # it will enter mempool individually
270 self.ctr += 1
271 parent_result2 = self.wallet.create_self_transfer(
272 fee=DEFAULT_FEE,
273 utxo_to_spend=coin2,
274 sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
275 )
276
277 # Child that spends both, violating cluster size rule due
278 # to in-mempool ancestry
279 self.ctr += 1
280 child_result = self.wallet.create_self_transfer_multi(
281 fee_per_output=int(DEFAULT_CHILD_FEE * COIN),
282 utxos_to_spend=[parent_result1["new_utxo"], parent_result2["new_utxo"]],
283 sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
284 )
285
286 package_hex2 = [parent_result1["hex"], parent_result2["hex"], child_result["hex"]]
287 package_txns2_succeed = [parent_result2["tx"]]
288
289 pkg_result = node.submitpackage(package_hex2)
290 assert_equal(pkg_result["package_msg"], 'package RBF failed: new transaction cannot have mempool ancestors')
291 self.assert_mempool_contents(expected=package_txns1 + package_txns2_succeed)
292 self.generate(node, 1)
293
294 def test_wrong_conflict_cluster_size_linear(self):
295 self.log.info("Test that conflicting with a cluster not sized two is rejected: linear chain")
296 node = self.nodes[0]
297
298 # Coins we will conflict with
299 coin1 = self.coins.pop()
300 coin2 = self.coins.pop()
301 coin3 = self.coins.pop()
302
303 # Three transactions chained; package RBF against any of these
304 # should be rejected
305 self.ctr += 1
306 parent_result = self.wallet.create_self_transfer(
307 fee=DEFAULT_FEE,
308 utxo_to_spend=coin1,
309 sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
310 )
311
312 self.ctr += 1
313 child_result = self.wallet.create_self_transfer_multi(
314 fee_per_output=int(DEFAULT_FEE * COIN),
315 utxos_to_spend=[parent_result["new_utxo"], coin2],
316 sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
317 )
318
319 self.ctr += 1
320 grandchild_result = self.wallet.create_self_transfer_multi(
321 fee_per_output=int(DEFAULT_FEE * COIN),
322 utxos_to_spend=[child_result["new_utxos"][0], coin3],
323 sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
324 )
325
326 expected_txns = [parent_result["tx"], child_result["tx"], grandchild_result["tx"]]
327 for tx in expected_txns:
328 node.sendrawtransaction(tx.serialize().hex())
329 self.assert_mempool_contents(expected=expected_txns)
330
331 # Now make conflicting packages for each coin
332 package_hex1, _package_txns1 = self.create_simple_package(coin1, DEFAULT_FEE, DEFAULT_CHILD_FEE)
333
334 package_result = node.submitpackage(package_hex1)
335 assert_equal(f"package RBF failed: {parent_result['tx'].rehash()} has 2 descendants, max 1 allowed", package_result["package_msg"])
336
337 package_hex2, _package_txns2 = self.create_simple_package(coin2, DEFAULT_FEE, DEFAULT_CHILD_FEE)
338 package_result = node.submitpackage(package_hex2)
339 assert_equal(f"package RBF failed: {child_result['tx'].rehash()} has both ancestor and descendant, exceeding cluster limit of 2", package_result["package_msg"])
340
341 package_hex3, _package_txns3 = self.create_simple_package(coin3, DEFAULT_FEE, DEFAULT_CHILD_FEE)
342 package_result = node.submitpackage(package_hex3)
343 assert_equal(f"package RBF failed: {grandchild_result['tx'].rehash()} has 2 ancestors, max 1 allowed", package_result["package_msg"])
344
345 # Check that replacements were actually rejected
346 self.assert_mempool_contents(expected=expected_txns)
347 self.generate(node, 1)
348
349 def test_wrong_conflict_cluster_size_parents_child(self):
350 self.log.info("Test that conflicting with a cluster not sized two is rejected: two parents one child")
351 node = self.nodes[0]
352
353 # Coins we will conflict with
354 coin1 = self.coins.pop()
355 coin2 = self.coins.pop()
356 coin3 = self.coins.pop()
357
358 self.ctr += 1
359 parent1_result = self.wallet.create_self_transfer(
360 fee=DEFAULT_FEE,
361 utxo_to_spend=coin1,
362 sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
363 )
364
365 self.ctr += 1
366 parent2_result = self.wallet.create_self_transfer_multi(
367 fee_per_output=int(DEFAULT_FEE * COIN),
368 utxos_to_spend=[coin2],
369 sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
370 )
371
372 self.ctr += 1
373 child_result = self.wallet.create_self_transfer_multi(
374 fee_per_output=int(DEFAULT_FEE * COIN),
375 utxos_to_spend=[parent1_result["new_utxo"], parent2_result["new_utxos"][0], coin3],
376 sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
377 )
378
379 expected_txns = [parent1_result["tx"], parent2_result["tx"], child_result["tx"]]
380 for tx in expected_txns:
381 node.sendrawtransaction(tx.serialize().hex())
382 self.assert_mempool_contents(expected=expected_txns)
383
384 # Now make conflicting packages for each coin
385 package_hex1, _package_txns1 = self.create_simple_package(coin1, DEFAULT_FEE, DEFAULT_CHILD_FEE)
386 package_result = node.submitpackage(package_hex1)
387 assert_equal(f"package RBF failed: {child_result['tx'].rehash()} has 2 ancestors, max 1 allowed", package_result["package_msg"])
388
389 package_hex2, _package_txns2 = self.create_simple_package(coin2, DEFAULT_FEE, DEFAULT_CHILD_FEE)
390 package_result = node.submitpackage(package_hex2)
391 assert_equal(f"package RBF failed: {child_result['tx'].rehash()} has 2 ancestors, max 1 allowed", package_result["package_msg"])
392
393 package_hex3, _package_txns3 = self.create_simple_package(coin3, DEFAULT_FEE, DEFAULT_CHILD_FEE)
394 package_result = node.submitpackage(package_hex3)
395 assert_equal(f"package RBF failed: {child_result['tx'].rehash()} has 2 ancestors, max 1 allowed", package_result["package_msg"])
396
397 # Check that replacements were actually rejected
398 self.assert_mempool_contents(expected=expected_txns)
399 self.generate(node, 1)
400
401 def test_wrong_conflict_cluster_size_parent_children(self):
402 self.log.info("Test that conflicting with a cluster not sized two is rejected: one parent two children")
403 node = self.nodes[0]
404
405 # Coins we will conflict with
406 coin1 = self.coins.pop()
407 coin2 = self.coins.pop()
408 coin3 = self.coins.pop()
409
410 self.ctr += 1
411 parent_result = self.wallet.create_self_transfer_multi(
412 fee_per_output=int(DEFAULT_FEE * COIN),
413 num_outputs=2,
414 utxos_to_spend=[coin1],
415 sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
416 )
417
418 self.ctr += 1
419 child1_result = self.wallet.create_self_transfer_multi(
420 fee_per_output=int(DEFAULT_FEE * COIN),
421 utxos_to_spend=[parent_result["new_utxos"][0], coin2],
422 sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
423 )
424
425 self.ctr += 1
426 child2_result = self.wallet.create_self_transfer_multi(
427 fee_per_output=int(DEFAULT_FEE * COIN),
428 utxos_to_spend=[parent_result["new_utxos"][1], coin3],
429 sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
430 )
431
432 # Submit them to mempool
433 expected_txns = [parent_result["tx"], child1_result["tx"], child2_result["tx"]]
434 for tx in expected_txns:
435 node.sendrawtransaction(tx.serialize().hex())
436 self.assert_mempool_contents(expected=expected_txns)
437
438 # Now make conflicting packages for each coin
439 package_hex1, _package_txns1 = self.create_simple_package(coin1, DEFAULT_FEE, DEFAULT_CHILD_FEE)
440 package_result = node.submitpackage(package_hex1)
441 assert_equal(f"package RBF failed: {child2_result['tx'].rehash()} is not the only child of parent {parent_result['tx'].rehash()}", package_result["package_msg"])
442
443 package_hex2, _package_txns2 = self.create_simple_package(coin2, DEFAULT_FEE, DEFAULT_CHILD_FEE)
444 package_result = node.submitpackage(package_hex2)
445 assert_equal(f"package RBF failed: {child1_result['tx'].rehash()} is not the only child of parent {parent_result['tx'].rehash()}", package_result["package_msg"])
446
447 package_hex3, _package_txns3 = self.create_simple_package(coin3, DEFAULT_FEE, DEFAULT_CHILD_FEE)
448 package_result = node.submitpackage(package_hex3)
449 assert_equal(f"package RBF failed: {child2_result['tx'].rehash()} is not the only child of parent {parent_result['tx'].rehash()}", package_result["package_msg"])
450
451 # Check that replacements were actually rejected
452 self.assert_mempool_contents(expected=expected_txns)
453 self.generate(node, 1)
454
455 def test_package_rbf_with_wrong_pkg_size(self):
456 self.log.info("Test that package RBF doesn't work with packages larger than 2 due to pkg size")
457 node = self.nodes[0]
458 coin1 = self.coins.pop()
459 coin2 = self.coins.pop()
460
461 # Two packages to require multiple direct conflicts, easier to set up illicit pkg size
462 package_hex1, package_txns1 = self.create_simple_package(coin1, DEFAULT_FEE, DEFAULT_CHILD_FEE)
463 package_hex2, package_txns2 = self.create_simple_package(coin2, DEFAULT_FEE, DEFAULT_CHILD_FEE)
464
465 node.submitpackage(package_hex1)
466 node.submitpackage(package_hex2)
467
468 self.assert_mempool_contents(expected=package_txns1 + package_txns2)
469 assert_equal(len(node.getrawmempool()), 4)
470
471 # Double-spends the first package
472 self.ctr += 1
473 parent_result1 = self.wallet.create_self_transfer(
474 fee=DEFAULT_FEE,
475 utxo_to_spend=coin1,
476 sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
477 )
478
479 # Double-spends the second package
480 self.ctr += 1
481 parent_result2 = self.wallet.create_self_transfer(
482 fee=DEFAULT_FEE,
483 utxo_to_spend=coin2,
484 sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
485 )
486
487 # Child that spends both, violating cluster size rule due
488 # to pkg size
489 self.ctr += 1
490 child_result = self.wallet.create_self_transfer_multi(
491 fee_per_output=int(DEFAULT_CHILD_FEE * COIN),
492 utxos_to_spend=[parent_result1["new_utxo"], parent_result2["new_utxo"]],
493 sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
494 )
495
496 package_hex3 = [parent_result1["hex"], parent_result2["hex"], child_result["hex"]]
497
498 pkg_result = node.submitpackage(package_hex3)
499 assert_equal(pkg_result["package_msg"], 'package RBF failed: package must be 1-parent-1-child')
500 self.assert_mempool_contents(expected=package_txns1 + package_txns2)
501 self.generate(node, 1)
502
503 def test_insufficient_feerate(self):
504 self.log.info("Check Package RBF must beat feerate of direct conflict")
505 node = self.nodes[0]
506 coin = self.coins.pop()
507
508 # Non-cpfp structure
509 package_hex1, package_txns1 = self.create_simple_package(coin, parent_fee=DEFAULT_CHILD_FEE, child_fee=DEFAULT_FEE)
510 node.submitpackage(package_hex1)
511 self.assert_mempool_contents(expected=package_txns1)
512
513 # Package 2 feerate is below the feerate of directly conflicted parent, so it fails even though
514 # total fees are higher than the original package
515 package_hex2, _package_txns2 = self.create_simple_package(coin, parent_fee=DEFAULT_CHILD_FEE - Decimal("0.00000001"), child_fee=DEFAULT_CHILD_FEE)
516 pkg_results2 = node.submitpackage(package_hex2)
517 assert_equal(pkg_results2["package_msg"], 'package RBF failed: insufficient feerate: does not improve feerate diagram')
518 self.assert_mempool_contents(expected=package_txns1)
519 self.generate(node, 1)
520
521 def test_0fee_package_rbf(self):
522 self.log.info("Test package RBF: TRUC 0-fee parent + high-fee child replaces parent's conflicts")
523 node = self.nodes[0]
524 # Reuse the same coins so that the transactions conflict with one another.
525 self.wallet.rescan_utxos()
526 parent_coin = self.wallet.get_utxo(confirmed_only=True)
527
528 # package1 pays default fee on both transactions
529 parent1 = self.wallet.create_self_transfer(utxo_to_spend=parent_coin, version=3)
530 child1 = self.wallet.create_self_transfer(utxo_to_spend=parent1["new_utxo"], version=3)
531 package_hex1 = [parent1["hex"], child1["hex"]]
532 fees_package1 = parent1["fee"] + child1["fee"]
533 submitres1 = node.submitpackage(package_hex1)
534 assert_equal(submitres1["package_msg"], "success")
535 self.assert_mempool_contents([parent1["tx"], child1["tx"]])
536
537 # package2 has a 0-fee parent (conflicting with package1) and very high fee child
538 parent2 = self.wallet.create_self_transfer(utxo_to_spend=parent_coin, fee=0, fee_rate=0, version=3)
539 child2 = self.wallet.create_self_transfer(utxo_to_spend=parent2["new_utxo"], fee=fees_package1*10, version=3)
540 package_hex2 = [parent2["hex"], child2["hex"]]
541
542 submitres2 = node.submitpackage(package_hex2)
543 assert_equal(submitres2["package_msg"], "success")
544 assert_equal(set(submitres2["replaced-transactions"]), set([parent1["txid"], child1["txid"]]))
545 self.assert_mempool_contents([parent2["tx"], child2["tx"]])
546
547 self.generate(node, 1)
548
549 def test_child_conflicts_parent_mempool_ancestor(self):
550 fill_mempool(self, self.nodes[0], tx_sync_fun=self.no_op)
551 # Reset coins since we filled the mempool with current coins
552 self.coins = self.wallet.get_utxos(mark_as_spent=False, confirmed_only=True)
553
554 self.log.info("Test that package RBF doesn't have issues with mempool<->package conflicts via inconsistency")
555 node = self.nodes[0]
556 coin = self.coins.pop()
557
558 self.ctr += 1
559 grandparent_result = self.wallet.create_self_transfer(
560 fee=DEFAULT_FEE,
561 utxo_to_spend=coin,
562 sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
563 )
564
565 node.sendrawtransaction(grandparent_result["hex"])
566 minrelayfeerate = node.getnetworkinfo()["relayfee"]
567
568 # Now make package of two descendants that looks
569 # like a cpfp where the parent can't get in on its own
570 self.ctr += 1
571 parent_result = self.wallet.create_self_transfer(
572 fee_rate=minrelayfeerate,
573 utxo_to_spend=grandparent_result["new_utxo"],
574 sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
575 )
576 # Last tx double-spends grandparent's coin,
577 # which is not inside the current package
578 self.ctr += 1
579 child_result = self.wallet.create_self_transfer_multi(
580 fee_per_output=int(DEFAULT_CHILD_FEE * COIN),
581 utxos_to_spend=[parent_result["new_utxo"], coin],
582 sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
583 )
584
585 pkg_result = node.submitpackage([parent_result["hex"], child_result["hex"]])
586 assert_equal(pkg_result["package_msg"], 'package RBF failed: new transaction cannot have mempool ancestors')
587 mempool_info = node.getrawmempool()
588 assert grandparent_result["txid"] in mempool_info
589 assert parent_result["txid"] not in mempool_info
590 assert child_result["txid"] not in mempool_info
591
592 if __name__ == "__main__":
593 PackageRBFTest(__file__).main()
594