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

Code before:
        assert_equal(node.testmempoolaccept([cpfp_parent["hex"]])[0]["reject-reason"], "mempool min fee not met")

        self.wallet.rescan_utxos()

        # Series of parents that don't need CPFP and are submitted individually. Each one is large and
        # high feerate, which means they should trigger eviction but not be evicted.
        parent_vsize = 25000
        num_big_parents = 3
        # Need to be large enough to trigger eviction
        # (note that the mempool usage of a tx is about three times its vsize)
        assert_greater_than(parent_vsize * num_big_parents * 3, current_info["maxmempool"] - current_info["bytes"])
        parent_feerate = 100 * mempoolmin_feerate

        big_parent_txids = []
        for i in range(num_big_parents):
            parent = self.wallet.create_self_transfer(fee_rate=parent_feerate, target_vsize=parent_vsize, confirmed_only=True)
            parent_utxos.append(parent["new_utxo"])
            package_hex.append(parent["hex"])
            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


Review comment (suggestion):
Comment could be clearer: This test is specifically trying to test that intra-package trimming isn't happening. We already know that a transaction with a high feerate descendant shouldn't be evicted; we specifically want to make sure that the eviction decision doesn't happen until the package is all the way in. So we check effective-includes to ensure that the transactions were evaluated separately.

Code after:
        for i in range(num_big_parents):
            # Last parent is higher feerate causing other parents to possibly
            # be evicted if trimming was allowed, which would cause the package to end up failing
            parent_feerate = mempoolmin_feerate + Decimal("0.00000001") if i == num_big_parents - 1 else mempoolmin_feerate
            parent = self.wallet.create_self_transfer(fee_rate=parent_feerate, target_vsize=parent_vsize, confirmed_only=True)
            parent_utxos.append(parent["new_utxo"])
            package_hex.append(parent["hex"])
            big_parent_txids.append(parent["txid"])
            big_parent_wtxids.append(parent["wtxid"])
            # There is room for each of these transactions independently
            assert node.testmempoolaccept([parent["hex"]])[0]["allowed"]

        # Create a child spending everything with an insane fee, bumping the package above mempool_entry_minrate
        child = self.wallet.create_self_transfer_multi(utxos_to_spend=parent_utxos, fee_per_output=10000000)
        package_hex.append(child["hex"])

        # Package should be submitted, temporarily exceeding maxmempool, but not evicted.
        package_res = None
        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)