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 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 all transaction versions 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 assert_equal(result_non_truc["package_msg"], "success") 653 self.check_mempool([tx_v2_0fee_parent["txid"], tx_v2_child["txid"], tx_v3_0fee_parent["txid"], tx_v3_child["txid"]]) 654 655 656 def run_test(self): 657 self.log.info("Generate blocks to create UTXOs") 658 node = self.nodes[0] 659 self.wallet = MiniWallet(node) 660 self.generate(self.wallet, 200) 661 self.test_truc_max_vsize() 662 self.test_truc_acceptance() 663 self.test_truc_replacement() 664 self.test_truc_reorg() 665 self.test_nondefault_package_limits() 666 self.test_truc_ancestors_package() 667 self.test_truc_ancestors_package_and_mempool() 668 self.test_sibling_eviction_package() 669 self.test_truc_package_inheritance() 670 self.test_truc_in_testmempoolaccept() 671 self.test_reorg_2child_rbf() 672 self.test_truc_sibling_eviction() 673 self.test_reorg_sibling_eviction_1p2c() 674 self.test_minrelay_in_package_combos() 675 676 677 if __name__ == "__main__": 678 MempoolTRUC(__file__).main()