mempool_package_rbf.py raw

   1  #!/usr/bin/env python3
   2  # Copyright (c) 2021-present The Bitcoin Core developers
   3  # Distributed under the MIT software license, see the accompanying
   4  # file COPYING or http://www.opensource.org/licenses/mit-license.php.
   5  
   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 BitcoinTestFramework
  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(BitcoinTestFramework):
  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              "-maxmempool=5",
  37          ]] * self.num_nodes
  38  
  39      def assert_mempool_contents(self, expected=None):
  40          mempool_util.assert_mempool_contents(self, self.nodes[0], expected, sync=False)
  41  
  42      def create_simple_package(self, parent_coin, parent_fee=DEFAULT_FEE, child_fee=DEFAULT_CHILD_FEE, heavy_child=False):
  43          """Create a 1 parent 1 child package using the coin passed in as the parent's input. The
  44          parent has 1 output, used to fund 1 child transaction.
  45          All transactions signal BIP125 replaceability, but nSequence changes based on self.ctr. This
  46          prevents identical txids between packages when the parents spend the same coin and have the
  47          same fee (i.e. 0sat).
  48  
  49          returns tuple (hex serialized txns, CTransaction objects)
  50          """
  51          self.ctr += 1
  52          # Use fee_rate=0 because create_self_transfer will use the default fee_rate value otherwise.
  53          # Passing in fee>0 overrides fee_rate, so this still works for non-zero parent_fee.
  54          parent_result = self.wallet.create_self_transfer(
  55              fee=parent_fee,
  56              utxo_to_spend=parent_coin,
  57              sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
  58          )
  59  
  60          num_child_outputs = 10 if heavy_child else 1
  61          child_result = self.wallet.create_self_transfer_multi(
  62              utxos_to_spend=[parent_result["new_utxo"]],
  63              num_outputs=num_child_outputs,
  64              fee_per_output=int(child_fee * COIN // num_child_outputs),
  65              sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
  66          )
  67          package_hex = [parent_result["hex"], child_result["hex"]]
  68          package_txns = [parent_result["tx"], child_result["tx"]]
  69          return package_hex, package_txns
  70  
  71      def run_test(self):
  72          # Counter used to count the number of times we constructed packages. Since we're constructing parent transactions with the same
  73          # coins (to create conflicts), and perhaps giving them the same fee, we might accidentally just create the same transaction again.
  74          # To prevent this, set nSequences to MAX_BIP125_RBF_SEQUENCE - self.ctr.
  75          self.ctr = 0
  76  
  77          self.log.info("Generate blocks to create UTXOs")
  78          self.wallet = MiniWallet(self.nodes[0])
  79  
  80          # Make more than enough coins for the sum of all tests,
  81          # otherwise a wallet rescan is needed later
  82          self.generate(self.wallet, 300)
  83          self.coins = self.wallet.get_utxos(mark_as_spent=False)
  84  
  85          self.test_package_rbf_basic()
  86          self.test_package_rbf_singleton()
  87          self.test_package_rbf_additional_fees()
  88          self.test_package_rbf_max_conflicts()
  89          self.test_too_numerous_ancestors()
  90          self.test_package_rbf_with_wrong_pkg_size()
  91          self.test_insufficient_feerate()
  92          self.test_0fee_package_rbf()
  93          self.test_child_conflicts_parent_mempool_ancestor()
  94  
  95      def test_package_rbf_basic(self):
  96          self.log.info("Test that a child can pay to replace its parents' conflicts")
  97          node = self.nodes[0]
  98          # Reuse the same coins so that the transactions conflict with one another.
  99          parent_coin = self.coins.pop()
 100          package_hex1, package_txns1 = self.create_simple_package(parent_coin, DEFAULT_FEE, DEFAULT_FEE)
 101          package_hex2, package_txns2 = self.create_simple_package(parent_coin, DEFAULT_FEE, DEFAULT_CHILD_FEE)
 102          node.submitpackage(package_hex1)
 103          self.assert_mempool_contents(expected=package_txns1)
 104  
 105          # Make sure 2nd node gets set up for basic package RBF
 106          self.sync_all()
 107  
 108          # Test run rejected because conflicts are not allowed in subpackage evaluation
 109          testres = node.testmempoolaccept(package_hex2)
 110          assert_equal(testres[0]["reject-reason"], "bip125-replacement-disallowed")
 111  
 112          # But accepted during normal submission
 113          submitres = node.submitpackage(package_hex2)
 114          assert_equal(set(submitres["replaced-transactions"]), set([tx.txid_hex for tx in package_txns1]))
 115          self.assert_mempool_contents(expected=package_txns2)
 116  
 117          # Make sure 2nd node gets a basic package RBF over p2p
 118          self.sync_all()
 119  
 120          self.generate(node, 1)
 121  
 122      def test_package_rbf_singleton(self):
 123          self.log.info("Test child can pay to replace a parent's single conflicted tx")
 124          node = self.nodes[0]
 125  
 126          # Make singleton tx to conflict with in next batch
 127          singleton_coin = self.coins.pop()
 128          singleton_tx = self.wallet.create_self_transfer(utxo_to_spend=singleton_coin)
 129          node.sendrawtransaction(singleton_tx["hex"])
 130          self.assert_mempool_contents(expected=[singleton_tx["tx"]])
 131  
 132          package_hex, package_txns = self.create_simple_package(singleton_coin, DEFAULT_FEE, singleton_tx["fee"] * 2)
 133  
 134          submitres = node.submitpackage(package_hex)
 135          assert_equal(submitres["replaced-transactions"], [singleton_tx["tx"].txid_hex])
 136          self.assert_mempool_contents(expected=package_txns)
 137  
 138          self.generate(node, 1)
 139  
 140      def test_package_rbf_additional_fees(self):
 141          self.log.info("Check Package RBF must increase the absolute fee")
 142          node = self.nodes[0]
 143          coin = self.coins.pop()
 144  
 145          package_hex1, package_txns1 = self.create_simple_package(coin, parent_fee=DEFAULT_FEE, child_fee=DEFAULT_CHILD_FEE, heavy_child=True)
 146          assert_greater_than_or_equal(1000, package_txns1[-1].get_vsize())
 147          node.submitpackage(package_hex1)
 148          self.assert_mempool_contents(expected=package_txns1)
 149  
 150          PACKAGE_FEE = DEFAULT_FEE + DEFAULT_CHILD_FEE
 151          PACKAGE_FEE_MINUS_ONE = PACKAGE_FEE - Decimal("0.00000001")
 152  
 153          # Package 2 has a higher feerate but lower absolute fee
 154          package_hex2, package_txns2 = self.create_simple_package(coin, parent_fee=DEFAULT_FEE, child_fee=DEFAULT_CHILD_FEE - Decimal("0.00000001"))
 155          pkg_results2 = node.submitpackage(package_hex2)
 156          assert_equal(f"package RBF failed: insufficient anti-DoS fees, rejecting replacement {package_txns2[1].txid_hex}, less fees than conflicting txs; {PACKAGE_FEE_MINUS_ONE} < {PACKAGE_FEE}", pkg_results2["package_msg"])
 157          self.assert_mempool_contents(expected=package_txns1)
 158  
 159          self.log.info("Check replacement pays for incremental bandwidth")
 160          _, placeholder_txns3 = self.create_simple_package(coin)
 161          package_3_size = sum([tx.get_vsize() for tx in placeholder_txns3])
 162          incremental_sats_required = (Decimal(package_3_size * 0.1) / COIN).quantize(Decimal("0.00000001"))
 163          incremental_sats_short = incremental_sats_required - Decimal("0.00000005")
 164          # Recreate the package with slightly higher fee once we know the size of the new package, but still short of required fee
 165          failure_package_hex3, failure_package_txns3 = self.create_simple_package(coin, parent_fee=DEFAULT_FEE, child_fee=DEFAULT_CHILD_FEE + incremental_sats_short)
 166          assert_equal(package_3_size, sum([tx.get_vsize() for tx in failure_package_txns3]))
 167          pkg_results3 = node.submitpackage(failure_package_hex3)
 168          assert_equal(f"package RBF failed: insufficient anti-DoS fees, rejecting replacement {failure_package_txns3[1].txid_hex}, not enough additional fees to relay; {incremental_sats_short:.8f} < {incremental_sats_required:.8f}", pkg_results3["package_msg"])
 169          self.assert_mempool_contents(expected=package_txns1)
 170  
 171          success_package_hex3, success_package_txns3 = self.create_simple_package(coin, parent_fee=DEFAULT_FEE, child_fee=DEFAULT_CHILD_FEE + incremental_sats_required)
 172          node.submitpackage(success_package_hex3)
 173          self.assert_mempool_contents(expected=success_package_txns3)
 174          self.generate(node, 1)
 175  
 176          self.log.info("Check Package RBF must have strict cpfp structure")
 177          coin = self.coins.pop()
 178          package_hex4, package_txns4 = self.create_simple_package(coin, parent_fee=DEFAULT_FEE, child_fee=DEFAULT_CHILD_FEE)
 179          node.submitpackage(package_hex4)
 180          self.assert_mempool_contents(expected=package_txns4)
 181          package_hex5, _package_txns5 = self.create_simple_package(coin, parent_fee=DEFAULT_CHILD_FEE, child_fee=DEFAULT_CHILD_FEE)
 182          pkg_results5 = node.submitpackage(package_hex5)
 183          assert 'package RBF failed: package feerate is less than or equal to parent feerate' in pkg_results5["package_msg"]
 184          self.assert_mempool_contents(expected=package_txns4)
 185  
 186          package_hex5_1, package_txns5_1 = self.create_simple_package(coin, parent_fee=DEFAULT_CHILD_FEE, child_fee=DEFAULT_CHILD_FEE + Decimal("0.00000001"))
 187          node.submitpackage(package_hex5_1)
 188          self.assert_mempool_contents(expected=package_txns5_1)
 189          self.generate(node, 1)
 190  
 191      def test_package_rbf_max_conflicts(self):
 192          node = self.nodes[0]
 193          self.log.info("Check Package RBF cannot conflict with  more than MAX_REPLACEMENT_CANDIDATES clusters")
 194          num_coins = 101
 195          parent_coins = self.coins[:num_coins]
 196          del self.coins[:num_coins]
 197  
 198          # Original transactions: 101 transactions with 2 descendants each -> 303 total transactions, 101 clusters
 199          size_three_clusters = []
 200          for coin in parent_coins:
 201              size_three_clusters.append(self.wallet.send_self_transfer_chain(from_node=node, chain_length=3, utxo_to_spend=coin))
 202          expected_txns = [txn["tx"] for parent_child_txns in size_three_clusters for txn in parent_child_txns]
 203          assert_equal(len(expected_txns), num_coins * 3)
 204          self.assert_mempool_contents(expected=expected_txns)
 205  
 206          # parent feeerate needs to be high enough for minrelay
 207          # child feerate needs to be large enough to trigger package rbf with a very large parent and
 208          # pay for all evicted fees. maxfeerate turned off for all submissions since child feerate
 209          # is extremely high
 210          parent_fee_per_conflict = 10000
 211          child_feerate = 10000 * DEFAULT_FEE
 212  
 213          # Conflict against all transactions by double-spending each parent, causing 101 cluster conflicts
 214          package_parent = self.wallet.create_self_transfer_multi(utxos_to_spend=parent_coins, fee_per_output=parent_fee_per_conflict)
 215          package_child = self.wallet.create_self_transfer(fee_rate=child_feerate, utxo_to_spend=package_parent["new_utxos"][0])
 216  
 217          pkg_results = node.submitpackage([package_parent["hex"], package_child["hex"]], maxfeerate=0)
 218          assert_equal("transaction failed", pkg_results["package_msg"])
 219          assert_equal(f"too many potential replacements, rejecting replacement {package_parent['txid']}; too many conflicting clusters (101 > 100)", pkg_results["tx-results"][package_parent["wtxid"]]["error"])
 220          self.assert_mempool_contents(expected=expected_txns)
 221  
 222          # Make singleton tx to conflict with in next batch
 223          singleton_coin = self.coins.pop()
 224          singleton_tx = self.wallet.create_self_transfer(utxo_to_spend=singleton_coin)
 225          node.sendrawtransaction(singleton_tx["hex"])
 226          expected_txns.append(singleton_tx["tx"])
 227  
 228          # Double-spend same set minus last, and double-spend singleton. This is still too many conflicted clusters.
 229          # N.B. we can't RBF just a child tx in the clusters, as that would make resulting cluster of size 3.
 230          double_spending_coins = parent_coins[:-1] + [singleton_coin]
 231          package_parent = self.wallet.create_self_transfer_multi(utxos_to_spend=double_spending_coins, fee_per_output=parent_fee_per_conflict)
 232          package_child = self.wallet.create_self_transfer(fee_rate=child_feerate, utxo_to_spend=package_parent["new_utxos"][0])
 233          pkg_results = node.submitpackage([package_parent["hex"], package_child["hex"]], maxfeerate=0)
 234          assert_equal("transaction failed", pkg_results["package_msg"])
 235          assert_equal(f"too many potential replacements, rejecting replacement {package_parent['txid']}; too many conflicting clusters (101 > 100)", pkg_results["tx-results"][package_parent["wtxid"]]["error"])
 236          self.assert_mempool_contents(expected=expected_txns)
 237  
 238          # Finally, conflict with MAX_REPLACEMENT_CANDIDATES clusters
 239          package_parent = self.wallet.create_self_transfer_multi(utxos_to_spend=parent_coins[:-1], fee_per_output=parent_fee_per_conflict)
 240          package_child = self.wallet.create_self_transfer(fee_rate=child_feerate, utxo_to_spend=package_parent["new_utxos"][0])
 241          pkg_results = node.submitpackage([package_parent["hex"], package_child["hex"]], maxfeerate=0)
 242          assert_equal(pkg_results["package_msg"], "success")
 243          self.assert_mempool_contents(expected=[singleton_tx["tx"], size_three_clusters[-1][0]["tx"], size_three_clusters[-1][1]["tx"], size_three_clusters[-1][2]["tx"], package_parent["tx"], package_child["tx"]] )
 244  
 245          self.generate(node, 1)
 246  
 247      def test_too_numerous_ancestors(self):
 248          self.log.info("Test that package RBF doesn't work with packages larger than 2 due to ancestors")
 249          node = self.nodes[0]
 250          coin = self.coins.pop()
 251  
 252          package_hex1, package_txns1 = self.create_simple_package(coin, DEFAULT_FEE, DEFAULT_CHILD_FEE)
 253          node.submitpackage(package_hex1)
 254          self.assert_mempool_contents(expected=package_txns1)
 255  
 256          # Double-spends the original package
 257          self.ctr += 1
 258          parent_result1 = self.wallet.create_self_transfer(
 259              fee=DEFAULT_FEE,
 260              utxo_to_spend=coin,
 261              sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
 262          )
 263  
 264          coin2 = self.coins.pop()
 265  
 266          # Added to make package too large for package RBF;
 267          # it will enter mempool individually
 268          self.ctr += 1
 269          parent_result2 = self.wallet.create_self_transfer(
 270              fee=DEFAULT_FEE,
 271              utxo_to_spend=coin2,
 272              sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
 273          )
 274  
 275          # Child that spends both, violating cluster size rule due
 276          # to in-mempool ancestry
 277          self.ctr += 1
 278          child_result = self.wallet.create_self_transfer_multi(
 279              fee_per_output=int(DEFAULT_CHILD_FEE * COIN),
 280              utxos_to_spend=[parent_result1["new_utxo"], parent_result2["new_utxo"]],
 281              sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
 282          )
 283  
 284          package_hex2 = [parent_result1["hex"], parent_result2["hex"], child_result["hex"]]
 285          package_txns2_succeed = [parent_result2["tx"]]
 286  
 287          pkg_result = node.submitpackage(package_hex2)
 288          assert_equal(pkg_result["package_msg"], 'package RBF failed: new transaction cannot have mempool ancestors')
 289          self.assert_mempool_contents(expected=package_txns1 + package_txns2_succeed)
 290          self.generate(node, 1)
 291  
 292      def test_package_rbf_with_wrong_pkg_size(self):
 293          self.log.info("Test that package RBF doesn't work with packages larger than 2 due to pkg size")
 294          node = self.nodes[0]
 295          coin1 = self.coins.pop()
 296          coin2 = self.coins.pop()
 297  
 298          # Two packages to require multiple direct conflicts, easier to set up illicit pkg size
 299          package_hex1, package_txns1 = self.create_simple_package(coin1, DEFAULT_FEE, DEFAULT_CHILD_FEE)
 300          package_hex2, package_txns2 = self.create_simple_package(coin2, DEFAULT_FEE, DEFAULT_CHILD_FEE)
 301  
 302          node.submitpackage(package_hex1)
 303          node.submitpackage(package_hex2)
 304  
 305          self.assert_mempool_contents(expected=package_txns1 + package_txns2)
 306          assert_equal(len(node.getrawmempool()), 4)
 307  
 308          # Double-spends the first package
 309          self.ctr += 1
 310          parent_result1 = self.wallet.create_self_transfer(
 311              fee=DEFAULT_FEE,
 312              utxo_to_spend=coin1,
 313              sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
 314          )
 315  
 316          # Double-spends the second package
 317          self.ctr += 1
 318          parent_result2 = self.wallet.create_self_transfer(
 319              fee=DEFAULT_FEE,
 320              utxo_to_spend=coin2,
 321              sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
 322          )
 323  
 324          # Child that spends both, violating cluster size rule due
 325          # to pkg size
 326          self.ctr += 1
 327          child_result = self.wallet.create_self_transfer_multi(
 328              fee_per_output=int(DEFAULT_CHILD_FEE * COIN),
 329              utxos_to_spend=[parent_result1["new_utxo"], parent_result2["new_utxo"]],
 330              sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
 331          )
 332  
 333          package_hex3 = [parent_result1["hex"], parent_result2["hex"], child_result["hex"]]
 334  
 335          pkg_result = node.submitpackage(package_hex3)
 336          assert_equal(pkg_result["package_msg"], 'package RBF failed: package must be 1-parent-1-child')
 337          self.assert_mempool_contents(expected=package_txns1 + package_txns2)
 338          self.generate(node, 1)
 339  
 340      def test_insufficient_feerate(self):
 341          self.log.info("Check Package RBF must beat feerate of direct conflict")
 342          node = self.nodes[0]
 343          coin = self.coins.pop()
 344  
 345          # Non-cpfp structure
 346          package_hex1, package_txns1 = self.create_simple_package(coin, parent_fee=DEFAULT_CHILD_FEE, child_fee=DEFAULT_FEE)
 347          node.submitpackage(package_hex1)
 348          self.assert_mempool_contents(expected=package_txns1)
 349  
 350          # Package 2 feerate is below the feerate of directly conflicted parent, so it fails even though
 351          # total fees are higher than the original package
 352          package_hex2, _package_txns2 = self.create_simple_package(coin, parent_fee=DEFAULT_CHILD_FEE - Decimal("0.00000001"), child_fee=DEFAULT_CHILD_FEE)
 353          pkg_results2 = node.submitpackage(package_hex2)
 354          assert_equal(pkg_results2["package_msg"], 'package RBF failed: insufficient feerate: does not improve feerate diagram')
 355          self.assert_mempool_contents(expected=package_txns1)
 356          self.generate(node, 1)
 357  
 358      def test_0fee_package_rbf(self):
 359          self.log.info("Test package RBF: TRUC 0-fee parent + high-fee child replaces parent's conflicts")
 360          node = self.nodes[0]
 361          # Reuse the same coins so that the transactions conflict with one another.
 362          self.wallet.rescan_utxos()
 363          parent_coin = self.wallet.get_utxo(confirmed_only=True)
 364  
 365          # package1 pays default fee on both transactions
 366          parent1 = self.wallet.create_self_transfer(utxo_to_spend=parent_coin, version=3)
 367          child1 = self.wallet.create_self_transfer(utxo_to_spend=parent1["new_utxo"], version=3)
 368          package_hex1 = [parent1["hex"], child1["hex"]]
 369          fees_package1 = parent1["fee"] + child1["fee"]
 370          submitres1 = node.submitpackage(package_hex1)
 371          assert_equal(submitres1["package_msg"], "success")
 372          self.assert_mempool_contents([parent1["tx"], child1["tx"]])
 373  
 374          # package2 has a 0-fee parent (conflicting with package1) and very high fee child
 375          parent2 = self.wallet.create_self_transfer(utxo_to_spend=parent_coin, fee=0, fee_rate=0, version=3)
 376          child2 = self.wallet.create_self_transfer(utxo_to_spend=parent2["new_utxo"], fee=fees_package1*10, version=3)
 377          package_hex2 = [parent2["hex"], child2["hex"]]
 378  
 379          submitres2 = node.submitpackage(package_hex2)
 380          assert_equal(submitres2["package_msg"], "success")
 381          assert_equal(set(submitres2["replaced-transactions"]), set([parent1["txid"], child1["txid"]]))
 382          self.assert_mempool_contents([parent2["tx"], child2["tx"]])
 383  
 384          self.generate(node, 1)
 385  
 386      def test_child_conflicts_parent_mempool_ancestor(self):
 387          fill_mempool(self, self.nodes[0], tx_sync_fun=self.no_op)
 388          # Reset coins since we filled the mempool with current coins
 389          self.coins = self.wallet.get_utxos(mark_as_spent=False, confirmed_only=True)
 390  
 391          self.log.info("Test that package RBF doesn't have issues with mempool<->package conflicts via inconsistency")
 392          node = self.nodes[0]
 393          coin = self.coins.pop()
 394  
 395          self.ctr += 1
 396          grandparent_result = self.wallet.create_self_transfer(
 397              fee=DEFAULT_FEE,
 398              utxo_to_spend=coin,
 399              sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
 400          )
 401  
 402          node.sendrawtransaction(grandparent_result["hex"])
 403          minrelayfeerate = node.getnetworkinfo()["relayfee"]
 404  
 405          # Now make package of two descendants that looks
 406          # like a cpfp where the parent can't get in on its own
 407          self.ctr += 1
 408          parent_result = self.wallet.create_self_transfer(
 409              fee_rate=minrelayfeerate,
 410              utxo_to_spend=grandparent_result["new_utxo"],
 411              sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
 412          )
 413          # Last tx double-spends grandparent's coin,
 414          # which is not inside the current package
 415          self.ctr += 1
 416          child_result = self.wallet.create_self_transfer_multi(
 417              fee_per_output=int(DEFAULT_CHILD_FEE * COIN),
 418              utxos_to_spend=[parent_result["new_utxo"], coin],
 419              sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
 420          )
 421  
 422          pkg_result = node.submitpackage([parent_result["hex"], child_result["hex"]])
 423          assert_equal(pkg_result["package_msg"], 'package RBF failed: new transaction cannot have mempool ancestors')
 424          mempool_info = node.getrawmempool()
 425          assert grandparent_result["txid"] in mempool_info
 426          assert parent_result["txid"] not in mempool_info
 427          assert child_result["txid"] not in mempool_info
 428  
 429  if __name__ == "__main__":
 430      PackageRBFTest(__file__).main()
 431