Skip to content

Commit 563a2ee

Browse files
committed
[policy] disallow transactions under min relay fee, even in packages
Avoid adding transactions below min relay feerate because, even if they were bumped through CPFP when entering the mempool, we do not have a DoS-resistant way of ensuring they always remain bumped. In the future, this rule can be relaxed (e.g. to allow packages to bump 0-fee transactions) if we find a way to do so.
1 parent c4554fe commit 563a2ee

File tree

3 files changed

+51
-21
lines changed

3 files changed

+51
-21
lines changed

doc/policy/packages.md

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -80,24 +80,37 @@ test accepts):
8080
If any transactions in the package are already in the mempool, they are not submitted again
8181
("deduplicated") and are thus excluded from this calculation.
8282

83-
To meet the two feerate requirements of a mempool, i.e., the pre-configured minimum relay feerate
84-
(`-minrelaytxfee`) and the dynamic mempool minimum feerate, the total package feerate is used instead
85-
of the individual feerate. The individual transactions are allowed to be below the feerate
86-
requirements if the package meets the feerate requirements. For example, the parent(s) in the
87-
package can pay no fees but be paid for by the child.
88-
89-
*Rationale*: This can be thought of as "CPFP within a package," solving the issue of a parent not
90-
meeting minimum fees on its own. This would allow contracting applications to adjust their fees at
91-
broadcast time instead of overshooting or risking becoming stuck or pinned.
92-
93-
*Rationale*: It would be incorrect to use the fees of transactions that are already in the mempool, as
94-
we do not want a transaction's fees to be double-counted.
83+
To meet the dynamic mempool minimum feerate, i.e., the feerate determined by the transactions
84+
evicted when the mempool reaches capacity (not the static minimum relay feerate), the total package
85+
feerate instead of individual feerate can be used. For example, if the mempool minimum feerate is
86+
5sat/vB and a 1sat/vB parent transaction has a high-feerate child, it may be accepted if
87+
submitted as a package.
88+
89+
*Rationale*: This can be thought of as "CPFP within a package," solving the issue of a presigned
90+
transaction (i.e. in which a replacement transaction with a higher fee cannot be signed) being
91+
rejected from the mempool when transaction volume is high and the mempool minimum feerate rises.
92+
93+
Note: Package feerate cannot be used to meet the minimum relay feerate (`-minrelaytxfee`)
94+
requirement. For example, if the mempool minimum feerate is 5sat/vB and the minimum relay feerate is
95+
set to 5satvB, a 1sat/vB parent transaction with a high-feerate child will not be accepted, even if
96+
submitted as a package.
97+
98+
*Rationale*: Avoid situations in which the mempool contains non-bumped transactions below min relay
99+
feerate (which we consider to have pay 0 fees and thus receiving free relay). While package
100+
submission would ensure these transactions are bumped at the time of entry, it is not guaranteed
101+
that the transaction will always be bumped. For example, a later transaction could replace the
102+
fee-bumping child without still bumping the parent. These no-longer-bumped transactions should be
103+
removed during a replacement, but we do not have a DoS-resistant way of removing them or enforcing a
104+
limit on their quantity. Instead, prevent their entry into the mempool.
95105

96106
Implementation Note: Transactions within a package are always validated individually first, and
97107
package validation is used for the transactions that failed. Since package feerate is only
98108
calculated using transactions that are not in the mempool, this implementation detail affects the
99109
outcome of package validation.
100110

111+
*Rationale*: It would be incorrect to use the fees of transactions that are already in the mempool, as
112+
we do not want a transaction's fees to be double-counted.
113+
101114
*Rationale*: Packages are intended for incentive-compatible fee-bumping: transaction B is a
102115
"legitimate" fee-bump for transaction A only if B is a descendant of A and has a *higher* feerate
103116
than A. We want to prevent "parents pay for children" behavior; fees of parents should not help

