mempool_package_rbf.py raw
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
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 BitcoinTestFramework
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(BitcoinTestFramework):
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 "-maxmempool=5",
37 ]] * self.num_nodes
38
39 def assert_mempool_contents(self, expected=None):
40 mempool_util.assert_mempool_contents(self, self.nodes[0], expected, sync=False)
41
42 def create_simple_package(self, parent_coin, parent_fee=DEFAULT_FEE, child_fee=DEFAULT_CHILD_FEE, heavy_child=False):
43 """Create a 1 parent 1 child package using the coin passed in as the parent's input. The
44 parent has 1 output, used to fund 1 child transaction.
45 All transactions signal BIP125 replaceability, but nSequence changes based on self.ctr. This
46 prevents identical txids between packages when the parents spend the same coin and have the
47 same fee (i.e. 0sat).
48
49 returns tuple (hex serialized txns, CTransaction objects)
50 """
51 self.ctr += 1
52 # Use fee_rate=0 because create_self_transfer will use the default fee_rate value otherwise.
53 # Passing in fee>0 overrides fee_rate, so this still works for non-zero parent_fee.
54 parent_result = self.wallet.create_self_transfer(
55 fee=parent_fee,
56 utxo_to_spend=parent_coin,
57 sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
58 )
59
60 num_child_outputs = 10 if heavy_child else 1
61 child_result = self.wallet.create_self_transfer_multi(
62 utxos_to_spend=[parent_result["new_utxo"]],
63 num_outputs=num_child_outputs,
64 fee_per_output=int(child_fee * COIN // num_child_outputs),
65 sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
66 )
67 package_hex = [parent_result["hex"], child_result["hex"]]
68 package_txns = [parent_result["tx"], child_result["tx"]]
69 return package_hex, package_txns
70
71 def run_test(self):
72 # Counter used to count the number of times we constructed packages. Since we're constructing parent transactions with the same
73 # coins (to create conflicts), and perhaps giving them the same fee, we might accidentally just create the same transaction again.
74 # To prevent this, set nSequences to MAX_BIP125_RBF_SEQUENCE - self.ctr.
75 self.ctr = 0
76
77 self.log.info("Generate blocks to create UTXOs")
78 self.wallet = MiniWallet(self.nodes[0])
79
80 # Make more than enough coins for the sum of all tests,
81 # otherwise a wallet rescan is needed later
82 self.generate(self.wallet, 300)
83 self.coins = self.wallet.get_utxos(mark_as_spent=False)
84
85 self.test_package_rbf_basic()
86 self.test_package_rbf_singleton()
87 self.test_package_rbf_additional_fees()
88 self.test_package_rbf_max_conflicts()
89 self.test_too_numerous_ancestors()
90 self.test_package_rbf_with_wrong_pkg_size()
91 self.test_insufficient_feerate()
92 self.test_0fee_package_rbf()
93 self.test_child_conflicts_parent_mempool_ancestor()
94
95 def test_package_rbf_basic(self):
96 self.log.info("Test that a child can pay to replace its parents' conflicts")
97 node = self.nodes[0]
98 # Reuse the same coins so that the transactions conflict with one another.
99 parent_coin = self.coins.pop()
100 package_hex1, package_txns1 = self.create_simple_package(parent_coin, DEFAULT_FEE, DEFAULT_FEE)
101 package_hex2, package_txns2 = self.create_simple_package(parent_coin, DEFAULT_FEE, DEFAULT_CHILD_FEE)
102 node.submitpackage(package_hex1)
103 self.assert_mempool_contents(expected=package_txns1)
104
105 # Make sure 2nd node gets set up for basic package RBF
106 self.sync_all()
107
108 # Test run rejected because conflicts are not allowed in subpackage evaluation
109 testres = node.testmempoolaccept(package_hex2)
110 assert_equal(testres[0]["reject-reason"], "bip125-replacement-disallowed")
111
112 # But accepted during normal submission
113 submitres = node.submitpackage(package_hex2)
114 assert_equal(set(submitres["replaced-transactions"]), set([tx.txid_hex for tx in package_txns1]))
115 self.assert_mempool_contents(expected=package_txns2)
116
117 # Make sure 2nd node gets a basic package RBF over p2p
118 self.sync_all()
119
120 self.generate(node, 1)
121
122 def test_package_rbf_singleton(self):
123 self.log.info("Test child can pay to replace a parent's single conflicted tx")
124 node = self.nodes[0]
125
126 # Make singleton tx to conflict with in next batch
127 singleton_coin = self.coins.pop()
128 singleton_tx = self.wallet.create_self_transfer(utxo_to_spend=singleton_coin)
129 node.sendrawtransaction(singleton_tx["hex"])
130 self.assert_mempool_contents(expected=[singleton_tx["tx"]])
131
132 package_hex, package_txns = self.create_simple_package(singleton_coin, DEFAULT_FEE, singleton_tx["fee"] * 2)
133
134 submitres = node.submitpackage(package_hex)
135 assert_equal(submitres["replaced-transactions"], [singleton_tx["tx"].txid_hex])
136 self.assert_mempool_contents(expected=package_txns)
137
138 self.generate(node, 1)
139
140 def test_package_rbf_additional_fees(self):
141 self.log.info("Check Package RBF must increase the absolute fee")
142 node = self.nodes[0]
143 coin = self.coins.pop()
144
145 package_hex1, package_txns1 = self.create_simple_package(coin, parent_fee=DEFAULT_FEE, child_fee=DEFAULT_CHILD_FEE, heavy_child=True)
146 assert_greater_than_or_equal(1000, package_txns1[-1].get_vsize())
147 node.submitpackage(package_hex1)
148 self.assert_mempool_contents(expected=package_txns1)
149
150 PACKAGE_FEE = DEFAULT_FEE + DEFAULT_CHILD_FEE
151 PACKAGE_FEE_MINUS_ONE = PACKAGE_FEE - Decimal("0.00000001")
152
153 # Package 2 has a higher feerate but lower absolute fee
154 package_hex2, package_txns2 = self.create_simple_package(coin, parent_fee=DEFAULT_FEE, child_fee=DEFAULT_CHILD_FEE - Decimal("0.00000001"))
155 pkg_results2 = node.submitpackage(package_hex2)
156 assert_equal(f"package RBF failed: insufficient anti-DoS fees, rejecting replacement {package_txns2[1].txid_hex}, less fees than conflicting txs; {PACKAGE_FEE_MINUS_ONE} < {PACKAGE_FEE}", pkg_results2["package_msg"])
157 self.assert_mempool_contents(expected=package_txns1)
158
159 self.log.info("Check replacement pays for incremental bandwidth")
160 _, placeholder_txns3 = self.create_simple_package(coin)
161 package_3_size = sum([tx.get_vsize() for tx in placeholder_txns3])
162 incremental_sats_required = (Decimal(package_3_size * 0.1) / COIN).quantize(Decimal("0.00000001"))
163 incremental_sats_short = incremental_sats_required - Decimal("0.00000005")
164 # Recreate the package with slightly higher fee once we know the size of the new package, but still short of required fee
165 failure_package_hex3, failure_package_txns3 = self.create_simple_package(coin, parent_fee=DEFAULT_FEE, child_fee=DEFAULT_CHILD_FEE + incremental_sats_short)
166 assert_equal(package_3_size, sum([tx.get_vsize() for tx in failure_package_txns3]))
167 pkg_results3 = node.submitpackage(failure_package_hex3)
168 assert_equal(f"package RBF failed: insufficient anti-DoS fees, rejecting replacement {failure_package_txns3[1].txid_hex}, not enough additional fees to relay; {incremental_sats_short:.8f} < {incremental_sats_required:.8f}", pkg_results3["package_msg"])
169 self.assert_mempool_contents(expected=package_txns1)
170
171 success_package_hex3, success_package_txns3 = self.create_simple_package(coin, parent_fee=DEFAULT_FEE, child_fee=DEFAULT_CHILD_FEE + incremental_sats_required)
172 node.submitpackage(success_package_hex3)
173 self.assert_mempool_contents(expected=success_package_txns3)
174 self.generate(node, 1)
175
176 self.log.info("Check Package RBF must have strict cpfp structure")
177 coin = self.coins.pop()
178 package_hex4, package_txns4 = self.create_simple_package(coin, parent_fee=DEFAULT_FEE, child_fee=DEFAULT_CHILD_FEE)
179 node.submitpackage(package_hex4)
180 self.assert_mempool_contents(expected=package_txns4)
181 package_hex5, _package_txns5 = self.create_simple_package(coin, parent_fee=DEFAULT_CHILD_FEE, child_fee=DEFAULT_CHILD_FEE)
182 pkg_results5 = node.submitpackage(package_hex5)
183 assert 'package RBF failed: package feerate is less than or equal to parent feerate' in pkg_results5["package_msg"]
184 self.assert_mempool_contents(expected=package_txns4)
185
186 package_hex5_1, package_txns5_1 = self.create_simple_package(coin, parent_fee=DEFAULT_CHILD_FEE, child_fee=DEFAULT_CHILD_FEE + Decimal("0.00000001"))
187 node.submitpackage(package_hex5_1)
188 self.assert_mempool_contents(expected=package_txns5_1)
189 self.generate(node, 1)
190
191 def test_package_rbf_max_conflicts(self):
192 node = self.nodes[0]
193 self.log.info("Check Package RBF cannot conflict with more than MAX_REPLACEMENT_CANDIDATES clusters")
194 num_coins = 101
195 parent_coins = self.coins[:num_coins]
196 del self.coins[:num_coins]
197
198 # Original transactions: 101 transactions with 2 descendants each -> 303 total transactions, 101 clusters
199 size_three_clusters = []
200 for coin in parent_coins:
201 size_three_clusters.append(self.wallet.send_self_transfer_chain(from_node=node, chain_length=3, utxo_to_spend=coin))
202 expected_txns = [txn["tx"] for parent_child_txns in size_three_clusters for txn in parent_child_txns]
203 assert_equal(len(expected_txns), num_coins * 3)
204 self.assert_mempool_contents(expected=expected_txns)
205
206 # parent feeerate needs to be high enough for minrelay
207 # child feerate needs to be large enough to trigger package rbf with a very large parent and
208 # pay for all evicted fees. maxfeerate turned off for all submissions since child feerate
209 # is extremely high
210 parent_fee_per_conflict = 10000
211 child_feerate = 10000 * DEFAULT_FEE
212
213 # Conflict against all transactions by double-spending each parent, causing 101 cluster conflicts
214 package_parent = self.wallet.create_self_transfer_multi(utxos_to_spend=parent_coins, fee_per_output=parent_fee_per_conflict)
215 package_child = self.wallet.create_self_transfer(fee_rate=child_feerate, utxo_to_spend=package_parent["new_utxos"][0])
216
217 pkg_results = node.submitpackage([package_parent["hex"], package_child["hex"]], maxfeerate=0)
218 assert_equal("transaction failed", pkg_results["package_msg"])
219 assert_equal(f"too many potential replacements, rejecting replacement {package_parent['txid']}; too many conflicting clusters (101 > 100)", pkg_results["tx-results"][package_parent["wtxid"]]["error"])
220 self.assert_mempool_contents(expected=expected_txns)
221
222 # Make singleton tx to conflict with in next batch
223 singleton_coin = self.coins.pop()
224 singleton_tx = self.wallet.create_self_transfer(utxo_to_spend=singleton_coin)
225 node.sendrawtransaction(singleton_tx["hex"])
226 expected_txns.append(singleton_tx["tx"])
227
228 # Double-spend same set minus last, and double-spend singleton. This is still too many conflicted clusters.
229 # N.B. we can't RBF just a child tx in the clusters, as that would make resulting cluster of size 3.
230 double_spending_coins = parent_coins[:-1] + [singleton_coin]
231 package_parent = self.wallet.create_self_transfer_multi(utxos_to_spend=double_spending_coins, fee_per_output=parent_fee_per_conflict)
232 package_child = self.wallet.create_self_transfer(fee_rate=child_feerate, utxo_to_spend=package_parent["new_utxos"][0])
233 pkg_results = node.submitpackage([package_parent["hex"], package_child["hex"]], maxfeerate=0)
234 assert_equal("transaction failed", pkg_results["package_msg"])
235 assert_equal(f"too many potential replacements, rejecting replacement {package_parent['txid']}; too many conflicting clusters (101 > 100)", pkg_results["tx-results"][package_parent["wtxid"]]["error"])
236 self.assert_mempool_contents(expected=expected_txns)
237
238 # Finally, conflict with MAX_REPLACEMENT_CANDIDATES clusters
239 package_parent = self.wallet.create_self_transfer_multi(utxos_to_spend=parent_coins[:-1], fee_per_output=parent_fee_per_conflict)
240 package_child = self.wallet.create_self_transfer(fee_rate=child_feerate, utxo_to_spend=package_parent["new_utxos"][0])
241 pkg_results = node.submitpackage([package_parent["hex"], package_child["hex"]], maxfeerate=0)
242 assert_equal(pkg_results["package_msg"], "success")
243 self.assert_mempool_contents(expected=[singleton_tx["tx"], size_three_clusters[-1][0]["tx"], size_three_clusters[-1][1]["tx"], size_three_clusters[-1][2]["tx"], package_parent["tx"], package_child["tx"]] )
244
245 self.generate(node, 1)
246
247 def test_too_numerous_ancestors(self):
248 self.log.info("Test that package RBF doesn't work with packages larger than 2 due to ancestors")
249 node = self.nodes[0]
250 coin = self.coins.pop()
251
252 package_hex1, package_txns1 = self.create_simple_package(coin, DEFAULT_FEE, DEFAULT_CHILD_FEE)
253 node.submitpackage(package_hex1)
254 self.assert_mempool_contents(expected=package_txns1)
255
256 # Double-spends the original package
257 self.ctr += 1
258 parent_result1 = self.wallet.create_self_transfer(
259 fee=DEFAULT_FEE,
260 utxo_to_spend=coin,
261 sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
262 )
263
264 coin2 = self.coins.pop()
265
266 # Added to make package too large for package RBF;
267 # it will enter mempool individually
268 self.ctr += 1
269 parent_result2 = self.wallet.create_self_transfer(
270 fee=DEFAULT_FEE,
271 utxo_to_spend=coin2,
272 sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
273 )
274
275 # Child that spends both, violating cluster size rule due
276 # to in-mempool ancestry
277 self.ctr += 1
278 child_result = self.wallet.create_self_transfer_multi(
279 fee_per_output=int(DEFAULT_CHILD_FEE * COIN),
280 utxos_to_spend=[parent_result1["new_utxo"], parent_result2["new_utxo"]],
281 sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
282 )
283
284 package_hex2 = [parent_result1["hex"], parent_result2["hex"], child_result["hex"]]
285 package_txns2_succeed = [parent_result2["tx"]]
286
287 pkg_result = node.submitpackage(package_hex2)
288 assert_equal(pkg_result["package_msg"], 'package RBF failed: new transaction cannot have mempool ancestors')
289 self.assert_mempool_contents(expected=package_txns1 + package_txns2_succeed)
290 self.generate(node, 1)
291
292 def test_package_rbf_with_wrong_pkg_size(self):
293 self.log.info("Test that package RBF doesn't work with packages larger than 2 due to pkg size")
294 node = self.nodes[0]
295 coin1 = self.coins.pop()
296 coin2 = self.coins.pop()
297
298 # Two packages to require multiple direct conflicts, easier to set up illicit pkg size
299 package_hex1, package_txns1 = self.create_simple_package(coin1, DEFAULT_FEE, DEFAULT_CHILD_FEE)
300 package_hex2, package_txns2 = self.create_simple_package(coin2, DEFAULT_FEE, DEFAULT_CHILD_FEE)
301
302 node.submitpackage(package_hex1)
303 node.submitpackage(package_hex2)
304
305 self.assert_mempool_contents(expected=package_txns1 + package_txns2)
306 assert_equal(len(node.getrawmempool()), 4)
307
308 # Double-spends the first package
309 self.ctr += 1
310 parent_result1 = self.wallet.create_self_transfer(
311 fee=DEFAULT_FEE,
312 utxo_to_spend=coin1,
313 sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
314 )
315
316 # Double-spends the second package
317 self.ctr += 1
318 parent_result2 = self.wallet.create_self_transfer(
319 fee=DEFAULT_FEE,
320 utxo_to_spend=coin2,
321 sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
322 )
323
324 # Child that spends both, violating cluster size rule due
325 # to pkg size
326 self.ctr += 1
327 child_result = self.wallet.create_self_transfer_multi(
328 fee_per_output=int(DEFAULT_CHILD_FEE * COIN),
329 utxos_to_spend=[parent_result1["new_utxo"], parent_result2["new_utxo"]],
330 sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
331 )
332
333 package_hex3 = [parent_result1["hex"], parent_result2["hex"], child_result["hex"]]
334
335 pkg_result = node.submitpackage(package_hex3)
336 assert_equal(pkg_result["package_msg"], 'package RBF failed: package must be 1-parent-1-child')
337 self.assert_mempool_contents(expected=package_txns1 + package_txns2)
338 self.generate(node, 1)
339
340 def test_insufficient_feerate(self):
341 self.log.info("Check Package RBF must beat feerate of direct conflict")
342 node = self.nodes[0]
343 coin = self.coins.pop()
344
345 # Non-cpfp structure
346 package_hex1, package_txns1 = self.create_simple_package(coin, parent_fee=DEFAULT_CHILD_FEE, child_fee=DEFAULT_FEE)
347 node.submitpackage(package_hex1)
348 self.assert_mempool_contents(expected=package_txns1)
349
350 # Package 2 feerate is below the feerate of directly conflicted parent, so it fails even though
351 # total fees are higher than the original package
352 package_hex2, _package_txns2 = self.create_simple_package(coin, parent_fee=DEFAULT_CHILD_FEE - Decimal("0.00000001"), child_fee=DEFAULT_CHILD_FEE)
353 pkg_results2 = node.submitpackage(package_hex2)
354 assert_equal(pkg_results2["package_msg"], 'package RBF failed: insufficient feerate: does not improve feerate diagram')
355 self.assert_mempool_contents(expected=package_txns1)
356 self.generate(node, 1)
357
358 def test_0fee_package_rbf(self):
359 self.log.info("Test package RBF: TRUC 0-fee parent + high-fee child replaces parent's conflicts")
360 node = self.nodes[0]
361 # Reuse the same coins so that the transactions conflict with one another.
362 self.wallet.rescan_utxos()
363 parent_coin = self.wallet.get_utxo(confirmed_only=True)
364
365 # package1 pays default fee on both transactions
366 parent1 = self.wallet.create_self_transfer(utxo_to_spend=parent_coin, version=3)
367 child1 = self.wallet.create_self_transfer(utxo_to_spend=parent1["new_utxo"], version=3)
368 package_hex1 = [parent1["hex"], child1["hex"]]
369 fees_package1 = parent1["fee"] + child1["fee"]
370 submitres1 = node.submitpackage(package_hex1)
371 assert_equal(submitres1["package_msg"], "success")
372 self.assert_mempool_contents([parent1["tx"], child1["tx"]])
373
374 # package2 has a 0-fee parent (conflicting with package1) and very high fee child
375 parent2 = self.wallet.create_self_transfer(utxo_to_spend=parent_coin, fee=0, fee_rate=0, version=3)
376 child2 = self.wallet.create_self_transfer(utxo_to_spend=parent2["new_utxo"], fee=fees_package1*10, version=3)
377 package_hex2 = [parent2["hex"], child2["hex"]]
378
379 submitres2 = node.submitpackage(package_hex2)
380 assert_equal(submitres2["package_msg"], "success")
381 assert_equal(set(submitres2["replaced-transactions"]), set([parent1["txid"], child1["txid"]]))
382 self.assert_mempool_contents([parent2["tx"], child2["tx"]])
383
384 self.generate(node, 1)
385
386 def test_child_conflicts_parent_mempool_ancestor(self):
387 fill_mempool(self, self.nodes[0], tx_sync_fun=self.no_op)
388 # Reset coins since we filled the mempool with current coins
389 self.coins = self.wallet.get_utxos(mark_as_spent=False, confirmed_only=True)
390
391 self.log.info("Test that package RBF doesn't have issues with mempool<->package conflicts via inconsistency")
392 node = self.nodes[0]
393 coin = self.coins.pop()
394
395 self.ctr += 1
396 grandparent_result = self.wallet.create_self_transfer(
397 fee=DEFAULT_FEE,
398 utxo_to_spend=coin,
399 sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
400 )
401
402 node.sendrawtransaction(grandparent_result["hex"])
403 minrelayfeerate = node.getnetworkinfo()["relayfee"]
404
405 # Now make package of two descendants that looks
406 # like a cpfp where the parent can't get in on its own
407 self.ctr += 1
408 parent_result = self.wallet.create_self_transfer(
409 fee_rate=minrelayfeerate,
410 utxo_to_spend=grandparent_result["new_utxo"],
411 sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
412 )
413 # Last tx double-spends grandparent's coin,
414 # which is not inside the current package
415 self.ctr += 1
416 child_result = self.wallet.create_self_transfer_multi(
417 fee_per_output=int(DEFAULT_CHILD_FEE * COIN),
418 utxos_to_spend=[parent_result["new_utxo"], coin],
419 sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
420 )
421
422 pkg_result = node.submitpackage([parent_result["hex"], child_result["hex"]])
423 assert_equal(pkg_result["package_msg"], 'package RBF failed: new transaction cannot have mempool ancestors')
424 mempool_info = node.getrawmempool()
425 assert grandparent_result["txid"] in mempool_info
426 assert parent_result["txid"] not in mempool_info
427 assert child_result["txid"] not in mempool_info
428
429 if __name__ == "__main__":
430 PackageRBFTest(__file__).main()
431