feature_rbf.py
1 #!/usr/bin/env python3 2 # Copyright (c) 2014-2022 The Bitcoin Core developers 3 # Distributed under the MIT software license, see the accompanying 4 # file COPYING or http://www.opensource.org/licenses/mit-license.php. 5 """Test the RBF code.""" 6 7 from decimal import Decimal 8 9 from test_framework.messages import ( 10 MAX_BIP125_RBF_SEQUENCE, 11 COIN, 12 ) 13 from test_framework.test_framework import BitcoinTestFramework 14 from test_framework.util import ( 15 assert_equal, 16 assert_raises_rpc_error, 17 ) 18 from test_framework.wallet import MiniWallet 19 from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE 20 21 MAX_REPLACEMENT_LIMIT = 100 22 class ReplaceByFeeTest(BitcoinTestFramework): 23 def set_test_params(self): 24 self.num_nodes = 2 25 self.extra_args = [ 26 [ 27 "-limitancestorcount=50", 28 "-limitancestorsize=101", 29 "-limitdescendantcount=200", 30 "-limitdescendantsize=101", 31 ], 32 # second node has default mempool parameters 33 [ 34 ], 35 ] 36 self.supports_cli = False 37 self.uses_wallet = None 38 39 def run_test(self): 40 self.wallet = MiniWallet(self.nodes[0]) 41 42 self.log.info("Running test simple doublespend...") 43 self.test_simple_doublespend() 44 45 self.log.info("Running test doublespend chain...") 46 self.test_doublespend_chain() 47 48 self.log.info("Running test doublespend tree...") 49 self.test_doublespend_tree() 50 51 self.log.info("Running test replacement feeperkb...") 52 self.test_replacement_feeperkb() 53 54 self.log.info("Running test spends of conflicting outputs...") 55 self.test_spends_of_conflicting_outputs() 56 57 self.log.info("Running test new unconfirmed inputs...") 58 self.test_new_unconfirmed_inputs() 59 60 self.log.info("Running test too many replacements...") 61 self.test_too_many_replacements() 62 63 self.log.info("Running test too many replacements using default mempool params...") 64 self.test_too_many_replacements_with_default_mempool_params() 65 66 self.log.info("Running test RPC...") 67 self.test_rpc() 68 69 self.log.info("Running test prioritised transactions...") 70 self.test_prioritised_transactions() 71 72 self.log.info("Running test replacement relay fee...") 73 self.test_replacement_relay_fee() 74 75 self.log.info("Running test full replace by fee...") 76 self.test_fullrbf() 77 78 self.log.info("Passed") 79 80 def make_utxo(self, node, amount, *, confirmed=True, scriptPubKey=None): 81 """Create a txout with a given amount and scriptPubKey 82 83 confirmed - txout created will be confirmed in the blockchain; 84 unconfirmed otherwise. 85 """ 86 tx = self.wallet.send_to(from_node=node, scriptPubKey=scriptPubKey or self.wallet.get_output_script(), amount=amount) 87 88 if confirmed: 89 mempool_size = len(node.getrawmempool()) 90 while mempool_size > 0: 91 self.generate(node, 1) 92 new_size = len(node.getrawmempool()) 93 # Error out if we have something stuck in the mempool, as this 94 # would likely be a bug. 95 assert new_size < mempool_size 96 mempool_size = new_size 97 98 return self.wallet.get_utxo(txid=tx["txid"], vout=tx["sent_vout"]) 99 100 def test_simple_doublespend(self): 101 """Simple doublespend""" 102 # we use MiniWallet to create a transaction template with inputs correctly set, 103 # and modify the output (amount, scriptPubKey) according to our needs 104 tx = self.wallet.create_self_transfer(fee_rate=Decimal("0.003"))["tx"] 105 tx1a_txid = self.nodes[0].sendrawtransaction(tx.serialize().hex()) 106 107 # Should fail because we haven't changed the fee 108 tx.vout[0].scriptPubKey[-1] ^= 1 109 tx.rehash() 110 tx_hex = tx.serialize().hex() 111 112 # This will raise an exception due to insufficient fee 113 reject_reason = "insufficient fee" 114 reject_details = f"{reject_reason}, rejecting replacement {tx.hash}; new feerate 0.00300000 BTC/kvB <= old feerate 0.00300000 BTC/kvB" 115 res = self.nodes[0].testmempoolaccept(rawtxs=[tx_hex])[0] 116 assert_equal(res["reject-reason"], reject_reason) 117 assert_equal(res["reject-details"], reject_details) 118 assert_raises_rpc_error(-26, f"{reject_details}", self.nodes[0].sendrawtransaction, tx_hex, 0) 119 120 121 # Extra 0.1 BTC fee 122 tx.vout[0].nValue -= int(0.1 * COIN) 123 tx1b_hex = tx.serialize().hex() 124 # Works when enabled 125 tx1b_txid = self.nodes[0].sendrawtransaction(tx1b_hex, 0) 126 127 mempool = self.nodes[0].getrawmempool() 128 129 assert tx1a_txid not in mempool 130 assert tx1b_txid in mempool 131 132 assert_equal(tx1b_hex, self.nodes[0].getrawtransaction(tx1b_txid)) 133 134 def test_doublespend_chain(self): 135 """Doublespend of a long chain""" 136 137 initial_nValue = 5 * COIN 138 tx0_outpoint = self.make_utxo(self.nodes[0], initial_nValue) 139 140 prevout = tx0_outpoint 141 remaining_value = initial_nValue 142 chain_txids = [] 143 while remaining_value > 1 * COIN: 144 remaining_value -= int(0.1 * COIN) 145 prevout = self.wallet.send_self_transfer( 146 from_node=self.nodes[0], 147 utxo_to_spend=prevout, 148 sequence=0, 149 fee=Decimal("0.1"), 150 )["new_utxo"] 151 chain_txids.append(prevout["txid"]) 152 153 # Whether the double-spend is allowed is evaluated by including all 154 # child fees - 4 BTC - so this attempt is rejected. 155 dbl_tx = self.wallet.create_self_transfer( 156 utxo_to_spend=tx0_outpoint, 157 sequence=0, 158 fee=Decimal("3"), 159 )["tx"] 160 dbl_tx_hex = dbl_tx.serialize().hex() 161 162 # This will raise an exception due to insufficient fee 163 reject_reason = "insufficient fee" 164 reject_details = f"{reject_reason}, rejecting replacement {dbl_tx.hash}, less fees than conflicting txs; 3.00 < 4.00" 165 res = self.nodes[0].testmempoolaccept(rawtxs=[dbl_tx_hex])[0] 166 assert_equal(res["reject-reason"], reject_reason) 167 assert_equal(res["reject-details"], reject_details) 168 assert_raises_rpc_error(-26, f"{reject_details}", self.nodes[0].sendrawtransaction, dbl_tx_hex, 0) 169 170 171 172 # Accepted with sufficient fee 173 dbl_tx.vout[0].nValue = int(0.1 * COIN) 174 dbl_tx_hex = dbl_tx.serialize().hex() 175 self.nodes[0].sendrawtransaction(dbl_tx_hex, 0) 176 177 mempool = self.nodes[0].getrawmempool() 178 for doublespent_txid in chain_txids: 179 assert doublespent_txid not in mempool 180 181 def test_doublespend_tree(self): 182 """Doublespend of a big tree of transactions""" 183 184 initial_nValue = 5 * COIN 185 tx0_outpoint = self.make_utxo(self.nodes[0], initial_nValue) 186 187 def branch(prevout, initial_value, max_txs, tree_width=5, fee=0.00001 * COIN, _total_txs=None): 188 if _total_txs is None: 189 _total_txs = [0] 190 if _total_txs[0] >= max_txs: 191 return 192 193 txout_value = (initial_value - fee) // tree_width 194 if txout_value < fee: 195 return 196 197 tx = self.wallet.send_self_transfer_multi( 198 utxos_to_spend=[prevout], 199 from_node=self.nodes[0], 200 sequence=0, 201 num_outputs=tree_width, 202 amount_per_output=txout_value, 203 ) 204 205 yield tx["txid"] 206 _total_txs[0] += 1 207 208 for utxo in tx["new_utxos"]: 209 for x in branch(utxo, txout_value, 210 max_txs, 211 tree_width=tree_width, fee=fee, 212 _total_txs=_total_txs): 213 yield x 214 215 fee = int(0.00001 * COIN) 216 n = MAX_REPLACEMENT_LIMIT 217 tree_txs = list(branch(tx0_outpoint, initial_nValue, n, fee=fee)) 218 assert_equal(len(tree_txs), n) 219 220 # Attempt double-spend, will fail because too little fee paid 221 dbl_tx_hex = self.wallet.create_self_transfer( 222 utxo_to_spend=tx0_outpoint, 223 sequence=0, 224 fee=(Decimal(fee) / COIN) * n, 225 )["hex"] 226 # This will raise an exception due to insufficient fee 227 assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, dbl_tx_hex, 0) 228 229 # 0.1 BTC fee is enough 230 dbl_tx_hex = self.wallet.create_self_transfer( 231 utxo_to_spend=tx0_outpoint, 232 sequence=0, 233 fee=(Decimal(fee) / COIN) * n + Decimal("0.1"), 234 )["hex"] 235 self.nodes[0].sendrawtransaction(dbl_tx_hex, 0) 236 237 mempool = self.nodes[0].getrawmempool() 238 239 for txid in tree_txs: 240 assert txid not in mempool 241 242 # Try again, but with more total transactions than the "max txs 243 # double-spent at once" anti-DoS limit. 244 for n in (MAX_REPLACEMENT_LIMIT + 1, MAX_REPLACEMENT_LIMIT * 2): 245 fee = int(0.00001 * COIN) 246 tx0_outpoint = self.make_utxo(self.nodes[0], initial_nValue) 247 tree_txs = list(branch(tx0_outpoint, initial_nValue, n, fee=fee)) 248 assert_equal(len(tree_txs), n) 249 250 dbl_tx_hex = self.wallet.create_self_transfer( 251 utxo_to_spend=tx0_outpoint, 252 sequence=0, 253 fee=2 * (Decimal(fee) / COIN) * n, 254 )["hex"] 255 # This will raise an exception 256 assert_raises_rpc_error(-26, "too many potential replacements", self.nodes[0].sendrawtransaction, dbl_tx_hex, 0) 257 258 for txid in tree_txs: 259 self.nodes[0].getrawtransaction(txid) 260 261 def test_replacement_feeperkb(self): 262 """Replacement requires fee-per-KB to be higher""" 263 tx0_outpoint = self.make_utxo(self.nodes[0], int(1.1 * COIN)) 264 265 self.wallet.send_self_transfer( 266 from_node=self.nodes[0], 267 utxo_to_spend=tx0_outpoint, 268 sequence=0, 269 fee=Decimal("0.1"), 270 ) 271 272 # Higher fee, but the fee per KB is much lower, so the replacement is 273 # rejected. 274 tx1b_hex = self.wallet.create_self_transfer_multi( 275 utxos_to_spend=[tx0_outpoint], 276 sequence=0, 277 num_outputs=100, 278 amount_per_output=1000, 279 )["hex"] 280 281 # This will raise an exception due to insufficient fee 282 assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx1b_hex, 0) 283 284 def test_spends_of_conflicting_outputs(self): 285 """Replacements that spend conflicting tx outputs are rejected""" 286 utxo1 = self.make_utxo(self.nodes[0], int(1.2 * COIN)) 287 utxo2 = self.make_utxo(self.nodes[0], 3 * COIN) 288 289 tx1a = self.wallet.send_self_transfer( 290 from_node=self.nodes[0], 291 utxo_to_spend=utxo1, 292 sequence=0, 293 fee=Decimal("0.1"), 294 ) 295 tx1a_utxo = tx1a["new_utxo"] 296 297 # Direct spend an output of the transaction we're replacing. 298 tx2 = self.wallet.create_self_transfer_multi( 299 utxos_to_spend=[utxo1, utxo2, tx1a_utxo], 300 sequence=0, 301 amount_per_output=int(COIN * tx1a_utxo["value"]), 302 )["tx"] 303 tx2_hex = tx2.serialize().hex() 304 305 # This will raise an exception 306 reject_reason = "bad-txns-spends-conflicting-tx" 307 reject_details = f"{reject_reason}, {tx2.hash} spends conflicting transaction {tx1a['tx'].hash}" 308 res = self.nodes[0].testmempoolaccept(rawtxs=[tx2_hex])[0] 309 assert_equal(res["reject-reason"], reject_reason) 310 assert_equal(res["reject-details"], reject_details) 311 assert_raises_rpc_error(-26, f"{reject_details}", self.nodes[0].sendrawtransaction, tx2_hex, 0) 312 313 314 # Spend tx1a's output to test the indirect case. 315 tx1b_utxo = self.wallet.send_self_transfer( 316 from_node=self.nodes[0], 317 utxo_to_spend=tx1a_utxo, 318 sequence=0, 319 fee=Decimal("0.1"), 320 )["new_utxo"] 321 322 tx2_hex = self.wallet.create_self_transfer_multi( 323 utxos_to_spend=[utxo1, utxo2, tx1b_utxo], 324 sequence=0, 325 amount_per_output=int(COIN * tx1a_utxo["value"]), 326 )["hex"] 327 328 # This will raise an exception 329 assert_raises_rpc_error(-26, "bad-txns-spends-conflicting-tx", self.nodes[0].sendrawtransaction, tx2_hex, 0) 330 331 def test_new_unconfirmed_inputs(self): 332 """Replacements that add new unconfirmed inputs are rejected""" 333 confirmed_utxo = self.make_utxo(self.nodes[0], int(1.1 * COIN)) 334 unconfirmed_utxo = self.make_utxo(self.nodes[0], int(0.1 * COIN), confirmed=False) 335 336 self.wallet.send_self_transfer( 337 from_node=self.nodes[0], 338 utxo_to_spend=confirmed_utxo, 339 sequence=0, 340 fee=Decimal("0.1"), 341 ) 342 343 tx2 = self.wallet.create_self_transfer_multi( 344 utxos_to_spend=[confirmed_utxo, unconfirmed_utxo], 345 sequence=0, 346 amount_per_output=1 * COIN, 347 )["tx"] 348 tx2_hex = tx2.serialize().hex() 349 350 # This will raise an exception 351 reject_reason = "replacement-adds-unconfirmed" 352 reject_details = f"{reject_reason}, replacement {tx2.hash} adds unconfirmed input, idx 1" 353 res = self.nodes[0].testmempoolaccept(rawtxs=[tx2_hex])[0] 354 assert_equal(res["reject-reason"], reject_reason) 355 assert_equal(res["reject-details"], reject_details) 356 assert_raises_rpc_error(-26, f"{reject_details}", self.nodes[0].sendrawtransaction, tx2_hex, 0) 357 358 359 def test_too_many_replacements(self): 360 """Replacements that evict too many transactions are rejected""" 361 # Try directly replacing more than MAX_REPLACEMENT_LIMIT 362 # transactions 363 364 # Start by creating a single transaction with many outputs 365 initial_nValue = 10 * COIN 366 utxo = self.make_utxo(self.nodes[0], initial_nValue) 367 fee = int(0.0001 * COIN) 368 split_value = int((initial_nValue - fee) / (MAX_REPLACEMENT_LIMIT + 1)) 369 370 splitting_tx_utxos = self.wallet.send_self_transfer_multi( 371 from_node=self.nodes[0], 372 utxos_to_spend=[utxo], 373 sequence=0, 374 num_outputs=MAX_REPLACEMENT_LIMIT + 1, 375 amount_per_output=split_value, 376 )["new_utxos"] 377 378 # Now spend each of those outputs individually 379 for utxo in splitting_tx_utxos: 380 self.wallet.send_self_transfer( 381 from_node=self.nodes[0], 382 utxo_to_spend=utxo, 383 sequence=0, 384 fee=Decimal(fee) / COIN, 385 ) 386 387 # Now create doublespend of the whole lot; should fail. 388 # Need a big enough fee to cover all spending transactions and have 389 # a higher fee rate 390 double_spend_value = (split_value - 100 * fee) * (MAX_REPLACEMENT_LIMIT + 1) 391 double_tx = self.wallet.create_self_transfer_multi( 392 utxos_to_spend=splitting_tx_utxos, 393 sequence=0, 394 amount_per_output=double_spend_value, 395 )["tx"] 396 double_tx_hex = double_tx.serialize().hex() 397 398 # This will raise an exception 399 reject_reason = "too many potential replacements" 400 reject_details = f"{reject_reason}, rejecting replacement {double_tx.hash}; too many potential replacements ({MAX_REPLACEMENT_LIMIT + 1} > {MAX_REPLACEMENT_LIMIT})" 401 res = self.nodes[0].testmempoolaccept(rawtxs=[double_tx_hex])[0] 402 assert_equal(res["reject-reason"], reject_reason) 403 assert_equal(res["reject-details"], reject_details) 404 assert_raises_rpc_error(-26, f"{reject_details}", self.nodes[0].sendrawtransaction, double_tx_hex, 0) 405 406 407 # If we remove an input, it should pass 408 double_tx.vin.pop() 409 double_tx_hex = double_tx.serialize().hex() 410 self.nodes[0].sendrawtransaction(double_tx_hex, 0) 411 412 def test_too_many_replacements_with_default_mempool_params(self): 413 """ 414 Test rule 5 (do not allow replacements that cause more than 100 415 evictions) without having to rely on non-default mempool parameters. 416 417 In order to do this, create a number of "root" UTXOs, and then hang 418 enough transactions off of each root UTXO to exceed the MAX_REPLACEMENT_LIMIT. 419 Then create a conflicting RBF replacement transaction. 420 """ 421 # Clear mempools to avoid cross-node sync failure. 422 for node in self.nodes: 423 self.generate(node, 1) 424 normal_node = self.nodes[1] 425 wallet = MiniWallet(normal_node) 426 427 # This has to be chosen so that the total number of transactions can exceed 428 # MAX_REPLACEMENT_LIMIT without having any one tx graph run into the descendant 429 # limit; 10 works. 430 num_tx_graphs = 10 431 432 # (Number of transactions per graph, rule 5 failure expected) 433 cases = [ 434 # Test the base case of evicting fewer than MAX_REPLACEMENT_LIMIT 435 # transactions. 436 ((MAX_REPLACEMENT_LIMIT // num_tx_graphs) - 1, False), 437 438 # Test hitting the rule 5 eviction limit. 439 (MAX_REPLACEMENT_LIMIT // num_tx_graphs, True), 440 ] 441 442 for (txs_per_graph, failure_expected) in cases: 443 self.log.debug(f"txs_per_graph: {txs_per_graph}, failure: {failure_expected}") 444 # "Root" utxos of each txn graph that we will attempt to double-spend with 445 # an RBF replacement. 446 root_utxos = [] 447 448 # For each root UTXO, create a package that contains the spend of that 449 # UTXO and `txs_per_graph` children tx. 450 for graph_num in range(num_tx_graphs): 451 root_utxos.append(wallet.get_utxo()) 452 453 parent_tx = wallet.send_self_transfer_multi( 454 from_node=normal_node, 455 utxos_to_spend=[root_utxos[graph_num]], 456 num_outputs=txs_per_graph, 457 ) 458 new_utxos = parent_tx['new_utxos'] 459 460 for utxo in new_utxos: 461 # Create spends for each output from the "root" of this graph. 462 child_tx = wallet.send_self_transfer( 463 from_node=normal_node, 464 utxo_to_spend=utxo, 465 ) 466 467 assert normal_node.getmempoolentry(child_tx['txid']) 468 469 num_txs_invalidated = len(root_utxos) + (num_tx_graphs * txs_per_graph) 470 471 if failure_expected: 472 assert num_txs_invalidated > MAX_REPLACEMENT_LIMIT 473 else: 474 assert num_txs_invalidated <= MAX_REPLACEMENT_LIMIT 475 476 # Now attempt to submit a tx that double-spends all the root tx inputs, which 477 # would invalidate `num_txs_invalidated` transactions. 478 tx_hex = wallet.create_self_transfer_multi( 479 utxos_to_spend=root_utxos, 480 fee_per_output=10_000_000, # absurdly high feerate 481 )["hex"] 482 483 if failure_expected: 484 assert_raises_rpc_error( 485 -26, "too many potential replacements", normal_node.sendrawtransaction, tx_hex, 0) 486 else: 487 txid = normal_node.sendrawtransaction(tx_hex, 0) 488 assert normal_node.getmempoolentry(txid) 489 490 # Clear the mempool once finished, and rescan the other nodes' wallet 491 # to account for the spends we've made on `normal_node`. 492 self.generate(normal_node, 1) 493 self.wallet.rescan_utxos() 494 495 def test_prioritised_transactions(self): 496 # Ensure that fee deltas used via prioritisetransaction are 497 # correctly used by replacement logic 498 499 # 1. Check that feeperkb uses modified fees 500 tx0_outpoint = self.make_utxo(self.nodes[0], int(1.1 * COIN)) 501 502 tx1a_txid = self.wallet.send_self_transfer( 503 from_node=self.nodes[0], 504 utxo_to_spend=tx0_outpoint, 505 sequence=0, 506 fee=Decimal("0.1"), 507 )["txid"] 508 509 # Higher fee, but the actual fee per KB is much lower. 510 tx1b_hex = self.wallet.create_self_transfer_multi( 511 utxos_to_spend=[tx0_outpoint], 512 sequence=0, 513 num_outputs=100, 514 amount_per_output=int(0.00001 * COIN), 515 )["hex"] 516 517 # Verify tx1b cannot replace tx1a. 518 assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx1b_hex, 0) 519 520 # Use prioritisetransaction to set tx1a's fee to 0. 521 self.nodes[0].prioritisetransaction(txid=tx1a_txid, fee_delta=int(-0.1 * COIN)) 522 523 # Now tx1b should be able to replace tx1a 524 tx1b_txid = self.nodes[0].sendrawtransaction(tx1b_hex, 0) 525 526 assert tx1b_txid in self.nodes[0].getrawmempool() 527 528 # 2. Check that absolute fee checks use modified fee. 529 tx1_outpoint = self.make_utxo(self.nodes[0], int(1.1 * COIN)) 530 531 # tx2a 532 self.wallet.send_self_transfer( 533 from_node=self.nodes[0], 534 utxo_to_spend=tx1_outpoint, 535 sequence=0, 536 fee=Decimal("0.1"), 537 ) 538 539 # Lower fee, but we'll prioritise it 540 tx2b = self.wallet.create_self_transfer( 541 utxo_to_spend=tx1_outpoint, 542 sequence=0, 543 fee=Decimal("0.09"), 544 ) 545 546 # Verify tx2b cannot replace tx2a. 547 assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx2b["hex"], 0) 548 549 # Now prioritise tx2b to have a higher modified fee 550 self.nodes[0].prioritisetransaction(txid=tx2b["txid"], fee_delta=int(0.1 * COIN)) 551 552 # tx2b should now be accepted 553 tx2b_txid = self.nodes[0].sendrawtransaction(tx2b["hex"], 0) 554 555 assert tx2b_txid in self.nodes[0].getrawmempool() 556 557 def test_rpc(self): 558 us0 = self.wallet.get_utxo() 559 ins = [us0] 560 outs = {ADDRESS_BCRT1_UNSPENDABLE: Decimal(1.0000000)} 561 rawtx0 = self.nodes[0].createrawtransaction(ins, outs, 0, True) 562 rawtx1 = self.nodes[0].createrawtransaction(ins, outs, 0, False) 563 json0 = self.nodes[0].decoderawtransaction(rawtx0) 564 json1 = self.nodes[0].decoderawtransaction(rawtx1) 565 assert_equal(json0["vin"][0]["sequence"], 4294967293) 566 assert_equal(json1["vin"][0]["sequence"], 4294967295) 567 568 if self.is_wallet_compiled(): 569 self.init_wallet(node=0) 570 rawtx2 = self.nodes[0].createrawtransaction([], outs) 571 frawtx2a = self.nodes[0].fundrawtransaction(rawtx2, {"replaceable": True}) 572 frawtx2b = self.nodes[0].fundrawtransaction(rawtx2, {"replaceable": False}) 573 574 json0 = self.nodes[0].decoderawtransaction(frawtx2a['hex']) 575 json1 = self.nodes[0].decoderawtransaction(frawtx2b['hex']) 576 assert_equal(json0["vin"][0]["sequence"], 4294967293) 577 assert_equal(json1["vin"][0]["sequence"], 4294967294) 578 579 def test_replacement_relay_fee(self): 580 tx = self.wallet.send_self_transfer(from_node=self.nodes[0])['tx'] 581 582 # Higher fee, higher feerate, different txid, but the replacement does not provide a relay 583 # fee conforming to node's `incrementalrelayfee` policy of 1000 sat per KB. 584 assert_equal(self.nodes[0].getmempoolinfo()["incrementalrelayfee"], Decimal("0.00001")) 585 tx.vout[0].nValue -= 1 586 assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx.serialize().hex()) 587 588 def test_fullrbf(self): 589 # BIP125 signaling is not respected 590 591 confirmed_utxo = self.make_utxo(self.nodes[0], int(2 * COIN)) 592 assert self.nodes[0].getmempoolinfo()["fullrbf"] 593 594 # Create an explicitly opt-out BIP125 transaction, which will be ignored 595 optout_tx = self.wallet.send_self_transfer( 596 from_node=self.nodes[0], 597 utxo_to_spend=confirmed_utxo, 598 sequence=MAX_BIP125_RBF_SEQUENCE + 1, 599 fee_rate=Decimal('0.01'), 600 ) 601 assert_equal(False, self.nodes[0].getmempoolentry(optout_tx['txid'])['bip125-replaceable']) 602 603 conflicting_tx = self.wallet.create_self_transfer( 604 utxo_to_spend=confirmed_utxo, 605 fee_rate=Decimal('0.02'), 606 ) 607 608 # Send the replacement transaction, conflicting with the optout_tx. 609 self.nodes[0].sendrawtransaction(conflicting_tx['hex'], 0) 610 611 # Optout_tx is not anymore in the mempool. 612 assert optout_tx['txid'] not in self.nodes[0].getrawmempool() 613 assert conflicting_tx['txid'] in self.nodes[0].getrawmempool() 614 615 if __name__ == '__main__': 616 ReplaceByFeeTest(__file__).main()