/ test / functional / rpc_packages.py
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()