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

Code before:
        assert_greater_than_or_equal(1000, package_txns1[-1].get_vsize())
        node.submitpackage(package_hex1)
        self.assert_mempool_contents(expected=package_txns1)

        PACKAGE_FEE = DEFAULT_FEE + DEFAULT_CHILD_FEE
        PACKAGE_FEE_MINUS_ONE = PACKAGE_FEE - Decimal("0.00000001")

        # Package 2 has a higher feerate but lower absolute fee
        package_hex2, package_txns2 = self.create_simple_package(coin, parent_fee=DEFAULT_FEE, child_fee=DEFAULT_CHILD_FEE - Decimal("0.00000001"))
        pkg_results2 = node.submitpackage(package_hex2)
        assert_equal(f"package RBF failed: insufficient anti-DoS fees, rejecting replacement {package_txns2[1].rehash()}, less fees than conflicting txs; {PACKAGE_FEE_MINUS_ONE} < {PACKAGE_FEE}", pkg_results2["package_msg"])
        self.assert_mempool_contents(expected=package_txns1)

        self.log.info("Check replacement pays for incremental bandwidth")
        package_hex3, package_txns3 = self.create_simple_package(coin, parent_fee=DEFAULT_FEE, child_fee=DEFAULT_CHILD_FEE)
        pkg_results3 = node.submitpackage(package_hex3)
        assert_equal(f"package RBF failed: insufficient anti-DoS fees, rejecting replacement {package_txns3[1].rehash()}, not enough additional fees to relay; 0.00 < 0.00000{sum([tx.get_vsize() for tx in package_txns3])}", pkg_results3["package_msg"])

        self.assert_mempool_contents(expected=package_txns1)
        self.generate(node, 1)

        self.log.info("Check Package RBF must have strict cpfp structure")
        coin = self.coins.pop()
        package_hex4, package_txns4 = self.create_simple_package(coin, parent_fee=DEFAULT_FEE, child_fee=DEFAULT_CHILD_FEE)
        node.submitpackage(package_hex4)
        self.assert_mempool_contents(expected=package_txns4)
        package_hex5, package_txns5 = self.create_simple_package(coin, parent_fee=DEFAULT_CHILD_FEE, child_fee=DEFAULT_CHILD_FEE - Decimal("0.00000001"))
        pkg_results5 = node.submitpackage(package_hex5)
        assert 'package RBF failed: package feerate is less than parent feerate' in pkg_results5["package_msg"]

        self.assert_mempool_contents(expected=package_txns4)
        self.generate(node, 1)

    def test_package_rbf_max_conflicts(self):
        node = self.nodes[0]
        self.log.info("Check Package RBF cannot replace more than MAX_REPLACEMENT_CANDIDATES transactions")
        num_coins = 51
        parent_coins = self.coins[:num_coins]
        del self.coins[:num_coins]

        # Original transactions: 51 transactions with 1 descendants each -> 102 total transactions
        size_two_clusters = []
        for coin in parent_coins:
            size_two_clusters.append(self.wallet.send_self_transfer_chain(from_node=node, chain_length=2, utxo_to_spend=coin))
        expected_txns = [txn["tx"] for parent_child_txns in size_two_clusters for txn in parent_child_txns]
        assert_equal(len(expected_txns), num_coins * 2)
        self.assert_mempool_contents(expected=expected_txns)

        # parent feeerate needs to be high enough for minrelay
        # child feerate needs to be large enough to trigger package rbf with a very large parent and
        # pay for all evicted fees. maxfeerate turned off for all submissions since child feerate

Review comment (bug):
nit: variable naming could be more clear, e.g. `fail_package_hex3` and `success_package_hex3` instead of just numbers

Code after:
        assert_greater_than_or_equal(1000, package_txns1[-1].get_vsize())
        node.submitpackage(package_hex1)
        self.assert_mempool_contents(expected=package_txns1)

        PACKAGE_FEE = DEFAULT_FEE + DEFAULT_CHILD_FEE
        PACKAGE_FEE_MINUS_ONE = PACKAGE_FEE - Decimal("0.00000001")

        # Package 2 has a higher feerate but lower absolute fee
        package_hex2, package_txns2 = self.create_simple_package(coin, parent_fee=DEFAULT_FEE, child_fee=DEFAULT_CHILD_FEE - Decimal("0.00000001"))
        pkg_results2 = node.submitpackage(package_hex2)
        assert_equal(f"package RBF failed: insufficient anti-DoS fees, rejecting replacement {package_txns2[1].rehash()}, less fees than conflicting txs; {PACKAGE_FEE_MINUS_ONE} < {PACKAGE_FEE}", pkg_results2["package_msg"])
        self.assert_mempool_contents(expected=package_txns1)

        self.log.info("Check replacement pays for incremental bandwidth")
        _, placeholder_txns3 = self.create_simple_package(coin)
        package_3_size = sum([tx.get_vsize() for tx in placeholder_txns3])
        incremental_sats_required = Decimal(package_3_size) / COIN
        incremental_sats_short = incremental_sats_required - Decimal("0.00000001")
        # Recreate the package with slightly higher fee once we know the size of the new package, but still short of required fee
        failure_package_hex3, failure_package_txns3 = self.create_simple_package(coin, parent_fee=DEFAULT_FEE, child_fee=DEFAULT_CHILD_FEE + incremental_sats_short)
        assert_equal(package_3_size, sum([tx.get_vsize() for tx in failure_package_txns3]))
        pkg_results3 = node.submitpackage(failure_package_hex3)
        assert_equal(f"package RBF failed: insufficient anti-DoS fees, rejecting replacement {failure_package_txns3[1].rehash()}, not enough additional fees to relay; {incremental_sats_short} < {incremental_sats_required}", pkg_results3["package_msg"])
        self.assert_mempool_contents(expected=package_txns1)

        success_package_hex3, success_package_txns3 = self.create_simple_package(coin, parent_fee=DEFAULT_FEE, child_fee=DEFAULT_CHILD_FEE + incremental_sats_required)
        node.submitpackage(success_package_hex3)
        self.assert_mempool_contents(expected=success_package_txns3)
        self.generate(node, 1)

        self.log.info("Check Package RBF must have strict cpfp structure")
        coin = self.coins.pop()
        package_hex4, package_txns4 = self.create_simple_package(coin, parent_fee=DEFAULT_FEE, child_fee=DEFAULT_CHILD_FEE)
        node.submitpackage(package_hex4)
        self.assert_mempool_contents(expected=package_txns4)
        package_hex5, package_txns5 = self.create_simple_package(coin, parent_fee=DEFAULT_CHILD_FEE, child_fee=DEFAULT_CHILD_FEE)
        pkg_results5 = node.submitpackage(package_hex5)
        assert 'package RBF failed: package feerate is less than or equal to parent feerate' in pkg_results5["package_msg"]
        self.assert_mempool_contents(expected=package_txns4)

        package_hex5_1, package_txns5_1 = self.create_simple_package(coin, parent_fee=DEFAULT_CHILD_FEE, child_fee=DEFAULT_CHILD_FEE + Decimal("0.00000001"))
        node.submitpackage(package_hex5_1)
        self.assert_mempool_contents(expected=package_txns5_1)
        self.generate(node, 1)

    def test_package_rbf_max_conflicts(self):
        node = self.nodes[0]
        self.log.info("Check Package RBF cannot replace more than MAX_REPLACEMENT_CANDIDATES transactions")
        num_coins = 51
        parent_coins = self.coins[:num_coins]
        del self.coins[:num_coins]