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()