Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
251# Add a child that spends both at high feerate to submit via submitpackage252tx_child = self.wallet.create_self_transfer_multi(253fee_per_output=int(DEFAULT_FEE * 5 * COIN),254utxos_to_spend=[tx1["new_utxo"], tx2["new_utxo"]],255)257testres = node.testmempoolaccept([tx1["hex"], tx2["hex"], tx_child["hex"]])259assert_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])265submitres = node.submitpackage([tx1["hex"], tx2["hex"], tx_child["hex"]])266expected = {267tx1["wtxid"]: {"txid": tx1["txid"], "error": "package-not-validated"},268tx2["wtxid"]: {"txid": tx2["txid"], "error": "package-not-validated"},269tx_child["wtxid"]: {"txid": tx_child["txid"], "error": "package-not-validated"},270}271assert_equal(submitres, {"package_msg": "conflict-in-package", "tx-results": expected,"replaced-transactions": []})273# Submit tx1 to mempool, then try the same package again274node.sendrawtransaction(tx1["hex"])276submitres = node.submitpackage([tx1["hex"], tx2["hex"], tx_child["hex"]])277expected = {278tx1["wtxid"]: {"txid": tx1["txid"], "error": "package-not-validated"},279tx2["wtxid"]: {"txid": tx2["txid"], "error": "package-not-validated"},280tx_child["wtxid"]: {"txid": tx_child["txid"], "error": "package-not-validated"},281}282assert_equal(submitres, {"package_msg": "conflict-in-package", "tx-results": expected,"replaced-transactions": []})283assert tx_child["txid"] not in node.getrawmempool()285# without the in-mempool ancestor tx1 included in the call, tx2 can be submitted, but286# tx_child is missing an input.287submitres = node.submitpackage([tx2["hex"], tx_child["hex"]])288assert_equal(submitres["tx-results"][tx_child["wtxid"]], {"txid": tx_child["txid"], "error": "bad-txns-inputs-missingorspent"})289assert tx2["txid"] in node.getrawmempool()291# Regardless of error type, the child can never enter the mempool292assert tx_child["txid"] not in node.getrawmempool()294def test_rbf(self):295node = self.nodes[0]297coin = self.wallet.get_utxo()298fee = Decimal("0.00125000")299replaceable_tx = self.wallet.create_self_transfer(utxo_to_spend=coin, sequence=MAX_BIP125_RBF_SEQUENCE, fee = fee)300testres_replaceable = node.testmempoolaccept([replaceable_tx["hex"]])[0]301assert_equal(testres_replaceable["txid"], replaceable_tx["txid"])302assert_equal(testres_replaceable["wtxid"], replaceable_tx["wtxid"])303assert testres_replaceable["allowed"]304assert_equal(testres_replaceable["vsize"], replaceable_tx["tx"].get_vsize())305assert_equal(testres_replaceable["vsize_bip141"], replaceable_tx["tx"].get_vsize())306assert_equal(testres_replaceable["fees"]["base"], fee)307assert_fee_amount(fee, replaceable_tx["tx"].get_vsize(), testres_replaceable["fees"]["effective-feerate"])308assert_equal(testres_replaceable["fees"]["effective-includes"], [replaceable_tx["wtxid"]])310# Replacement transaction is identical except has double the fee311replacement_tx = self.wallet.create_self_transfer(utxo_to_spend=coin, sequence=MAX_BIP125_RBF_SEQUENCE, fee = 2 * fee)312testres_rbf_conflicting = node.testmempoolaccept([replaceable_tx["hex"], replacement_tx["hex"]])313assert_equal(testres_rbf_conflicting, [314{"txid": replaceable_tx["txid"], "wtxid": replaceable_tx["wtxid"], "package-error": "conflict-in-package"},315{"txid": replacement_tx["txid"], "wtxid": replacement_tx["wtxid"], "package-error": "conflict-in-package"}316])318self.log.info("Test that packages cannot conflict with mempool transactions, even if a valid BIP125 RBF")319# This transaction is a valid BIP125 replace-by-fee320self.wallet.sendrawtransaction(from_node=node, tx_hex=replaceable_tx["hex"])321testres_rbf_single = node.testmempoolaccept([replacement_tx["hex"]])322assert testres_rbf_single[0]["allowed"]323testres_rbf_package = self.independent_txns_testres_blank + [{324"txid": replacement_tx["txid"], "wtxid": replacement_tx["wtxid"], "allowed": False,325"reject-reason": "bip125-replacement-disallowed",326"reject-details": "bip125-replacement-disallowed"327}]328self.assert_testres_equal(self.independent_txns_hex + [replacement_tx["hex"]], testres_rbf_package)330def assert_equal_package_results(self, node, testmempoolaccept_result, submitpackage_result):331"""Assert that a successful submitpackage result is consistent with testmempoolaccept332results and getmempoolentry info. Note that the result structs are different and, due to333policy differences between testmempoolaccept and submitpackage (i.e. package feerate),334some information may be different.335"""336for testres_tx in testmempoolaccept_result:337# Grab this result from the submitpackage_result338submitres_tx = submitpackage_result["tx-results"][testres_tx["wtxid"]]339assert_equal(submitres_tx["txid"], testres_tx["txid"])340# No "allowed" if the tx was already in the mempool341if "allowed" in testres_tx and testres_tx["allowed"]:342assert_equal(submitres_tx["vsize"], testres_tx["vsize"])343assert_equal(submitres_tx["vsize_bip141"], testres_tx["vsize"])344assert_equal(submitres_tx["fees"]["base"], testres_tx["fees"]["base"])345entry_info = node.getmempoolentry(submitres_tx["txid"])346assert_equal(submitres_tx["vsize"], entry_info["vsize"])347assert_equal(submitres_tx["vsize_bip141"], entry_info["vsize"])348assert_equal(submitres_tx["fees"]["base"], entry_info["fees"]["base"])350def test_submit_child_with_parents(self, num_parents, partial_submit):351node = self.nodes[0]352peer = node.add_p2p_connection(P2PTxInvStore())354package_txns = []355presubmitted_wtxids = set()356for _ in range(num_parents):357parent_tx = self.wallet.create_self_transfer(fee=DEFAULT_FEE)358package_txns.append(parent_tx)359if partial_submit and random.choice([True, False]):360node.sendrawtransaction(parent_tx["hex"])361presubmitted_wtxids.add(parent_tx["wtxid"])362child_tx = self.wallet.create_self_transfer_multi(utxos_to_spend=[tx["new_utxo"] for tx in package_txns], fee_per_output=10000) #DEFAULT_FEE363package_txns.append(child_tx)365testmempoolaccept_result = node.testmempoolaccept(rawtxs=[tx["hex"] for tx in package_txns])366submitpackage_result = node.submitpackage(package=[tx["hex"] for tx in package_txns])368# Check that each result is present, with the correct size and fees369assert_equal(submitpackage_result["package_msg"], "success")370for package_txn in package_txns:371tx = package_txn["tx"]372assert tx.wtxid_hex in submitpackage_result["tx-results"]373wtxid = tx.wtxid_hex374assert wtxid in submitpackage_result["tx-results"]375tx_result = submitpackage_result["tx-results"][wtxid]376assert_equal(tx_result["txid"], tx.txid_hex)377assert_equal(tx_result["vsize_adjusted"], tx.get_vsize())378assert_equal(tx_result["vsize"], tx.get_vsize())379assert_equal(tx_result["vsize_bip141"], tx.get_vsize())380assert_equal(tx_result["fees"]["base"], DEFAULT_FEE)381if wtxid not in presubmitted_wtxids:382assert_fee_amount(DEFAULT_FEE, tx.get_vsize(), tx_result["fees"]["effective-feerate"])383assert_equal(tx_result["fees"]["effective-includes"], [wtxid])385# submitpackage result should be consistent with testmempoolaccept and getmempoolentry386self.assert_equal_package_results(node, testmempoolaccept_result, submitpackage_result)388# The node should announce each transaction. No guarantees for propagation.389peer.wait_for_broadcast([tx["tx"].wtxid_hex for tx in package_txns])390self.generate(node, 1)392def test_submitpackage(self):393node = self.nodes[0]395self.log.info("Submitpackage only allows valid hex inputs")396valid_tx_list = self.wallet.create_self_transfer_chain(chain_length=2)397hex_list = [valid_tx_list[0]["hex"][:-1] + 'X', valid_tx_list[1]["hex"]]398txid_list = [valid_tx_list[0]["txid"], valid_tx_list[1]["txid"]]399assert_raises_rpc_error(-22, "TX decode failed:", node.submitpackage, hex_list)400assert txid_list[0] not in node.getrawmempool()401assert txid_list[1] not in node.getrawmempool()403self.log.info("Submitpackage valid packages with 1 child and some number of parents (or none)")404for num_parents in [0, 1, 2, 24]:405self.test_submit_child_with_parents(num_parents, False)406self.test_submit_child_with_parents(num_parents, True)408self.log.info("Submitpackage only allows packages of 1 child with its parents")409# Chain of 3 transactions has too many generations410legacy_pool = node.getrawmempool()411chain_hex = [t["hex"] for t in self.wallet.create_self_transfer_chain(chain_length=3)]412assert_raises_rpc_error(-25, "package topology disallowed", node.submitpackage, chain_hex)413assert_equal(legacy_pool, node.getrawmempool())415assert_raises_rpc_error(-8, f"Array must contain between 1 and {MAX_PACKAGE_COUNT} transactions.", node.submitpackage, [])416assert_raises_rpc_error(417-8, f"Array must contain between 1 and {MAX_PACKAGE_COUNT} transactions.",418node.submitpackage, [chain_hex[0]] * (MAX_PACKAGE_COUNT + 1)419)421# Create a transaction chain such as only the parent gets accepted (by making the child's422# version non-standard). Make sure the parent does get broadcast.423self.log.info("If a package is partially submitted, transactions included in mempool get broadcast")424peer = node.add_p2p_connection(P2PTxInvStore())425txs = self.wallet.create_self_transfer_chain(chain_length=2)426bad_child = tx_from_hex(txs[1]["hex"])427bad_child.version = 0xffffffff428hex_partial_acceptance = [txs[0]["hex"], bad_child.serialize().hex()]429res = node.submitpackage(hex_partial_acceptance)430assert_equal(res["package_msg"], "transaction failed")431first_wtxid = txs[0]["tx"].wtxid_hex432assert "error" not in res["tx-results"][first_wtxid]433sec_wtxid = bad_child.wtxid_hex434assert_equal(res["tx-results"][sec_wtxid]["error"], "version")435peer.wait_for_broadcast([first_wtxid])437def test_maxfeerate_submitpackage(self):438node = self.nodes[0]439# clear mempool440deterministic_address = node.get_deterministic_priv_key().address441self.generatetoaddress(node, 1, deterministic_address)443self.log.info("Submitpackage maxfeerate arg testing")444chained_txns = self.wallet.create_self_transfer_chain(chain_length=2)445minrate_btc_kvb = min([chained_txn["fee"] / chained_txn["tx"].get_vsize() * 1000 for chained_txn in chained_txns])446chain_hex = [t["hex"] for t in chained_txns]447pkg_result = node.submitpackage(chain_hex, maxfeerate=minrate_btc_kvb - Decimal("0.00000001"))449# First tx failed in single transaction evaluation, so package message is generic450assert_equal(pkg_result["package_msg"], "transaction failed")451assert_equal(pkg_result["tx-results"][chained_txns[0]["wtxid"]]["error"], "max feerate exceeded")452assert_equal(pkg_result["tx-results"][chained_txns[1]["wtxid"]]["error"], "bad-txns-inputs-missingorspent")453assert_equal(node.getrawmempool(), [])455# Make chain of two transactions where parent doesn't make minfee threshold456# but child is too high fee457# Lower mempool limit to make it easier to fill_mempool458self.restart_node(0, extra_args=[459"-maxmempool=5",460"-persistmempool=0",461])462self.wallet.rescan_utxos()464fill_mempool(self, node)466minrelay = node.getmempoolinfo()["minrelaytxfee"]467parent = self.wallet.create_self_transfer(468fee_rate=minrelay,469confirmed_only=True,470)472child = self.wallet.create_self_transfer(473fee_rate=DEFAULT_FEE,474utxo_to_spend=parent["new_utxo"],475)477pkg_result = node.submitpackage([parent["hex"], child["hex"]], maxfeerate=DEFAULT_FEE - Decimal("0.00000001"))479# Child is connected even though parent is invalid and still reports fee exceeded480# this implies sub-package evaluation of both entries together.481assert_equal(pkg_result["package_msg"], "transaction failed")482assert "mempool min fee not met" in pkg_result["tx-results"][parent["wtxid"]]["error"]483assert_equal(pkg_result["tx-results"][child["wtxid"]]["error"], "max feerate exceeded")484assert parent["txid"] not in node.getrawmempool()485assert child["txid"] not in node.getrawmempool()487# Reset maxmempool, reset dynamic mempool minimum feerate, and empty mempool.488self.restart_node(0)489self.wallet.rescan_utxos()491assert_equal(node.getrawmempool(), [])493def test_maxburn_submitpackage(self):494node = self.nodes[0]496assert_equal(node.getrawmempool(), [])498self.log.info("Submitpackage maxburnamount arg testing")499chained_txns_burn = self.wallet.create_self_transfer_chain(500chain_length=2,
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.