1 #!/usr/bin/env python3
2 # Copyright (c) 2021-2022 The Limenka 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 LimenkaTestFramework
20 from test_framework.util import (
21 assert_equal,
22 assert_equal_without_usage,
23 assert_fee_amount,
24 assert_raises_rpc_error,
25 )
26 from test_framework.wallet import (
27 COIN,
28 DEFAULT_FEE,
29 MiniWallet,
30 )
31 32 33 MAX_PACKAGE_COUNT = 25
34 35 36 class RPCPackagesTest(LimenkaTestFramework):
37 def set_test_params(self):
38 self.num_nodes = 1
39 self.setup_clean_chain = True
40 # whitelist peers to speed up tx relay / mempool sync
41 self.noban_tx_relay = True
42 43 def assert_testres_equal(self, package_hex, testres_expected):
44 """Shuffle package_hex and assert that the testmempoolaccept result matches testres_expected. This should only
45 be used to test packages where the order does not matter. The ordering of transactions in package_hex and
46 testres_expected must match.
47 """
48 shuffled_indeces = list(range(len(package_hex)))
49 random.shuffle(shuffled_indeces)
50 shuffled_package = [package_hex[i] for i in shuffled_indeces]
51 shuffled_testres = [testres_expected[i] for i in shuffled_indeces]
52 assert_equal_without_usage(self.nodes[0].testmempoolaccept(shuffled_package), shuffled_testres)
53 54 def run_test(self):
55 node = self.nodes[0]
56 57 # get an UTXO that requires signature to be spent
58 deterministic_address = node.get_deterministic_priv_key().address
59 blockhash = self.generatetoaddress(node, 1, deterministic_address)[0]
60 coinbase = node.getblock(blockhash=blockhash, verbosity=2)["tx"][0]
61 coin = {
62 "txid": coinbase["txid"],
63 "amount": coinbase["vout"][0]["value"],
64 "scriptPubKey": coinbase["vout"][0]["scriptPubKey"],
65 "vout": 0,
66 "height": 0
67 }
68 69 self.wallet = MiniWallet(self.nodes[0])
70 self.generate(self.wallet, COINBASE_MATURITY + 100) # blocks generated for inputs
71 72 self.log.info("Create some transactions")
73 # Create some transactions that can be reused throughout the test. Never submit these to mempool.
74 self.independent_txns_hex = []
75 self.independent_txns_testres = []
76 for _ in range(3):
77 tx_hex = self.wallet.create_self_transfer(fee_rate=Decimal("0.0001"))["hex"]
78 testres = self.nodes[0].testmempoolaccept([tx_hex])
79 assert testres[0]["allowed"]
80 self.independent_txns_hex.append(tx_hex)
81 # testmempoolaccept returns a list of length one, avoid creating a 2D list
82 self.independent_txns_testres.append(testres[0])
83 self.independent_txns_testres_blank = [{
84 "txid": res["txid"], "wtxid": res["wtxid"]} for res in self.independent_txns_testres]
85 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.rehash(), "wtxid": tx.getwtxid(), "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.rehash()
122 tx_bad_sig_wtxid = tx_bad_sig.getwtxid()
123 assert_equal_without_usage(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_without_usage(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_without_usage(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_without_usage(node.testmempoolaccept(rawtxs=chain_hex[::-1]),
150 [{"txid": tx.rehash(), "wtxid": tx.getwtxid(), "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_without_usage(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_without_usage(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_without_usage(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_without_usage(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 assert_equal(submitres, {'package_msg': 'conflict-in-package', 'tx-results': {}, 'replaced-transactions': []})
267 268 # Submit tx1 to mempool, then try the same package again
269 node.sendrawtransaction(tx1["hex"])
270 271 submitres = node.submitpackage([tx1["hex"], tx2["hex"], tx_child["hex"]])
272 assert_equal(submitres, {'package_msg': 'conflict-in-package', 'tx-results': {}, 'replaced-transactions': []})
273 assert tx_child["txid"] not in node.getrawmempool()
274 275 # ... and without the in-mempool ancestor tx1 included in the call
276 submitres = node.submitpackage([tx2["hex"], tx_child["hex"]])
277 assert_equal(submitres, {'package_msg': 'package-not-child-with-unconfirmed-parents', 'tx-results': {}, 'replaced-transactions': []})
278 279 # Regardless of error type, the child can never enter the mempool
280 assert tx_child["txid"] not in node.getrawmempool()
281 282 def test_rbf(self):
283 node = self.nodes[0]
284 285 coin = self.wallet.get_utxo()
286 fee = Decimal("0.00125000")
287 replaceable_tx = self.wallet.create_self_transfer(utxo_to_spend=coin, sequence=MAX_BIP125_RBF_SEQUENCE, fee = fee)
288 testres_replaceable = node.testmempoolaccept([replaceable_tx["hex"]])[0]
289 assert_equal(testres_replaceable["txid"], replaceable_tx["txid"])
290 assert_equal(testres_replaceable["wtxid"], replaceable_tx["wtxid"])
291 assert testres_replaceable["allowed"]
292 assert_equal(testres_replaceable["vsize"], replaceable_tx["tx"].get_vsize())
293 assert_equal(testres_replaceable["fees"]["base"], fee)
294 assert_fee_amount(fee, replaceable_tx["tx"].get_vsize(), testres_replaceable["fees"]["effective-feerate"])
295 assert_equal(testres_replaceable["fees"]["effective-includes"], [replaceable_tx["wtxid"]])
296 297 # Replacement transaction is identical except has double the fee
298 replacement_tx = self.wallet.create_self_transfer(utxo_to_spend=coin, sequence=MAX_BIP125_RBF_SEQUENCE, fee = 2 * fee)
299 testres_rbf_conflicting = node.testmempoolaccept([replaceable_tx["hex"], replacement_tx["hex"]])
300 assert_equal_without_usage(testres_rbf_conflicting, [
301 {"txid": replaceable_tx["txid"], "wtxid": replaceable_tx["wtxid"], "package-error": "conflict-in-package"},
302 {"txid": replacement_tx["txid"], "wtxid": replacement_tx["wtxid"], "package-error": "conflict-in-package"}
303 ])
304 305 self.log.info("Test that packages cannot conflict with mempool transactions, even if a valid BIP125 RBF")
306 # This transaction is a valid BIP125 replace-by-fee
307 self.wallet.sendrawtransaction(from_node=node, tx_hex=replaceable_tx["hex"])
308 testres_rbf_single = node.testmempoolaccept([replacement_tx["hex"]])
309 assert testres_rbf_single[0]["allowed"]
310 testres_rbf_package = self.independent_txns_testres_blank + [{
311 "txid": replacement_tx["txid"], "wtxid": replacement_tx["wtxid"], "allowed": False,
312 "reject-reason": "bip125-replacement-disallowed",
313 "reject-details": "bip125-replacement-disallowed"
314 }]
315 self.assert_testres_equal(self.independent_txns_hex + [replacement_tx["hex"]], testres_rbf_package)
316 317 def assert_equal_package_results(self, node, testmempoolaccept_result, submitpackage_result):
318 """Assert that a successful submitpackage result is consistent with testmempoolaccept
319 results and getmempoolentry info. Note that the result structs are different and, due to
320 policy differences between testmempoolaccept and submitpackage (i.e. package feerate),
321 some information may be different.
322 """
323 for testres_tx in testmempoolaccept_result:
324 # Grab this result from the submitpackage_result
325 submitres_tx = submitpackage_result["tx-results"][testres_tx["wtxid"]]
326 assert_equal(submitres_tx["txid"], testres_tx["txid"])
327 # No "allowed" if the tx was already in the mempool
328 if "allowed" in testres_tx and testres_tx["allowed"]:
329 assert_equal(submitres_tx["vsize"], testres_tx["vsize"])
330 assert_equal(submitres_tx["fees"]["base"], testres_tx["fees"]["base"])
331 entry_info = node.getmempoolentry(submitres_tx["txid"])
332 assert_equal(submitres_tx["vsize"], entry_info["vsize"])
333 assert_equal(submitres_tx["fees"]["base"], entry_info["fees"]["base"])
334 335 def test_submit_child_with_parents(self, num_parents, partial_submit):
336 node = self.nodes[0]
337 peer = node.add_p2p_connection(P2PTxInvStore())
338 339 package_txns = []
340 presubmitted_wtxids = set()
341 for _ in range(num_parents):
342 parent_tx = self.wallet.create_self_transfer(fee=DEFAULT_FEE)
343 package_txns.append(parent_tx)
344 if partial_submit and random.choice([True, False]):
345 node.sendrawtransaction(parent_tx["hex"])
346 presubmitted_wtxids.add(parent_tx["wtxid"])
347 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
348 package_txns.append(child_tx)
349 350 testmempoolaccept_result = node.testmempoolaccept(rawtxs=[tx["hex"] for tx in package_txns])
351 submitpackage_result = node.submitpackage(package=[tx["hex"] for tx in package_txns])
352 353 # Check that each result is present, with the correct size and fees
354 assert_equal(submitpackage_result["package_msg"], "success")
355 for package_txn in package_txns:
356 tx = package_txn["tx"]
357 assert tx.getwtxid() in submitpackage_result["tx-results"]
358 wtxid = tx.getwtxid()
359 assert wtxid in submitpackage_result["tx-results"]
360 tx_result = submitpackage_result["tx-results"][wtxid]
361 assert_equal(tx_result["txid"], tx.rehash())
362 assert_equal(tx_result["vsize"], tx.get_vsize())
363 assert_equal(tx_result["fees"]["base"], DEFAULT_FEE)
364 if wtxid not in presubmitted_wtxids:
365 assert_fee_amount(DEFAULT_FEE, tx.get_vsize(), tx_result["fees"]["effective-feerate"])
366 assert_equal(tx_result["fees"]["effective-includes"], [wtxid])
367 368 # submitpackage result should be consistent with testmempoolaccept and getmempoolentry
369 self.assert_equal_package_results(node, testmempoolaccept_result, submitpackage_result)
370 371 # The node should announce each transaction. No guarantees for propagation.
372 peer.wait_for_broadcast([tx["tx"].getwtxid() for tx in package_txns])
373 self.generate(node, 1)
374 375 def test_submitpackage(self):
376 node = self.nodes[0]
377 378 self.log.info("Submitpackage only allows valid hex inputs")
379 valid_tx_list = self.wallet.create_self_transfer_chain(chain_length=2)
380 hex_list = [valid_tx_list[0]["hex"][:-1] + 'X', valid_tx_list[1]["hex"]]
381 txid_list = [valid_tx_list[0]["txid"], valid_tx_list[1]["txid"]]
382 assert_raises_rpc_error(-22, "TX decode failed:", node.submitpackage, hex_list)
383 assert txid_list[0] not in node.getrawmempool()
384 assert txid_list[1] not in node.getrawmempool()
385 386 self.log.info("Submitpackage valid packages with 1 child and some number of parents (or none)")
387 for num_parents in [0, 1, 2, 24]:
388 self.test_submit_child_with_parents(num_parents, False)
389 self.test_submit_child_with_parents(num_parents, True)
390 391 self.log.info("Submitpackage only allows packages of 1 child with its parents")
392 # Chain of 3 transactions has too many generations
393 legacy_pool = node.getrawmempool()
394 chain_hex = [t["hex"] for t in self.wallet.create_self_transfer_chain(chain_length=3)]
395 assert_raises_rpc_error(-25, "package topology disallowed", node.submitpackage, chain_hex)
396 assert_equal(legacy_pool, node.getrawmempool())
397 398 assert_raises_rpc_error(-8, f"Array must contain between 1 and {MAX_PACKAGE_COUNT} transactions.", node.submitpackage, [])
399 assert_raises_rpc_error(
400 -8, f"Array must contain between 1 and {MAX_PACKAGE_COUNT} transactions.",
401 node.submitpackage, [chain_hex[0]] * (MAX_PACKAGE_COUNT + 1)
402 )
403 404 # Create a transaction chain such as only the parent gets accepted (by making the child's
405 # version non-standard). Make sure the parent does get broadcast.
406 self.log.info("If a package is partially submitted, transactions included in mempool get broadcast")
407 peer = node.add_p2p_connection(P2PTxInvStore())
408 txs = self.wallet.create_self_transfer_chain(chain_length=2)
409 bad_child = tx_from_hex(txs[1]["hex"])
410 bad_child.version = 0xffffffff
411 hex_partial_acceptance = [txs[0]["hex"], bad_child.serialize().hex()]
412 res = node.submitpackage(hex_partial_acceptance)
413 assert_equal(res["package_msg"], "transaction failed")
414 first_wtxid = txs[0]["tx"].getwtxid()
415 assert "error" not in res["tx-results"][first_wtxid]
416 sec_wtxid = bad_child.getwtxid()
417 assert_equal(res["tx-results"][sec_wtxid]["error"], "version")
418 peer.wait_for_broadcast([first_wtxid])
419 420 def test_maxfeerate_submitpackage(self):
421 node = self.nodes[0]
422 # clear mempool
423 deterministic_address = node.get_deterministic_priv_key().address
424 self.generatetoaddress(node, 1, deterministic_address)
425 426 self.log.info("Submitpackage maxfeerate arg testing")
427 chained_txns = self.wallet.create_self_transfer_chain(chain_length=2)
428 minrate_btc_kvb = min([chained_txn["fee"] / chained_txn["tx"].get_vsize() * 1000 for chained_txn in chained_txns])
429 chain_hex = [t["hex"] for t in chained_txns]
430 pkg_result = node.submitpackage(chain_hex, maxfeerate=minrate_btc_kvb - Decimal("0.00000001"))
431 432 # First tx failed in single transaction evaluation, so package message is generic
433 assert_equal(pkg_result["package_msg"], "transaction failed")
434 assert_equal(pkg_result["tx-results"][chained_txns[0]["wtxid"]]["error"], "max feerate exceeded")
435 assert_equal(pkg_result["tx-results"][chained_txns[1]["wtxid"]]["error"], "bad-txns-inputs-missingorspent")
436 assert_equal(node.getrawmempool(), [])
437 438 # Make chain of two transactions where parent doesn't make minfee threshold
439 # but child is too high fee
440 # Lower mempool limit to make it easier to fill_mempool
441 self.restart_node(0, extra_args=[
442 "-datacarriersize=100000",
443 "-maxmempool=5",
444 "-persistmempool=0",
445 ])
446 self.wallet.rescan_utxos()
447 448 fill_mempool(self, node)
449 450 minrelay = node.getmempoolinfo()["minrelaytxfee"]
451 parent = self.wallet.create_self_transfer(
452 fee_rate=minrelay,
453 confirmed_only=True,
454 )
455 456 child = self.wallet.create_self_transfer(
457 fee_rate=DEFAULT_FEE,
458 utxo_to_spend=parent["new_utxo"],
459 )
460 461 pkg_result = node.submitpackage([parent["hex"], child["hex"]], maxfeerate=DEFAULT_FEE - Decimal("0.00000001"))
462 463 # Child is connected even though parent is invalid and still reports fee exceeded
464 # this implies sub-package evaluation of both entries together.
465 assert_equal(pkg_result["package_msg"], "transaction failed")
466 assert "mempool min fee not met" in pkg_result["tx-results"][parent["wtxid"]]["error"]
467 assert_equal(pkg_result["tx-results"][child["wtxid"]]["error"], "max feerate exceeded")
468 assert parent["txid"] not in node.getrawmempool()
469 assert child["txid"] not in node.getrawmempool()
470 471 # Reset maxmempool, datacarriersize, reset dynamic mempool minimum feerate, and empty mempool.
472 self.restart_node(0)
473 self.wallet.rescan_utxos()
474 475 assert_equal(node.getrawmempool(), [])
476 477 def test_maxburn_submitpackage(self):
478 node = self.nodes[0]
479 480 assert_equal(node.getrawmempool(), [])
481 482 self.log.info("Submitpackage maxburnamount arg testing")
483 chained_txns_burn = self.wallet.create_self_transfer_chain(
484 chain_length=2,
485 utxo_to_spend=self.wallet.get_utxo(confirmed_only=True),
486 )
487 chained_burn_hex = [t["hex"] for t in chained_txns_burn]
488 489 tx = tx_from_hex(chained_burn_hex[1])
490 tx.vout[-1].scriptPubKey = b'a' * 10001 # scriptPubKey bigger than 10k IsUnspendable
491 chained_burn_hex = [chained_burn_hex[0], tx.serialize().hex()]
492 # burn test is run before any package evaluation; nothing makes it in and we get broader exception
493 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"))
494 assert_equal(node.getrawmempool(), [])
495 496 minrate_btc_kvb_burn = min([chained_txn_burn["fee"] / chained_txn_burn["tx"].get_vsize() * 1000 for chained_txn_burn in chained_txns_burn])
497 498 # Relax the restrictions for both and send it; parent gets through as own subpackage
499 pkg_result = node.submitpackage(chained_burn_hex, maxfeerate=minrate_btc_kvb_burn, maxburnamount=chained_txns_burn[1]["new_utxo"]["value"])
500 assert "error" not in pkg_result["tx-results"][chained_txns_burn[0]["wtxid"]]
501 assert_equal(pkg_result["tx-results"][tx.getwtxid()]["error"], "scriptpubkey")
502 assert_equal(node.getrawmempool(), [chained_txns_burn[0]["txid"]])
503 504 if __name__ == "__main__":
505 RPCPackagesTest(__file__).main()
506