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