src/test/txpackage_tests.cpp

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -671,10 +671,13 @@ BOOST_FIXTURE_TEST_CASE(package_cpfp_tests, TestChain100Setup)
671671
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
672672
const auto submit_cpfp_deprio = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
673673
package_cpfp, /*test_accept=*/ false);
674-
BOOST_CHECK_EQUAL(submit_cpfp_deprio.m_state.GetResult(), PackageValidationResult::PCKG_POLICY);
675-
BOOST_CHECK_MESSAGE(submit_cpfp_deprio.m_state.IsInvalid(),
676-
"Package validation unexpectedly succeeded: " << submit_cpfp_deprio.m_state.GetRejectReason());
677-
BOOST_CHECK(submit_cpfp_deprio.m_tx_results.empty());
674+
BOOST_CHECK_EQUAL(submit_cpfp_deprio.m_state.GetResult(), PackageValidationResult::PCKG_TX);
675+
BOOST_CHECK(submit_cpfp_deprio.m_state.IsInvalid());
676+
BOOST_CHECK_EQUAL(submit_cpfp_deprio.m_tx_results.find(tx_parent->GetWitnessHash())->second.m_state.GetResult(),
677+
TxValidationResult::TX_MEMPOOL_POLICY);
678+
BOOST_CHECK_EQUAL(submit_cpfp_deprio.m_tx_results.find(tx_child->GetWitnessHash())->second.m_state.GetResult(),
679+
TxValidationResult::TX_MISSING_INPUTS);
680+
BOOST_CHECK(submit_cpfp_deprio.m_tx_results.find(tx_parent->GetWitnessHash())->second.m_state.GetRejectReason() == "min relay fee not met");
678681
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
679682
const CFeeRate expected_feerate(0, GetVirtualTransactionSize(*tx_parent) + GetVirtualTransactionSize(*tx_child));
680683
}
@@ -808,8 +811,8 @@ BOOST_FIXTURE_TEST_CASE(package_cpfp_tests, TestChain100Setup)
808811
BOOST_CHECK_MESSAGE(submit_rich_parent.m_state.IsInvalid(), "Package validation unexpectedly succeeded");
809812

810813
// The child would have been validated on its own and failed, then submitted as a "package" of 1.
811-
BOOST_CHECK_EQUAL(submit_rich_parent.m_state.GetResult(), PackageValidationResult::PCKG_POLICY);
812-
BOOST_CHECK_EQUAL(submit_rich_parent.m_state.GetRejectReason(), "package-fee-too-low");
814+
BOOST_CHECK_EQUAL(submit_rich_parent.m_state.GetResult(), PackageValidationResult::PCKG_TX);
815+
BOOST_CHECK_EQUAL(submit_rich_parent.m_state.GetRejectReason(), "transaction failed");
813816

814817
auto it_parent = submit_rich_parent.m_tx_results.find(tx_parent_rich->GetWitnessHash());
815818
BOOST_CHECK(it_parent != submit_rich_parent.m_tx_results.end());
@@ -818,6 +821,11 @@ BOOST_FIXTURE_TEST_CASE(package_cpfp_tests, TestChain100Setup)
818821
BOOST_CHECK_MESSAGE(it_parent->second.m_base_fees.value() == high_parent_fee,
819822
strprintf("rich parent: expected fee %s, got %s", high_parent_fee, it_parent->second.m_base_fees.value()));
820823
BOOST_CHECK(it_parent->second.m_effective_feerate == CFeeRate(high_parent_fee, GetVirtualTransactionSize(*tx_parent_rich)));
824+
auto it_child = submit_rich_parent.m_tx_results.find(tx_child_poor->GetWitnessHash());
825+
BOOST_CHECK(it_child != submit_rich_parent.m_tx_results.end());
826+
BOOST_CHECK_EQUAL(it_child->second.m_result_type, MempoolAcceptResult::ResultType::INVALID);
827+
BOOST_CHECK_EQUAL(it_child->second.m_state.GetResult(), TxValidationResult::TX_MEMPOOL_POLICY);
828+
BOOST_CHECK(it_child->second.m_state.GetRejectReason() == "min relay fee not met");
821829

822830
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
823831
BOOST_CHECK(m_node.mempool->exists(GenTxid::Txid(tx_parent_rich->GetHash())));

src/validation.cpp

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -844,9 +844,18 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
844844
return state.Invalid(TxValidationResult::TX_NOT_STANDARD, "bad-txns-too-many-sigops",
845845
strprintf("%d", nSigOpsCost));
846846

847-
// No individual transactions are allowed below the min relay feerate and mempool min feerate except from
848-
// disconnected blocks and transactions in a package. Package transactions will be checked using
849-
// package feerate later.
847+
// No individual transactions are allowed below the min relay feerate except from disconnected blocks.
848+
// This requirement, unlike CheckFeeRate, cannot be bypassed using m_package_feerates because,
849+
// while a tx could be package CPFP'd when entering the mempool, we do not have a DoS-resistant
850+
// method of ensuring the tx remains bumped. For example, the fee-bumping child could disappear
851+
// due to a replacement.
852+
if (!bypass_limits && ws.m_modified_fees < m_pool.m_min_relay_feerate.GetFee(ws.m_vsize)) {
853+
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "min relay fee not met",
854+
strprintf("%d < %d", ws.m_modified_fees, m_pool.m_min_relay_feerate.GetFee(ws.m_vsize)));
855+
}
856+
// No individual transactions are allowed below the mempool min feerate except from disconnected
857+
// blocks and transactions in a package. Package transactions will be checked using package
858+
// feerate later.
850859
if (!bypass_limits && !args.m_package_feerates && !CheckFeeRate(ws.m_vsize, ws.m_modified_fees, state)) return false;
851860

852861
ws.m_iters_conflicting = m_pool.GetIterSet(ws.m_conflicts);

0 commit comments

Comments
 (0)