mempool_package_rbf.py raw

   1  #!/usr/bin/env python3
   2  # Copyright (c) 2021 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  
   6  from decimal import Decimal
   7  
   8  from test_framework.messages import (
   9      COIN,
  10      MAX_BIP125_RBF_SEQUENCE,
  11  )
  12  from test_framework.test_framework import LimenkaTestFramework
  13  from test_framework.mempool_util import fill_mempool
  14  from test_framework.util import (
  15      assert_greater_than_or_equal,
  16      assert_equal,
  17  )
  18  from test_framework.wallet import (
  19      DEFAULT_FEE,
  20      MiniWallet,
  21  )
  22  from test_framework import mempool_util
  23  
  24  MAX_REPLACEMENT_CANDIDATES = 100
  25  
  26  # Value high enough to cause evictions in each subtest
  27  # for typical cases
  28  DEFAULT_CHILD_FEE = DEFAULT_FEE * 4
  29  
  30  class PackageRBFTest(LimenkaTestFramework):
  31      def set_test_params(self):
  32          self.num_nodes = 2
  33          self.setup_clean_chain = True
  34          # Required for fill_mempool()
  35          self.extra_args = [[
  36              "-datacarriersize=100000",
  37              "-maxmempool=5",
  38          ]] * self.num_nodes
  39  
  40      def assert_mempool_contents(self, expected=None):
  41          mempool_util.assert_mempool_contents(self, self.nodes[0], expected, sync=False)
  42  
  43      def create_simple_package(self, parent_coin, parent_fee=DEFAULT_FEE, child_fee=DEFAULT_CHILD_FEE, heavy_child=False):
  44          """Create a 1 parent 1 child package using the coin passed in as the parent's input. The
  45          parent has 1 output, used to fund 1 child transaction.
  46          All transactions signal BIP125 replaceability, but nSequence changes based on self.ctr. This
  47          prevents identical txids between packages when the parents spend the same coin and have the
  48          same fee (i.e. 0sat).
  49  
  50          returns tuple (hex serialized txns, CTransaction objects)
  51          """
  52          self.ctr += 1
  53          # Use fee_rate=0 because create_self_transfer will use the default fee_rate value otherwise.
  54          # Passing in fee>0 overrides fee_rate, so this still works for non-zero parent_fee.
  55          parent_result = self.wallet.create_self_transfer(
  56              fee=parent_fee,
  57              utxo_to_spend=parent_coin,
  58              sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
  59          )
  60  
  61          num_child_outputs = 10 if heavy_child else 1
  62          child_result = self.wallet.create_self_transfer_multi(
  63              utxos_to_spend=[parent_result["new_utxo"]],
  64              num_outputs=num_child_outputs,
  65              fee_per_output=int(child_fee * COIN // num_child_outputs),
  66              sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
  67          )
  68          package_hex = [parent_result["hex"], child_result["hex"]]
  69          package_txns = [parent_result["tx"], child_result["tx"]]
  70          return package_hex, package_txns
  71  
  72      def run_test(self):
  73          # Counter used to count the number of times we constructed packages. Since we're constructing parent transactions with the same
  74          # coins (to create conflicts), and perhaps giving them the same fee, we might accidentally just create the same transaction again.
  75          # To prevent this, set nSequences to MAX_BIP125_RBF_SEQUENCE - self.ctr.
  76          self.ctr = 0
  77  
  78          self.log.info("Generate blocks to create UTXOs")
  79          self.wallet = MiniWallet(self.nodes[0])
  80  
  81          # Make more than enough coins for the sum of all tests,
  82          # otherwise a wallet rescan is needed later
  83          self.generate(self.wallet, 300)
  84          self.coins = self.wallet.get_utxos(mark_as_spent=False)
  85  
  86          self.test_package_rbf_basic()
  87          self.test_package_rbf_singleton()
  88          self.test_package_rbf_additional_fees()
  89          self.test_package_rbf_max_conflicts()
  90          self.test_too_numerous_ancestors()
  91          self.test_package_rbf_with_wrong_pkg_size()
  92          self.test_insufficient_feerate()
  93          self.test_wrong_conflict_cluster_size_linear()
  94          self.test_wrong_conflict_cluster_size_parents_child()
  95          self.test_wrong_conflict_cluster_size_parent_children()
  96          self.test_0fee_package_rbf()
  97          self.test_child_conflicts_parent_mempool_ancestor()
  98  
  99      def test_package_rbf_basic(self):
 100          self.log.info("Test that a child can pay to replace its parents' conflicts of cluster size 2")
 101          node = self.nodes[0]
 102          # Reuse the same coins so that the transactions conflict with one another.
 103          parent_coin = self.coins.pop()
 104          package_hex1, package_txns1 = self.create_simple_package(parent_coin, DEFAULT_FEE, DEFAULT_FEE)
 105          package_hex2, package_txns2 = self.create_simple_package(parent_coin, DEFAULT_FEE, DEFAULT_CHILD_FEE)
 106          node.submitpackage(package_hex1)
 107          self.assert_mempool_contents(expected=package_txns1)
 108  
 109          # Make sure 2nd node gets set up for basic package RBF
 110          self.sync_all()
 111  
 112          # Test run rejected because conflicts are not allowed in subpackage evaluation
 113          testres = node.testmempoolaccept(package_hex2)
 114          assert_equal(testres[0]["reject-reason"], "bip125-replacement-disallowed")
 115  
 116          # But accepted during normal submission
 117          submitres = node.submitpackage(package_hex2)
 118          assert_equal(set(submitres["replaced-transactions"]), set([tx.rehash() for tx in package_txns1]))
 119          self.assert_mempool_contents(expected=package_txns2)
 120  
 121          # Make sure 2nd node gets a basic package RBF over p2p
 122          self.sync_all()
 123  
 124          self.generate(node, 1)
 125  
 126      def test_package_rbf_singleton(self):
 127          self.log.info("Test child can pay to replace a parent's single conflicted tx")
 128          node = self.nodes[0]
 129  
 130          # Make singleton tx to conflict with in next batch
 131          singleton_coin = self.coins.pop()
 132          singleton_tx = self.wallet.create_self_transfer(utxo_to_spend=singleton_coin)
 133          node.sendrawtransaction(singleton_tx["hex"])
 134          self.assert_mempool_contents(expected=[singleton_tx["tx"]])
 135  
 136          package_hex, package_txns = self.create_simple_package(singleton_coin, DEFAULT_FEE, singleton_tx["fee"] * 2)
 137  
 138          submitres = node.submitpackage(package_hex)
 139          assert_equal(submitres["replaced-transactions"], [singleton_tx["tx"].rehash()])
 140          self.assert_mempool_contents(expected=package_txns)
 141  
 142          self.generate(node, 1)
 143  
 144      def test_package_rbf_additional_fees(self):
 145          self.log.info("Check Package RBF must increase the absolute fee")
 146          node = self.nodes[0]
 147          coin = self.coins.pop()
 148  
 149          package_hex1, package_txns1 = self.create_simple_package(coin, parent_fee=DEFAULT_FEE, child_fee=DEFAULT_CHILD_FEE, heavy_child=True)
 150          assert_greater_than_or_equal(1000, package_txns1[-1].get_vsize())
 151          node.submitpackage(package_hex1)
 152          self.assert_mempool_contents(expected=package_txns1)
 153  
 154          PACKAGE_FEE = DEFAULT_FEE + DEFAULT_CHILD_FEE
 155          PACKAGE_FEE_MINUS_ONE = PACKAGE_FEE - Decimal("0.00000001")
 156  
 157          # Package 2 has a higher feerate but lower absolute fee
 158          package_hex2, package_txns2 = self.create_simple_package(coin, parent_fee=DEFAULT_FEE, child_fee=DEFAULT_CHILD_FEE - Decimal("0.00000001"))
 159          pkg_results2 = node.submitpackage(package_hex2)
 160          assert_equal(f"package RBF failed: insufficient anti-DoS fees, rejecting replacement {package_txns2[1].rehash()}, less fees than conflicting txs; {PACKAGE_FEE_MINUS_ONE} < {PACKAGE_FEE}", pkg_results2["package_msg"])
 161          self.assert_mempool_contents(expected=package_txns1)
 162  
 163          self.log.info("Check replacement pays for incremental bandwidth")
 164          _, placeholder_txns3 = self.create_simple_package(coin)
 165          package_3_size = sum([tx.get_vsize() for tx in placeholder_txns3])
 166          incremental_sats_required = (Decimal(package_3_size * 0.1) / COIN).quantize(Decimal("0.00000001"))
 167          incremental_sats_short = incremental_sats_required - Decimal("0.00000005")
 168          # Recreate the package with slightly higher fee once we know the size of the new package, but still short of required fee
 169          failure_package_hex3, failure_package_txns3 = self.create_simple_package(coin, parent_fee=DEFAULT_FEE, child_fee=DEFAULT_CHILD_FEE + incremental_sats_short)
 170          assert_equal(package_3_size, sum([tx.get_vsize() for tx in failure_package_txns3]))
 171          pkg_results3 = node.submitpackage(failure_package_hex3)
 172          assert_equal(f"package RBF failed: insufficient anti-DoS fees, rejecting replacement {failure_package_txns3[1].rehash()}, not enough additional fees to relay; {incremental_sats_short:.8f} < {incremental_sats_required:.8f}", pkg_results3["package_msg"])
 173          self.assert_mempool_contents(expected=package_txns1)
 174  
 175          success_package_hex3, success_package_txns3 = self.create_simple_package(coin, parent_fee=DEFAULT_FEE, child_fee=DEFAULT_CHILD_FEE + incremental_sats_required)
 176          node.submitpackage(success_package_hex3)
 177          self.assert_mempool_contents(expected=success_package_txns3)
 178          self.generate(node, 1)
 179  
 180          self.log.info("Check Package RBF must have strict cpfp structure")
 181          coin = self.coins.pop()
 182          package_hex4, package_txns4 = self.create_simple_package(coin, parent_fee=DEFAULT_FEE, child_fee=DEFAULT_CHILD_FEE)
 183          node.submitpackage(package_hex4)
 184          self.assert_mempool_contents(expected=package_txns4)
 185          package_hex5, _package_txns5 = self.create_simple_package(coin, parent_fee=DEFAULT_CHILD_FEE, child_fee=DEFAULT_CHILD_FEE)
 186          pkg_results5 = node.submitpackage(package_hex5)
 187          assert 'package RBF failed: package feerate is less than or equal to parent feerate' in pkg_results5["package_msg"]
 188          self.assert_mempool_contents(expected=package_txns4)
 189  
 190          package_hex5_1, package_txns5_1 = self.create_simple_package(coin, parent_fee=DEFAULT_CHILD_FEE, child_fee=DEFAULT_CHILD_FEE + Decimal("0.00000001"))
 191          node.submitpackage(package_hex5_1)
 192          self.assert_mempool_contents(expected=package_txns5_1)
 193          self.generate(node, 1)
 194  
 195      def test_package_rbf_max_conflicts(self):
 196          node = self.nodes[0]
 197          self.log.info("Check Package RBF cannot replace more than MAX_REPLACEMENT_CANDIDATES transactions")
 198          num_coins = 51
 199          parent_coins = self.coins[:num_coins]
 200          del self.coins[:num_coins]
 201  
 202          # Original transactions: 51 transactions with 1 descendants each -> 102 total transactions
 203          size_two_clusters = []
 204          for coin in parent_coins:
 205              size_two_clusters.append(self.wallet.send_self_transfer_chain(from_node=node, chain_length=2, utxo_to_spend=coin))
 206          expected_txns = [txn["tx"] for parent_child_txns in size_two_clusters for txn in parent_child_txns]
 207          assert_equal(len(expected_txns), num_coins * 2)
 208          self.assert_mempool_contents(expected=expected_txns)
 209  
 210          # parent feeerate needs to be high enough for minrelay
 211          # child feerate needs to be large enough to trigger package rbf with a very large parent and
 212          # pay for all evicted fees. maxfeerate turned off for all submissions since child feerate
 213          # is extremely high
 214          parent_fee_per_conflict = 10000
 215          child_feerate = 10000 * DEFAULT_FEE
 216  
 217          # Conflict against all transactions by double-spending each parent, causing 102 evictions
 218          package_parent = self.wallet.create_self_transfer_multi(utxos_to_spend=parent_coins, fee_per_output=parent_fee_per_conflict)
 219          package_child = self.wallet.create_self_transfer(fee_rate=child_feerate, utxo_to_spend=package_parent["new_utxos"][0])
 220  
 221          pkg_results = node.submitpackage([package_parent["hex"], package_child["hex"]], maxfeerate=0)
 222          assert_equal(f"package RBF failed: too many potential replacements, rejecting replacement {package_child['tx'].rehash()}; too many potential replacements (102 > 100)", pkg_results["package_msg"])
 223          self.assert_mempool_contents(expected=expected_txns)
 224  
 225          # Make singleton tx to conflict with in next batch
 226          singleton_coin = self.coins.pop()
 227          singleton_tx = self.wallet.create_self_transfer(utxo_to_spend=singleton_coin)
 228          node.sendrawtransaction(singleton_tx["hex"])
 229          expected_txns.append(singleton_tx["tx"])
 230  
 231          # Double-spend same set minus last, and double-spend singleton. This hits 101 evictions; should still fail.
 232          # N.B. we can't RBF just a child tx in the clusters, as that would make resulting cluster of size 3.
 233          double_spending_coins = parent_coins[:-1] + [singleton_coin]
 234          package_parent = self.wallet.create_self_transfer_multi(utxos_to_spend=double_spending_coins, fee_per_output=parent_fee_per_conflict)
 235          package_child = self.wallet.create_self_transfer(fee_rate=child_feerate, utxo_to_spend=package_parent["new_utxos"][0])
 236          pkg_results = node.submitpackage([package_parent["hex"], package_child["hex"]], maxfeerate=0)
 237          assert_equal(f"package RBF failed: too many potential replacements, rejecting replacement {package_child['tx'].rehash()}; too many potential replacements (101 > 100)", pkg_results["package_msg"])
 238          self.assert_mempool_contents(expected=expected_txns)
 239  
 240          # Finally, evict MAX_REPLACEMENT_CANDIDATES
 241          package_parent = self.wallet.create_self_transfer_multi(utxos_to_spend=parent_coins[:-1], fee_per_output=parent_fee_per_conflict)
 242          package_child = self.wallet.create_self_transfer(fee_rate=child_feerate, utxo_to_spend=package_parent["new_utxos"][0])
 243          pkg_results = node.submitpackage([package_parent["hex"], package_child["hex"]], maxfeerate=0)
 244          assert_equal(pkg_results["package_msg"], "success")
 245          self.assert_mempool_contents(expected=[singleton_tx["tx"], size_two_clusters[-1][0]["tx"], size_two_clusters[-1][1]["tx"], package_parent["tx"], package_child["tx"]] )
 246  
 247          self.generate(node, 1)
 248  
 249      def test_too_numerous_ancestors(self):
 250          self.log.info("Test that package RBF doesn't work with packages larger than 2 due to ancestors")
 251          node = self.nodes[0]
 252          coin = self.coins.pop()
 253  
 254          package_hex1, package_txns1 = self.create_simple_package(coin, DEFAULT_FEE, DEFAULT_CHILD_FEE)
 255          node.submitpackage(package_hex1)
 256          self.assert_mempool_contents(expected=package_txns1)
 257  
 258          # Double-spends the original package
 259          self.ctr += 1
 260          parent_result1 = self.wallet.create_self_transfer(
 261              fee=DEFAULT_FEE,
 262              utxo_to_spend=coin,
 263              sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
 264          )
 265  
 266          coin2 = self.coins.pop()
 267  
 268          # Added to make package too large for package RBF;
 269          # it will enter mempool individually
 270          self.ctr += 1
 271          parent_result2 = self.wallet.create_self_transfer(
 272              fee=DEFAULT_FEE,
 273              utxo_to_spend=coin2,
 274              sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
 275          )
 276  
 277          # Child that spends both, violating cluster size rule due
 278          # to in-mempool ancestry
 279          self.ctr += 1
 280          child_result = self.wallet.create_self_transfer_multi(
 281              fee_per_output=int(DEFAULT_CHILD_FEE * COIN),
 282              utxos_to_spend=[parent_result1["new_utxo"], parent_result2["new_utxo"]],
 283              sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
 284          )
 285  
 286          package_hex2 = [parent_result1["hex"], parent_result2["hex"], child_result["hex"]]
 287          package_txns2_succeed = [parent_result2["tx"]]
 288  
 289          pkg_result = node.submitpackage(package_hex2)
 290          assert_equal(pkg_result["package_msg"], 'package RBF failed: new transaction cannot have mempool ancestors')
 291          self.assert_mempool_contents(expected=package_txns1 + package_txns2_succeed)
 292          self.generate(node, 1)
 293  
 294      def test_wrong_conflict_cluster_size_linear(self):
 295          self.log.info("Test that conflicting with a cluster not sized two is rejected: linear chain")
 296          node = self.nodes[0]
 297  
 298          # Coins we will conflict with
 299          coin1 = self.coins.pop()
 300          coin2 = self.coins.pop()
 301          coin3 = self.coins.pop()
 302  
 303          # Three transactions chained; package RBF against any of these
 304          # should be rejected
 305          self.ctr += 1
 306          parent_result = self.wallet.create_self_transfer(
 307              fee=DEFAULT_FEE,
 308              utxo_to_spend=coin1,
 309              sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
 310          )
 311  
 312          self.ctr += 1
 313          child_result = self.wallet.create_self_transfer_multi(
 314              fee_per_output=int(DEFAULT_FEE * COIN),
 315              utxos_to_spend=[parent_result["new_utxo"], coin2],
 316              sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
 317          )
 318  
 319          self.ctr += 1
 320          grandchild_result = self.wallet.create_self_transfer_multi(
 321              fee_per_output=int(DEFAULT_FEE * COIN),
 322              utxos_to_spend=[child_result["new_utxos"][0], coin3],
 323              sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
 324          )
 325  
 326          expected_txns = [parent_result["tx"], child_result["tx"], grandchild_result["tx"]]
 327          for tx in expected_txns:
 328              node.sendrawtransaction(tx.serialize().hex())
 329          self.assert_mempool_contents(expected=expected_txns)
 330  
 331          # Now make conflicting packages for each coin
 332          package_hex1, _package_txns1 = self.create_simple_package(coin1, DEFAULT_FEE, DEFAULT_CHILD_FEE)
 333  
 334          package_result = node.submitpackage(package_hex1)
 335          assert_equal(f"package RBF failed: {parent_result['tx'].rehash()} has 2 descendants, max 1 allowed", package_result["package_msg"])
 336  
 337          package_hex2, _package_txns2 = self.create_simple_package(coin2, DEFAULT_FEE, DEFAULT_CHILD_FEE)
 338          package_result = node.submitpackage(package_hex2)
 339          assert_equal(f"package RBF failed: {child_result['tx'].rehash()} has both ancestor and descendant, exceeding cluster limit of 2", package_result["package_msg"])
 340  
 341          package_hex3, _package_txns3 = self.create_simple_package(coin3, DEFAULT_FEE, DEFAULT_CHILD_FEE)
 342          package_result = node.submitpackage(package_hex3)
 343          assert_equal(f"package RBF failed: {grandchild_result['tx'].rehash()} has 2 ancestors, max 1 allowed", package_result["package_msg"])
 344  
 345          # Check that replacements were actually rejected
 346          self.assert_mempool_contents(expected=expected_txns)
 347          self.generate(node, 1)
 348  
 349      def test_wrong_conflict_cluster_size_parents_child(self):
 350          self.log.info("Test that conflicting with a cluster not sized two is rejected: two parents one child")
 351          node = self.nodes[0]
 352  
 353          # Coins we will conflict with
 354          coin1 = self.coins.pop()
 355          coin2 = self.coins.pop()
 356          coin3 = self.coins.pop()
 357  
 358          self.ctr += 1
 359          parent1_result = self.wallet.create_self_transfer(
 360              fee=DEFAULT_FEE,
 361              utxo_to_spend=coin1,
 362              sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
 363          )
 364  
 365          self.ctr += 1
 366          parent2_result = self.wallet.create_self_transfer_multi(
 367              fee_per_output=int(DEFAULT_FEE * COIN),
 368              utxos_to_spend=[coin2],
 369              sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
 370          )
 371  
 372          self.ctr += 1
 373          child_result = self.wallet.create_self_transfer_multi(
 374              fee_per_output=int(DEFAULT_FEE * COIN),
 375              utxos_to_spend=[parent1_result["new_utxo"], parent2_result["new_utxos"][0], coin3],
 376              sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
 377          )
 378  
 379          expected_txns = [parent1_result["tx"], parent2_result["tx"], child_result["tx"]]
 380          for tx in expected_txns:
 381              node.sendrawtransaction(tx.serialize().hex())
 382          self.assert_mempool_contents(expected=expected_txns)
 383  
 384          # Now make conflicting packages for each coin
 385          package_hex1, _package_txns1 = self.create_simple_package(coin1, DEFAULT_FEE, DEFAULT_CHILD_FEE)
 386          package_result = node.submitpackage(package_hex1)
 387          assert_equal(f"package RBF failed: {child_result['tx'].rehash()} has 2 ancestors, max 1 allowed", package_result["package_msg"])
 388  
 389          package_hex2, _package_txns2 = self.create_simple_package(coin2, DEFAULT_FEE, DEFAULT_CHILD_FEE)
 390          package_result = node.submitpackage(package_hex2)
 391          assert_equal(f"package RBF failed: {child_result['tx'].rehash()} has 2 ancestors, max 1 allowed", package_result["package_msg"])
 392  
 393          package_hex3, _package_txns3 = self.create_simple_package(coin3, DEFAULT_FEE, DEFAULT_CHILD_FEE)
 394          package_result = node.submitpackage(package_hex3)
 395          assert_equal(f"package RBF failed: {child_result['tx'].rehash()} has 2 ancestors, max 1 allowed", package_result["package_msg"])
 396  
 397          # Check that replacements were actually rejected
 398          self.assert_mempool_contents(expected=expected_txns)
 399          self.generate(node, 1)
 400  
 401      def test_wrong_conflict_cluster_size_parent_children(self):
 402          self.log.info("Test that conflicting with a cluster not sized two is rejected: one parent two children")
 403          node = self.nodes[0]
 404  
 405          # Coins we will conflict with
 406          coin1 = self.coins.pop()
 407          coin2 = self.coins.pop()
 408          coin3 = self.coins.pop()
 409  
 410          self.ctr += 1
 411          parent_result = self.wallet.create_self_transfer_multi(
 412              fee_per_output=int(DEFAULT_FEE * COIN),
 413              num_outputs=2,
 414              utxos_to_spend=[coin1],
 415              sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
 416          )
 417  
 418          self.ctr += 1
 419          child1_result = self.wallet.create_self_transfer_multi(
 420              fee_per_output=int(DEFAULT_FEE * COIN),
 421              utxos_to_spend=[parent_result["new_utxos"][0], coin2],
 422              sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
 423          )
 424  
 425          self.ctr += 1
 426          child2_result = self.wallet.create_self_transfer_multi(
 427              fee_per_output=int(DEFAULT_FEE * COIN),
 428              utxos_to_spend=[parent_result["new_utxos"][1], coin3],
 429              sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
 430          )
 431  
 432          # Submit them to mempool
 433          expected_txns = [parent_result["tx"], child1_result["tx"], child2_result["tx"]]
 434          for tx in expected_txns:
 435              node.sendrawtransaction(tx.serialize().hex())
 436          self.assert_mempool_contents(expected=expected_txns)
 437  
 438          # Now make conflicting packages for each coin
 439          package_hex1, _package_txns1 = self.create_simple_package(coin1, DEFAULT_FEE, DEFAULT_CHILD_FEE)
 440          package_result = node.submitpackage(package_hex1)
 441          assert_equal(f"package RBF failed: {child2_result['tx'].rehash()} is not the only child of parent {parent_result['tx'].rehash()}", package_result["package_msg"])
 442  
 443          package_hex2, _package_txns2 = self.create_simple_package(coin2, DEFAULT_FEE, DEFAULT_CHILD_FEE)
 444          package_result = node.submitpackage(package_hex2)
 445          assert_equal(f"package RBF failed: {child1_result['tx'].rehash()} is not the only child of parent {parent_result['tx'].rehash()}", package_result["package_msg"])
 446  
 447          package_hex3, _package_txns3 = self.create_simple_package(coin3, DEFAULT_FEE, DEFAULT_CHILD_FEE)
 448          package_result = node.submitpackage(package_hex3)
 449          assert_equal(f"package RBF failed: {child2_result['tx'].rehash()} is not the only child of parent {parent_result['tx'].rehash()}", package_result["package_msg"])
 450  
 451          # Check that replacements were actually rejected
 452          self.assert_mempool_contents(expected=expected_txns)
 453          self.generate(node, 1)
 454  
 455      def test_package_rbf_with_wrong_pkg_size(self):
 456          self.log.info("Test that package RBF doesn't work with packages larger than 2 due to pkg size")
 457          node = self.nodes[0]
 458          coin1 = self.coins.pop()
 459          coin2 = self.coins.pop()
 460  
 461          # Two packages to require multiple direct conflicts, easier to set up illicit pkg size
 462          package_hex1, package_txns1 = self.create_simple_package(coin1, DEFAULT_FEE, DEFAULT_CHILD_FEE)
 463          package_hex2, package_txns2 = self.create_simple_package(coin2, DEFAULT_FEE, DEFAULT_CHILD_FEE)
 464  
 465          node.submitpackage(package_hex1)
 466          node.submitpackage(package_hex2)
 467  
 468          self.assert_mempool_contents(expected=package_txns1 + package_txns2)
 469          assert_equal(len(node.getrawmempool()), 4)
 470  
 471          # Double-spends the first package
 472          self.ctr += 1
 473          parent_result1 = self.wallet.create_self_transfer(
 474              fee=DEFAULT_FEE,
 475              utxo_to_spend=coin1,
 476              sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
 477          )
 478  
 479          # Double-spends the second package
 480          self.ctr += 1
 481          parent_result2 = self.wallet.create_self_transfer(
 482              fee=DEFAULT_FEE,
 483              utxo_to_spend=coin2,
 484              sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
 485          )
 486  
 487          # Child that spends both, violating cluster size rule due
 488          # to pkg size
 489          self.ctr += 1
 490          child_result = self.wallet.create_self_transfer_multi(
 491              fee_per_output=int(DEFAULT_CHILD_FEE * COIN),
 492              utxos_to_spend=[parent_result1["new_utxo"], parent_result2["new_utxo"]],
 493              sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
 494          )
 495  
 496          package_hex3 = [parent_result1["hex"], parent_result2["hex"], child_result["hex"]]
 497  
 498          pkg_result = node.submitpackage(package_hex3)
 499          assert_equal(pkg_result["package_msg"], 'package RBF failed: package must be 1-parent-1-child')
 500          self.assert_mempool_contents(expected=package_txns1 + package_txns2)
 501          self.generate(node, 1)
 502  
 503      def test_insufficient_feerate(self):
 504          self.log.info("Check Package RBF must beat feerate of direct conflict")
 505          node = self.nodes[0]
 506          coin = self.coins.pop()
 507  
 508          # Non-cpfp structure
 509          package_hex1, package_txns1 = self.create_simple_package(coin, parent_fee=DEFAULT_CHILD_FEE, child_fee=DEFAULT_FEE)
 510          node.submitpackage(package_hex1)
 511          self.assert_mempool_contents(expected=package_txns1)
 512  
 513          # Package 2 feerate is below the feerate of directly conflicted parent, so it fails even though
 514          # total fees are higher than the original package
 515          package_hex2, _package_txns2 = self.create_simple_package(coin, parent_fee=DEFAULT_CHILD_FEE - Decimal("0.00000001"), child_fee=DEFAULT_CHILD_FEE)
 516          pkg_results2 = node.submitpackage(package_hex2)
 517          assert_equal(pkg_results2["package_msg"], 'package RBF failed: insufficient feerate: does not improve feerate diagram')
 518          self.assert_mempool_contents(expected=package_txns1)
 519          self.generate(node, 1)
 520  
 521      def test_0fee_package_rbf(self):
 522          self.log.info("Test package RBF: TRUC 0-fee parent + high-fee child replaces parent's conflicts")
 523          node = self.nodes[0]
 524          # Reuse the same coins so that the transactions conflict with one another.
 525          self.wallet.rescan_utxos()
 526          parent_coin = self.wallet.get_utxo(confirmed_only=True)
 527  
 528          # package1 pays default fee on both transactions
 529          parent1 = self.wallet.create_self_transfer(utxo_to_spend=parent_coin, version=3)
 530          child1 = self.wallet.create_self_transfer(utxo_to_spend=parent1["new_utxo"], version=3)
 531          package_hex1 = [parent1["hex"], child1["hex"]]
 532          fees_package1 = parent1["fee"] + child1["fee"]
 533          submitres1 = node.submitpackage(package_hex1)
 534          assert_equal(submitres1["package_msg"], "success")
 535          self.assert_mempool_contents([parent1["tx"], child1["tx"]])
 536  
 537          # package2 has a 0-fee parent (conflicting with package1) and very high fee child
 538          parent2 = self.wallet.create_self_transfer(utxo_to_spend=parent_coin, fee=0, fee_rate=0, version=3)
 539          child2 = self.wallet.create_self_transfer(utxo_to_spend=parent2["new_utxo"], fee=fees_package1*10, version=3)
 540          package_hex2 = [parent2["hex"], child2["hex"]]
 541  
 542          submitres2 = node.submitpackage(package_hex2)
 543          assert_equal(submitres2["package_msg"], "success")
 544          assert_equal(set(submitres2["replaced-transactions"]), set([parent1["txid"], child1["txid"]]))
 545          self.assert_mempool_contents([parent2["tx"], child2["tx"]])
 546  
 547          self.generate(node, 1)
 548  
 549      def test_child_conflicts_parent_mempool_ancestor(self):
 550          fill_mempool(self, self.nodes[0], tx_sync_fun=self.no_op)
 551          # Reset coins since we filled the mempool with current coins
 552          self.coins = self.wallet.get_utxos(mark_as_spent=False, confirmed_only=True)
 553  
 554          self.log.info("Test that package RBF doesn't have issues with mempool<->package conflicts via inconsistency")
 555          node = self.nodes[0]
 556          coin = self.coins.pop()
 557  
 558          self.ctr += 1
 559          grandparent_result = self.wallet.create_self_transfer(
 560              fee=DEFAULT_FEE,
 561              utxo_to_spend=coin,
 562              sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
 563          )
 564  
 565          node.sendrawtransaction(grandparent_result["hex"])
 566          minrelayfeerate = node.getnetworkinfo()["relayfee"]
 567  
 568          # Now make package of two descendants that looks
 569          # like a cpfp where the parent can't get in on its own
 570          self.ctr += 1
 571          parent_result = self.wallet.create_self_transfer(
 572              fee_rate=minrelayfeerate,
 573              utxo_to_spend=grandparent_result["new_utxo"],
 574              sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
 575          )
 576          # Last tx double-spends grandparent's coin,
 577          # which is not inside the current package
 578          self.ctr += 1
 579          child_result = self.wallet.create_self_transfer_multi(
 580              fee_per_output=int(DEFAULT_CHILD_FEE * COIN),
 581              utxos_to_spend=[parent_result["new_utxo"], coin],
 582              sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
 583          )
 584  
 585          pkg_result = node.submitpackage([parent_result["hex"], child_result["hex"]])
 586          assert_equal(pkg_result["package_msg"], 'package RBF failed: new transaction cannot have mempool ancestors')
 587          mempool_info = node.getrawmempool()
 588          assert grandparent_result["txid"] in mempool_info
 589          assert parent_result["txid"] not in mempool_info
 590          assert child_result["txid"] not in mempool_info
 591  
 592  if __name__ == "__main__":
 593      PackageRBFTest(__file__).main()
 594