rpc_packages.py
1 #!/usr/bin/env python3 2 # Copyright (c) 2021-2022 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 """RPCs that handle raw transaction packages.""" 6 7 from decimal import Decimal 8 import random 9 10 from test_framework.blocktools import COINBASE_MATURITY 11 from test_framework.mempool_util import ( 12 fill_mempool, 13 ) 14 from test_framework.messages import ( 15 MAX_BIP125_RBF_SEQUENCE, 16 tx_from_hex, 17 ) 18 from test_framework.p2p import P2PTxInvStore 19 from test_framework.test_framework import BitcoinTestFramework 20 from test_framework.util import ( 21 assert_equal, 22 assert_fee_amount, 23 assert_raises_rpc_error, 24 ) 25 from test_framework.wallet import ( 26 COIN, 27 DEFAULT_FEE, 28 MiniWallet, 29 ) 30 31 32 MAX_PACKAGE_COUNT = 25 33 34 35 class RPCPackagesTest(BitcoinTestFramework): 36 def set_test_params(self): 37 self.num_nodes = 1 38 self.setup_clean_chain = True 39 # whitelist peers to speed up tx relay / mempool sync 40 self.noban_tx_relay = True 41 42 def assert_testres_equal(self, package_hex, testres_expected): 43 """Shuffle package_hex and assert that the testmempoolaccept result matches testres_expected. This should only 44 be used to test packages where the order does not matter. The ordering of transactions in package_hex and 45 testres_expected must match. 46 """ 47 shuffled_indeces = list(range(len(package_hex))) 48 random.shuffle(shuffled_indeces) 49 shuffled_package = [package_hex[i] for i in shuffled_indeces] 50 shuffled_testres = [testres_expected[i] for i in shuffled_indeces] 51 assert_equal(shuffled_testres, self.nodes[0].testmempoolaccept(shuffled_package)) 52 53 def run_test(self): 54 node = self.nodes[0] 55 56 # get an UTXO that requires signature to be spent 57 deterministic_address = node.get_deterministic_priv_key().address 58 blockhash = self.generatetoaddress(node, 1, deterministic_address)[0] 59 coinbase = node.getblock(blockhash=blockhash, verbosity=2)["tx"][0] 60 coin = { 61 "txid": coinbase["txid"], 62 "amount": coinbase["vout"][0]["value"], 63 "scriptPubKey": coinbase["vout"][0]["scriptPubKey"], 64 "vout": 0, 65 "height": 0 66 } 67 68 self.wallet = MiniWallet(self.nodes[0]) 69 self.generate(self.wallet, COINBASE_MATURITY + 100) # blocks generated for inputs 70 71 self.log.info("Create some transactions") 72 # Create some transactions that can be reused throughout the test. Never submit these to mempool. 73 self.independent_txns_hex = [] 74 self.independent_txns_testres = [] 75 for _ in range(3): 76 tx_hex = self.wallet.create_self_transfer(fee_rate=Decimal("0.0001"))["hex"] 77 testres = self.nodes[0].testmempoolaccept([tx_hex]) 78 assert testres[0]["allowed"] 79 self.independent_txns_hex.append(tx_hex) 80 # testmempoolaccept returns a list of length one, avoid creating a 2D list 81 self.independent_txns_testres.append(testres[0]) 82 self.independent_txns_testres_blank = [{ 83 "txid": res["txid"], "wtxid": res["wtxid"]} for res in self.independent_txns_testres] 84 85 self.test_independent(coin) 86 self.test_chain() 87 self.test_multiple_children() 88 self.test_multiple_parents() 89 self.test_conflicting() 90 self.test_rbf() 91 self.test_submitpackage() 92 self.test_maxfeerate_submitpackage() 93 self.test_maxburn_submitpackage() 94 95 def test_independent(self, coin): 96 self.log.info("Test multiple independent transactions in a package") 97 node = self.nodes[0] 98 # For independent transactions, order doesn't matter. 99 self.assert_testres_equal(self.independent_txns_hex, self.independent_txns_testres) 100 101 self.log.info("Test an otherwise valid package with an extra garbage tx appended") 102 address = node.get_deterministic_priv_key().address 103 garbage_tx = node.createrawtransaction([{"txid": "00" * 32, "vout": 5}], {address: 1}) 104 tx = tx_from_hex(garbage_tx) 105 # Only the txid and wtxids are returned because validation is incomplete for the independent txns. 106 # Package validation is atomic: if the node cannot find a UTXO for any single tx in the package, 107 # it terminates immediately to avoid unnecessary, expensive signature verification. 108 package_bad = self.independent_txns_hex + [garbage_tx] 109 testres_bad = self.independent_txns_testres_blank + [{"txid": tx.rehash(), "wtxid": tx.getwtxid(), "allowed": False, "reject-reason": "missing-inputs"}] 110 self.assert_testres_equal(package_bad, testres_bad) 111 112 self.log.info("Check testmempoolaccept tells us when some transactions completed validation successfully") 113 tx_bad_sig_hex = node.createrawtransaction([{"txid": coin["txid"], "vout": coin["vout"]}], 114 {address : coin["amount"] - Decimal("0.0001")}) 115 tx_bad_sig = tx_from_hex(tx_bad_sig_hex) 116 testres_bad_sig = node.testmempoolaccept(self.independent_txns_hex + [tx_bad_sig_hex]) 117 # By the time the signature for the last transaction is checked, all the other transactions 118 # have been fully validated, which is why the node returns full validation results for all 119 # transactions here but empty results in other cases. 120 tx_bad_sig_txid = tx_bad_sig.rehash() 121 tx_bad_sig_wtxid = tx_bad_sig.getwtxid() 122 assert_equal(testres_bad_sig, self.independent_txns_testres + [{ 123 "txid": tx_bad_sig_txid, 124 "wtxid": tx_bad_sig_wtxid, "allowed": False, 125 "reject-reason": "mandatory-script-verify-flag-failed (Operation not valid with the current stack size)", 126 "reject-details": "mandatory-script-verify-flag-failed (Operation not valid with the current stack size), " + 127 f"input 0 of {tx_bad_sig_txid} (wtxid {tx_bad_sig_wtxid}), spending {coin['txid']}:{coin['vout']}" 128 }]) 129 130 self.log.info("Check testmempoolaccept reports txns in packages that exceed max feerate") 131 tx_high_fee = self.wallet.create_self_transfer(fee=Decimal("0.999")) 132 testres_high_fee = node.testmempoolaccept([tx_high_fee["hex"]]) 133 assert_equal(testres_high_fee, [ 134 {"txid": tx_high_fee["txid"], "wtxid": tx_high_fee["wtxid"], "allowed": False, "reject-reason": "max-fee-exceeded"} 135 ]) 136 package_high_fee = [tx_high_fee["hex"]] + self.independent_txns_hex 137 testres_package_high_fee = node.testmempoolaccept(package_high_fee) 138 assert_equal(testres_package_high_fee, testres_high_fee + self.independent_txns_testres_blank) 139 140 def test_chain(self): 141 node = self.nodes[0] 142 143 chain = self.wallet.create_self_transfer_chain(chain_length=25) 144 chain_hex = [t["hex"] for t in chain] 145 chain_txns = [t["tx"] for t in chain] 146 147 self.log.info("Check that testmempoolaccept requires packages to be sorted by dependency") 148 assert_equal(node.testmempoolaccept(rawtxs=chain_hex[::-1]), 149 [{"txid": tx.rehash(), "wtxid": tx.getwtxid(), "package-error": "package-not-sorted"} for tx in chain_txns[::-1]]) 150 151 self.log.info("Testmempoolaccept a chain of 25 transactions") 152 testres_multiple = node.testmempoolaccept(rawtxs=chain_hex) 153 154 testres_single = [] 155 # Test accept and then submit each one individually, which should be identical to package test accept 156 for rawtx in chain_hex: 157 testres = node.testmempoolaccept([rawtx]) 158 testres_single.append(testres[0]) 159 # Submit the transaction now so its child should have no problem validating 160 node.sendrawtransaction(rawtx) 161 assert_equal(testres_single, testres_multiple) 162 163 # Clean up by clearing the mempool 164 self.generate(node, 1) 165 166 def test_multiple_children(self): 167 node = self.nodes[0] 168 self.log.info("Testmempoolaccept a package in which a transaction has two children within the package") 169 170 parent_tx = self.wallet.create_self_transfer_multi(num_outputs=2) 171 assert node.testmempoolaccept([parent_tx["hex"]])[0]["allowed"] 172 173 # Child A 174 child_a_tx = self.wallet.create_self_transfer(utxo_to_spend=parent_tx["new_utxos"][0]) 175 assert not node.testmempoolaccept([child_a_tx["hex"]])[0]["allowed"] 176 177 # Child B 178 child_b_tx = self.wallet.create_self_transfer(utxo_to_spend=parent_tx["new_utxos"][1]) 179 assert not node.testmempoolaccept([child_b_tx["hex"]])[0]["allowed"] 180 181 self.log.info("Testmempoolaccept with entire package, should work with children in either order") 182 testres_multiple_ab = node.testmempoolaccept(rawtxs=[parent_tx["hex"], child_a_tx["hex"], child_b_tx["hex"]]) 183 testres_multiple_ba = node.testmempoolaccept(rawtxs=[parent_tx["hex"], child_b_tx["hex"], child_a_tx["hex"]]) 184 assert all([testres["allowed"] for testres in testres_multiple_ab + testres_multiple_ba]) 185 186 testres_single = [] 187 # Test accept and then submit each one individually, which should be identical to package testaccept 188 for rawtx in [parent_tx["hex"], child_a_tx["hex"], child_b_tx["hex"]]: 189 testres = node.testmempoolaccept([rawtx]) 190 testres_single.append(testres[0]) 191 # Submit the transaction now so its child should have no problem validating 192 node.sendrawtransaction(rawtx) 193 assert_equal(testres_single, testres_multiple_ab) 194 195 def test_multiple_parents(self): 196 node = self.nodes[0] 197 self.log.info("Testmempoolaccept a package in which a transaction has multiple parents within the package") 198 199 for num_parents in [2, 10, 24]: 200 # Test a package with num_parents parents and 1 child transaction. 201 parent_coins = [] 202 package_hex = [] 203 204 for _ in range(num_parents): 205 # Package accept should work with the parents in any order (as long as parents come before child) 206 parent_tx = self.wallet.create_self_transfer() 207 parent_coins.append(parent_tx["new_utxo"]) 208 package_hex.append(parent_tx["hex"]) 209 210 child_tx = self.wallet.create_self_transfer_multi(utxos_to_spend=parent_coins, fee_per_output=2000) 211 for _ in range(10): 212 random.shuffle(package_hex) 213 testres_multiple = node.testmempoolaccept(rawtxs=package_hex + [child_tx['hex']]) 214 assert all([testres["allowed"] for testres in testres_multiple]) 215 216 testres_single = [] 217 # Test accept and then submit each one individually, which should be identical to package testaccept 218 for rawtx in package_hex + [child_tx["hex"]]: 219 testres_single.append(node.testmempoolaccept([rawtx])[0]) 220 # Submit the transaction now so its child should have no problem validating 221 node.sendrawtransaction(rawtx) 222 assert_equal(testres_single, testres_multiple) 223 224 def test_conflicting(self): 225 node = self.nodes[0] 226 coin = self.wallet.get_utxo() 227 228 # tx1 and tx2 share the same inputs 229 tx1 = self.wallet.create_self_transfer(utxo_to_spend=coin, fee_rate=DEFAULT_FEE) 230 tx2 = self.wallet.create_self_transfer(utxo_to_spend=coin, fee_rate=2*DEFAULT_FEE) 231 232 # Ensure tx1 and tx2 are valid by themselves 233 assert node.testmempoolaccept([tx1["hex"]])[0]["allowed"] 234 assert node.testmempoolaccept([tx2["hex"]])[0]["allowed"] 235 236 self.log.info("Test duplicate transactions in the same package") 237 testres = node.testmempoolaccept([tx1["hex"], tx1["hex"]]) 238 assert_equal(testres, [ 239 {"txid": tx1["txid"], "wtxid": tx1["wtxid"], "package-error": "package-contains-duplicates"}, 240 {"txid": tx1["txid"], "wtxid": tx1["wtxid"], "package-error": "package-contains-duplicates"} 241 ]) 242 243 self.log.info("Test conflicting transactions in the same package") 244 testres = node.testmempoolaccept([tx1["hex"], tx2["hex"]]) 245 assert_equal(testres, [ 246 {"txid": tx1["txid"], "wtxid": tx1["wtxid"], "package-error": "conflict-in-package"}, 247 {"txid": tx2["txid"], "wtxid": tx2["wtxid"], "package-error": "conflict-in-package"} 248 ]) 249 250 # Add a child that spends both at high feerate to submit via submitpackage 251 tx_child = self.wallet.create_self_transfer_multi( 252 fee_per_output=int(DEFAULT_FEE * 5 * COIN), 253 utxos_to_spend=[tx1["new_utxo"], tx2["new_utxo"]], 254 ) 255 256 testres = node.testmempoolaccept([tx1["hex"], tx2["hex"], tx_child["hex"]]) 257 258 assert_equal(testres, [ 259 {"txid": tx1["txid"], "wtxid": tx1["wtxid"], "package-error": "conflict-in-package"}, 260 {"txid": tx2["txid"], "wtxid": tx2["wtxid"], "package-error": "conflict-in-package"}, 261 {"txid": tx_child["txid"], "wtxid": tx_child["wtxid"], "package-error": "conflict-in-package"} 262 ]) 263 264 submitres = node.submitpackage([tx1["hex"], tx2["hex"], tx_child["hex"]]) 265 assert_equal(submitres, {'package_msg': 'conflict-in-package', 'tx-results': {}, 'replaced-transactions': []}) 266 267 # Submit tx1 to mempool, then try the same package again 268 node.sendrawtransaction(tx1["hex"]) 269 270 submitres = node.submitpackage([tx1["hex"], tx2["hex"], tx_child["hex"]]) 271 assert_equal(submitres, {'package_msg': 'conflict-in-package', 'tx-results': {}, 'replaced-transactions': []}) 272 assert tx_child["txid"] not in node.getrawmempool() 273 274 # ... and without the in-mempool ancestor tx1 included in the call 275 submitres = node.submitpackage([tx2["hex"], tx_child["hex"]]) 276 assert_equal(submitres, {'package_msg': 'package-not-child-with-unconfirmed-parents', 'tx-results': {}, 'replaced-transactions': []}) 277 278 # Regardless of error type, the child can never enter the mempool 279 assert tx_child["txid"] not in node.getrawmempool() 280 281 def test_rbf(self): 282 node = self.nodes[0] 283 284 coin = self.wallet.get_utxo() 285 fee = Decimal("0.00125000") 286 replaceable_tx = self.wallet.create_self_transfer(utxo_to_spend=coin, sequence=MAX_BIP125_RBF_SEQUENCE, fee = fee) 287 testres_replaceable = node.testmempoolaccept([replaceable_tx["hex"]])[0] 288 assert_equal(testres_replaceable["txid"], replaceable_tx["txid"]) 289 assert_equal(testres_replaceable["wtxid"], replaceable_tx["wtxid"]) 290 assert testres_replaceable["allowed"] 291 assert_equal(testres_replaceable["vsize"], replaceable_tx["tx"].get_vsize()) 292 assert_equal(testres_replaceable["fees"]["base"], fee) 293 assert_fee_amount(fee, replaceable_tx["tx"].get_vsize(), testres_replaceable["fees"]["effective-feerate"]) 294 assert_equal(testres_replaceable["fees"]["effective-includes"], [replaceable_tx["wtxid"]]) 295 296 # Replacement transaction is identical except has double the fee 297 replacement_tx = self.wallet.create_self_transfer(utxo_to_spend=coin, sequence=MAX_BIP125_RBF_SEQUENCE, fee = 2 * fee) 298 testres_rbf_conflicting = node.testmempoolaccept([replaceable_tx["hex"], replacement_tx["hex"]]) 299 assert_equal(testres_rbf_conflicting, [ 300 {"txid": replaceable_tx["txid"], "wtxid": replaceable_tx["wtxid"], "package-error": "conflict-in-package"}, 301 {"txid": replacement_tx["txid"], "wtxid": replacement_tx["wtxid"], "package-error": "conflict-in-package"} 302 ]) 303 304 self.log.info("Test that packages cannot conflict with mempool transactions, even if a valid BIP125 RBF") 305 # This transaction is a valid BIP125 replace-by-fee 306 self.wallet.sendrawtransaction(from_node=node, tx_hex=replaceable_tx["hex"]) 307 testres_rbf_single = node.testmempoolaccept([replacement_tx["hex"]]) 308 assert testres_rbf_single[0]["allowed"] 309 testres_rbf_package = self.independent_txns_testres_blank + [{ 310 "txid": replacement_tx["txid"], "wtxid": replacement_tx["wtxid"], "allowed": False, 311 "reject-reason": "bip125-replacement-disallowed", 312 "reject-details": "bip125-replacement-disallowed" 313 }] 314 self.assert_testres_equal(self.independent_txns_hex + [replacement_tx["hex"]], testres_rbf_package) 315 316 def assert_equal_package_results(self, node, testmempoolaccept_result, submitpackage_result): 317 """Assert that a successful submitpackage result is consistent with testmempoolaccept 318 results and getmempoolentry info. Note that the result structs are different and, due to 319 policy differences between testmempoolaccept and submitpackage (i.e. package feerate), 320 some information may be different. 321 """ 322 for testres_tx in testmempoolaccept_result: 323 # Grab this result from the submitpackage_result 324 submitres_tx = submitpackage_result["tx-results"][testres_tx["wtxid"]] 325 assert_equal(submitres_tx["txid"], testres_tx["txid"]) 326 # No "allowed" if the tx was already in the mempool 327 if "allowed" in testres_tx and testres_tx["allowed"]: 328 assert_equal(submitres_tx["vsize"], testres_tx["vsize"]) 329 assert_equal(submitres_tx["fees"]["base"], testres_tx["fees"]["base"]) 330 entry_info = node.getmempoolentry(submitres_tx["txid"]) 331 assert_equal(submitres_tx["vsize"], entry_info["vsize"]) 332 assert_equal(submitres_tx["fees"]["base"], entry_info["fees"]["base"]) 333 334 def test_submit_child_with_parents(self, num_parents, partial_submit): 335 node = self.nodes[0] 336 peer = node.add_p2p_connection(P2PTxInvStore()) 337 338 package_txns = [] 339 presubmitted_wtxids = set() 340 for _ in range(num_parents): 341 parent_tx = self.wallet.create_self_transfer(fee=DEFAULT_FEE) 342 package_txns.append(parent_tx) 343 if partial_submit and random.choice([True, False]): 344 node.sendrawtransaction(parent_tx["hex"]) 345 presubmitted_wtxids.add(parent_tx["wtxid"]) 346 child_tx = self.wallet.create_self_transfer_multi(utxos_to_spend=[tx["new_utxo"] for tx in package_txns], fee_per_output=10000) #DEFAULT_FEE 347 package_txns.append(child_tx) 348 349 testmempoolaccept_result = node.testmempoolaccept(rawtxs=[tx["hex"] for tx in package_txns]) 350 submitpackage_result = node.submitpackage(package=[tx["hex"] for tx in package_txns]) 351 352 # Check that each result is present, with the correct size and fees 353 assert_equal(submitpackage_result["package_msg"], "success") 354 for package_txn in package_txns: 355 tx = package_txn["tx"] 356 assert tx.getwtxid() in submitpackage_result["tx-results"] 357 wtxid = tx.getwtxid() 358 assert wtxid in submitpackage_result["tx-results"] 359 tx_result = submitpackage_result["tx-results"][wtxid] 360 assert_equal(tx_result["txid"], tx.rehash()) 361 assert_equal(tx_result["vsize"], tx.get_vsize()) 362 assert_equal(tx_result["fees"]["base"], DEFAULT_FEE) 363 if wtxid not in presubmitted_wtxids: 364 assert_fee_amount(DEFAULT_FEE, tx.get_vsize(), tx_result["fees"]["effective-feerate"]) 365 assert_equal(tx_result["fees"]["effective-includes"], [wtxid]) 366 367 # submitpackage result should be consistent with testmempoolaccept and getmempoolentry 368 self.assert_equal_package_results(node, testmempoolaccept_result, submitpackage_result) 369 370 # The node should announce each transaction. No guarantees for propagation. 371 peer.wait_for_broadcast([tx["tx"].getwtxid() for tx in package_txns]) 372 self.generate(node, 1) 373 374 def test_submitpackage(self): 375 node = self.nodes[0] 376 377 self.log.info("Submitpackage only allows valid hex inputs") 378 valid_tx_list = self.wallet.create_self_transfer_chain(chain_length=2) 379 hex_list = [valid_tx_list[0]["hex"][:-1] + 'X', valid_tx_list[1]["hex"]] 380 txid_list = [valid_tx_list[0]["txid"], valid_tx_list[1]["txid"]] 381 assert_raises_rpc_error(-22, "TX decode failed:", node.submitpackage, hex_list) 382 assert txid_list[0] not in node.getrawmempool() 383 assert txid_list[1] not in node.getrawmempool() 384 385 self.log.info("Submitpackage valid packages with 1 child and some number of parents (or none)") 386 for num_parents in [0, 1, 2, 24]: 387 self.test_submit_child_with_parents(num_parents, False) 388 self.test_submit_child_with_parents(num_parents, True) 389 390 self.log.info("Submitpackage only allows packages of 1 child with its parents") 391 # Chain of 3 transactions has too many generations 392 legacy_pool = node.getrawmempool() 393 chain_hex = [t["hex"] for t in self.wallet.create_self_transfer_chain(chain_length=3)] 394 assert_raises_rpc_error(-25, "package topology disallowed", node.submitpackage, chain_hex) 395 assert_equal(legacy_pool, node.getrawmempool()) 396 397 assert_raises_rpc_error(-8, f"Array must contain between 1 and {MAX_PACKAGE_COUNT} transactions.", node.submitpackage, []) 398 assert_raises_rpc_error( 399 -8, f"Array must contain between 1 and {MAX_PACKAGE_COUNT} transactions.", 400 node.submitpackage, [chain_hex[0]] * (MAX_PACKAGE_COUNT + 1) 401 ) 402 403 # Create a transaction chain such as only the parent gets accepted (by making the child's 404 # version non-standard). Make sure the parent does get broadcast. 405 self.log.info("If a package is partially submitted, transactions included in mempool get broadcast") 406 peer = node.add_p2p_connection(P2PTxInvStore()) 407 txs = self.wallet.create_self_transfer_chain(chain_length=2) 408 bad_child = tx_from_hex(txs[1]["hex"]) 409 bad_child.version = 0xffffffff 410 hex_partial_acceptance = [txs[0]["hex"], bad_child.serialize().hex()] 411 res = node.submitpackage(hex_partial_acceptance) 412 assert_equal(res["package_msg"], "transaction failed") 413 first_wtxid = txs[0]["tx"].getwtxid() 414 assert "error" not in res["tx-results"][first_wtxid] 415 sec_wtxid = bad_child.getwtxid() 416 assert_equal(res["tx-results"][sec_wtxid]["error"], "version") 417 peer.wait_for_broadcast([first_wtxid]) 418 419 def test_maxfeerate_submitpackage(self): 420 node = self.nodes[0] 421 # clear mempool 422 deterministic_address = node.get_deterministic_priv_key().address 423 self.generatetoaddress(node, 1, deterministic_address) 424 425 self.log.info("Submitpackage maxfeerate arg testing") 426 chained_txns = self.wallet.create_self_transfer_chain(chain_length=2) 427 minrate_btc_kvb = min([chained_txn["fee"] / chained_txn["tx"].get_vsize() * 1000 for chained_txn in chained_txns]) 428 chain_hex = [t["hex"] for t in chained_txns] 429 pkg_result = node.submitpackage(chain_hex, maxfeerate=minrate_btc_kvb - Decimal("0.00000001")) 430 431 # First tx failed in single transaction evaluation, so package message is generic 432 assert_equal(pkg_result["package_msg"], "transaction failed") 433 assert_equal(pkg_result["tx-results"][chained_txns[0]["wtxid"]]["error"], "max feerate exceeded") 434 assert_equal(pkg_result["tx-results"][chained_txns[1]["wtxid"]]["error"], "bad-txns-inputs-missingorspent") 435 assert_equal(node.getrawmempool(), []) 436 437 # Make chain of two transactions where parent doesn't make minfee threshold 438 # but child is too high fee 439 # Lower mempool limit to make it easier to fill_mempool 440 self.restart_node(0, extra_args=[ 441 "-datacarriersize=100000", 442 "-maxmempool=5", 443 "-persistmempool=0", 444 ]) 445 self.wallet.rescan_utxos() 446 447 fill_mempool(self, node) 448 449 minrelay = node.getmempoolinfo()["minrelaytxfee"] 450 parent = self.wallet.create_self_transfer( 451 fee_rate=minrelay, 452 confirmed_only=True, 453 ) 454 455 child = self.wallet.create_self_transfer( 456 fee_rate=DEFAULT_FEE, 457 utxo_to_spend=parent["new_utxo"], 458 ) 459 460 pkg_result = node.submitpackage([parent["hex"], child["hex"]], maxfeerate=DEFAULT_FEE - Decimal("0.00000001")) 461 462 # Child is connected even though parent is invalid and still reports fee exceeded 463 # this implies sub-package evaluation of both entries together. 464 assert_equal(pkg_result["package_msg"], "transaction failed") 465 assert "mempool min fee not met" in pkg_result["tx-results"][parent["wtxid"]]["error"] 466 assert_equal(pkg_result["tx-results"][child["wtxid"]]["error"], "max feerate exceeded") 467 assert parent["txid"] not in node.getrawmempool() 468 assert child["txid"] not in node.getrawmempool() 469 470 # Reset maxmempool, datacarriersize, reset dynamic mempool minimum feerate, and empty mempool. 471 self.restart_node(0) 472 self.wallet.rescan_utxos() 473 474 assert_equal(node.getrawmempool(), []) 475 476 def test_maxburn_submitpackage(self): 477 node = self.nodes[0] 478 479 assert_equal(node.getrawmempool(), []) 480 481 self.log.info("Submitpackage maxburnamount arg testing") 482 chained_txns_burn = self.wallet.create_self_transfer_chain( 483 chain_length=2, 484 utxo_to_spend=self.wallet.get_utxo(confirmed_only=True), 485 ) 486 chained_burn_hex = [t["hex"] for t in chained_txns_burn] 487 488 tx = tx_from_hex(chained_burn_hex[1]) 489 tx.vout[-1].scriptPubKey = b'a' * 10001 # scriptPubKey bigger than 10k IsUnspendable 490 chained_burn_hex = [chained_burn_hex[0], tx.serialize().hex()] 491 # burn test is run before any package evaluation; nothing makes it in and we get broader exception 492 assert_raises_rpc_error(-25, "Unspendable output exceeds maximum configured by user", node.submitpackage, chained_burn_hex, 0, chained_txns_burn[1]["new_utxo"]["value"] - Decimal("0.00000001")) 493 assert_equal(node.getrawmempool(), []) 494 495 minrate_btc_kvb_burn = min([chained_txn_burn["fee"] / chained_txn_burn["tx"].get_vsize() * 1000 for chained_txn_burn in chained_txns_burn]) 496 497 # Relax the restrictions for both and send it; parent gets through as own subpackage 498 pkg_result = node.submitpackage(chained_burn_hex, maxfeerate=minrate_btc_kvb_burn, maxburnamount=chained_txns_burn[1]["new_utxo"]["value"]) 499 assert "error" not in pkg_result["tx-results"][chained_txns_burn[0]["wtxid"]] 500 assert_equal(pkg_result["tx-results"][tx.getwtxid()]["error"], "scriptpubkey") 501 assert_equal(node.getrawmempool(), [chained_txns_burn[0]["txid"]]) 502 503 if __name__ == "__main__": 504 RPCPackagesTest(__file__).main()