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