feature_rbf.py
1 #!/usr/bin/env python3 2 # Copyright (c) 2014-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 """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_greater_than, 17 assert_greater_than_or_equal, 18 assert_raises_rpc_error, 19 get_fee, 20 ) 21 from test_framework.wallet import MiniWallet 22 from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE 23 from test_framework.mempool_util import DEFAULT_CLUSTER_LIMIT 24 25 MAX_REPLACEMENT_LIMIT = 100 26 27 class ReplaceByFeeTest(BitcoinTestFramework): 28 def set_test_params(self): 29 self.num_nodes = 2 30 self.uses_wallet = None 31 self.extra_args = [["-deprecatedrpc=fullrbf", "-deprecatedrpc=bip125"], []] 32 33 def run_test(self): 34 self.wallet = MiniWallet(self.nodes[0]) 35 self.generate(self.nodes[0], 100) 36 37 self.log.info("Running test simple doublespend...") 38 self.test_simple_doublespend() 39 40 self.log.info("Running test doublespend chain...") 41 self.test_doublespend_chain() 42 43 self.log.info("Running test doublespend tree...") 44 self.test_doublespend_tree() 45 46 self.log.info("Running test replacement feeperkb...") 47 self.test_replacement_feeperkb() 48 49 self.log.info("Running test spends of conflicting outputs...") 50 self.test_spends_of_conflicting_outputs() 51 52 self.log.info("Running test new unconfirmed inputs...") 53 self.test_new_unconfirmed_inputs() 54 55 self.log.info("Running test new unconfirmed input from low feerate tx...") 56 self.test_new_unconfirmed_input_with_low_feerate() 57 58 self.log.info("Running test too many replacements...") 59 self.test_too_many_replacements() 60 61 self.log.info("Running test RPC...") 62 self.test_rpc() 63 64 self.log.info("Running test prioritised transactions...") 65 self.test_prioritised_transactions() 66 67 self.log.info("Running test replacement relay fee...") 68 self.test_replacement_relay_fee() 69 70 self.log.info("Running test full replace by fee...") 71 self.test_fullrbf() 72 73 self.log.info("Running test incremental relay feerates...") 74 self.test_incremental_relay_feerates() 75 76 self.log.info("Passed") 77 78 def make_utxo(self, node, amount, *, confirmed=True, scriptPubKey=None): 79 """Create a txout with a given amount and scriptPubKey 80 81 confirmed - txout created will be confirmed in the blockchain; 82 unconfirmed otherwise. 83 """ 84 tx = self.wallet.send_to(from_node=node, scriptPubKey=scriptPubKey or self.wallet.get_output_script(), amount=amount) 85 86 if confirmed: 87 mempool_size = len(node.getrawmempool()) 88 while mempool_size > 0: 89 self.generate(node, 1) 90 new_size = len(node.getrawmempool()) 91 # Error out if we have something stuck in the mempool, as this 92 # would likely be a bug. 93 assert new_size < mempool_size 94 mempool_size = new_size 95 96 return self.wallet.get_utxo(txid=tx["txid"], vout=tx["sent_vout"]) 97 98 def test_simple_doublespend(self): 99 """Simple doublespend""" 100 # we use MiniWallet to create a transaction template with inputs correctly set, 101 # and modify the output (amount, scriptPubKey) according to our needs 102 tx = self.wallet.create_self_transfer(fee_rate=Decimal("0.003"))["tx"] 103 tx1a_txid = self.nodes[0].sendrawtransaction(tx.serialize().hex()) 104 105 # Should fail because we haven't changed the fee 106 tx.vout[0].scriptPubKey[-1] ^= 1 107 tx_hex = tx.serialize().hex() 108 109 # This will raise an exception due to insufficient fee 110 reject_reason = "insufficient fee" 111 reject_details = f"{reject_reason}, rejecting replacement {tx.txid_hex}, not enough additional fees to relay; 0.00 < 0.00000011" 112 res = self.nodes[0].testmempoolaccept(rawtxs=[tx_hex])[0] 113 assert_equal(res["reject-reason"], reject_reason) 114 assert_equal(res["reject-details"], reject_details) 115 assert_raises_rpc_error(-26, f"{reject_details}", self.nodes[0].sendrawtransaction, tx_hex, 0) 116 117 118 # Extra 0.1 BTC fee 119 tx.vout[0].nValue -= int(0.1 * COIN) 120 tx1b_hex = tx.serialize().hex() 121 # Works when enabled 122 tx1b_txid = self.nodes[0].sendrawtransaction(tx1b_hex, 0) 123 124 mempool = self.nodes[0].getrawmempool() 125 126 assert tx1a_txid not in mempool 127 assert tx1b_txid in mempool 128 129 assert_equal(tx1b_hex, self.nodes[0].getrawtransaction(tx1b_txid)) 130 131 def test_doublespend_chain(self): 132 """Doublespend of a long chain""" 133 134 initial_nValue = 5 * COIN 135 tx0_outpoint = self.make_utxo(self.nodes[0], initial_nValue) 136 137 prevout = tx0_outpoint 138 remaining_value = initial_nValue 139 chain_txids = [] 140 for _ in range(DEFAULT_CLUSTER_LIMIT): 141 if remaining_value <= 1 * COIN: 142 break 143 remaining_value -= int(0.1 * COIN) 144 prevout = self.wallet.send_self_transfer( 145 from_node=self.nodes[0], 146 utxo_to_spend=prevout, 147 sequence=0, 148 fee=Decimal("0.1"), 149 )["new_utxo"] 150 chain_txids.append(prevout["txid"]) 151 152 # Whether the double-spend is allowed is evaluated by including all 153 # child fees - 4 BTC - so this attempt is rejected. 154 dbl_tx = self.wallet.create_self_transfer( 155 utxo_to_spend=tx0_outpoint, 156 sequence=0, 157 fee=Decimal("3"), 158 )["tx"] 159 dbl_tx_hex = dbl_tx.serialize().hex() 160 161 # This will raise an exception due to insufficient fee 162 reject_reason = "insufficient fee" 163 reject_details = f"{reject_reason}, rejecting replacement {dbl_tx.txid_hex}, less fees than conflicting txs; 3.00 < 4.00" 164 res = self.nodes[0].testmempoolaccept(rawtxs=[dbl_tx_hex])[0] 165 assert_equal(res["reject-reason"], reject_reason) 166 assert_equal(res["reject-details"], reject_details) 167 assert_raises_rpc_error(-26, f"{reject_details}", self.nodes[0].sendrawtransaction, dbl_tx_hex, 0) 168 169 170 171 # Accepted with sufficient fee 172 dbl_tx.vout[0].nValue = int(0.1 * COIN) 173 dbl_tx_hex = dbl_tx.serialize().hex() 174 self.nodes[0].sendrawtransaction(dbl_tx_hex, 0) 175 176 mempool = self.nodes[0].getrawmempool() 177 for doublespent_txid in chain_txids: 178 assert doublespent_txid not in mempool 179 180 def test_doublespend_tree(self): 181 """Doublespend of a big tree of transactions""" 182 183 initial_nValue = 5 * COIN 184 tx0_outpoint = self.make_utxo(self.nodes[0], initial_nValue) 185 186 def branch(prevout, initial_value, max_txs, tree_width=5, fee=0.00001 * COIN, _total_txs=None): 187 if _total_txs is None: 188 _total_txs = [0] 189 if _total_txs[0] >= max_txs: 190 return 191 192 txout_value = (initial_value - fee) // tree_width 193 if txout_value < fee: 194 return 195 196 tx = self.wallet.send_self_transfer_multi( 197 utxos_to_spend=[prevout], 198 from_node=self.nodes[0], 199 sequence=0, 200 num_outputs=tree_width, 201 amount_per_output=txout_value, 202 ) 203 204 yield tx["txid"] 205 _total_txs[0] += 1 206 207 for utxo in tx["new_utxos"]: 208 for x in branch(utxo, txout_value, 209 max_txs, 210 tree_width=tree_width, fee=fee, 211 _total_txs=_total_txs): 212 yield x 213 214 fee = int(0.00001 * COIN) 215 n = DEFAULT_CLUSTER_LIMIT 216 tree_txs = list(branch(tx0_outpoint, initial_nValue, n, fee=fee)) 217 assert_equal(len(tree_txs), n) 218 219 # Attempt double-spend, will fail because too little fee paid 220 dbl_tx_hex = self.wallet.create_self_transfer( 221 utxo_to_spend=tx0_outpoint, 222 sequence=0, 223 fee=(Decimal(fee) / COIN) * n, 224 )["hex"] 225 # This will raise an exception due to insufficient fee 226 assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, dbl_tx_hex, 0) 227 228 # 0.1 BTC fee is enough 229 dbl_tx_hex = self.wallet.create_self_transfer( 230 utxo_to_spend=tx0_outpoint, 231 sequence=0, 232 fee=(Decimal(fee) / COIN) * n + Decimal("0.1"), 233 )["hex"] 234 self.nodes[0].sendrawtransaction(dbl_tx_hex, 0) 235 236 mempool = self.nodes[0].getrawmempool() 237 238 for txid in tree_txs: 239 assert txid not in mempool 240 241 def test_replacement_feeperkb(self): 242 """Replacement requires fee-per-KB to be higher""" 243 tx0_outpoint = self.make_utxo(self.nodes[0], int(1.1 * COIN)) 244 245 self.wallet.send_self_transfer( 246 from_node=self.nodes[0], 247 utxo_to_spend=tx0_outpoint, 248 sequence=0, 249 fee=Decimal("0.1"), 250 ) 251 252 # Higher fee, but the fee per KB is much lower, so the replacement is 253 # rejected. 254 tx1b_hex = self.wallet.create_self_transfer_multi( 255 utxos_to_spend=[tx0_outpoint], 256 sequence=0, 257 num_outputs=100, 258 amount_per_output=1000, 259 )["hex"] 260 261 # This will raise an exception due to insufficient fee 262 assert_raises_rpc_error(-26, "does not improve feerate diagram", self.nodes[0].sendrawtransaction, tx1b_hex, 0) 263 264 def test_spends_of_conflicting_outputs(self): 265 """Replacements that spend conflicting tx outputs are rejected""" 266 utxo1 = self.make_utxo(self.nodes[0], int(1.2 * COIN)) 267 utxo2 = self.make_utxo(self.nodes[0], 3 * COIN) 268 269 tx1a = self.wallet.send_self_transfer( 270 from_node=self.nodes[0], 271 utxo_to_spend=utxo1, 272 sequence=0, 273 fee=Decimal("0.1"), 274 ) 275 tx1a_utxo = tx1a["new_utxo"] 276 277 # Direct spend an output of the transaction we're replacing. 278 tx2 = self.wallet.create_self_transfer_multi( 279 utxos_to_spend=[utxo1, utxo2, tx1a_utxo], 280 sequence=0, 281 amount_per_output=int(COIN * tx1a_utxo["value"]), 282 )["tx"] 283 tx2_hex = tx2.serialize().hex() 284 285 # This will raise an exception 286 reject_reason = "bad-txns-spends-conflicting-tx" 287 reject_details = f"{reject_reason}, {tx2.txid_hex} spends conflicting transaction {tx1a['tx'].txid_hex}" 288 res = self.nodes[0].testmempoolaccept(rawtxs=[tx2_hex])[0] 289 assert_equal(res["reject-reason"], reject_reason) 290 assert_equal(res["reject-details"], reject_details) 291 assert_raises_rpc_error(-26, f"{reject_details}", self.nodes[0].sendrawtransaction, tx2_hex, 0) 292 293 294 # Spend tx1a's output to test the indirect case. 295 tx1b_utxo = self.wallet.send_self_transfer( 296 from_node=self.nodes[0], 297 utxo_to_spend=tx1a_utxo, 298 sequence=0, 299 fee=Decimal("0.1"), 300 )["new_utxo"] 301 302 tx2_hex = self.wallet.create_self_transfer_multi( 303 utxos_to_spend=[utxo1, utxo2, tx1b_utxo], 304 sequence=0, 305 amount_per_output=int(COIN * tx1a_utxo["value"]), 306 )["hex"] 307 308 # This will raise an exception 309 assert_raises_rpc_error(-26, "bad-txns-spends-conflicting-tx", self.nodes[0].sendrawtransaction, tx2_hex, 0) 310 311 def test_new_unconfirmed_inputs(self): 312 """Replacements that add new unconfirmed inputs may be accepted""" 313 confirmed_utxo = self.make_utxo(self.nodes[0], int(1.1 * COIN)) 314 unconfirmed_utxo = self.make_utxo(self.nodes[0], int(0.2 * COIN), confirmed=False) 315 316 self.wallet.send_self_transfer( 317 from_node=self.nodes[0], 318 utxo_to_spend=confirmed_utxo, 319 sequence=0, 320 fee=Decimal("0.1"), 321 ) 322 323 tx2 = self.wallet.create_self_transfer_multi( 324 utxos_to_spend=[confirmed_utxo, unconfirmed_utxo], 325 sequence=0, 326 amount_per_output=1 * COIN, 327 )["tx"] 328 tx2_hex = tx2.serialize().hex() 329 330 # This will not raise an exception 331 tx2_id = self.nodes[0].sendrawtransaction(tx2_hex, 0) 332 assert tx2_id in self.nodes[0].getrawmempool() 333 334 def test_new_unconfirmed_input_with_low_feerate(self): 335 """Replacements that add new unconfirmed inputs are allowed, but must pass the feerate diagram check""" 336 confirmed_utxos = [self.make_utxo(self.nodes[0], int(1.1 * COIN)) for _ in range(3)] 337 large_low_feerate = self.wallet.create_self_transfer(utxo_to_spend=confirmed_utxos[0], target_vsize=10000, fee=Decimal("0.00001000")) 338 self.nodes[0].sendrawtransaction(large_low_feerate['hex']) 339 unconfirmed_utxo = large_low_feerate['new_utxo'] 340 341 # These two transactions are approximately the same size. The replacement tx pays twice the fee. 342 tx_to_replace = self.wallet.create_self_transfer_multi(utxos_to_spend=[confirmed_utxos[1], confirmed_utxos[2]], fee_per_output=2000) 343 tx_replacement = self.wallet.create_self_transfer_multi(utxos_to_spend=[confirmed_utxos[1], unconfirmed_utxo], fee_per_output=4000) 344 assert_greater_than(tx_replacement['fee']*tx_to_replace['tx'].get_vsize(), tx_to_replace['fee']*tx_replacement['tx'].get_vsize()) 345 346 self.nodes[0].sendrawtransaction(tx_to_replace['hex']) 347 assert_raises_rpc_error(-26, "insufficient feerate: does not improve feerate diagram", self.nodes[0].sendrawtransaction, tx_replacement['hex']) 348 349 350 def test_too_many_replacements(self): 351 """Replacements that conflict with too many clusters are rejected""" 352 # Try directly replacing transactions in more than MAX_REPLACEMENT_LIMIT 353 # distinct clusters 354 355 # Start by creating a single transaction with many outputs 356 initial_nValue = 10 * COIN 357 utxo = self.make_utxo(self.nodes[0], initial_nValue) 358 fee = int(0.0001 * COIN) 359 split_value = int((initial_nValue - fee) / (MAX_REPLACEMENT_LIMIT + 1)) 360 361 splitting_tx_utxos = self.wallet.send_self_transfer_multi( 362 from_node=self.nodes[0], 363 utxos_to_spend=[utxo], 364 sequence=0, 365 num_outputs=MAX_REPLACEMENT_LIMIT + 1, 366 amount_per_output=split_value, 367 )["new_utxos"] 368 369 self.generate(self.nodes[0], 1) 370 371 # Now spend each of those outputs individually 372 for utxo in splitting_tx_utxos: 373 self.wallet.send_self_transfer( 374 from_node=self.nodes[0], 375 utxo_to_spend=utxo, 376 sequence=0, 377 fee=Decimal(fee) / COIN, 378 ) 379 380 # Now create doublespend of the whole lot; should fail. 381 # Need a big enough fee to cover all spending transactions and have 382 # a higher fee rate 383 double_spend_value = (split_value - 100 * fee) * (MAX_REPLACEMENT_LIMIT + 1) 384 double_tx = self.wallet.create_self_transfer_multi( 385 utxos_to_spend=splitting_tx_utxos, 386 sequence=0, 387 amount_per_output=double_spend_value, 388 )["tx"] 389 double_tx_hex = double_tx.serialize().hex() 390 391 # This will raise an exception 392 reject_reason = "too many potential replacements" 393 reject_details = f"{reject_reason}, rejecting replacement {double_tx.txid_hex}; too many conflicting clusters ({MAX_REPLACEMENT_LIMIT + 1} > {MAX_REPLACEMENT_LIMIT})" 394 res = self.nodes[0].testmempoolaccept(rawtxs=[double_tx_hex])[0] 395 assert_equal(res["reject-reason"], reject_reason) 396 assert_equal(res["reject-details"], reject_details) 397 assert_raises_rpc_error(-26, f"{reject_details}", self.nodes[0].sendrawtransaction, double_tx_hex, 0) 398 399 400 # If we remove an input, it should pass 401 double_tx.vin.pop() 402 double_tx_hex = double_tx.serialize().hex() 403 self.nodes[0].sendrawtransaction(double_tx_hex, 0) 404 405 def test_prioritised_transactions(self): 406 # Ensure that fee deltas used via prioritisetransaction are 407 # correctly used by replacement logic 408 409 # 1. Check that feeperkb uses modified fees 410 tx0_outpoint = self.make_utxo(self.nodes[0], int(1.1 * COIN)) 411 412 tx1a_txid = self.wallet.send_self_transfer( 413 from_node=self.nodes[0], 414 utxo_to_spend=tx0_outpoint, 415 sequence=0, 416 fee=Decimal("0.1"), 417 )["txid"] 418 419 # Higher fee, but the actual fee per KB is much lower. 420 tx1b_hex = self.wallet.create_self_transfer_multi( 421 utxos_to_spend=[tx0_outpoint], 422 sequence=0, 423 num_outputs=100, 424 amount_per_output=int(0.00001 * COIN), 425 )["hex"] 426 427 # Verify tx1b cannot replace tx1a. 428 assert_raises_rpc_error(-26, "does not improve feerate diagram", self.nodes[0].sendrawtransaction, tx1b_hex, 0) 429 430 # Use prioritisetransaction to set tx1a's fee to 0. 431 self.nodes[0].prioritisetransaction(txid=tx1a_txid, fee_delta=int(-0.1 * COIN)) 432 433 # Now tx1b should be able to replace tx1a 434 tx1b_txid = self.nodes[0].sendrawtransaction(tx1b_hex, 0) 435 436 assert tx1b_txid in self.nodes[0].getrawmempool() 437 438 # 2. Check that absolute fee checks use modified fee. 439 tx1_outpoint = self.make_utxo(self.nodes[0], int(1.1 * COIN)) 440 441 # tx2a 442 self.wallet.send_self_transfer( 443 from_node=self.nodes[0], 444 utxo_to_spend=tx1_outpoint, 445 sequence=0, 446 fee=Decimal("0.1"), 447 ) 448 449 # Lower fee, but we'll prioritise it 450 tx2b = self.wallet.create_self_transfer( 451 utxo_to_spend=tx1_outpoint, 452 sequence=0, 453 fee=Decimal("0.09"), 454 ) 455 456 # Verify tx2b cannot replace tx2a. 457 assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx2b["hex"], 0) 458 459 # Now prioritise tx2b to have a higher modified fee 460 self.nodes[0].prioritisetransaction(txid=tx2b["txid"], fee_delta=int(0.1 * COIN)) 461 462 # tx2b should now be accepted 463 tx2b_txid = self.nodes[0].sendrawtransaction(tx2b["hex"], 0) 464 465 assert tx2b_txid in self.nodes[0].getrawmempool() 466 467 def test_rpc(self): 468 us0 = self.wallet.get_utxo() 469 ins = [us0] 470 outs = {ADDRESS_BCRT1_UNSPENDABLE: Decimal(1.0000000)} 471 rawtx0 = self.nodes[0].createrawtransaction(ins, outs, 0, True) 472 rawtx1 = self.nodes[0].createrawtransaction(ins, outs, 0, False) 473 json0 = self.nodes[0].decoderawtransaction(rawtx0) 474 json1 = self.nodes[0].decoderawtransaction(rawtx1) 475 assert_equal(json0["vin"][0]["sequence"], 4294967293) 476 assert_equal(json1["vin"][0]["sequence"], 4294967295) 477 478 if self.is_wallet_compiled(): 479 self.init_wallet(node=0) 480 rawtx2 = self.nodes[0].createrawtransaction([], outs) 481 frawtx2a = self.nodes[0].fundrawtransaction(rawtx2, {"replaceable": True}) 482 frawtx2b = self.nodes[0].fundrawtransaction(rawtx2, {"replaceable": False}) 483 484 json0 = self.nodes[0].decoderawtransaction(frawtx2a['hex']) 485 json1 = self.nodes[0].decoderawtransaction(frawtx2b['hex']) 486 assert_equal(json0["vin"][0]["sequence"], 4294967293) 487 assert_equal(json1["vin"][0]["sequence"], 4294967294) 488 489 def test_replacement_relay_fee(self): 490 tx = self.wallet.send_self_transfer(from_node=self.nodes[0])['tx'] 491 492 # Higher fee, higher feerate, different txid, but the replacement does not provide a relay 493 # fee conforming to node's `incrementalrelayfee` policy of 100 sat per KB. 494 assert_equal(self.nodes[0].getmempoolinfo()["incrementalrelayfee"], Decimal("0.000001")) 495 tx.vout[0].nValue -= 1 496 assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx.serialize().hex()) 497 498 def test_incremental_relay_feerates(self): 499 self.log.info("Test that incremental relay fee is applied correctly in RBF for various settings...") 500 node = self.nodes[0] 501 for incremental_setting in (0, 5, 10, 50, 100, 234, 1000, 5000, 21000): 502 incremental_setting_decimal = incremental_setting / Decimal(COIN) 503 self.log.info(f"-> Test -incrementalrelayfee={incremental_setting:.8f}sat/kvB...") 504 self.restart_node(0, extra_args=[f"-incrementalrelayfee={incremental_setting_decimal:.8f}", "-persistmempool=0"]) 505 506 # When incremental relay feerate is higher than min relay feerate, min relay feerate is automatically increased. 507 min_relay_feerate = node.getmempoolinfo()["minrelaytxfee"] 508 assert_greater_than_or_equal(min_relay_feerate, incremental_setting_decimal) 509 510 low_feerate = min_relay_feerate * 2 511 confirmed_utxo = self.wallet.get_utxo(confirmed_only=True) 512 # Use different versions to avoid creating an identical transaction when failed_replacement_tx is created. 513 # Use a target vsize that is small, but something larger than the minimum so that we can create a transaction that is 1vB smaller later. 514 replacee_tx = self.wallet.create_self_transfer(utxo_to_spend=confirmed_utxo, fee_rate=low_feerate, version=3, target_vsize=200) 515 node.sendrawtransaction(replacee_tx['hex']) 516 517 replacement_placeholder_tx = self.wallet.create_self_transfer(utxo_to_spend=confirmed_utxo, target_vsize=200) 518 replacement_expected_size = replacement_placeholder_tx['tx'].get_vsize() 519 replacement_required_fee = get_fee(replacement_expected_size, incremental_setting_decimal) + replacee_tx['fee'] 520 521 # Show that replacement fails when paying 1 satoshi shy of the required fee 522 failed_replacement_tx = self.wallet.create_self_transfer(utxo_to_spend=confirmed_utxo, fee=replacement_required_fee - Decimal("0.00000001"), version=2, target_vsize=200) 523 assert_raises_rpc_error(-26, "insufficient fee", node.sendrawtransaction, failed_replacement_tx['hex']) 524 replacement_tx = self.wallet.create_self_transfer(utxo_to_spend=confirmed_utxo, fee=replacement_required_fee, version=2, target_vsize=200) 525 526 if incremental_setting == 0: 527 # When incremental relay feerate is 0, additional fees are not required, but higher feerate is still required. 528 assert_raises_rpc_error(-26, "insufficient fee", node.sendrawtransaction, replacement_tx['hex']) 529 replacement_tx_smaller = self.wallet.create_self_transfer(utxo_to_spend=confirmed_utxo, fee=replacement_required_fee, version=2, target_vsize=199) 530 node.sendrawtransaction(replacement_tx_smaller['hex']) 531 else: 532 node.sendrawtransaction(replacement_tx['hex']) 533 534 def test_fullrbf(self): 535 # BIP125 signaling is not respected 536 537 confirmed_utxo = self.make_utxo(self.nodes[0], int(2 * COIN)) 538 assert self.nodes[0].getmempoolinfo()["fullrbf"] 539 540 # Create an explicitly opt-out BIP125 transaction, which will be ignored 541 optout_tx = self.wallet.send_self_transfer( 542 from_node=self.nodes[0], 543 utxo_to_spend=confirmed_utxo, 544 sequence=MAX_BIP125_RBF_SEQUENCE + 1, 545 fee_rate=Decimal('0.01'), 546 ) 547 assert_equal(False, self.nodes[0].getmempoolentry(optout_tx['txid'])['bip125-replaceable']) 548 549 conflicting_tx = self.wallet.create_self_transfer( 550 utxo_to_spend=confirmed_utxo, 551 fee_rate=Decimal('0.02'), 552 ) 553 554 # Send the replacement transaction, conflicting with the optout_tx. 555 self.nodes[0].sendrawtransaction(conflicting_tx['hex'], 0) 556 557 # Optout_tx is not anymore in the mempool. 558 assert optout_tx['txid'] not in self.nodes[0].getrawmempool() 559 assert conflicting_tx['txid'] in self.nodes[0].getrawmempool() 560 561 if __name__ == '__main__': 562 ReplaceByFeeTest(__file__).main()