Repository: bitcoin/bitcoin
File: test/functional/mempool_limit.py (Python)

Code before:
            big_parent_txids.append(parent["txid"])
            # There is room for each of these transactions independently
            assert node.testmempoolaccept([parent["hex"]])[0]["allowed"]

        # Create a child spending everything, bumping cpfp_parent just above mempool minimum
        # feerate. It's important not to bump too much as otherwise mempool_evicted_tx would not be
        # evicted, making this test much less meaningful.
        approx_child_vsize = self.wallet.create_self_transfer_multi(utxos_to_spend=parent_utxos)["tx"].get_vsize()
        cpfp_fee = (mempoolmin_feerate / 1000) * (cpfp_parent["tx"].get_vsize() + approx_child_vsize) - cpfp_parent["fee"]
        # Specific number of satoshis to fit within a small window. The parent_cpfp + child package needs to be
        # - When there is mid-package eviction, high enough feerate to meet the new mempoolminfee
        # - When there is no mid-package eviction, low enough feerate to be evicted immediately after submission.
        magic_satoshis = 1200
        cpfp_satoshis = int(cpfp_fee * COIN) + magic_satoshis

        child = self.wallet.create_self_transfer_multi(utxos_to_spend=parent_utxos, fee_per_output=cpfp_satoshis)
        package_hex.append(child["hex"])

        # Package should be submitted, temporarily exceeding maxmempool, and then evicted.
        with node.assert_debug_log(expected_msgs=["rolling minimum fee bumped"]):
            assert_equal(node.submitpackage(package_hex)["package_msg"], "transaction failed")

        # Maximum size must never be exceeded.
        assert_greater_than(node.getmempoolinfo()["maxmempool"], node.getmempoolinfo()["bytes"])

        # Evicted transaction and its descendants must not be in mempool.
        resulting_mempool_txids = node.getrawmempool()
        assert mempool_evicted_tx["txid"] not in resulting_mempool_txids
        assert cpfp_parent["txid"] not in resulting_mempool_txids
        assert child["txid"] not in resulting_mempool_txids
        for txid in big_parent_txids:
            assert txid in resulting_mempool_txids

    def test_mid_package_replacement(self):
        node = self.nodes[0]
        self.log.info("Check a package where an early tx depends on a later-replaced mempool tx")

        self.restart_node(0, extra_args=self.extra_args[0])

        # Restarting the node resets mempool minimum feerate
        assert_equal(node.getmempoolinfo()['minrelaytxfee'], Decimal('0.00001000'))
        assert_equal(node.getmempoolinfo()['mempoolminfee'], Decimal('0.00001000'))

        fill_mempool(self, node)
        current_info = node.getmempoolinfo()
        mempoolmin_feerate = current_info["mempoolminfee"]

        # Mempool transaction which is evicted due to being at the "bottom" of the mempool when the
        # mempool overflows and evicts by descendant score. It's important that the eviction doesn't
        # happen in the middle of package evaluation, as it can invalidate the coins cache.
        double_spent_utxo = self.wallet.get_utxo(confirmed_only=True)

Review comment (suggestion):
`- set(big_parent_txids)`

I suppose this can be dropped based on the assertion above on lines 167/8?`big_parent_txids` is already in `resulting_mempool_txids `.

Code after:
        with node.assert_debug_log(expected_msgs=["rolling minimum fee bumped"]):
            package_res = node.submitpackage(package=package_hex, maxfeerate=0)

        assert_equal(package_res["package_msg"], "success")

        # Ensure that intra-package trimming is not happening.
        # Each transaction separately satisfies the current
        # minfee and shouldn't need package evaluation to
        # be included. If trimming of a parent were to happen,
        # package evaluation would happen to reintrodce the evicted
        # parent.
        assert_equal(len(package_res["tx-results"]), len(big_parent_wtxids) + 1)
        for wtxid in big_parent_wtxids + [child["wtxid"]]:
            assert_equal(len(package_res["tx-results"][wtxid]["fees"]["effective-includes"]), 1)

        # Maximum size must never be exceeded.
        assert_greater_than(node.getmempoolinfo()["maxmempool"], node.getmempoolinfo()["bytes"])

        # Package found in mempool still
        resulting_mempool_txids = node.getrawmempool()
        assert child["txid"] in resulting_mempool_txids
        for txid in big_parent_txids:
            assert txid in resulting_mempool_txids

        # Check every evicted tx was higher feerate than parents which evicted it
        eviction_set = set(mempool_txids) - set(resulting_mempool_txids) - set(big_parent_txids)
        parent_entries = [node.getmempoolentry(entry) for entry in big_parent_txids]
        max_parent_feerate = max([entry["fees"]["modified"] / (Decimal(entry["vsize"]) / 1000) for entry in parent_entries])
        for eviction in eviction_set:
            assert eviction in mempool_txids
            for txid, entry in zip(mempool_txids, mempool_entries):
                if txid == eviction:
                    evicted_feerate_btc_per_kvb = entry["fees"]["modified"] / (Decimal(entry["vsize"]) / 1000)
                    assert_greater_than(evicted_feerate_btc_per_kvb, max_parent_feerate)

    def test_mid_package_eviction(self):
        node = self.nodes[0]
        self.log.info("Check a package where each parent passes the current mempoolminfee but would cause eviction before package submission terminates")

        self.restart_node(0, extra_args=self.extra_args[0])

        # Restarting the node resets mempool minimum feerate
        assert_equal(node.getmempoolinfo()['minrelaytxfee'], Decimal('0.00001000'))
        assert_equal(node.getmempoolinfo()['mempoolminfee'], Decimal('0.00001000'))

        fill_mempool(self, node)
        current_info = node.getmempoolinfo()
        mempoolmin_feerate = current_info["mempoolminfee"]

        package_hex = []
        # UTXOs to be spent by the ultimate child transaction