/ test / functional / feature_rbf.py
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()