rpc_packages.py raw

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