mempool_truc.py raw

   1  #!/usr/bin/env python3
   2  # Copyright (c) 2024-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  from decimal import Decimal
   6  
   7  from test_framework.test_framework import BitcoinTestFramework
   8  from test_framework.util import (
   9      assert_not_equal,
  10      assert_equal,
  11      assert_greater_than,
  12      assert_greater_than_or_equal,
  13      assert_raises_rpc_error,
  14      get_fee,
  15  )
  16  from test_framework.wallet import (
  17      COIN,
  18      DEFAULT_FEE,
  19      MiniWallet,
  20  )
  21  from test_framework.blocktools import (
  22      create_empty_fork,
  23  )
  24  
  25  MAX_REPLACEMENT_CANDIDATES = 100
  26  TRUC_MAX_VSIZE = 10000
  27  TRUC_CHILD_MAX_VSIZE = 1000
  28  
  29  def cleanup(extra_args=None):
  30      def decorator(func):
  31          def wrapper(self):
  32              if extra_args is not None:
  33                  self.restart_node(0, extra_args=extra_args)
  34              func(self)
  35  
  36              # Clear mempool again after test
  37              self.generate(self.nodes[0], 1)
  38              if extra_args is not None:
  39                  self.restart_node(0)
  40          return wrapper
  41      return decorator
  42  
  43  class MempoolTRUC(BitcoinTestFramework):
  44      def set_test_params(self):
  45          self.num_nodes = 1
  46          self.extra_args = [[]]
  47          self.setup_clean_chain = True
  48  
  49      def check_mempool(self, txids):
  50          """Assert exact contents of the node's mempool (by txid)."""
  51          mempool_contents = self.nodes[0].getrawmempool()
  52          assert_equal(len(txids), len(mempool_contents))
  53          assert all([txid in txids for txid in mempool_contents])
  54  
  55      def trigger_reorg(self, fork_blocks):
  56          """Trigger reorg of the fork blocks."""
  57          for block in fork_blocks:
  58              self.nodes[0].submitblock(block.serialize().hex())
  59          assert_equal(self.nodes[0].getbestblockhash(), fork_blocks[-1].hash_hex)
  60  
  61      @cleanup()
  62      def test_truc_max_vsize(self):
  63          node = self.nodes[0]
  64          self.log.info("Test TRUC-specific maximum transaction vsize")
  65          tx_v3_heavy = self.wallet.create_self_transfer(target_vsize=TRUC_MAX_VSIZE + 1, version=3)
  66          assert_greater_than_or_equal(tx_v3_heavy["tx"].get_vsize(), TRUC_MAX_VSIZE)
  67          expected_error_heavy = f"TRUC-violation, version=3 tx {tx_v3_heavy['txid']} (wtxid={tx_v3_heavy['wtxid']}) is too big"
  68          assert_raises_rpc_error(-26, expected_error_heavy, node.sendrawtransaction, tx_v3_heavy["hex"])
  69          self.check_mempool([])
  70  
  71          # Ensure we are hitting the TRUC-specific limit and not something else
  72          tx_v2_heavy = self.wallet.send_self_transfer(from_node=node, target_vsize=TRUC_MAX_VSIZE + 1, version=2)
  73          self.check_mempool([tx_v2_heavy["txid"]])
  74  
  75      @cleanup()
  76      def test_truc_acceptance(self):
  77          node = self.nodes[0]
  78          self.log.info("Test a child of a TRUC transaction cannot be more than 1000vB")
  79          tx_v3_parent_normal = self.wallet.send_self_transfer(from_node=node, version=3)
  80          self.check_mempool([tx_v3_parent_normal["txid"]])
  81          tx_v3_child_heavy = self.wallet.create_self_transfer(
  82              utxo_to_spend=tx_v3_parent_normal["new_utxo"],
  83              target_vsize=TRUC_CHILD_MAX_VSIZE + 1,
  84              version=3
  85          )
  86          assert_greater_than_or_equal(tx_v3_child_heavy["tx"].get_vsize(), TRUC_CHILD_MAX_VSIZE)
  87          expected_error_child_heavy = f"TRUC-violation, version=3 child tx {tx_v3_child_heavy['txid']} (wtxid={tx_v3_child_heavy['wtxid']}) is too big"
  88          assert_raises_rpc_error(-26, expected_error_child_heavy, node.sendrawtransaction, tx_v3_child_heavy["hex"])
  89          self.check_mempool([tx_v3_parent_normal["txid"]])
  90          # tx has no descendants
  91          assert_equal(node.getmempoolentry(tx_v3_parent_normal["txid"])["descendantcount"], 1)
  92  
  93          self.log.info("Test that, during replacements, only the new transaction counts for TRUC descendant limit")
  94          tx_v3_child_almost_heavy = self.wallet.send_self_transfer(
  95              from_node=node,
  96              fee_rate=DEFAULT_FEE,
  97              utxo_to_spend=tx_v3_parent_normal["new_utxo"],
  98              target_vsize=TRUC_CHILD_MAX_VSIZE - 3,
  99              version=3
 100          )
 101          assert_greater_than_or_equal(TRUC_CHILD_MAX_VSIZE, tx_v3_child_almost_heavy["tx"].get_vsize())
 102          self.check_mempool([tx_v3_parent_normal["txid"], tx_v3_child_almost_heavy["txid"]])
 103          assert_equal(node.getmempoolentry(tx_v3_parent_normal["txid"])["descendantcount"], 2)
 104          tx_v3_child_almost_heavy_rbf = self.wallet.send_self_transfer(
 105              from_node=node,
 106              fee_rate=DEFAULT_FEE * 2,
 107              utxo_to_spend=tx_v3_parent_normal["new_utxo"],
 108              target_vsize=875,
 109              version=3
 110          )
 111          assert_greater_than_or_equal(tx_v3_child_almost_heavy["tx"].get_vsize() + tx_v3_child_almost_heavy_rbf["tx"].get_vsize(),
 112                                       TRUC_CHILD_MAX_VSIZE)
 113          self.check_mempool([tx_v3_parent_normal["txid"], tx_v3_child_almost_heavy_rbf["txid"]])
 114          assert_equal(node.getmempoolentry(tx_v3_parent_normal["txid"])["descendantcount"], 2)
 115  
 116      @cleanup(extra_args=None)
 117      def test_truc_replacement(self):
 118          node = self.nodes[0]
 119          self.log.info("Test TRUC transactions may be replaced by TRUC transactions")
 120          utxo_v3_bip125 = self.wallet.get_utxo()
 121          tx_v3_bip125 = self.wallet.send_self_transfer(
 122              from_node=node,
 123              fee_rate=DEFAULT_FEE,
 124              utxo_to_spend=utxo_v3_bip125,
 125              version=3
 126          )
 127          self.check_mempool([tx_v3_bip125["txid"]])
 128  
 129          tx_v3_bip125_rbf = self.wallet.send_self_transfer(
 130              from_node=node,
 131              fee_rate=DEFAULT_FEE * 2,
 132              utxo_to_spend=utxo_v3_bip125,
 133              version=3
 134          )
 135          self.check_mempool([tx_v3_bip125_rbf["txid"]])
 136  
 137          self.log.info("Test TRUC transactions may be replaced by non-TRUC (BIP125) transactions")
 138          tx_v3_bip125_rbf_v2 = self.wallet.send_self_transfer(
 139              from_node=node,
 140              fee_rate=DEFAULT_FEE * 3,
 141              utxo_to_spend=utxo_v3_bip125,
 142              version=2
 143          )
 144          self.check_mempool([tx_v3_bip125_rbf_v2["txid"]])
 145  
 146          self.log.info("Test that replacements cannot cause violation of inherited TRUC")
 147          utxo_v3_parent = self.wallet.get_utxo()
 148          tx_v3_parent = self.wallet.send_self_transfer(
 149              from_node=node,
 150              fee_rate=DEFAULT_FEE,
 151              utxo_to_spend=utxo_v3_parent,
 152              version=3
 153          )
 154          tx_v3_child = self.wallet.send_self_transfer(
 155              from_node=node,
 156              fee_rate=DEFAULT_FEE,
 157              utxo_to_spend=tx_v3_parent["new_utxo"],
 158              version=3
 159          )
 160          self.check_mempool([tx_v3_bip125_rbf_v2["txid"], tx_v3_parent["txid"], tx_v3_child["txid"]])
 161  
 162          tx_v3_child_rbf_v2 = self.wallet.create_self_transfer(
 163              fee_rate=DEFAULT_FEE * 2,
 164              utxo_to_spend=tx_v3_parent["new_utxo"],
 165              version=2
 166          )
 167          expected_error_v2_v3 = f"TRUC-violation, non-version=3 tx {tx_v3_child_rbf_v2['txid']} (wtxid={tx_v3_child_rbf_v2['wtxid']}) cannot spend from version=3 tx {tx_v3_parent['txid']} (wtxid={tx_v3_parent['wtxid']})"
 168          assert_raises_rpc_error(-26, expected_error_v2_v3, node.sendrawtransaction, tx_v3_child_rbf_v2["hex"])
 169          self.check_mempool([tx_v3_bip125_rbf_v2["txid"], tx_v3_parent["txid"], tx_v3_child["txid"]])
 170  
 171  
 172      @cleanup()
 173      def test_truc_reorg(self):
 174          node = self.nodes[0]
 175  
 176          # Prep for fork
 177          fork_blocks = create_empty_fork(node)
 178          self.log.info("Test that, during a reorg, TRUC rules are not enforced")
 179          self.check_mempool([])
 180  
 181          # Testing 2<-3 versions allowed
 182          tx_v2_block = self.wallet.create_self_transfer(version=2)
 183  
 184          # Testing 3<-2 versions allowed
 185          tx_v3_block = self.wallet.create_self_transfer(version=3)
 186  
 187          # Testing overly-large child size
 188          tx_v3_block2 = self.wallet.create_self_transfer(version=3)
 189  
 190          # Also create a linear chain of 3 TRUC transactions that will be directly mined, followed by one v2 in-mempool after block is made
 191          tx_chain_1 = self.wallet.create_self_transfer(version=3)
 192          tx_chain_2 = self.wallet.create_self_transfer(utxo_to_spend=tx_chain_1["new_utxo"], version=3)
 193          tx_chain_3 = self.wallet.create_self_transfer(utxo_to_spend=tx_chain_2["new_utxo"], version=3)
 194  
 195          tx_to_mine = [tx_v3_block["hex"], tx_v2_block["hex"], tx_v3_block2["hex"], tx_chain_1["hex"], tx_chain_2["hex"], tx_chain_3["hex"]]
 196          self.generateblock(node, output="raw(42)", transactions=tx_to_mine)
 197  
 198          self.check_mempool([])
 199  
 200          tx_v2_from_v3 = self.wallet.send_self_transfer(from_node=node, utxo_to_spend=tx_v3_block["new_utxo"], version=2)
 201          tx_v3_from_v2 = self.wallet.send_self_transfer(from_node=node, utxo_to_spend=tx_v2_block["new_utxo"], version=3)
 202          tx_v3_child_large = self.wallet.send_self_transfer(from_node=node, utxo_to_spend=tx_v3_block2["new_utxo"], target_vsize=1250, version=3)
 203          assert_greater_than(node.getmempoolentry(tx_v3_child_large["txid"])["vsize"], TRUC_CHILD_MAX_VSIZE)
 204          tx_chain_4 = self.wallet.send_self_transfer(from_node=node, utxo_to_spend=tx_chain_3["new_utxo"], version=2)
 205          self.check_mempool([tx_v2_from_v3["txid"], tx_v3_from_v2["txid"], tx_v3_child_large["txid"], tx_chain_4["txid"]])
 206  
 207          # Reorg should have all block transactions re-accepted, ignoring TRUC enforcement
 208          self.trigger_reorg(fork_blocks)
 209          self.check_mempool([tx_v3_block["txid"], tx_v2_block["txid"], tx_v3_block2["txid"], tx_v2_from_v3["txid"], tx_v3_from_v2["txid"], tx_v3_child_large["txid"], tx_chain_1["txid"], tx_chain_2["txid"], tx_chain_3["txid"], tx_chain_4["txid"]])
 210  
 211      @cleanup(extra_args=["-limitclustercount=1"])
 212      def test_nondefault_package_limits(self):
 213          """
 214          Max standard tx size + TRUC rules imply the cluster rules (at their default
 215          values), but those checks must not be skipped. Ensure both sets of checks are done by
 216          changing the cluster limit configurations.
 217          """
 218          node = self.nodes[0]
 219          self.log.info("Test that a decreased cluster count limit also applies to TRUC child")
 220          parent_target_vsize = 9990
 221          child_target_vsize = 500
 222          tx_v3_parent_large1 = self.wallet.send_self_transfer(
 223              from_node=node,
 224              target_vsize=parent_target_vsize,
 225              version=3
 226          )
 227          tx_v3_child_large1 = self.wallet.create_self_transfer(
 228              utxo_to_spend=tx_v3_parent_large1["new_utxo"],
 229              target_vsize=child_target_vsize,
 230              version=3
 231          )
 232  
 233          # Parent and child are within v3 limits, but cluster count limit is exceeded.
 234          assert_greater_than_or_equal(TRUC_MAX_VSIZE, tx_v3_parent_large1["tx"].get_vsize())
 235          assert_greater_than_or_equal(TRUC_CHILD_MAX_VSIZE, tx_v3_child_large1["tx"].get_vsize())
 236  
 237          assert_raises_rpc_error(-26, "too-large-cluster", node.sendrawtransaction, tx_v3_child_large1["hex"])
 238          self.check_mempool([tx_v3_parent_large1["txid"]])
 239          assert_equal(node.getmempoolentry(tx_v3_parent_large1["txid"])["descendantcount"], 1)
 240          self.generate(node, 1)
 241  
 242          self.log.info("Test that a decreased limitclustersize also applies to TRUC child")
 243          self.restart_node(0, extra_args=["-limitclustersize=10", "-acceptnonstdtxn=1"])
 244          tx_v3_parent_large2 = self.wallet.send_self_transfer(from_node=node, target_vsize=parent_target_vsize, version=3)
 245          tx_v3_child_large2 = self.wallet.create_self_transfer(utxo_to_spend=tx_v3_parent_large2["new_utxo"], target_vsize=child_target_vsize, version=3)
 246          # Parent and child are within TRUC limits
 247          assert_greater_than_or_equal(TRUC_MAX_VSIZE, tx_v3_parent_large2["tx"].get_vsize())
 248          assert_greater_than_or_equal(TRUC_CHILD_MAX_VSIZE, tx_v3_child_large2["tx"].get_vsize())
 249          assert_raises_rpc_error(-26, "too-large-cluster", node.sendrawtransaction, tx_v3_child_large2["hex"])
 250          self.log.info("Test that a decreased limitclustercount also applies to TRUC transactions")
 251          self.restart_node(0, extra_args=["-limitclustercount=1", "-acceptnonstdtxn=1"])
 252          assert_raises_rpc_error(-26, "too-large-cluster", node.sendrawtransaction, tx_v3_child_large2["hex"])
 253          self.check_mempool([tx_v3_parent_large2["txid"]])
 254  
 255      @cleanup()
 256      def test_truc_ancestors_package(self):
 257          self.log.info("Test that TRUC ancestor limits are checked within the package")
 258          node = self.nodes[0]
 259          tx_v3_parent_normal = self.wallet.create_self_transfer(
 260              fee_rate=0,
 261              target_vsize=1001,
 262              version=3
 263          )
 264          tx_v3_parent_2_normal = self.wallet.create_self_transfer(
 265              fee_rate=0,
 266              target_vsize=1001,
 267              version=3
 268          )
 269          tx_v3_child_multiparent = self.wallet.create_self_transfer_multi(
 270              utxos_to_spend=[tx_v3_parent_normal["new_utxo"], tx_v3_parent_2_normal["new_utxo"]],
 271              fee_per_output=10000,
 272              version=3
 273          )
 274          tx_v3_child_heavy = self.wallet.create_self_transfer_multi(
 275              utxos_to_spend=[tx_v3_parent_normal["new_utxo"]],
 276              target_vsize=TRUC_CHILD_MAX_VSIZE + 1,
 277              fee_per_output=10000,
 278              version=3
 279          )
 280  
 281          self.check_mempool([])
 282          result = node.submitpackage([tx_v3_parent_normal["hex"], tx_v3_parent_2_normal["hex"], tx_v3_child_multiparent["hex"]])
 283          assert_equal(result['package_msg'], f"TRUC-violation, tx {tx_v3_child_multiparent['txid']} (wtxid={tx_v3_child_multiparent['wtxid']}) would have too many ancestors")
 284          self.check_mempool([])
 285  
 286          self.check_mempool([])
 287          result = node.submitpackage([tx_v3_parent_normal["hex"], tx_v3_child_heavy["hex"]])
 288          # tx_v3_child_heavy is heavy based on vsize, not sigops.
 289          assert_equal(result['package_msg'], f"TRUC-violation, version=3 child tx {tx_v3_child_heavy['txid']} (wtxid={tx_v3_child_heavy['wtxid']}) is too big: {tx_v3_child_heavy['tx'].get_vsize()} > 1000 virtual bytes")
 290          self.check_mempool([])
 291  
 292          tx_v3_parent = self.wallet.create_self_transfer(version=3)
 293          tx_v3_child = self.wallet.create_self_transfer(utxo_to_spend=tx_v3_parent["new_utxo"], version=3)
 294          tx_v3_grandchild = self.wallet.create_self_transfer(utxo_to_spend=tx_v3_child["new_utxo"], version=3)
 295          result = node.testmempoolaccept([tx_v3_parent["hex"], tx_v3_child["hex"], tx_v3_grandchild["hex"]])
 296          for txresult in result:
 297              assert_equal(txresult["package-error"], f"TRUC-violation, tx {tx_v3_grandchild['txid']} (wtxid={tx_v3_grandchild['wtxid']}) would have too many ancestors")
 298  
 299      @cleanup(extra_args=None)
 300      def test_truc_ancestors_package_and_mempool(self):
 301          """
 302          A TRUC transaction in a package cannot have 2 TRUC parents.
 303          Test that if we have a transaction graph A -> B -> C, where A, B, C are
 304          all TRUC transactions, that we cannot use submitpackage to get the
 305          transactions all into the mempool.
 306  
 307          Verify, in particular, that if A is already in the mempool, then
 308          submitpackage(B, C) will fail.
 309          """
 310          node = self.nodes[0]
 311          self.log.info("Test that TRUC ancestor limits include transactions within the package and all in-mempool ancestors")
 312          # This is our transaction "A":
 313          tx_in_mempool = self.wallet.send_self_transfer(from_node=node, version=3)
 314  
 315          # Verify that A is in the mempool
 316          self.check_mempool([tx_in_mempool["txid"]])
 317  
 318          # tx_0fee_parent is our transaction "B"; just create it.
 319          tx_0fee_parent = self.wallet.create_self_transfer(utxo_to_spend=tx_in_mempool["new_utxo"], fee=0, fee_rate=0, version=3)
 320  
 321          # tx_child_violator is our transaction "C"; create it:
 322          tx_child_violator = self.wallet.create_self_transfer_multi(utxos_to_spend=[tx_0fee_parent["new_utxo"]], version=3)
 323  
 324          # submitpackage(B, C) should fail
 325          result = node.submitpackage([tx_0fee_parent["hex"], tx_child_violator["hex"]])
 326          assert_equal(result['package_msg'], f"TRUC-violation, tx {tx_child_violator['txid']} (wtxid={tx_child_violator['wtxid']}) would have too many ancestors")
 327          self.check_mempool([tx_in_mempool["txid"]])
 328  
 329      @cleanup(extra_args=None)
 330      def test_sibling_eviction_package(self):
 331          """
 332          When a transaction has a mempool sibling, it may be eligible for sibling eviction.
 333          However, this option is only available in single transaction acceptance. It doesn't work in
 334          a multi-testmempoolaccept (where RBF is disabled) or when doing package CPFP.
 335          """
 336          self.log.info("Test TRUC sibling eviction in submitpackage and multi-testmempoolaccept")
 337          node = self.nodes[0]
 338          # Add a parent + child to mempool
 339          tx_mempool_parent = self.wallet.send_self_transfer_multi(
 340              from_node=node,
 341              utxos_to_spend=[self.wallet.get_utxo()],
 342              num_outputs=2,
 343              version=3
 344          )
 345          tx_mempool_sibling = self.wallet.send_self_transfer(
 346              from_node=node,
 347              utxo_to_spend=tx_mempool_parent["new_utxos"][0],
 348              version=3
 349          )
 350          self.check_mempool([tx_mempool_parent["txid"], tx_mempool_sibling["txid"]])
 351  
 352          tx_sibling_1 = self.wallet.create_self_transfer(
 353              utxo_to_spend=tx_mempool_parent["new_utxos"][1],
 354              version=3,
 355              fee_rate=DEFAULT_FEE*100,
 356          )
 357          tx_has_mempool_uncle = self.wallet.create_self_transfer(utxo_to_spend=tx_sibling_1["new_utxo"], version=3)
 358  
 359          tx_sibling_2 = self.wallet.create_self_transfer(
 360              utxo_to_spend=tx_mempool_parent["new_utxos"][0],
 361              version=3,
 362              fee_rate=DEFAULT_FEE*200,
 363          )
 364  
 365          tx_sibling_3 = self.wallet.create_self_transfer(
 366              utxo_to_spend=tx_mempool_parent["new_utxos"][1],
 367              version=3,
 368              fee_rate=0,
 369          )
 370          tx_bumps_parent_with_sibling = self.wallet.create_self_transfer(
 371              utxo_to_spend=tx_sibling_3["new_utxo"],
 372              version=3,
 373              fee_rate=DEFAULT_FEE*300,
 374          )
 375  
 376          # Fails with another non-related transaction via testmempoolaccept
 377          tx_unrelated = self.wallet.create_self_transfer(version=3)
 378          result_test_unrelated = node.testmempoolaccept([tx_sibling_1["hex"], tx_unrelated["hex"]])
 379          assert_equal(result_test_unrelated[0]["reject-reason"], "TRUC-violation")
 380  
 381          # Fails in a package via testmempoolaccept
 382          result_test_1p1c = node.testmempoolaccept([tx_sibling_1["hex"], tx_has_mempool_uncle["hex"]])
 383          assert_equal(result_test_1p1c[0]["reject-reason"], "TRUC-violation")
 384  
 385          # Allowed when tx is submitted in a package and evaluated individually.
 386          # Note that the child failed since it would be the 3rd generation.
 387          result_package_indiv = node.submitpackage([tx_sibling_1["hex"], tx_has_mempool_uncle["hex"]])
 388          self.check_mempool([tx_mempool_parent["txid"], tx_sibling_1["txid"]])
 389          expected_error_gen3 = f"TRUC-violation, tx {tx_has_mempool_uncle['txid']} (wtxid={tx_has_mempool_uncle['wtxid']}) would have too many ancestors"
 390  
 391          assert_equal(result_package_indiv["tx-results"][tx_has_mempool_uncle['wtxid']]['error'], expected_error_gen3)
 392  
 393          # Allowed when tx is submitted in a package with in-mempool parent (which is deduplicated).
 394          node.submitpackage([tx_mempool_parent["hex"], tx_sibling_2["hex"]])
 395          self.check_mempool([tx_mempool_parent["txid"], tx_sibling_2["txid"]])
 396  
 397          # Child cannot pay for sibling eviction for parent, as it violates TRUC topology limits
 398          result_package_cpfp = node.submitpackage([tx_sibling_3["hex"], tx_bumps_parent_with_sibling["hex"]])
 399          self.check_mempool([tx_mempool_parent["txid"], tx_sibling_2["txid"]])
 400          expected_error_cpfp = f"TRUC-violation, tx {tx_mempool_parent['txid']} (wtxid={tx_mempool_parent['wtxid']}) would exceed descendant count limit"
 401  
 402          assert_equal(result_package_cpfp["tx-results"][tx_sibling_3['wtxid']]['error'], expected_error_cpfp)
 403  
 404  
 405      @cleanup()
 406      def test_truc_package_inheritance(self):
 407          self.log.info("Test that TRUC inheritance is checked within package")
 408          node = self.nodes[0]
 409          tx_v3_parent = self.wallet.create_self_transfer(
 410              fee_rate=0,
 411              target_vsize=1001,
 412              version=3
 413          )
 414          tx_v2_child = self.wallet.create_self_transfer_multi(
 415              utxos_to_spend=[tx_v3_parent["new_utxo"]],
 416              fee_per_output=10000,
 417              version=2
 418          )
 419          self.check_mempool([])
 420          result = node.submitpackage([tx_v3_parent["hex"], tx_v2_child["hex"]])
 421          assert_equal(result['package_msg'], f"TRUC-violation, non-version=3 tx {tx_v2_child['txid']} (wtxid={tx_v2_child['wtxid']}) cannot spend from version=3 tx {tx_v3_parent['txid']} (wtxid={tx_v3_parent['wtxid']})")
 422          self.check_mempool([])
 423  
 424      @cleanup(extra_args=None)
 425      def test_truc_in_testmempoolaccept(self):
 426          node = self.nodes[0]
 427  
 428          self.log.info("Test that TRUC inheritance is accurately assessed in testmempoolaccept")
 429          tx_v2 = self.wallet.create_self_transfer(version=2)
 430          tx_v2_from_v2 = self.wallet.create_self_transfer(utxo_to_spend=tx_v2["new_utxo"], version=2)
 431          tx_v3_from_v2 = self.wallet.create_self_transfer(utxo_to_spend=tx_v2["new_utxo"], version=3)
 432          tx_v3 = self.wallet.create_self_transfer(version=3)
 433          tx_v2_from_v3 = self.wallet.create_self_transfer(utxo_to_spend=tx_v3["new_utxo"], version=2)
 434          tx_v3_from_v3 = self.wallet.create_self_transfer(utxo_to_spend=tx_v3["new_utxo"], version=3)
 435  
 436          # testmempoolaccept paths don't require child-with-parents topology. Ensure that topology
 437          # assumptions aren't made in inheritance checks.
 438          test_accept_v2_and_v3 = node.testmempoolaccept([tx_v2["hex"], tx_v3["hex"]])
 439          assert all([result["allowed"] for result in test_accept_v2_and_v3])
 440  
 441          test_accept_v3_from_v2 = node.testmempoolaccept([tx_v2["hex"], tx_v3_from_v2["hex"]])
 442          expected_error_v3_from_v2 = f"TRUC-violation, version=3 tx {tx_v3_from_v2['txid']} (wtxid={tx_v3_from_v2['wtxid']}) cannot spend from non-version=3 tx {tx_v2['txid']} (wtxid={tx_v2['wtxid']})"
 443          for result in test_accept_v3_from_v2:
 444              assert_equal(result["package-error"], expected_error_v3_from_v2)
 445  
 446          test_accept_v2_from_v3 = node.testmempoolaccept([tx_v3["hex"], tx_v2_from_v3["hex"]])
 447          expected_error_v2_from_v3 = f"TRUC-violation, non-version=3 tx {tx_v2_from_v3['txid']} (wtxid={tx_v2_from_v3['wtxid']}) cannot spend from version=3 tx {tx_v3['txid']} (wtxid={tx_v3['wtxid']})"
 448          for result in test_accept_v2_from_v3:
 449              assert_equal(result["package-error"], expected_error_v2_from_v3)
 450  
 451          test_accept_pairs = node.testmempoolaccept([tx_v2["hex"], tx_v3["hex"], tx_v2_from_v2["hex"], tx_v3_from_v3["hex"]])
 452          assert all([result["allowed"] for result in test_accept_pairs])
 453  
 454          self.log.info("Test that descendant violations are caught in testmempoolaccept")
 455          tx_v3_independent = self.wallet.create_self_transfer(version=3)
 456          tx_v3_parent = self.wallet.create_self_transfer_multi(num_outputs=2, version=3)
 457          tx_v3_child_1 = self.wallet.create_self_transfer(utxo_to_spend=tx_v3_parent["new_utxos"][0], version=3)
 458          tx_v3_child_2 = self.wallet.create_self_transfer(utxo_to_spend=tx_v3_parent["new_utxos"][1], version=3)
 459          test_accept_2children = node.testmempoolaccept([tx_v3_parent["hex"], tx_v3_child_1["hex"], tx_v3_child_2["hex"]])
 460          expected_error_2children = f"TRUC-violation, tx {tx_v3_parent['txid']} (wtxid={tx_v3_parent['wtxid']}) would exceed descendant count limit"
 461          for result in test_accept_2children:
 462              assert_equal(result["package-error"], expected_error_2children)
 463  
 464          # Extra TRUC transaction does not get incorrectly marked as extra descendant
 465          test_accept_1child_with_exra = node.testmempoolaccept([tx_v3_parent["hex"], tx_v3_child_1["hex"], tx_v3_independent["hex"]])
 466          assert all([result["allowed"] for result in test_accept_1child_with_exra])
 467  
 468          # Extra TRUC transaction does not make us ignore the extra descendant
 469          test_accept_2children_with_exra = node.testmempoolaccept([tx_v3_parent["hex"], tx_v3_child_1["hex"], tx_v3_child_2["hex"], tx_v3_independent["hex"]])
 470          expected_error_extra = f"TRUC-violation, tx {tx_v3_parent['txid']} (wtxid={tx_v3_parent['wtxid']}) would exceed descendant count limit"
 471          for result in test_accept_2children_with_exra:
 472              assert_equal(result["package-error"], expected_error_extra)
 473          # Same result if the parent is already in mempool
 474          node.sendrawtransaction(tx_v3_parent["hex"])
 475          test_accept_2children_with_in_mempool_parent = node.testmempoolaccept([tx_v3_child_1["hex"], tx_v3_child_2["hex"]])
 476          for result in test_accept_2children_with_in_mempool_parent:
 477              assert_equal(result["package-error"], expected_error_extra)
 478  
 479      @cleanup(extra_args=None)
 480      def test_reorg_2child_rbf(self):
 481          node = self.nodes[0]
 482          self.log.info("Test that children of a TRUC transaction can be replaced individually, even if there are multiple due to reorg")
 483  
 484          # Prep for fork
 485          fork_blocks = create_empty_fork(node)
 486          ancestor_tx = self.wallet.send_self_transfer_multi(from_node=node, num_outputs=2, version=3)
 487          self.check_mempool([ancestor_tx["txid"]])
 488  
 489          self.generate(node, 1)[0]
 490          self.check_mempool([])
 491  
 492          child_1 = self.wallet.send_self_transfer(from_node=node, version=3, utxo_to_spend=ancestor_tx["new_utxos"][0])
 493          child_2 = self.wallet.send_self_transfer(from_node=node, version=3, utxo_to_spend=ancestor_tx["new_utxos"][1])
 494          self.check_mempool([child_1["txid"], child_2["txid"]])
 495  
 496          # Create a reorg, causing ancestor_tx to exceed the 1-child limit
 497          self.trigger_reorg(fork_blocks)
 498          self.check_mempool([ancestor_tx["txid"], child_1["txid"], child_2["txid"]])
 499          assert_equal(node.getmempoolentry(ancestor_tx["txid"])["descendantcount"], 3)
 500  
 501          # Create a replacement of child_1. It does not conflict with child_2.
 502          child_1_conflict = self.wallet.send_self_transfer(from_node=node, version=3, utxo_to_spend=ancestor_tx["new_utxos"][0], fee_rate=Decimal("0.01"))
 503  
 504          # Ensure child_1 and child_1_conflict are different transactions
 505          assert_not_equal(child_1_conflict["txid"], child_1["txid"])
 506          self.check_mempool([ancestor_tx["txid"], child_1_conflict["txid"], child_2["txid"]])
 507          assert_equal(node.getmempoolentry(ancestor_tx["txid"])["descendantcount"], 3)
 508  
 509      @cleanup(extra_args=None)
 510      def test_truc_sibling_eviction(self):
 511          self.log.info("Test sibling eviction for TRUC")
 512          node = self.nodes[0]
 513          tx_v3_parent = self.wallet.send_self_transfer_multi(from_node=node, num_outputs=2, version=3)
 514          # This is the sibling to replace
 515          tx_v3_child_1 = self.wallet.send_self_transfer(
 516              from_node=node, utxo_to_spend=tx_v3_parent["new_utxos"][0], fee_rate=DEFAULT_FEE * 2, version=3
 517          )
 518          assert tx_v3_child_1["txid"] in node.getrawmempool()
 519  
 520          self.log.info("Test tx must be higher feerate than sibling to evict it")
 521          tx_v3_child_2_rule6 = self.wallet.create_self_transfer(
 522              utxo_to_spend=tx_v3_parent["new_utxos"][1], fee_rate=DEFAULT_FEE, version=3
 523          )
 524          rule6_str = f"insufficient fee (including sibling eviction), rejecting replacement {tx_v3_child_2_rule6['txid']}"
 525          assert_raises_rpc_error(-26, rule6_str, node.sendrawtransaction, tx_v3_child_2_rule6["hex"])
 526          self.check_mempool([tx_v3_parent['txid'], tx_v3_child_1['txid']])
 527  
 528          self.log.info("Test tx must meet absolute fee rules to evict sibling")
 529          tx_v3_child_2_rule4 = self.wallet.create_self_transfer(
 530              utxo_to_spend=tx_v3_parent["new_utxos"][1], fee_rate=2 * DEFAULT_FEE + Decimal("0.00000001"), version=3
 531          )
 532          rule4_str = f"insufficient fee (including sibling eviction), rejecting replacement {tx_v3_child_2_rule4['txid']}, not enough additional fees to relay"
 533          assert_raises_rpc_error(-26, rule4_str, node.sendrawtransaction, tx_v3_child_2_rule4["hex"])
 534          self.check_mempool([tx_v3_parent['txid'], tx_v3_child_1['txid']])
 535  
 536          self.log.info("Test tx cannot cause more than 100 evictions including RBF and sibling eviction")
 537          # First add 4 groups of 25 transactions.
 538          utxos_for_conflict = []
 539          txids_v2_100 = []
 540          for _ in range(4):
 541              confirmed_utxo = self.wallet.get_utxo(confirmed_only=True)
 542              utxos_for_conflict.append(confirmed_utxo)
 543              # 25 is within descendant limits
 544              chain_length = int(MAX_REPLACEMENT_CANDIDATES / 4)
 545              chain = self.wallet.create_self_transfer_chain(chain_length=chain_length, utxo_to_spend=confirmed_utxo)
 546              for item in chain:
 547                  txids_v2_100.append(item["txid"])
 548                  node.sendrawtransaction(item["hex"])
 549          self.check_mempool(txids_v2_100 + [tx_v3_parent["txid"], tx_v3_child_1["txid"]])
 550  
 551          # Replacing 100 transactions is fine
 552          tx_v3_replacement_only = self.wallet.create_self_transfer_multi(utxos_to_spend=utxos_for_conflict, fee_per_output=4000000)
 553          # Override maxfeerate - it costs a lot to replace these 100 transactions.
 554          assert node.testmempoolaccept([tx_v3_replacement_only["hex"]], maxfeerate=0)[0]["allowed"]
 555          self.check_mempool(txids_v2_100 + [tx_v3_parent["txid"], tx_v3_child_1["txid"]])
 556  
 557          self.log.info("Test sibling eviction is successful if it meets all RBF rules")
 558          tx_v3_child_2 = self.wallet.create_self_transfer(
 559              utxo_to_spend=tx_v3_parent["new_utxos"][1], fee_rate=DEFAULT_FEE*10, version=3
 560          )
 561          node.sendrawtransaction(tx_v3_child_2["hex"])
 562          self.check_mempool(txids_v2_100 + [tx_v3_parent["txid"], tx_v3_child_2["txid"]])
 563  
 564          self.log.info("Test that it's possible to do a sibling eviction and RBF at the same time")
 565          utxo_unrelated_conflict = self.wallet.get_utxo(confirmed_only=True)
 566          tx_unrelated_replacee = self.wallet.send_self_transfer(from_node=node, utxo_to_spend=utxo_unrelated_conflict)
 567          assert tx_unrelated_replacee["txid"] in node.getrawmempool()
 568  
 569          fee_to_beat = max(int(tx_v3_child_2["fee"] * COIN), int(tx_unrelated_replacee["fee"]*COIN))
 570  
 571          tx_v3_child_3 = self.wallet.create_self_transfer_multi(
 572              utxos_to_spend=[tx_v3_parent["new_utxos"][0], utxo_unrelated_conflict], fee_per_output=fee_to_beat*4, version=3
 573          )
 574          node.sendrawtransaction(tx_v3_child_3["hex"])
 575          self.check_mempool(txids_v2_100 + [tx_v3_parent["txid"], tx_v3_child_3["txid"]])
 576  
 577      @cleanup(extra_args=None)
 578      def test_reorg_sibling_eviction_1p2c(self):
 579          node = self.nodes[0]
 580          self.log.info("Test that sibling eviction is not allowed when multiple siblings exist")
 581  
 582          # Prep for fork
 583          fork_blocks = create_empty_fork(node)
 584          tx_with_multi_children = self.wallet.send_self_transfer_multi(from_node=node, num_outputs=3, version=3, confirmed_only=True)
 585          self.check_mempool([tx_with_multi_children["txid"]])
 586  
 587          self.generate(node, 1)
 588          self.check_mempool([])
 589  
 590          tx_with_sibling1 = self.wallet.send_self_transfer(from_node=node, version=3, utxo_to_spend=tx_with_multi_children["new_utxos"][0])
 591          tx_with_sibling2 = self.wallet.send_self_transfer(from_node=node, version=3, utxo_to_spend=tx_with_multi_children["new_utxos"][1])
 592          self.check_mempool([tx_with_sibling1["txid"], tx_with_sibling2["txid"]])
 593  
 594          # Create a reorg, bringing tx_with_multi_children back into the mempool with a descendant count of 3.
 595          self.trigger_reorg(fork_blocks)
 596          self.check_mempool([tx_with_multi_children["txid"], tx_with_sibling1["txid"], tx_with_sibling2["txid"]])
 597          assert_equal(node.getmempoolentry(tx_with_multi_children["txid"])["descendantcount"], 3)
 598  
 599          # Sibling eviction is not allowed because there are two siblings
 600          tx_with_sibling3 = self.wallet.create_self_transfer(
 601              version=3,
 602              utxo_to_spend=tx_with_multi_children["new_utxos"][2],
 603              fee_rate=DEFAULT_FEE*50
 604          )
 605          expected_error_2siblings = f"TRUC-violation, tx {tx_with_multi_children['txid']} (wtxid={tx_with_multi_children['wtxid']}) would exceed descendant count limit"
 606          assert_raises_rpc_error(-26, expected_error_2siblings, node.sendrawtransaction, tx_with_sibling3["hex"])
 607  
 608          # However, an RBF (with conflicting inputs) is possible even if the resulting cluster size exceeds 2
 609          tx_with_sibling3_rbf = self.wallet.send_self_transfer(
 610              from_node=node,
 611              version=3,
 612              utxo_to_spend=tx_with_multi_children["new_utxos"][0],
 613              fee_rate=DEFAULT_FEE*50
 614          )
 615          self.check_mempool([tx_with_multi_children["txid"], tx_with_sibling3_rbf["txid"], tx_with_sibling2["txid"]])
 616  
 617      @cleanup(extra_args=None)
 618      def test_minrelay_in_package_combos(self):
 619          node = self.nodes[0]
 620          self.log.info("Test that all transaction versions can be under minrelaytxfee for various settings...")
 621  
 622          for minrelay_setting in (0, 5, 10, 100, 500, 1000, 5000, 333333, 2500000):
 623              self.log.info(f"-> Test -minrelaytxfee={minrelay_setting}sat/kvB...")
 624              setting_decimal = minrelay_setting / Decimal(COIN)
 625              self.restart_node(0, extra_args=[f"-minrelaytxfee={setting_decimal:.8f}", "-persistmempool=0"])
 626              minrelayfeerate = node.getmempoolinfo()["minrelaytxfee"]
 627              high_feerate = minrelayfeerate * 50
 628  
 629              tx_v3_0fee_parent = self.wallet.create_self_transfer(fee=0, fee_rate=0, confirmed_only=True, version=3)
 630              tx_v3_child = self.wallet.create_self_transfer(utxo_to_spend=tx_v3_0fee_parent["new_utxo"], fee_rate=high_feerate, version=3)
 631              total_v3_fee = tx_v3_child["fee"] + tx_v3_0fee_parent["fee"]
 632              total_v3_size = tx_v3_child["tx"].get_vsize() + tx_v3_0fee_parent["tx"].get_vsize()
 633              assert_greater_than_or_equal(total_v3_fee, get_fee(total_v3_size, minrelayfeerate))
 634              if minrelayfeerate > 0:
 635                  assert_greater_than(get_fee(tx_v3_0fee_parent["tx"].get_vsize(), minrelayfeerate), 0)
 636                  # Always need to pay at least 1 satoshi for entry, even if minimum feerate is very low
 637                  assert_greater_than(total_v3_fee, 0)
 638                  # Also create a version where the child is at minrelaytxfee
 639                  tx_v3_child_minrelay = self.wallet.create_self_transfer(utxo_to_spend=tx_v3_0fee_parent["new_utxo"], fee_rate=minrelayfeerate, version=3)
 640                  result_truc_minrelay = node.submitpackage([tx_v3_0fee_parent["hex"], tx_v3_child_minrelay["hex"]])
 641                  assert_equal(result_truc_minrelay["package_msg"], "transaction failed")
 642  
 643              tx_v2_0fee_parent = self.wallet.create_self_transfer(fee=0, fee_rate=0, confirmed_only=True, version=2)
 644              tx_v2_child = self.wallet.create_self_transfer(utxo_to_spend=tx_v2_0fee_parent["new_utxo"], fee_rate=high_feerate, version=2)
 645              total_v2_fee = tx_v2_child["fee"] + tx_v2_0fee_parent["fee"]
 646              total_v2_size = tx_v2_child["tx"].get_vsize() + tx_v2_0fee_parent["tx"].get_vsize()
 647              assert_greater_than_or_equal(total_v2_fee, get_fee(total_v2_size, minrelayfeerate))
 648              if minrelayfeerate > 0:
 649                  assert_greater_than(get_fee(tx_v2_0fee_parent["tx"].get_vsize(), minrelayfeerate), 0)
 650                  # Always need to pay at least 1 satoshi for entry, even if minimum feerate is very low
 651                  assert_greater_than(total_v2_fee, 0)
 652  
 653              result_truc = node.submitpackage([tx_v3_0fee_parent["hex"], tx_v3_child["hex"]], maxfeerate=0)
 654              assert_equal(result_truc["package_msg"], "success")
 655  
 656              result_non_truc = node.submitpackage([tx_v2_0fee_parent["hex"], tx_v2_child["hex"]], maxfeerate=0)
 657              assert_equal(result_non_truc["package_msg"], "success")
 658              self.check_mempool([tx_v2_0fee_parent["txid"], tx_v2_child["txid"], tx_v3_0fee_parent["txid"], tx_v3_child["txid"]])
 659  
 660  
 661      def run_test(self):
 662          self.log.info("Generate blocks to create UTXOs")
 663          node = self.nodes[0]
 664          self.wallet = MiniWallet(node)
 665          self.generate(self.wallet, 200)
 666          self.test_truc_max_vsize()
 667          self.test_truc_acceptance()
 668          self.test_truc_replacement()
 669          self.test_truc_reorg()
 670          self.test_nondefault_package_limits()
 671          self.test_truc_ancestors_package()
 672          self.test_truc_ancestors_package_and_mempool()
 673          self.test_sibling_eviction_package()
 674          self.test_truc_package_inheritance()
 675          self.test_truc_in_testmempoolaccept()
 676          self.test_reorg_2child_rbf()
 677          self.test_truc_sibling_eviction()
 678          self.test_reorg_sibling_eviction_1p2c()
 679          self.test_minrelay_in_package_combos()
 680  
 681  
 682  if __name__ == "__main__":
 683      MempoolTRUC(__file__).main()
 684