/ test / functional / mempool_truc.py
mempool_truc.py
  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              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          for txresult in result:
298              assert_equal(txresult["package-error"], f"TRUC-violation, tx {tx_v3_grandchild['txid']} (wtxid={tx_v3_grandchild['wtxid']}) would have too many ancestors")
299  
300      @cleanup(extra_args=None)
301      def test_truc_ancestors_package_and_mempool(self):
302          """
303          A TRUC transaction in a package cannot have 2 TRUC parents.
304          Test that if we have a transaction graph A -> B -> C, where A, B, C are
305          all TRUC transactions, that we cannot use submitpackage to get the
306          transactions all into the mempool.
307  
308          Verify, in particular, that if A is already in the mempool, then
309          submitpackage(B, C) will fail.
310          """
311          node = self.nodes[0]
312          self.log.info("Test that TRUC ancestor limits include transactions within the package and all in-mempool ancestors")
313          # This is our transaction "A":
314          tx_in_mempool = self.wallet.send_self_transfer(from_node=node, version=3)
315  
316          # Verify that A is in the mempool
317          self.check_mempool([tx_in_mempool["txid"]])
318  
319          # tx_0fee_parent is our transaction "B"; just create it.
320          tx_0fee_parent = self.wallet.create_self_transfer(utxo_to_spend=tx_in_mempool["new_utxo"], fee=0, fee_rate=0, version=3)
321  
322          # tx_child_violator is our transaction "C"; create it:
323          tx_child_violator = self.wallet.create_self_transfer_multi(utxos_to_spend=[tx_0fee_parent["new_utxo"]], version=3)
324  
325          # submitpackage(B, C) should fail
326          result = node.submitpackage([tx_0fee_parent["hex"], tx_child_violator["hex"]])
327          assert_equal(result['package_msg'], f"TRUC-violation, tx {tx_child_violator['txid']} (wtxid={tx_child_violator['wtxid']}) would have too many ancestors")
328          self.check_mempool([tx_in_mempool["txid"]])
329  
330      @cleanup(extra_args=None)
331      def test_sibling_eviction_package(self):
332          """
333          When a transaction has a mempool sibling, it may be eligible for sibling eviction.
334          However, this option is only available in single transaction acceptance. It doesn't work in
335          a multi-testmempoolaccept (where RBF is disabled) or when doing package CPFP.
336          """
337          self.log.info("Test TRUC sibling eviction in submitpackage and multi-testmempoolaccept")
338          node = self.nodes[0]
339          # Add a parent + child to mempool
340          tx_mempool_parent = self.wallet.send_self_transfer_multi(
341              from_node=node,
342              utxos_to_spend=[self.wallet.get_utxo()],
343              num_outputs=2,
344              version=3
345          )
346          tx_mempool_sibling = self.wallet.send_self_transfer(
347              from_node=node,
348              utxo_to_spend=tx_mempool_parent["new_utxos"][0],
349              version=3
350          )
351          self.check_mempool([tx_mempool_parent["txid"], tx_mempool_sibling["txid"]])
352  
353          tx_sibling_1 = self.wallet.create_self_transfer(
354              utxo_to_spend=tx_mempool_parent["new_utxos"][1],
355              version=3,
356              fee_rate=DEFAULT_FEE*100,
357          )
358          tx_has_mempool_uncle = self.wallet.create_self_transfer(utxo_to_spend=tx_sibling_1["new_utxo"], version=3)
359  
360          tx_sibling_2 = self.wallet.create_self_transfer(
361              utxo_to_spend=tx_mempool_parent["new_utxos"][0],
362              version=3,
363              fee_rate=DEFAULT_FEE*200,
364          )
365  
366          tx_sibling_3 = self.wallet.create_self_transfer(
367              utxo_to_spend=tx_mempool_parent["new_utxos"][1],
368              version=3,
369              fee_rate=0,
370          )
371          tx_bumps_parent_with_sibling = self.wallet.create_self_transfer(
372              utxo_to_spend=tx_sibling_3["new_utxo"],
373              version=3,
374              fee_rate=DEFAULT_FEE*300,
375          )
376  
377          # Fails with another non-related transaction via testmempoolaccept
378          tx_unrelated = self.wallet.create_self_transfer(version=3)
379          result_test_unrelated = node.testmempoolaccept([tx_sibling_1["hex"], tx_unrelated["hex"]])
380          assert_equal(result_test_unrelated[0]["reject-reason"], "TRUC-violation")
381  
382          # Fails in a package via testmempoolaccept
383          result_test_1p1c = node.testmempoolaccept([tx_sibling_1["hex"], tx_has_mempool_uncle["hex"]])
384          assert_equal(result_test_1p1c[0]["reject-reason"], "TRUC-violation")
385  
386          # Allowed when tx is submitted in a package and evaluated individually.
387          # Note that the child failed since it would be the 3rd generation.
388          result_package_indiv = node.submitpackage([tx_sibling_1["hex"], tx_has_mempool_uncle["hex"]])
389          self.check_mempool([tx_mempool_parent["txid"], tx_sibling_1["txid"]])
390          expected_error_gen3 = f"TRUC-violation, tx {tx_has_mempool_uncle['txid']} (wtxid={tx_has_mempool_uncle['wtxid']}) would have too many ancestors"
391  
392          assert_equal(result_package_indiv["tx-results"][tx_has_mempool_uncle['wtxid']]['error'], expected_error_gen3)
393  
394          # Allowed when tx is submitted in a package with in-mempool parent (which is deduplicated).
395          node.submitpackage([tx_mempool_parent["hex"], tx_sibling_2["hex"]])
396          self.check_mempool([tx_mempool_parent["txid"], tx_sibling_2["txid"]])
397  
398          # Child cannot pay for sibling eviction for parent, as it violates TRUC topology limits
399          result_package_cpfp = node.submitpackage([tx_sibling_3["hex"], tx_bumps_parent_with_sibling["hex"]])
400          self.check_mempool([tx_mempool_parent["txid"], tx_sibling_2["txid"]])
401          expected_error_cpfp = f"TRUC-violation, tx {tx_mempool_parent['txid']} (wtxid={tx_mempool_parent['wtxid']}) would exceed descendant count limit"
402  
403          assert_equal(result_package_cpfp["tx-results"][tx_sibling_3['wtxid']]['error'], expected_error_cpfp)
404  
405  
406      @cleanup()
407      def test_truc_package_inheritance(self):
408          self.log.info("Test that TRUC inheritance is checked within package")
409          node = self.nodes[0]
410          tx_v3_parent = self.wallet.create_self_transfer(
411              fee_rate=0,
412              target_vsize=1001,
413              version=3
414          )
415          tx_v2_child = self.wallet.create_self_transfer_multi(
416              utxos_to_spend=[tx_v3_parent["new_utxo"]],
417              fee_per_output=10000,
418              version=2
419          )
420          self.check_mempool([])
421          result = node.submitpackage([tx_v3_parent["hex"], tx_v2_child["hex"]])
422          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']})")
423          self.check_mempool([])
424  
425      @cleanup(extra_args=None)
426      def test_truc_in_testmempoolaccept(self):
427          node = self.nodes[0]
428  
429          self.log.info("Test that TRUC inheritance is accurately assessed in testmempoolaccept")
430          tx_v2 = self.wallet.create_self_transfer(version=2)
431          tx_v2_from_v2 = self.wallet.create_self_transfer(utxo_to_spend=tx_v2["new_utxo"], version=2)
432          tx_v3_from_v2 = self.wallet.create_self_transfer(utxo_to_spend=tx_v2["new_utxo"], version=3)
433          tx_v3 = self.wallet.create_self_transfer(version=3)
434          tx_v2_from_v3 = self.wallet.create_self_transfer(utxo_to_spend=tx_v3["new_utxo"], version=2)
435          tx_v3_from_v3 = self.wallet.create_self_transfer(utxo_to_spend=tx_v3["new_utxo"], version=3)
436  
437          # testmempoolaccept paths don't require child-with-parents topology. Ensure that topology
438          # assumptions aren't made in inheritance checks.
439          test_accept_v2_and_v3 = node.testmempoolaccept([tx_v2["hex"], tx_v3["hex"]])
440          assert all([result["allowed"] for result in test_accept_v2_and_v3])
441  
442          test_accept_v3_from_v2 = node.testmempoolaccept([tx_v2["hex"], tx_v3_from_v2["hex"]])
443          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']})"
444          for result in test_accept_v3_from_v2:
445              assert_equal(result["package-error"], expected_error_v3_from_v2)
446  
447          test_accept_v2_from_v3 = node.testmempoolaccept([tx_v3["hex"], tx_v2_from_v3["hex"]])
448          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']})"
449          for result in test_accept_v2_from_v3:
450              assert_equal(result["package-error"], expected_error_v2_from_v3)
451  
452          test_accept_pairs = node.testmempoolaccept([tx_v2["hex"], tx_v3["hex"], tx_v2_from_v2["hex"], tx_v3_from_v3["hex"]])
453          assert all([result["allowed"] for result in test_accept_pairs])
454  
455          self.log.info("Test that descendant violations are caught in testmempoolaccept")
456          tx_v3_independent = self.wallet.create_self_transfer(version=3)
457          tx_v3_parent = self.wallet.create_self_transfer_multi(num_outputs=2, version=3)
458          tx_v3_child_1 = self.wallet.create_self_transfer(utxo_to_spend=tx_v3_parent["new_utxos"][0], version=3)
459          tx_v3_child_2 = self.wallet.create_self_transfer(utxo_to_spend=tx_v3_parent["new_utxos"][1], version=3)
460          test_accept_2children = node.testmempoolaccept([tx_v3_parent["hex"], tx_v3_child_1["hex"], tx_v3_child_2["hex"]])
461          expected_error_2children = f"TRUC-violation, tx {tx_v3_parent['txid']} (wtxid={tx_v3_parent['wtxid']}) would exceed descendant count limit"
462          for result in test_accept_2children:
463              assert_equal(result["package-error"], expected_error_2children)
464  
465          # Extra TRUC transaction does not get incorrectly marked as extra descendant
466          test_accept_1child_with_exra = node.testmempoolaccept([tx_v3_parent["hex"], tx_v3_child_1["hex"], tx_v3_independent["hex"]])
467          assert all([result["allowed"] for result in test_accept_1child_with_exra])
468  
469          # Extra TRUC transaction does not make us ignore the extra descendant
470          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"]])
471          expected_error_extra = f"TRUC-violation, tx {tx_v3_parent['txid']} (wtxid={tx_v3_parent['wtxid']}) would exceed descendant count limit"
472          for result in test_accept_2children_with_exra:
473              assert_equal(result["package-error"], expected_error_extra)
474          # Same result if the parent is already in mempool
475          node.sendrawtransaction(tx_v3_parent["hex"])
476          test_accept_2children_with_in_mempool_parent = node.testmempoolaccept([tx_v3_child_1["hex"], tx_v3_child_2["hex"]])
477          for result in test_accept_2children_with_in_mempool_parent:
478              assert_equal(result["package-error"], expected_error_extra)
479  
480      @cleanup(extra_args=None)
481      def test_reorg_2child_rbf(self):
482          node = self.nodes[0]
483          self.log.info("Test that children of a TRUC transaction can be replaced individually, even if there are multiple due to reorg")
484  
485          # Prep for fork
486          fork_blocks = create_empty_fork(node)
487          ancestor_tx = self.wallet.send_self_transfer_multi(from_node=node, num_outputs=2, version=3)
488          self.check_mempool([ancestor_tx["txid"]])
489  
490          self.generate(node, 1)[0]
491          self.check_mempool([])
492  
493          child_1 = self.wallet.send_self_transfer(from_node=node, version=3, utxo_to_spend=ancestor_tx["new_utxos"][0])
494          child_2 = self.wallet.send_self_transfer(from_node=node, version=3, utxo_to_spend=ancestor_tx["new_utxos"][1])
495          self.check_mempool([child_1["txid"], child_2["txid"]])
496  
497          # Create a reorg, causing ancestor_tx to exceed the 1-child limit
498          self.trigger_reorg(fork_blocks)
499          self.check_mempool([ancestor_tx["txid"], child_1["txid"], child_2["txid"]])
500          assert_equal(node.getmempoolentry(ancestor_tx["txid"])["descendantcount"], 3)
501  
502          # Create a replacement of child_1. It does not conflict with child_2.
503          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"))
504  
505          # Ensure child_1 and child_1_conflict are different transactions
506          assert_not_equal(child_1_conflict["txid"], child_1["txid"])
507          self.check_mempool([ancestor_tx["txid"], child_1_conflict["txid"], child_2["txid"]])
508          assert_equal(node.getmempoolentry(ancestor_tx["txid"])["descendantcount"], 3)
509  
510      @cleanup(extra_args=None)
511      def test_truc_sibling_eviction(self):
512          self.log.info("Test sibling eviction for TRUC")
513          node = self.nodes[0]
514          tx_v3_parent = self.wallet.send_self_transfer_multi(from_node=node, num_outputs=2, version=3)
515          # This is the sibling to replace
516          tx_v3_child_1 = self.wallet.send_self_transfer(
517              from_node=node, utxo_to_spend=tx_v3_parent["new_utxos"][0], fee_rate=DEFAULT_FEE * 2, version=3
518          )
519          assert tx_v3_child_1["txid"] in node.getrawmempool()
520  
521          self.log.info("Test tx must be higher feerate than sibling to evict it")
522          tx_v3_child_2_rule6 = self.wallet.create_self_transfer(
523              utxo_to_spend=tx_v3_parent["new_utxos"][1], fee_rate=DEFAULT_FEE, version=3
524          )
525          rule6_str = f"insufficient fee (including sibling eviction), rejecting replacement {tx_v3_child_2_rule6['txid']}"
526          assert_raises_rpc_error(-26, rule6_str, node.sendrawtransaction, tx_v3_child_2_rule6["hex"])
527          self.check_mempool([tx_v3_parent['txid'], tx_v3_child_1['txid']])
528  
529          self.log.info("Test tx must meet absolute fee rules to evict sibling")
530          tx_v3_child_2_rule4 = self.wallet.create_self_transfer(
531              utxo_to_spend=tx_v3_parent["new_utxos"][1], fee_rate=2 * DEFAULT_FEE + Decimal("0.00000001"), version=3
532          )
533          rule4_str = f"insufficient fee (including sibling eviction), rejecting replacement {tx_v3_child_2_rule4['txid']}, not enough additional fees to relay"
534          assert_raises_rpc_error(-26, rule4_str, node.sendrawtransaction, tx_v3_child_2_rule4["hex"])
535          self.check_mempool([tx_v3_parent['txid'], tx_v3_child_1['txid']])
536  
537          self.log.info("Test tx cannot cause more than 100 evictions including RBF and sibling eviction")
538          # First add 4 groups of 25 transactions.
539          utxos_for_conflict = []
540          txids_v2_100 = []
541          for _ in range(4):
542              confirmed_utxo = self.wallet.get_utxo(confirmed_only=True)
543              utxos_for_conflict.append(confirmed_utxo)
544              # 25 is within descendant limits
545              chain_length = int(MAX_REPLACEMENT_CANDIDATES / 4)
546              chain = self.wallet.create_self_transfer_chain(chain_length=chain_length, utxo_to_spend=confirmed_utxo)
547              for item in chain:
548                  txids_v2_100.append(item["txid"])
549                  node.sendrawtransaction(item["hex"])
550          self.check_mempool(txids_v2_100 + [tx_v3_parent["txid"], tx_v3_child_1["txid"]])
551  
552          # Replacing 100 transactions is fine
553          tx_v3_replacement_only = self.wallet.create_self_transfer_multi(utxos_to_spend=utxos_for_conflict, fee_per_output=4000000)
554          # Override maxfeerate - it costs a lot to replace these 100 transactions.
555          assert node.testmempoolaccept([tx_v3_replacement_only["hex"]], maxfeerate=0)[0]["allowed"]
556          self.check_mempool(txids_v2_100 + [tx_v3_parent["txid"], tx_v3_child_1["txid"]])
557  
558          self.log.info("Test sibling eviction is successful if it meets all RBF rules")
559          tx_v3_child_2 = self.wallet.create_self_transfer(
560              utxo_to_spend=tx_v3_parent["new_utxos"][1], fee_rate=DEFAULT_FEE*10, version=3
561          )
562          node.sendrawtransaction(tx_v3_child_2["hex"])
563          self.check_mempool(txids_v2_100 + [tx_v3_parent["txid"], tx_v3_child_2["txid"]])
564  
565          self.log.info("Test that it's possible to do a sibling eviction and RBF at the same time")
566          utxo_unrelated_conflict = self.wallet.get_utxo(confirmed_only=True)
567          tx_unrelated_replacee = self.wallet.send_self_transfer(from_node=node, utxo_to_spend=utxo_unrelated_conflict)
568          assert tx_unrelated_replacee["txid"] in node.getrawmempool()
569  
570          fee_to_beat = max(int(tx_v3_child_2["fee"] * COIN), int(tx_unrelated_replacee["fee"]*COIN))
571  
572          tx_v3_child_3 = self.wallet.create_self_transfer_multi(
573              utxos_to_spend=[tx_v3_parent["new_utxos"][0], utxo_unrelated_conflict], fee_per_output=fee_to_beat*4, version=3
574          )
575          node.sendrawtransaction(tx_v3_child_3["hex"])
576          self.check_mempool(txids_v2_100 + [tx_v3_parent["txid"], tx_v3_child_3["txid"]])
577  
578      @cleanup(extra_args=None)
579      def test_reorg_sibling_eviction_1p2c(self):
580          node = self.nodes[0]
581          self.log.info("Test that sibling eviction is not allowed when multiple siblings exist")
582  
583          # Prep for fork
584          fork_blocks = create_empty_fork(node)
585          tx_with_multi_children = self.wallet.send_self_transfer_multi(from_node=node, num_outputs=3, version=3, confirmed_only=True)
586          self.check_mempool([tx_with_multi_children["txid"]])
587  
588          self.generate(node, 1)
589          self.check_mempool([])
590  
591          tx_with_sibling1 = self.wallet.send_self_transfer(from_node=node, version=3, utxo_to_spend=tx_with_multi_children["new_utxos"][0])
592          tx_with_sibling2 = self.wallet.send_self_transfer(from_node=node, version=3, utxo_to_spend=tx_with_multi_children["new_utxos"][1])
593          self.check_mempool([tx_with_sibling1["txid"], tx_with_sibling2["txid"]])
594  
595          # Create a reorg, bringing tx_with_multi_children back into the mempool with a descendant count of 3.
596          self.trigger_reorg(fork_blocks)
597          self.check_mempool([tx_with_multi_children["txid"], tx_with_sibling1["txid"], tx_with_sibling2["txid"]])
598          assert_equal(node.getmempoolentry(tx_with_multi_children["txid"])["descendantcount"], 3)
599  
600          # Sibling eviction is not allowed because there are two siblings
601          tx_with_sibling3 = self.wallet.create_self_transfer(
602              version=3,
603              utxo_to_spend=tx_with_multi_children["new_utxos"][2],
604              fee_rate=DEFAULT_FEE*50
605          )
606          expected_error_2siblings = f"TRUC-violation, tx {tx_with_multi_children['txid']} (wtxid={tx_with_multi_children['wtxid']}) would exceed descendant count limit"
607          assert_raises_rpc_error(-26, expected_error_2siblings, node.sendrawtransaction, tx_with_sibling3["hex"])
608  
609          # However, an RBF (with conflicting inputs) is possible even if the resulting cluster size exceeds 2
610          tx_with_sibling3_rbf = self.wallet.send_self_transfer(
611              from_node=node,
612              version=3,
613              utxo_to_spend=tx_with_multi_children["new_utxos"][0],
614              fee_rate=DEFAULT_FEE*50
615          )
616          self.check_mempool([tx_with_multi_children["txid"], tx_with_sibling3_rbf["txid"], tx_with_sibling2["txid"]])
617  
618      @cleanup(extra_args=None)
619      def test_minrelay_in_package_combos(self):
620          node = self.nodes[0]
621          self.log.info("Test that all transaction versions can be under minrelaytxfee for various settings...")
622  
623          for minrelay_setting in (0, 5, 10, 100, 500, 1000, 5000, 333333, 2500000):
624              self.log.info(f"-> Test -minrelaytxfee={minrelay_setting}sat/kvB...")
625              setting_decimal = minrelay_setting / Decimal(COIN)
626              self.restart_node(0, extra_args=[f"-minrelaytxfee={setting_decimal:.8f}", "-persistmempool=0"])
627              minrelayfeerate = node.getmempoolinfo()["minrelaytxfee"]
628              high_feerate = minrelayfeerate * 50
629  
630              tx_v3_0fee_parent = self.wallet.create_self_transfer(fee=0, fee_rate=0, confirmed_only=True, version=3)
631              tx_v3_child = self.wallet.create_self_transfer(utxo_to_spend=tx_v3_0fee_parent["new_utxo"], fee_rate=high_feerate, version=3)
632              total_v3_fee = tx_v3_child["fee"] + tx_v3_0fee_parent["fee"]
633              total_v3_size = tx_v3_child["tx"].get_vsize() + tx_v3_0fee_parent["tx"].get_vsize()
634              assert_greater_than_or_equal(total_v3_fee, get_fee(total_v3_size, minrelayfeerate))
635              if minrelayfeerate > 0:
636                  assert_greater_than(get_fee(tx_v3_0fee_parent["tx"].get_vsize(), minrelayfeerate), 0)
637                  # Always need to pay at least 1 satoshi for entry, even if minimum feerate is very low
638                  assert_greater_than(total_v3_fee, 0)
639                  # Also create a version where the child is at minrelaytxfee
640                  tx_v3_child_minrelay = self.wallet.create_self_transfer(utxo_to_spend=tx_v3_0fee_parent["new_utxo"], fee_rate=minrelayfeerate, version=3)
641                  result_truc_minrelay = node.submitpackage([tx_v3_0fee_parent["hex"], tx_v3_child_minrelay["hex"]])
642                  assert_equal(result_truc_minrelay["package_msg"], "transaction failed")
643  
644              tx_v2_0fee_parent = self.wallet.create_self_transfer(fee=0, fee_rate=0, confirmed_only=True, version=2)
645              tx_v2_child = self.wallet.create_self_transfer(utxo_to_spend=tx_v2_0fee_parent["new_utxo"], fee_rate=high_feerate, version=2)
646              total_v2_fee = tx_v2_child["fee"] + tx_v2_0fee_parent["fee"]
647              total_v2_size = tx_v2_child["tx"].get_vsize() + tx_v2_0fee_parent["tx"].get_vsize()
648              assert_greater_than_or_equal(total_v2_fee, get_fee(total_v2_size, minrelayfeerate))
649              if minrelayfeerate > 0:
650                  assert_greater_than(get_fee(tx_v2_0fee_parent["tx"].get_vsize(), minrelayfeerate), 0)
651                  # Always need to pay at least 1 satoshi for entry, even if minimum feerate is very low
652                  assert_greater_than(total_v2_fee, 0)
653  
654              result_truc = node.submitpackage([tx_v3_0fee_parent["hex"], tx_v3_child["hex"]], maxfeerate=0)
655              assert_equal(result_truc["package_msg"], "success")
656  
657              result_non_truc = node.submitpackage([tx_v2_0fee_parent["hex"], tx_v2_child["hex"]], maxfeerate=0)
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()