feature_rbf.py raw
1 #!/usr/bin/env python3
2 # Copyright (c) 2014-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 the RBF code."""
6
7 from decimal import Decimal
8
9 from test_framework.messages import (
10 MAX_BIP125_RBF_SEQUENCE,
11 COIN,
12 NODE_REPLACE_BY_FEE,
13 SEQUENCE_FINAL,
14 )
15 from test_framework.test_framework import LimenkaTestFramework
16 from test_framework.util import (
17 assert_equal,
18 assert_greater_than,
19 assert_greater_than_or_equal,
20 assert_raises_rpc_error,
21 get_fee,
22 )
23 from test_framework.wallet import MiniWallet
24 from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE
25
26 MAX_REPLACEMENT_LIMIT = 100
27 class ReplaceByFeeTest(LimenkaTestFramework):
28 def add_options(self, parser):
29 self.add_wallet_options(parser)
30
31 def set_test_params(self):
32 self.num_nodes = 4
33 # both nodes disable full-rbf to test BIP125 signaling
34 self.extra_args = [
35 [
36 "-mempoolfullrbf=0",
37 "-limitancestorcount=50",
38 "-limitancestorsize=101",
39 "-limitdescendantcount=200",
40 "-limitdescendantsize=101",
41 "-mempooltruc=accept",
42 "-paytxfee=0.00001", # this test confuses the fee estimator into nearly 1 BTC fees
43 ],
44 # second node has default mempool parameters, besides mempoolfullrbf being disabled
45 [
46 "-mempoolfullrbf=0",
47 ],
48 [
49 "-acceptnonstdtxn=1",
50 "-mempoolreplacement=0",
51 ],
52 ]
53 self.extra_args.append(
54 [
55 *self.extra_args[0],
56 "-mempoolreplacement=fee,-optin",
57 ],
58 )
59 self.supports_cli = False
60
61 def run_test(self):
62 self.wallet = MiniWallet(self.nodes[0])
63
64 self.log.info("Running test RPC rbf_policy")
65 def test_rpc_rbf_policy():
66 assert_equal(self.nodes[0].getmempoolinfo()["rbf_policy"], 'optin')
67 assert_equal(self.nodes[1].getmempoolinfo()["rbf_policy"], 'optin')
68 assert_equal(self.nodes[2].getmempoolinfo()["rbf_policy"], 'never')
69 assert_equal(self.nodes[3].getmempoolinfo()["rbf_policy"], 'always')
70 test_rpc_rbf_policy()
71
72 self.log.info("Running test no service flag")
73 def test_service_flag():
74 for i in range(4):
75 assert not (int(self.nodes[i].getnetworkinfo()['localservices'], 0x10) & NODE_REPLACE_BY_FEE)
76 assert 'REPLACE_BY_FEE?' not in self.nodes[i].getnetworkinfo()['localservicesnames']
77 test_service_flag()
78
79 self.log.info("Running test simple doublespend...")
80 self.test_simple_doublespend()
81
82 self.log.info("Running test doublespend chain...")
83 self.test_doublespend_chain()
84
85 self.log.info("Running test doublespend tree...")
86 self.test_doublespend_tree()
87
88 self.log.info("Running test replacement feeperkb...")
89 self.test_replacement_feeperkb()
90
91 self.log.info("Running test spends of conflicting outputs...")
92 self.test_spends_of_conflicting_outputs()
93
94 self.log.info("Running test new unconfirmed inputs...")
95 self.test_new_unconfirmed_inputs()
96
97 self.log.info("Running test too many replacements...")
98 self.test_too_many_replacements()
99
100 self.log.info("Running test too many replacements using default mempool params...")
101 self.test_too_many_replacements_with_default_mempool_params()
102
103 self.log.info("Running test opt-in...")
104 self.test_opt_in(fullrbf=False)
105 self.test_opt_in(fullrbf=False, use_truc=True)
106 self.nodes[0], self.nodes[-1] = self.nodes[-1], self.nodes[0]
107 self.test_opt_in(fullrbf=True)
108 self.test_opt_in(fullrbf=True, use_truc=True)
109 self.nodes[0], self.nodes[-1] = self.nodes[-1], self.nodes[0]
110
111 self.log.info("Running test RPC...")
112 self.test_rpc()
113
114 self.log.info("Running test prioritised transactions...")
115 self.test_prioritised_transactions()
116
117 self.log.info("Running test no inherited signaling...")
118 self.test_no_inherited_signaling()
119
120 self.log.info("Running test replacement relay fee...")
121 self.test_replacement_relay_fee()
122
123 self.log.info("Running test full replace by fee...")
124 self.test_fullrbf()
125
126 self.log.info("Running test incremental relay feerates...")
127 self.test_incremental_relay_feerates()
128
129 self.log.info("Passed")
130
131 def make_utxo(self, node, amount, *, confirmed=True, scriptPubKey=None):
132 """Create a txout with a given amount and scriptPubKey
133
134 confirmed - txout created will be confirmed in the blockchain;
135 unconfirmed otherwise.
136 """
137 tx = self.wallet.send_to(from_node=node, scriptPubKey=scriptPubKey or self.wallet.get_output_script(), amount=amount)
138
139 if confirmed:
140 mempool_size = len(node.getrawmempool())
141 while mempool_size > 0:
142 self.generate(node, 1)
143 new_size = len(node.getrawmempool())
144 # Error out if we have something stuck in the mempool, as this
145 # would likely be a bug.
146 assert new_size < mempool_size
147 mempool_size = new_size
148
149 return self.wallet.get_utxo(txid=tx["txid"], vout=tx["sent_vout"])
150
151 def test_simple_doublespend(self):
152 """Simple doublespend"""
153 # we use MiniWallet to create a transaction template with inputs correctly set,
154 # and modify the output (amount, scriptPubKey) according to our needs
155 tx = self.wallet.create_self_transfer(fee_rate=Decimal("0.003"))["tx"]
156 tx1a_hex = tx.serialize().hex()
157 tx1a_txid = self.nodes[0].sendrawtransaction(tx1a_hex)
158 assert_equal(tx1a_txid, self.nodes[2].sendrawtransaction(tx1a_hex))
159
160 # Should fail because we haven't changed the fee
161 tx.vout[0].scriptPubKey[-1] ^= 1
162 tx.rehash()
163 tx_hex = tx.serialize().hex()
164
165 # This will raise an exception due to insufficient fee
166 reject_reason = "insufficient fee"
167 reject_details = f"{reject_reason}, rejecting replacement {tx.hash}; new feerate 0.00300000 BTC/kvB <= old feerate 0.00300000 BTC/kvB"
168 res = self.nodes[0].testmempoolaccept(rawtxs=[tx_hex])[0]
169 assert_equal(res["reject-reason"], reject_reason)
170 assert_equal(res["reject-details"], reject_details)
171 assert_raises_rpc_error(-26, f"{reject_details}", self.nodes[0].sendrawtransaction, tx_hex, 0)
172 # This will raise an exception due to transaction replacement being disabled
173 assert_raises_rpc_error(-26, "txn-mempool-conflict", self.nodes[2].sendrawtransaction, tx_hex, 0)
174
175 # Extra 0.1 BTC fee
176 tx.vout[0].nValue -= int(0.1 * COIN)
177 tx1b_hex = tx.serialize().hex()
178 # Replacement still disabled even with "enough fee"
179 assert_raises_rpc_error(-26, "txn-mempool-conflict", self.nodes[2].sendrawtransaction, tx1b_hex, 0)
180 # Works when enabled
181 tx1b_txid = self.nodes[0].sendrawtransaction(tx1b_hex, 0)
182
183 mempool = self.nodes[0].getrawmempool()
184
185 assert tx1a_txid not in mempool
186 assert tx1b_txid in mempool
187
188 assert_equal(tx1b_hex, self.nodes[0].getrawtransaction(tx1b_txid))
189
190 # Third node is running mempoolreplacement=0, will not replace originally-seen txn
191 mempool = self.nodes[2].getrawmempool()
192 assert tx1a_txid in mempool
193 assert tx1b_txid not in mempool
194
195 def test_doublespend_chain(self):
196 """Doublespend of a long chain"""
197
198 initial_nValue = 5 * COIN
199 tx0_outpoint = self.make_utxo(self.nodes[0], initial_nValue)
200
201 prevout = tx0_outpoint
202 remaining_value = initial_nValue
203 chain_txids = []
204 while remaining_value > 1 * COIN:
205 remaining_value -= int(0.1 * COIN)
206 prevout = self.wallet.send_self_transfer(
207 from_node=self.nodes[0],
208 utxo_to_spend=prevout,
209 sequence=0,
210 fee=Decimal("0.1"),
211 )["new_utxo"]
212 chain_txids.append(prevout["txid"])
213
214 # Whether the double-spend is allowed is evaluated by including all
215 # child fees - 4 BTC - so this attempt is rejected.
216 dbl_tx = self.wallet.create_self_transfer(
217 utxo_to_spend=tx0_outpoint,
218 sequence=0,
219 fee=Decimal("3"),
220 )["tx"]
221 dbl_tx_hex = dbl_tx.serialize().hex()
222
223 # This will raise an exception due to insufficient fee
224 reject_reason = "insufficient fee"
225 reject_details = f"{reject_reason}, rejecting replacement {dbl_tx.hash}, less fees than conflicting txs; 3.00 < 4.00"
226 res = self.nodes[0].testmempoolaccept(rawtxs=[dbl_tx_hex])[0]
227 assert_equal(res["reject-reason"], reject_reason)
228 assert_equal(res["reject-details"], reject_details)
229 assert_raises_rpc_error(-26, f"{reject_details}", self.nodes[0].sendrawtransaction, dbl_tx_hex, 0)
230
231
232
233 # Accepted with sufficient fee
234 dbl_tx.vout[0].nValue = int(0.1 * COIN)
235 dbl_tx_hex = dbl_tx.serialize().hex()
236 self.nodes[0].sendrawtransaction(dbl_tx_hex, 0)
237
238 mempool = self.nodes[0].getrawmempool()
239 for doublespent_txid in chain_txids:
240 assert doublespent_txid not in mempool
241
242 def test_doublespend_tree(self):
243 """Doublespend of a big tree of transactions"""
244
245 initial_nValue = 5 * COIN
246 tx0_outpoint = self.make_utxo(self.nodes[0], initial_nValue)
247
248 def branch(prevout, initial_value, max_txs, tree_width=5, fee=0.00001 * COIN, _total_txs=None):
249 if _total_txs is None:
250 _total_txs = [0]
251 if _total_txs[0] >= max_txs:
252 return
253
254 txout_value = (initial_value - fee) // tree_width
255 if txout_value < fee:
256 return
257
258 tx = self.wallet.send_self_transfer_multi(
259 utxos_to_spend=[prevout],
260 from_node=self.nodes[0],
261 sequence=0,
262 num_outputs=tree_width,
263 amount_per_output=txout_value,
264 )
265
266 yield tx["txid"]
267 _total_txs[0] += 1
268
269 for utxo in tx["new_utxos"]:
270 for x in branch(utxo, txout_value,
271 max_txs,
272 tree_width=tree_width, fee=fee,
273 _total_txs=_total_txs):
274 yield x
275
276 fee = int(0.00001 * COIN)
277 n = MAX_REPLACEMENT_LIMIT
278 tree_txs = list(branch(tx0_outpoint, initial_nValue, n, fee=fee))
279 assert_equal(len(tree_txs), n)
280
281 # Attempt double-spend, will fail because too little fee paid
282 dbl_tx_hex = self.wallet.create_self_transfer(
283 utxo_to_spend=tx0_outpoint,
284 sequence=0,
285 fee=(Decimal(fee) / COIN) * n,
286 )["hex"]
287 # This will raise an exception due to insufficient fee
288 assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, dbl_tx_hex, 0)
289
290 # 0.1 BTC fee is enough
291 dbl_tx_hex = self.wallet.create_self_transfer(
292 utxo_to_spend=tx0_outpoint,
293 sequence=0,
294 fee=(Decimal(fee) / COIN) * n + Decimal("0.1"),
295 )["hex"]
296 self.nodes[0].sendrawtransaction(dbl_tx_hex, 0)
297
298 mempool = self.nodes[0].getrawmempool()
299
300 for txid in tree_txs:
301 assert txid not in mempool
302
303 # Try again, but with more total transactions than the "max txs
304 # double-spent at once" anti-DoS limit.
305 for n in (MAX_REPLACEMENT_LIMIT + 1, MAX_REPLACEMENT_LIMIT * 2):
306 fee = int(0.00001 * COIN)
307 tx0_outpoint = self.make_utxo(self.nodes[0], initial_nValue)
308 tree_txs = list(branch(tx0_outpoint, initial_nValue, n, fee=fee))
309 assert_equal(len(tree_txs), n)
310
311 dbl_tx_hex = self.wallet.create_self_transfer(
312 utxo_to_spend=tx0_outpoint,
313 sequence=0,
314 fee=2 * (Decimal(fee) / COIN) * n,
315 )["hex"]
316 # This will raise an exception
317 assert_raises_rpc_error(-26, "too many potential replacements", self.nodes[0].sendrawtransaction, dbl_tx_hex, 0)
318
319 for txid in tree_txs:
320 self.nodes[0].getrawtransaction(txid)
321
322 def test_replacement_feeperkb(self):
323 """Replacement requires fee-per-KB to be higher"""
324 tx0_outpoint = self.make_utxo(self.nodes[0], int(1.1 * COIN))
325
326 self.wallet.send_self_transfer(
327 from_node=self.nodes[0],
328 utxo_to_spend=tx0_outpoint,
329 sequence=0,
330 fee=Decimal("0.1"),
331 )
332
333 # Higher fee, but the fee per KB is much lower, so the replacement is
334 # rejected.
335 tx1b_hex = self.wallet.create_self_transfer_multi(
336 utxos_to_spend=[tx0_outpoint],
337 sequence=0,
338 num_outputs=100,
339 amount_per_output=1000,
340 )["hex"]
341
342 # This will raise an exception due to insufficient fee
343 assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx1b_hex, 0)
344
345 def test_spends_of_conflicting_outputs(self):
346 """Replacements that spend conflicting tx outputs are rejected"""
347 utxo1 = self.make_utxo(self.nodes[0], int(1.2 * COIN))
348 utxo2 = self.make_utxo(self.nodes[0], 3 * COIN)
349
350 tx1a = self.wallet.send_self_transfer(
351 from_node=self.nodes[0],
352 utxo_to_spend=utxo1,
353 sequence=0,
354 fee=Decimal("0.1"),
355 )
356 tx1a_utxo = tx1a["new_utxo"]
357
358 # Direct spend an output of the transaction we're replacing.
359 tx2 = self.wallet.create_self_transfer_multi(
360 utxos_to_spend=[utxo1, utxo2, tx1a_utxo],
361 sequence=0,
362 amount_per_output=int(COIN * tx1a_utxo["value"]),
363 )["tx"]
364 tx2_hex = tx2.serialize().hex()
365
366 # This will raise an exception
367 reject_reason = "bad-txns-spends-conflicting-tx"
368 reject_details = f"{reject_reason}, {tx2.hash} spends conflicting transaction {tx1a['tx'].hash}"
369 res = self.nodes[0].testmempoolaccept(rawtxs=[tx2_hex])[0]
370 assert_equal(res["reject-reason"], reject_reason)
371 assert_equal(res["reject-details"], reject_details)
372 assert_raises_rpc_error(-26, f"{reject_details}", self.nodes[0].sendrawtransaction, tx2_hex, 0)
373
374
375 # Spend tx1a's output to test the indirect case.
376 tx1b_utxo = self.wallet.send_self_transfer(
377 from_node=self.nodes[0],
378 utxo_to_spend=tx1a_utxo,
379 sequence=0,
380 fee=Decimal("0.1"),
381 )["new_utxo"]
382
383 tx2_hex = self.wallet.create_self_transfer_multi(
384 utxos_to_spend=[utxo1, utxo2, tx1b_utxo],
385 sequence=0,
386 amount_per_output=int(COIN * tx1a_utxo["value"]),
387 )["hex"]
388
389 # This will raise an exception
390 assert_raises_rpc_error(-26, "bad-txns-spends-conflicting-tx", self.nodes[0].sendrawtransaction, tx2_hex, 0)
391
392 def test_new_unconfirmed_inputs(self):
393 """Replacements that add new unconfirmed inputs are rejected"""
394 confirmed_utxo = self.make_utxo(self.nodes[0], int(1.1 * COIN))
395 unconfirmed_utxo = self.make_utxo(self.nodes[0], int(0.1 * COIN), confirmed=False)
396
397 self.wallet.send_self_transfer(
398 from_node=self.nodes[0],
399 utxo_to_spend=confirmed_utxo,
400 sequence=0,
401 fee=Decimal("0.1"),
402 )
403
404 tx2 = self.wallet.create_self_transfer_multi(
405 utxos_to_spend=[confirmed_utxo, unconfirmed_utxo],
406 sequence=0,
407 amount_per_output=1 * COIN,
408 )["tx"]
409 tx2_hex = tx2.serialize().hex()
410
411 # This will raise an exception
412 reject_reason = "replacement-adds-unconfirmed"
413 reject_details = f"{reject_reason}, replacement {tx2.hash} adds unconfirmed input, idx 1"
414 res = self.nodes[0].testmempoolaccept(rawtxs=[tx2_hex])[0]
415 assert_equal(res["reject-reason"], reject_reason)
416 assert_equal(res["reject-details"], reject_details)
417 assert_raises_rpc_error(-26, f"{reject_details}", self.nodes[0].sendrawtransaction, tx2_hex, 0)
418
419
420 def test_too_many_replacements(self):
421 """Replacements that evict too many transactions are rejected"""
422 # Try directly replacing more than MAX_REPLACEMENT_LIMIT
423 # transactions
424
425 # Start by creating a single transaction with many outputs
426 initial_nValue = 10 * COIN
427 utxo = self.make_utxo(self.nodes[0], initial_nValue)
428 fee = int(0.0001 * COIN)
429 split_value = int((initial_nValue - fee) / (MAX_REPLACEMENT_LIMIT + 1))
430
431 splitting_tx_utxos = self.wallet.send_self_transfer_multi(
432 from_node=self.nodes[0],
433 utxos_to_spend=[utxo],
434 sequence=0,
435 num_outputs=MAX_REPLACEMENT_LIMIT + 1,
436 amount_per_output=split_value,
437 )["new_utxos"]
438
439 # Now spend each of those outputs individually
440 for utxo in splitting_tx_utxos:
441 self.wallet.send_self_transfer(
442 from_node=self.nodes[0],
443 utxo_to_spend=utxo,
444 sequence=0,
445 fee=Decimal(fee) / COIN,
446 )
447
448 # Now create doublespend of the whole lot; should fail.
449 # Need a big enough fee to cover all spending transactions and have
450 # a higher fee rate
451 double_spend_value = (split_value - 100 * fee) * (MAX_REPLACEMENT_LIMIT + 1)
452 double_tx = self.wallet.create_self_transfer_multi(
453 utxos_to_spend=splitting_tx_utxos,
454 sequence=0,
455 amount_per_output=double_spend_value,
456 )["tx"]
457 double_tx_hex = double_tx.serialize().hex()
458
459 # This will raise an exception
460 reject_reason = "too many potential replacements"
461 reject_details = f"{reject_reason}, rejecting replacement {double_tx.hash}; too many potential replacements ({MAX_REPLACEMENT_LIMIT + 1} > {MAX_REPLACEMENT_LIMIT})"
462 res = self.nodes[0].testmempoolaccept(rawtxs=[double_tx_hex])[0]
463 assert_equal(res["reject-reason"], reject_reason)
464 assert_equal(res["reject-details"], reject_details)
465 assert_raises_rpc_error(-26, f"{reject_details}", self.nodes[0].sendrawtransaction, double_tx_hex, 0)
466
467
468 # If we remove an input, it should pass
469 double_tx.vin.pop()
470 double_tx_hex = double_tx.serialize().hex()
471 self.nodes[0].sendrawtransaction(double_tx_hex, 0)
472
473 def test_too_many_replacements_with_default_mempool_params(self):
474 """
475 Test rule 5 (do not allow replacements that cause more than 100
476 evictions) without having to rely on non-default mempool parameters.
477
478 In order to do this, create a number of "root" UTXOs, and then hang
479 enough transactions off of each root UTXO to exceed the MAX_REPLACEMENT_LIMIT.
480 Then create a conflicting RBF replacement transaction.
481 """
482 # Clear mempools to avoid cross-node sync failure.
483 for node in self.nodes:
484 self.generate(node, 1)
485 normal_node = self.nodes[1]
486 wallet = MiniWallet(normal_node)
487
488 # This has to be chosen so that the total number of transactions can exceed
489 # MAX_REPLACEMENT_LIMIT without having any one tx graph run into the descendant
490 # limit; 10 works.
491 num_tx_graphs = 10
492
493 # (Number of transactions per graph, rule 5 failure expected)
494 cases = [
495 # Test the base case of evicting fewer than MAX_REPLACEMENT_LIMIT
496 # transactions.
497 ((MAX_REPLACEMENT_LIMIT // num_tx_graphs) - 1, False),
498
499 # Test hitting the rule 5 eviction limit.
500 (MAX_REPLACEMENT_LIMIT // num_tx_graphs, True),
501 ]
502
503 for (txs_per_graph, failure_expected) in cases:
504 self.log.debug(f"txs_per_graph: {txs_per_graph}, failure: {failure_expected}")
505 # "Root" utxos of each txn graph that we will attempt to double-spend with
506 # an RBF replacement.
507 root_utxos = []
508
509 # For each root UTXO, create a package that contains the spend of that
510 # UTXO and `txs_per_graph` children tx.
511 for graph_num in range(num_tx_graphs):
512 root_utxos.append(wallet.get_utxo())
513
514 optin_parent_tx = wallet.send_self_transfer_multi(
515 from_node=normal_node,
516 sequence=MAX_BIP125_RBF_SEQUENCE,
517 utxos_to_spend=[root_utxos[graph_num]],
518 num_outputs=txs_per_graph,
519 )
520 assert_equal(True, normal_node.getmempoolentry(optin_parent_tx['txid'])['bip125-replaceable'])
521 new_utxos = optin_parent_tx['new_utxos']
522
523 for utxo in new_utxos:
524 # Create spends for each output from the "root" of this graph.
525 child_tx = wallet.send_self_transfer(
526 from_node=normal_node,
527 utxo_to_spend=utxo,
528 )
529
530 assert normal_node.getmempoolentry(child_tx['txid'])
531
532 num_txs_invalidated = len(root_utxos) + (num_tx_graphs * txs_per_graph)
533
534 if failure_expected:
535 assert num_txs_invalidated > MAX_REPLACEMENT_LIMIT
536 else:
537 assert num_txs_invalidated <= MAX_REPLACEMENT_LIMIT
538
539 # Now attempt to submit a tx that double-spends all the root tx inputs, which
540 # would invalidate `num_txs_invalidated` transactions.
541 tx_hex = wallet.create_self_transfer_multi(
542 utxos_to_spend=root_utxos,
543 fee_per_output=10_000_000, # absurdly high feerate
544 )["hex"]
545
546 if failure_expected:
547 assert_raises_rpc_error(
548 -26, "too many potential replacements", normal_node.sendrawtransaction, tx_hex, 0)
549 else:
550 txid = normal_node.sendrawtransaction(tx_hex, 0)
551 assert normal_node.getmempoolentry(txid)
552
553 # Clear the mempool once finished, and rescan the other nodes' wallet
554 # to account for the spends we've made on `normal_node`.
555 self.generate(normal_node, 1)
556 self.wallet.rescan_utxos()
557
558 def test_opt_in(self, fullrbf, use_truc=False):
559 """Replacing should only work if orig tx opted in"""
560 tx0_outpoint = self.make_utxo(self.nodes[0], int(1.1 * COIN))
561
562 # Create a non-opting in transaction
563 tx1a_utxo = self.wallet.send_self_transfer(
564 from_node=self.nodes[0],
565 utxo_to_spend=tx0_outpoint,
566 sequence=SEQUENCE_FINAL,
567 fee=Decimal("0.1"),
568 )["new_utxo"]
569
570 # This transaction isn't shown as replaceable
571 assert_equal(self.nodes[0].getmempoolentry(tx1a_utxo["txid"])['bip125-replaceable'], False)
572
573 # Shouldn't be able to double-spend
574 tx1b_st = self.wallet.create_self_transfer(
575 utxo_to_spend=tx0_outpoint,
576 sequence=0,
577 fee=Decimal("0.2"),
578 )
579 tx1b_hex = tx1b_st["hex"]
580
581 if fullrbf:
582 self.nodes[0].sendrawtransaction(tx1b_hex, 0)
583 tx1a_utxo = tx1b_st["new_utxo"]
584 else:
585 # This will raise an exception
586 assert_raises_rpc_error(-26, "txn-mempool-conflict", self.nodes[0].sendrawtransaction, tx1b_hex, 0)
587
588 tx1_outpoint = self.make_utxo(self.nodes[0], int(1.1 * COIN))
589
590 # Create a different non-opting in transaction
591 tx2a_utxo = self.wallet.send_self_transfer(
592 from_node=self.nodes[0],
593 utxo_to_spend=tx1_outpoint,
594 sequence=0xfffffffe,
595 fee=Decimal("0.1"),
596 )["new_utxo"]
597
598 # Still shouldn't be able to double-spend
599 tx2b_st = self.wallet.create_self_transfer(
600 utxo_to_spend=tx1_outpoint,
601 sequence=0,
602 fee=Decimal("0.2"),
603 )
604 tx2b_hex = tx2b_st["hex"]
605
606 if fullrbf:
607 self.nodes[0].sendrawtransaction(tx2b_hex, 0)
608 tx2a_utxo = tx2b_st["new_utxo"]
609 else:
610 # This will raise an exception
611 assert_raises_rpc_error(-26, "txn-mempool-conflict", self.nodes[0].sendrawtransaction, tx2b_hex, 0)
612
613 # Now create a new transaction that spends from tx1a and tx2a
614 # opt-in on one of the inputs
615 # Transaction should be replaceable on either input
616
617 self.generate(self.nodes[0], 1) # clean mempool so parent txs don't trigger BIP125
618 if use_truc:
619 kwargs = {'sequence': SEQUENCE_FINAL, 'version': 3}
620 else:
621 kwargs = {'sequence': [SEQUENCE_FINAL, 0xfffffffd]}
622
623 tx3a_txid = self.wallet.send_self_transfer_multi(
624 from_node=self.nodes[0],
625 utxos_to_spend=[tx1a_utxo, tx2a_utxo],
626 fee_per_output=int(0.1 * COIN),
627 **kwargs
628 )["txid"]
629
630 # This transaction is shown as replaceable
631 if use_truc:
632 assert_equal(self.nodes[0].getmempoolentry(tx3a_txid)['bip125-replaceable'], False)
633 else:
634 assert_equal(self.nodes[0].getmempoolentry(tx3a_txid)['bip125-replaceable'], True)
635
636 self.wallet.send_self_transfer(
637 from_node=self.nodes[0],
638 utxo_to_spend=tx1a_utxo,
639 sequence=0,
640 fee=Decimal("0.4"),
641 )
642
643 # If tx3b was accepted, tx3c won't look like a replacement,
644 # but make sure it is accepted anyway
645 self.wallet.send_self_transfer(
646 from_node=self.nodes[0],
647 utxo_to_spend=tx2a_utxo,
648 sequence=0,
649 fee=Decimal("0.4"),
650 )
651
652 self.generate(self.nodes[0], 1) # clean mempool
653
654 def test_prioritised_transactions(self):
655 # Ensure that fee deltas used via prioritisetransaction are
656 # correctly used by replacement logic
657
658 # 1. Check that feeperkb uses modified fees
659 tx0_outpoint = self.make_utxo(self.nodes[0], int(1.1 * COIN))
660
661 tx1a_txid = self.wallet.send_self_transfer(
662 from_node=self.nodes[0],
663 utxo_to_spend=tx0_outpoint,
664 sequence=0,
665 fee=Decimal("0.1"),
666 )["txid"]
667
668 # Higher fee, but the actual fee per KB is much lower.
669 tx1b_hex = self.wallet.create_self_transfer_multi(
670 utxos_to_spend=[tx0_outpoint],
671 sequence=0,
672 num_outputs=100,
673 amount_per_output=int(0.00001 * COIN),
674 )["hex"]
675
676 # Verify tx1b cannot replace tx1a.
677 assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx1b_hex, 0)
678
679 # Use prioritisetransaction to set tx1a's fee to 0.
680 self.nodes[0].prioritisetransaction(txid=tx1a_txid, fee_delta=int(-0.1 * COIN))
681
682 # Now tx1b should be able to replace tx1a
683 tx1b_txid = self.nodes[0].sendrawtransaction(tx1b_hex, 0)
684
685 assert tx1b_txid in self.nodes[0].getrawmempool()
686
687 # 2. Check that absolute fee checks use modified fee.
688 tx1_outpoint = self.make_utxo(self.nodes[0], int(1.1 * COIN))
689
690 # tx2a
691 self.wallet.send_self_transfer(
692 from_node=self.nodes[0],
693 utxo_to_spend=tx1_outpoint,
694 sequence=0,
695 fee=Decimal("0.1"),
696 )
697
698 # Lower fee, but we'll prioritise it
699 tx2b = self.wallet.create_self_transfer(
700 utxo_to_spend=tx1_outpoint,
701 sequence=0,
702 fee=Decimal("0.09"),
703 )
704
705 # Verify tx2b cannot replace tx2a.
706 assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx2b["hex"], 0)
707
708 # Now prioritise tx2b to have a higher modified fee
709 self.nodes[0].prioritisetransaction(txid=tx2b["txid"], fee_delta=int(0.1 * COIN))
710
711 # tx2b should now be accepted
712 tx2b_txid = self.nodes[0].sendrawtransaction(tx2b["hex"], 0)
713
714 assert tx2b_txid in self.nodes[0].getrawmempool()
715
716 def test_rpc(self):
717 us0 = self.wallet.get_utxo()
718 ins = [us0]
719 outs = {ADDRESS_BCRT1_UNSPENDABLE: Decimal(1.0000000)}
720 rawtx0 = self.nodes[0].createrawtransaction(ins, outs, 0, True)
721 rawtx1 = self.nodes[0].createrawtransaction(ins, outs, 0, False)
722 json0 = self.nodes[0].decoderawtransaction(rawtx0)
723 json1 = self.nodes[0].decoderawtransaction(rawtx1)
724 assert_equal(json0["vin"][0]["sequence"], 4294967293)
725 assert_equal(json1["vin"][0]["sequence"], 4294967295)
726
727 if self.is_specified_wallet_compiled():
728 self.init_wallet(node=0)
729 rawtx2 = self.nodes[0].createrawtransaction([], outs)
730 frawtx2a = self.nodes[0].fundrawtransaction(rawtx2, {"replaceable": True})
731 frawtx2b = self.nodes[0].fundrawtransaction(rawtx2, {"replaceable": False})
732
733 json0 = self.nodes[0].decoderawtransaction(frawtx2a['hex'])
734 json1 = self.nodes[0].decoderawtransaction(frawtx2b['hex'])
735 assert_equal(json0["vin"][0]["sequence"], 4294967293)
736 assert_equal(json1["vin"][0]["sequence"], 4294967294)
737
738 def test_no_inherited_signaling(self):
739 confirmed_utxo = self.wallet.get_utxo()
740
741 # Create an explicitly opt-in parent transaction
742 optin_parent_tx = self.wallet.send_self_transfer(
743 from_node=self.nodes[0],
744 utxo_to_spend=confirmed_utxo,
745 sequence=MAX_BIP125_RBF_SEQUENCE,
746 fee_rate=Decimal('0.01'),
747 )
748 assert_equal(True, self.nodes[0].getmempoolentry(optin_parent_tx['txid'])['bip125-replaceable'])
749
750 replacement_parent_tx = self.wallet.create_self_transfer(
751 utxo_to_spend=confirmed_utxo,
752 sequence=MAX_BIP125_RBF_SEQUENCE,
753 fee_rate=Decimal('0.02'),
754 )
755
756 # Test if parent tx can be replaced.
757 res = self.nodes[0].testmempoolaccept(rawtxs=[replacement_parent_tx['hex']])[0]
758
759 # Parent can be replaced.
760 assert_equal(res['allowed'], True)
761
762 # Create an opt-out child tx spending the opt-in parent
763 parent_utxo = self.wallet.get_utxo(txid=optin_parent_tx['txid'])
764 optout_child_tx = self.wallet.send_self_transfer(
765 from_node=self.nodes[0],
766 utxo_to_spend=parent_utxo,
767 sequence=SEQUENCE_FINAL,
768 fee_rate=Decimal('0.01'),
769 )
770
771 # Reports true due to inheritance
772 assert_equal(True, self.nodes[0].getmempoolentry(optout_child_tx['txid'])['bip125-replaceable'])
773
774 replacement_child_tx = self.wallet.create_self_transfer(
775 utxo_to_spend=parent_utxo,
776 sequence=SEQUENCE_FINAL,
777 fee_rate=Decimal('0.02'),
778 )
779
780 # Broadcast replacement child tx
781 # BIP 125 :
782 # 1. The original transactions signal replaceability explicitly or through inheritance as described in the above
783 # Summary section.
784 # The original transaction (`optout_child_tx`) doesn't signal RBF but its parent (`optin_parent_tx`) does.
785 # The replacement transaction (`replacement_child_tx`) should be able to replace the original transaction.
786 # See CVE-2021-31876 for further explanations.
787 assert_equal(True, self.nodes[0].getmempoolentry(optin_parent_tx['txid'])['bip125-replaceable'])
788 assert_raises_rpc_error(-26, 'txn-mempool-conflict', self.nodes[0].sendrawtransaction, replacement_child_tx["hex"], 0)
789
790 self.log.info('Check that the child tx can still be replaced (via a tx that also replaces the parent)')
791 replacement_parent_tx = self.wallet.send_self_transfer(
792 from_node=self.nodes[0],
793 utxo_to_spend=confirmed_utxo,
794 sequence=SEQUENCE_FINAL,
795 fee_rate=Decimal('0.03'),
796 )
797 # Check that child is removed and update wallet utxo state
798 assert_raises_rpc_error(-5, 'Transaction not in mempool', self.nodes[0].getmempoolentry, optout_child_tx['txid'])
799 self.wallet.get_utxo(txid=optout_child_tx['txid'])
800
801 def test_replacement_relay_fee(self):
802 tx = self.wallet.send_self_transfer(from_node=self.nodes[0])['tx']
803
804 # Higher fee, higher feerate, different txid, but the replacement does not provide a relay
805 # fee conforming to node's `incrementalrelayfee` policy of 1000 sat per KB.
806 assert_equal(self.nodes[0].getmempoolinfo()["incrementalrelayfee"], Decimal("0.000001"))
807 tx.vout[0].nValue -= 1
808 assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx.serialize().hex())
809
810 def test_incremental_relay_feerates(self):
811 self.log.info("Test that incremental relay fee is applied correctly in RBF for various settings...")
812 node = self.nodes[0]
813 for incremental_setting in (0, 5, 10, 50, 100, 234, 1000, 5000, 21000):
814 incremental_setting_decimal = incremental_setting / Decimal(COIN)
815 self.log.info(f"-> Test -incrementalrelayfee={incremental_setting_decimal:.8f}sat/kvB...")
816 self.restart_node(0, extra_args=[f"-incrementalrelayfee={incremental_setting_decimal:.8f}", "-datacarriersize=5000", "-persistmempool=0"])
817
818 # When incremental relay feerate is higher than min relay feerate, min relay feerate is automatically increased.
819 min_relay_feerate = node.getmempoolinfo()["minrelaytxfee"]
820 assert_greater_than_or_equal(min_relay_feerate, incremental_setting_decimal)
821
822 low_feerate = min_relay_feerate * 2
823 confirmed_utxo = self.wallet.get_utxo(confirmed_only=True)
824 replacee_tx = self.wallet.create_self_transfer(utxo_to_spend=confirmed_utxo, fee_rate=low_feerate, target_vsize=5000)
825 node.sendrawtransaction(replacee_tx['hex'])
826
827 replacement_placeholder_tx = self.wallet.create_self_transfer(utxo_to_spend=confirmed_utxo)
828 replacement_expected_size = replacement_placeholder_tx['tx'].get_vsize()
829 replacement_required_fee = get_fee(replacement_expected_size, incremental_setting_decimal) + replacee_tx['fee']
830
831 # Should always be required to pay additional fees
832 if incremental_setting > 0:
833 assert_greater_than(replacement_required_fee, replacee_tx['fee'])
834
835 # 1 satoshi shy of the required fee
836 failed_replacement_tx = self.wallet.create_self_transfer(utxo_to_spend=confirmed_utxo, fee=replacement_required_fee - Decimal("0.00000001"))
837 assert_raises_rpc_error(-26, "insufficient fee", node.sendrawtransaction, failed_replacement_tx['hex'])
838
839 replacement_tx = self.wallet.create_self_transfer(utxo_to_spend=confirmed_utxo, fee=replacement_required_fee)
840 node.sendrawtransaction(replacement_tx['hex'])
841
842 def test_fullrbf(self):
843
844 confirmed_utxo = self.make_utxo(self.nodes[0], int(2 * COIN))
845 self.restart_node(0, extra_args=["-mempoolfullrbf=1"])
846 assert self.nodes[0].getmempoolinfo()["fullrbf"]
847 assert_equal(self.nodes[0].getmempoolinfo()["rbf_policy"], 'always')
848
849 # Create an explicitly opt-out transaction
850 optout_tx = self.wallet.send_self_transfer(
851 from_node=self.nodes[0],
852 utxo_to_spend=confirmed_utxo,
853 sequence=MAX_BIP125_RBF_SEQUENCE + 1,
854 fee_rate=Decimal('0.01'),
855 )
856 assert_equal(False, self.nodes[0].getmempoolentry(optout_tx['txid'])['bip125-replaceable'])
857
858 conflicting_tx = self.wallet.create_self_transfer(
859 utxo_to_spend=confirmed_utxo,
860 sequence=SEQUENCE_FINAL,
861 fee_rate=Decimal('0.02'),
862 )
863
864 # Send the replacement transaction, conflicting with the optout_tx.
865 self.nodes[0].sendrawtransaction(conflicting_tx['hex'], 0)
866
867 # Optout_tx is not anymore in the mempool.
868 assert optout_tx['txid'] not in self.nodes[0].getrawmempool()
869 assert conflicting_tx['txid'] in self.nodes[0].getrawmempool()
870
871 if __name__ == '__main__':
872 ReplaceByFeeTest(__file__).main()
873