diff --git a/doc/policy/packages.md b/doc/policy/packages.md index a220bdd17fa..9b321799f15 100644 --- a/doc/policy/packages.md +++ b/doc/policy/packages.md @@ -38,11 +38,11 @@ The following rules are enforced for all packages: * Only limited package replacements are currently considered. (#28984) - - All direct conflicts must signal replacement (or have `-mempoolfullrbf=1` set). + - All direct conflicts must signal replacement (or the node must have `-mempoolfullrbf=1` set). - Packages are 1-parent-1-child, with no in-mempool ancestors of the package. - - All conflicting clusters(connected components of mempool transactions) must be clusters of up to size 2. + - All conflicting clusters (connected components of mempool transactions) must be clusters of up to size 2. - No more than MAX_REPLACEMENT_CANDIDATES transactions can be replaced, analogous to regular [replacement rule](./mempool-replacements.md) 5). @@ -56,7 +56,7 @@ The following rules are enforced for all packages: - *Rationale*: Basic support for package RBF can be used by wallets by making chains of no longer than two, then directly conflicting - those chains when needed. Combined with V3 transactions this can + those chains when needed. Combined with TRUC transactions this can result in more robust fee bumping. More general package RBF may be enabled in the future. diff --git a/src/validation.cpp b/src/validation.cpp index 74f0e4975c4..988df3802a1 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1208,7 +1208,7 @@ bool MemPoolAccept::PackageMempoolChecks(const std::vector& txn const CFeeRate package_feerate(m_subpackage.m_total_modified_fees, m_subpackage.m_total_vsize); if (package_feerate <= parent_feerate) { return package_state.Invalid(PackageValidationResult::PCKG_POLICY, - "package RBF failed: package feerate is less than parent feerate", + "package RBF failed: package feerate is less than or equal to parent feerate", strprintf("package feerate %s <= parent feerate is %s", package_feerate.ToString(), parent_feerate.ToString())); } diff --git a/test/functional/mempool_package_rbf.py b/test/functional/mempool_package_rbf.py index ceb95303941..e9658aa8d02 100755 --- a/test/functional/mempool_package_rbf.py +++ b/test/functional/mempool_package_rbf.py @@ -168,11 +168,20 @@ class PackageRBFTest(BitcoinTestFramework): 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"]) - + _, 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") @@ -180,11 +189,14 @@ class PackageRBFTest(BitcoinTestFramework): 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")) + 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 parent feerate' in pkg_results5["package_msg"] - + 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):