mempool_truc.py raw

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