/ test / functional / mempool_truc.py
mempool_truc.py
  1  #!/usr/bin/env python3
  2  # Copyright (c) 2024 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              try:
 33                  if extra_args is not None:
 34                      self.restart_node(0, extra_args=extra_args)
 35                  func(self)
 36              finally:
 37                  # Clear mempool again after test
 38                  self.generate(self.nodes[0], 1)
 39                  if extra_args is not None:
 40                      self.restart_node(0)
 41          return wrapper
 42      return decorator
 43  
 44  class MempoolTRUC(BitcoinTestFramework):
 45      def set_test_params(self):
 46          self.num_nodes = 1
 47          self.extra_args = [[]]
 48          self.setup_clean_chain = True
 49  
 50      def check_mempool(self, txids):
 51          """Assert exact contents of the node's mempool (by txid)."""
 52          mempool_contents = self.nodes[0].getrawmempool()
 53          assert_equal(len(txids), len(mempool_contents))
 54          assert all([txid in txids for txid in mempool_contents])
 55  
 56      def trigger_reorg(self, fork_blocks):
 57          """Trigger reorg of the fork blocks."""
 58          for block in fork_blocks:
 59              self.nodes[0].submitblock(block.serialize().hex())
 60          assert_equal(self.nodes[0].getbestblockhash(), fork_blocks[-1].hash_hex)
 61  
 62      @cleanup()
 63      def test_truc_max_vsize(self):
 64          node = self.nodes[0]
 65          self.log.info("Test TRUC-specific maximum transaction vsize")
 66          tx_v3_heavy = self.wallet.create_self_transfer(target_vsize=TRUC_MAX_VSIZE + 1, version=3)
 67          assert_greater_than_or_equal(tx_v3_heavy["tx"].get_vsize(), TRUC_MAX_VSIZE)
 68          expected_error_heavy = f"TRUC-violation, version=3 tx {tx_v3_heavy['txid']} (wtxid={tx_v3_heavy['wtxid']}) is too big"
 69          assert_raises_rpc_error(-26, expected_error_heavy, node.sendrawtransaction, tx_v3_heavy["hex"])
 70          self.check_mempool([])
 71  
 72          # Ensure we are hitting the TRUC-specific limit and not something else
 73          tx_v2_heavy = self.wallet.send_self_transfer(from_node=node, target_vsize=TRUC_MAX_VSIZE + 1, version=2)
 74          self.check_mempool([tx_v2_heavy["txid"]])
 75  
 76      @cleanup()
 77      def test_truc_acceptance(self):
 78          node = self.nodes[0]
 79          self.log.info("Test a child of a TRUC transaction cannot be more than 1000vB")
 80          tx_v3_parent_normal = self.wallet.send_self_transfer(from_node=node, version=3)
 81          self.check_mempool([tx_v3_parent_normal["txid"]])
 82          tx_v3_child_heavy = self.wallet.create_self_transfer(
 83              utxo_to_spend=tx_v3_parent_normal["new_utxo"],
 84              target_vsize=TRUC_CHILD_MAX_VSIZE + 1,
 85              version=3
 86          )
 87          assert_greater_than_or_equal(tx_v3_child_heavy["tx"].get_vsize(), TRUC_CHILD_MAX_VSIZE)
 88          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"
 89          assert_raises_rpc_error(-26, expected_error_child_heavy, node.sendrawtransaction, tx_v3_child_heavy["hex"])
 90          self.check_mempool([tx_v3_parent_normal["txid"]])
 91          # tx has no descendants
 92          assert_equal(node.getmempoolentry(tx_v3_parent_normal["txid"])["descendantcount"], 1)
 93  
 94          self.log.info("Test that, during replacements, only the new transaction counts for TRUC descendant limit")
 95          tx_v3_child_almost_heavy = self.wallet.send_self_transfer(
 96              from_node=node,
 97              fee_rate=DEFAULT_FEE,
 98              utxo_to_spend=tx_v3_parent_normal["new_utxo"],
 99              target_vsize=TRUC_CHILD_MAX_VSIZE - 3,
100              version=3
101          )
102          assert_greater_than_or_equal(TRUC_CHILD_MAX_VSIZE, tx_v3_child_almost_heavy["tx"].get_vsize())
103          self.check_mempool([tx_v3_parent_normal["txid"], tx_v3_child_almost_heavy["txid"]])
104          assert_equal(node.getmempoolentry(tx_v3_parent_normal["txid"])["descendantcount"], 2)
105          tx_v3_child_almost_heavy_rbf = self.wallet.send_self_transfer(
106              from_node=node,
107              fee_rate=DEFAULT_FEE * 2,
108              utxo_to_spend=tx_v3_parent_normal["new_utxo"],
109              target_vsize=875,
110              version=3
111          )
112          assert_greater_than_or_equal(tx_v3_child_almost_heavy["tx"].get_vsize() + tx_v3_child_almost_heavy_rbf["tx"].get_vsize(),
113                                       TRUC_CHILD_MAX_VSIZE)
114          self.check_mempool([tx_v3_parent_normal["txid"], tx_v3_child_almost_heavy_rbf["txid"]])
115          assert_equal(node.getmempoolentry(tx_v3_parent_normal["txid"])["descendantcount"], 2)
116  
117      @cleanup(extra_args=None)
118      def test_truc_replacement(self):
119          node = self.nodes[0]
120          self.log.info("Test TRUC transactions may be replaced by TRUC transactions")
121          utxo_v3_bip125 = self.wallet.get_utxo()
122          tx_v3_bip125 = self.wallet.send_self_transfer(
123              from_node=node,
124              fee_rate=DEFAULT_FEE,
125              utxo_to_spend=utxo_v3_bip125,
126              version=3
127          )
128          self.check_mempool([tx_v3_bip125["txid"]])
129  
130          tx_v3_bip125_rbf = self.wallet.send_self_transfer(
131              from_node=node,
132              fee_rate=DEFAULT_FEE * 2,
133              utxo_to_spend=utxo_v3_bip125,
134              version=3
135          )
136          self.check_mempool([tx_v3_bip125_rbf["txid"]])
137  
138          self.log.info("Test TRUC transactions may be replaced by non-TRUC (BIP125) transactions")
139          tx_v3_bip125_rbf_v2 = self.wallet.send_self_transfer(
140              from_node=node,
141              fee_rate=DEFAULT_FEE * 3,
142              utxo_to_spend=utxo_v3_bip125,
143              version=2
144          )
145          self.check_mempool([tx_v3_bip125_rbf_v2["txid"]])
146  
147          self.log.info("Test that replacements cannot cause violation of inherited TRUC")
148          utxo_v3_parent = self.wallet.get_utxo()
149          tx_v3_parent = self.wallet.send_self_transfer(
150              from_node=node,
151              fee_rate=DEFAULT_FEE,
152              utxo_to_spend=utxo_v3_parent,
153              version=3
154          )
155          tx_v3_child = self.wallet.send_self_transfer(
156              from_node=node,
157              fee_rate=DEFAULT_FEE,
158              utxo_to_spend=tx_v3_parent["new_utxo"],
159              version=3
160          )
161          self.check_mempool([tx_v3_bip125_rbf_v2["txid"], tx_v3_parent["txid"], tx_v3_child["txid"]])
162  
163          tx_v3_child_rbf_v2 = self.wallet.create_self_transfer(
164              fee_rate=DEFAULT_FEE * 2,
165              utxo_to_spend=tx_v3_parent["new_utxo"],
166              version=2
167          )
168          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']})"
169          assert_raises_rpc_error(-26, expected_error_v2_v3, node.sendrawtransaction, tx_v3_child_rbf_v2["hex"])
170          self.check_mempool([tx_v3_bip125_rbf_v2["txid"], tx_v3_parent["txid"], tx_v3_child["txid"]])
171  
172  
173      @cleanup()
174      def test_truc_reorg(self):
175          node = self.nodes[0]
176  
177          # Prep for fork
178          fork_blocks = create_empty_fork(node)
179          self.log.info("Test that, during a reorg, TRUC rules are not enforced")
180          self.check_mempool([])
181  
182          # Testing 2<-3 versions allowed
183          tx_v2_block = self.wallet.create_self_transfer(version=2)
184  
185          # Testing 3<-2 versions allowed
186          tx_v3_block = self.wallet.create_self_transfer(version=3)
187  
188          # Testing overly-large child size
189          tx_v3_block2 = self.wallet.create_self_transfer(version=3)
190  
191          # Also create a linear chain of 3 TRUC transactions that will be directly mined, followed by one v2 in-mempool after block is made
192          tx_chain_1 = self.wallet.create_self_transfer(version=3)
193          tx_chain_2 = self.wallet.create_self_transfer(utxo_to_spend=tx_chain_1["new_utxo"], version=3)
194          tx_chain_3 = self.wallet.create_self_transfer(utxo_to_spend=tx_chain_2["new_utxo"], version=3)
195  
196          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"]]
197          self.generateblock(node, output="raw(42)", transactions=tx_to_mine)
198  
199          self.check_mempool([])
200  
201          tx_v2_from_v3 = self.wallet.send_self_transfer(from_node=node, utxo_to_spend=tx_v3_block["new_utxo"], version=2)
202          tx_v3_from_v2 = self.wallet.send_self_transfer(from_node=node, utxo_to_spend=tx_v2_block["new_utxo"], version=3)
203          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)
204          assert_greater_than(node.getmempoolentry(tx_v3_child_large["txid"])["vsize"], TRUC_CHILD_MAX_VSIZE)
205          tx_chain_4 = self.wallet.send_self_transfer(from_node=node, utxo_to_spend=tx_chain_3["new_utxo"], version=2)
206          self.check_mempool([tx_v2_from_v3["txid"], tx_v3_from_v2["txid"], tx_v3_child_large["txid"], tx_chain_4["txid"]])
207  
208          # Reorg should have all block transactions re-accepted, ignoring TRUC enforcement
209          self.trigger_reorg(fork_blocks)
210          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"]])
211  
212      @cleanup(extra_args=["-limitclustercount=1"])
213      def test_nondefault_package_limits(self):
214          """
215          Max standard tx size + TRUC rules imply the cluster rules (at their default
216          values), but those checks must not be skipped. Ensure both sets of checks are done by
217          changing the cluster limit configurations.
218          """
219          node = self.nodes[0]
220          self.log.info("Test that a decreased cluster count limit also applies to TRUC child")
221          parent_target_vsize = 9990
222          child_target_vsize = 500
223          tx_v3_parent_large1 = self.wallet.send_self_transfer(
224              from_node=node,
225              target_vsize=parent_target_vsize,
226              version=3
227          )
228          tx_v3_child_large1 = self.wallet.create_self_transfer(
229              utxo_to_spend=tx_v3_parent_large1["new_utxo"],
230              target_vsize=child_target_vsize,
231              version=3
232          )
233  
234          # Parent and child are within v3 limits, but cluster count limit is exceeded.
235          assert_greater_than_or_equal(TRUC_MAX_VSIZE, tx_v3_parent_large1["tx"].get_vsize())
236          assert_greater_than_or_equal(TRUC_CHILD_MAX_VSIZE, tx_v3_child_large1["tx"].get_vsize())
237  
238          assert_raises_rpc_error(-26, "too-large-cluster", node.sendrawtransaction, tx_v3_child_large1["hex"])
239          self.check_mempool([tx_v3_parent_large1["txid"]])
240          assert_equal(node.getmempoolentry(tx_v3_parent_large1["txid"])["descendantcount"], 1)
241          self.generate(node, 1)
242  
243          self.log.info("Test that a decreased limitclustersize also applies to TRUC child")
244          self.restart_node(0, extra_args=["-limitclustersize=10", "-acceptnonstdtxn=1"])
245          tx_v3_parent_large2 = self.wallet.send_self_transfer(from_node=node, target_vsize=parent_target_vsize, version=3)
246          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)
247          # Parent and child are within TRUC limits
248          assert_greater_than_or_equal(TRUC_MAX_VSIZE, tx_v3_parent_large2["tx"].get_vsize())
249          assert_greater_than_or_equal(TRUC_CHILD_MAX_VSIZE, tx_v3_child_large2["tx"].get_vsize())
250          assert_raises_rpc_error(-26, "too-large-cluster", node.sendrawtransaction, tx_v3_child_large2["hex"])
251          self.log.info("Test that a decreased limitclustercount also applies to TRUC transactions")
252          self.restart_node(0, extra_args=["-limitclustercount=1", "-acceptnonstdtxn=1"])
253          assert_raises_rpc_error(-26, "too-large-cluster", node.sendrawtransaction, tx_v3_child_large2["hex"])
254          self.check_mempool([tx_v3_parent_large2["txid"]])
255  
256      @cleanup()
257      def test_truc_ancestors_package(self):
258          self.log.info("Test that TRUC ancestor limits are checked within the package")
259          node = self.nodes[0]
260          tx_v3_parent_normal = self.wallet.create_self_transfer(
261              fee_rate=0,
262              target_vsize=1001,
263              version=3
264          )
265          tx_v3_parent_2_normal = self.wallet.create_self_transfer(
266              fee_rate=0,
267              target_vsize=1001,
268              version=3
269          )
270          tx_v3_child_multiparent = self.wallet.create_self_transfer_multi(
271              utxos_to_spend=[tx_v3_parent_normal["new_utxo"], tx_v3_parent_2_normal["new_utxo"]],
272              fee_per_output=10000,
273              version=3
274          )
275          tx_v3_child_heavy = self.wallet.create_self_transfer_multi(
276              utxos_to_spend=[tx_v3_parent_normal["new_utxo"]],
277              target_vsize=TRUC_CHILD_MAX_VSIZE + 1,
278              fee_per_output=10000,
279              version=3
280          )
281  
282          self.check_mempool([])
283          result = node.submitpackage([tx_v3_parent_normal["hex"], tx_v3_parent_2_normal["hex"], tx_v3_child_multiparent["hex"]])
284          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")
285          self.check_mempool([])
286  
287          self.check_mempool([])
288          result = node.submitpackage([tx_v3_parent_normal["hex"], tx_v3_child_heavy["hex"]])
289          # tx_v3_child_heavy is heavy based on vsize, not sigops.
290          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")
291          self.check_mempool([])
292  
293          tx_v3_parent = self.wallet.create_self_transfer(version=3)
294          tx_v3_child = self.wallet.create_self_transfer(utxo_to_spend=tx_v3_parent["new_utxo"], version=3)
295          tx_v3_grandchild = self.wallet.create_self_transfer(utxo_to_spend=tx_v3_child["new_utxo"], version=3)
296          result = node.testmempoolaccept([tx_v3_parent["hex"], tx_v3_child["hex"], tx_v3_grandchild["hex"]])
297          assert all([txresult["package-error"] == f"TRUC-violation, tx {tx_v3_grandchild['txid']} (wtxid={tx_v3_grandchild['wtxid']}) would have too many ancestors" for txresult in result])
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          assert all([result["package-error"] == expected_error_v3_from_v2 for result in test_accept_v3_from_v2])
444  
445          test_accept_v2_from_v3 = node.testmempoolaccept([tx_v3["hex"], tx_v2_from_v3["hex"]])
446          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']})"
447          assert all([result["package-error"] == expected_error_v2_from_v3 for result in test_accept_v2_from_v3])
448  
449          test_accept_pairs = node.testmempoolaccept([tx_v2["hex"], tx_v3["hex"], tx_v2_from_v2["hex"], tx_v3_from_v3["hex"]])
450          assert all([result["allowed"] for result in test_accept_pairs])
451  
452          self.log.info("Test that descendant violations are caught in testmempoolaccept")
453          tx_v3_independent = self.wallet.create_self_transfer(version=3)
454          tx_v3_parent = self.wallet.create_self_transfer_multi(num_outputs=2, version=3)
455          tx_v3_child_1 = self.wallet.create_self_transfer(utxo_to_spend=tx_v3_parent["new_utxos"][0], version=3)
456          tx_v3_child_2 = self.wallet.create_self_transfer(utxo_to_spend=tx_v3_parent["new_utxos"][1], version=3)
457          test_accept_2children = node.testmempoolaccept([tx_v3_parent["hex"], tx_v3_child_1["hex"], tx_v3_child_2["hex"]])
458          expected_error_2children = f"TRUC-violation, tx {tx_v3_parent['txid']} (wtxid={tx_v3_parent['wtxid']}) would exceed descendant count limit"
459          assert all([result["package-error"] == expected_error_2children for result in test_accept_2children])
460  
461          # Extra TRUC transaction does not get incorrectly marked as extra descendant
462          test_accept_1child_with_exra = node.testmempoolaccept([tx_v3_parent["hex"], tx_v3_child_1["hex"], tx_v3_independent["hex"]])
463          assert all([result["allowed"] for result in test_accept_1child_with_exra])
464  
465          # Extra TRUC transaction does not make us ignore the extra descendant
466          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"]])
467          expected_error_extra = f"TRUC-violation, tx {tx_v3_parent['txid']} (wtxid={tx_v3_parent['wtxid']}) would exceed descendant count limit"
468          assert all([result["package-error"] == expected_error_extra for result in test_accept_2children_with_exra])
469          # Same result if the parent is already in mempool
470          node.sendrawtransaction(tx_v3_parent["hex"])
471          test_accept_2children_with_in_mempool_parent = node.testmempoolaccept([tx_v3_child_1["hex"], tx_v3_child_2["hex"]])
472          assert all([result["package-error"] == expected_error_extra for result in test_accept_2children_with_in_mempool_parent])
473  
474      @cleanup(extra_args=None)
475      def test_reorg_2child_rbf(self):
476          node = self.nodes[0]
477          self.log.info("Test that children of a TRUC transaction can be replaced individually, even if there are multiple due to reorg")
478  
479          # Prep for fork
480          fork_blocks = create_empty_fork(node)
481          ancestor_tx = self.wallet.send_self_transfer_multi(from_node=node, num_outputs=2, version=3)
482          self.check_mempool([ancestor_tx["txid"]])
483  
484          self.generate(node, 1)[0]
485          self.check_mempool([])
486  
487          child_1 = self.wallet.send_self_transfer(from_node=node, version=3, utxo_to_spend=ancestor_tx["new_utxos"][0])
488          child_2 = self.wallet.send_self_transfer(from_node=node, version=3, utxo_to_spend=ancestor_tx["new_utxos"][1])
489          self.check_mempool([child_1["txid"], child_2["txid"]])
490  
491          # Create a reorg, causing ancestor_tx to exceed the 1-child limit
492          self.trigger_reorg(fork_blocks)
493          self.check_mempool([ancestor_tx["txid"], child_1["txid"], child_2["txid"]])
494          assert_equal(node.getmempoolentry(ancestor_tx["txid"])["descendantcount"], 3)
495  
496          # Create a replacement of child_1. It does not conflict with child_2.
497          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"))
498  
499          # Ensure child_1 and child_1_conflict are different transactions
500          assert_not_equal(child_1_conflict["txid"], child_1["txid"])
501          self.check_mempool([ancestor_tx["txid"], child_1_conflict["txid"], child_2["txid"]])
502          assert_equal(node.getmempoolentry(ancestor_tx["txid"])["descendantcount"], 3)
503  
504      @cleanup(extra_args=None)
505      def test_truc_sibling_eviction(self):
506          self.log.info("Test sibling eviction for TRUC")
507          node = self.nodes[0]
508          tx_v3_parent = self.wallet.send_self_transfer_multi(from_node=node, num_outputs=2, version=3)
509          # This is the sibling to replace
510          tx_v3_child_1 = self.wallet.send_self_transfer(
511              from_node=node, utxo_to_spend=tx_v3_parent["new_utxos"][0], fee_rate=DEFAULT_FEE * 2, version=3
512          )
513          assert tx_v3_child_1["txid"] in node.getrawmempool()
514  
515          self.log.info("Test tx must be higher feerate than sibling to evict it")
516          tx_v3_child_2_rule6 = self.wallet.create_self_transfer(
517              utxo_to_spend=tx_v3_parent["new_utxos"][1], fee_rate=DEFAULT_FEE, version=3
518          )
519          rule6_str = f"insufficient fee (including sibling eviction), rejecting replacement {tx_v3_child_2_rule6['txid']}"
520          assert_raises_rpc_error(-26, rule6_str, node.sendrawtransaction, tx_v3_child_2_rule6["hex"])
521          self.check_mempool([tx_v3_parent['txid'], tx_v3_child_1['txid']])
522  
523          self.log.info("Test tx must meet absolute fee rules to evict sibling")
524          tx_v3_child_2_rule4 = self.wallet.create_self_transfer(
525              utxo_to_spend=tx_v3_parent["new_utxos"][1], fee_rate=2 * DEFAULT_FEE + Decimal("0.00000001"), version=3
526          )
527          rule4_str = f"insufficient fee (including sibling eviction), rejecting replacement {tx_v3_child_2_rule4['txid']}, not enough additional fees to relay"
528          assert_raises_rpc_error(-26, rule4_str, node.sendrawtransaction, tx_v3_child_2_rule4["hex"])
529          self.check_mempool([tx_v3_parent['txid'], tx_v3_child_1['txid']])
530  
531          self.log.info("Test tx cannot cause more than 100 evictions including RBF and sibling eviction")
532          # First add 4 groups of 25 transactions.
533          utxos_for_conflict = []
534          txids_v2_100 = []
535          for _ in range(4):
536              confirmed_utxo = self.wallet.get_utxo(confirmed_only=True)
537              utxos_for_conflict.append(confirmed_utxo)
538              # 25 is within descendant limits
539              chain_length = int(MAX_REPLACEMENT_CANDIDATES / 4)
540              chain = self.wallet.create_self_transfer_chain(chain_length=chain_length, utxo_to_spend=confirmed_utxo)
541              for item in chain:
542                  txids_v2_100.append(item["txid"])
543                  node.sendrawtransaction(item["hex"])
544          self.check_mempool(txids_v2_100 + [tx_v3_parent["txid"], tx_v3_child_1["txid"]])
545  
546          # Replacing 100 transactions is fine
547          tx_v3_replacement_only = self.wallet.create_self_transfer_multi(utxos_to_spend=utxos_for_conflict, fee_per_output=4000000)
548          # Override maxfeerate - it costs a lot to replace these 100 transactions.
549          assert node.testmempoolaccept([tx_v3_replacement_only["hex"]], maxfeerate=0)[0]["allowed"]
550          self.check_mempool(txids_v2_100 + [tx_v3_parent["txid"], tx_v3_child_1["txid"]])
551  
552          self.log.info("Test sibling eviction is successful if it meets all RBF rules")
553          tx_v3_child_2 = self.wallet.create_self_transfer(
554              utxo_to_spend=tx_v3_parent["new_utxos"][1], fee_rate=DEFAULT_FEE*10, version=3
555          )
556          node.sendrawtransaction(tx_v3_child_2["hex"])
557          self.check_mempool(txids_v2_100 + [tx_v3_parent["txid"], tx_v3_child_2["txid"]])
558  
559          self.log.info("Test that it's possible to do a sibling eviction and RBF at the same time")
560          utxo_unrelated_conflict = self.wallet.get_utxo(confirmed_only=True)
561          tx_unrelated_replacee = self.wallet.send_self_transfer(from_node=node, utxo_to_spend=utxo_unrelated_conflict)
562          assert tx_unrelated_replacee["txid"] in node.getrawmempool()
563  
564          fee_to_beat = max(int(tx_v3_child_2["fee"] * COIN), int(tx_unrelated_replacee["fee"]*COIN))
565  
566          tx_v3_child_3 = self.wallet.create_self_transfer_multi(
567              utxos_to_spend=[tx_v3_parent["new_utxos"][0], utxo_unrelated_conflict], fee_per_output=fee_to_beat*4, version=3
568          )
569          node.sendrawtransaction(tx_v3_child_3["hex"])
570          self.check_mempool(txids_v2_100 + [tx_v3_parent["txid"], tx_v3_child_3["txid"]])
571  
572      @cleanup(extra_args=None)
573      def test_reorg_sibling_eviction_1p2c(self):
574          node = self.nodes[0]
575          self.log.info("Test that sibling eviction is not allowed when multiple siblings exist")
576  
577          # Prep for fork
578          fork_blocks = create_empty_fork(node)
579          tx_with_multi_children = self.wallet.send_self_transfer_multi(from_node=node, num_outputs=3, version=3, confirmed_only=True)
580          self.check_mempool([tx_with_multi_children["txid"]])
581  
582          self.generate(node, 1)
583          self.check_mempool([])
584  
585          tx_with_sibling1 = self.wallet.send_self_transfer(from_node=node, version=3, utxo_to_spend=tx_with_multi_children["new_utxos"][0])
586          tx_with_sibling2 = self.wallet.send_self_transfer(from_node=node, version=3, utxo_to_spend=tx_with_multi_children["new_utxos"][1])
587          self.check_mempool([tx_with_sibling1["txid"], tx_with_sibling2["txid"]])
588  
589          # Create a reorg, bringing tx_with_multi_children back into the mempool with a descendant count of 3.
590          self.trigger_reorg(fork_blocks)
591          self.check_mempool([tx_with_multi_children["txid"], tx_with_sibling1["txid"], tx_with_sibling2["txid"]])
592          assert_equal(node.getmempoolentry(tx_with_multi_children["txid"])["descendantcount"], 3)
593  
594          # Sibling eviction is not allowed because there are two siblings
595          tx_with_sibling3 = self.wallet.create_self_transfer(
596              version=3,
597              utxo_to_spend=tx_with_multi_children["new_utxos"][2],
598              fee_rate=DEFAULT_FEE*50
599          )
600          expected_error_2siblings = f"TRUC-violation, tx {tx_with_multi_children['txid']} (wtxid={tx_with_multi_children['wtxid']}) would exceed descendant count limit"
601          assert_raises_rpc_error(-26, expected_error_2siblings, node.sendrawtransaction, tx_with_sibling3["hex"])
602  
603          # However, an RBF (with conflicting inputs) is possible even if the resulting cluster size exceeds 2
604          tx_with_sibling3_rbf = self.wallet.send_self_transfer(
605              from_node=node,
606              version=3,
607              utxo_to_spend=tx_with_multi_children["new_utxos"][0],
608              fee_rate=DEFAULT_FEE*50
609          )
610          self.check_mempool([tx_with_multi_children["txid"], tx_with_sibling3_rbf["txid"], tx_with_sibling2["txid"]])
611  
612      @cleanup(extra_args=None)
613      def test_minrelay_in_package_combos(self):
614          node = self.nodes[0]
615          self.log.info("Test that only TRUC transactions can be under minrelaytxfee for various settings...")
616  
617          for minrelay_setting in (0, 5, 10, 100, 500, 1000, 5000, 333333, 2500000):
618              self.log.info(f"-> Test -minrelaytxfee={minrelay_setting}sat/kvB...")
619              setting_decimal = minrelay_setting / Decimal(COIN)
620              self.restart_node(0, extra_args=[f"-minrelaytxfee={setting_decimal:.8f}", "-persistmempool=0"])
621              minrelayfeerate = node.getmempoolinfo()["minrelaytxfee"]
622              high_feerate = minrelayfeerate * 50
623  
624              tx_v3_0fee_parent = self.wallet.create_self_transfer(fee=0, fee_rate=0, confirmed_only=True, version=3)
625              tx_v3_child = self.wallet.create_self_transfer(utxo_to_spend=tx_v3_0fee_parent["new_utxo"], fee_rate=high_feerate, version=3)
626              total_v3_fee = tx_v3_child["fee"] + tx_v3_0fee_parent["fee"]
627              total_v3_size = tx_v3_child["tx"].get_vsize() + tx_v3_0fee_parent["tx"].get_vsize()
628              assert_greater_than_or_equal(total_v3_fee, get_fee(total_v3_size, minrelayfeerate))
629              if minrelayfeerate > 0:
630                  assert_greater_than(get_fee(tx_v3_0fee_parent["tx"].get_vsize(), minrelayfeerate), 0)
631                  # Always need to pay at least 1 satoshi for entry, even if minimum feerate is very low
632                  assert_greater_than(total_v3_fee, 0)
633                  # Also create a version where the child is at minrelaytxfee
634                  tx_v3_child_minrelay = self.wallet.create_self_transfer(utxo_to_spend=tx_v3_0fee_parent["new_utxo"], fee_rate=minrelayfeerate, version=3)
635                  result_truc_minrelay = node.submitpackage([tx_v3_0fee_parent["hex"], tx_v3_child_minrelay["hex"]])
636                  assert_equal(result_truc_minrelay["package_msg"], "transaction failed")
637  
638              tx_v2_0fee_parent = self.wallet.create_self_transfer(fee=0, fee_rate=0, confirmed_only=True, version=2)
639              tx_v2_child = self.wallet.create_self_transfer(utxo_to_spend=tx_v2_0fee_parent["new_utxo"], fee_rate=high_feerate, version=2)
640              total_v2_fee = tx_v2_child["fee"] + tx_v2_0fee_parent["fee"]
641              total_v2_size = tx_v2_child["tx"].get_vsize() + tx_v2_0fee_parent["tx"].get_vsize()
642              assert_greater_than_or_equal(total_v2_fee, get_fee(total_v2_size, minrelayfeerate))
643              if minrelayfeerate > 0:
644                  assert_greater_than(get_fee(tx_v2_0fee_parent["tx"].get_vsize(), minrelayfeerate), 0)
645                  # Always need to pay at least 1 satoshi for entry, even if minimum feerate is very low
646                  assert_greater_than(total_v2_fee, 0)
647  
648              result_truc = node.submitpackage([tx_v3_0fee_parent["hex"], tx_v3_child["hex"]], maxfeerate=0)
649              assert_equal(result_truc["package_msg"], "success")
650  
651              result_non_truc = node.submitpackage([tx_v2_0fee_parent["hex"], tx_v2_child["hex"]], maxfeerate=0)
652              if minrelayfeerate > 0:
653                  assert_equal(result_non_truc["package_msg"], "transaction failed")
654                  min_fee_parent = int(get_fee(tx_v2_0fee_parent["tx"].get_vsize(), minrelayfeerate) * COIN)
655                  assert_equal(result_non_truc["tx-results"][tx_v2_0fee_parent["wtxid"]]["error"], f"min relay fee not met, 0 < {min_fee_parent}")
656                  self.check_mempool([tx_v3_0fee_parent["txid"], tx_v3_child["txid"]])
657              else:
658                  assert_equal(result_non_truc["package_msg"], "success")
659                  self.check_mempool([tx_v2_0fee_parent["txid"], tx_v2_child["txid"], tx_v3_0fee_parent["txid"], tx_v3_child["txid"]])
660  
661  
662      def run_test(self):
663          self.log.info("Generate blocks to create UTXOs")
664          node = self.nodes[0]
665          self.wallet = MiniWallet(node)
666          self.generate(self.wallet, 200)
667          self.test_truc_max_vsize()
668          self.test_truc_acceptance()
669          self.test_truc_replacement()
670          self.test_truc_reorg()
671          self.test_nondefault_package_limits()
672          self.test_truc_ancestors_package()
673          self.test_truc_ancestors_package_and_mempool()
674          self.test_sibling_eviction_package()
675          self.test_truc_package_inheritance()
676          self.test_truc_in_testmempoolaccept()
677          self.test_reorg_2child_rbf()
678          self.test_truc_sibling_eviction()
679          self.test_reorg_sibling_eviction_1p2c()
680          self.test_minrelay_in_package_combos()
681  
682  
683  if __name__ == "__main__":
684      MempoolTRUC(__file__).main()