[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.
This commit is contained in:
glozow committed 2023-04-17 09:53:59 +01:00
1 parent c4554fe894
commit 563a2ee4f5
3 files changed
+49 -19

No files matched your search

+23 -10
View File
@@ -80,24 +80,37 @@ test accepts):
If any transactions in the package are already in the mempool, they are not submitted again If any transactions in the package are already in the mempool, they are not submitted again
("deduplicated") and are thus excluded from this calculation. ("deduplicated") and are thus excluded from this calculation.
To meet the two feerate requirements of a mempool, i.e., the pre-configured minimum relay feerate To meet the dynamic mempool minimum feerate, i.e., the feerate determined by the transactions
(`-minrelaytxfee`) and the dynamic mempool minimum feerate, the total package feerate is used instead evicted when the mempool reaches capacity (not the static minimum relay feerate), the total package
of the individual feerate. The individual transactions are allowed to be below the feerate feerate instead of individual feerate can be used. For example, if the mempool minimum feerate is
requirements if the package meets the feerate requirements. For example, the parent(s) in the 5sat/vB and a 1sat/vB parent transaction has a high-feerate child, it may be accepted if
package can pay no fees but be paid for by the child. submitted as a package.
*Rationale*: This can be thought of as "CPFP within a package," solving the issue of a parent not *Rationale*: This can be thought of as "CPFP within a package," solving the issue of a presigned
meeting minimum fees on its own. This would allow contracting applications to adjust their fees at transaction (i.e. in which a replacement transaction with a higher fee cannot be signed) being
broadcast time instead of overshooting or risking becoming stuck or pinned. rejected from the mempool when transaction volume is high and the mempool minimum feerate rises.
*Rationale*: It would be incorrect to use the fees of transactions that are already in the mempool, as Note: Package feerate cannot be used to meet the minimum relay feerate (`-minrelaytxfee`)
we do not want a transaction's fees to be double-counted. requirement. For example, if the mempool minimum feerate is 5sat/vB and the minimum relay feerate is
set to 5satvB, a 1sat/vB parent transaction with a high-feerate child will not be accepted, even if
submitted as a package.
*Rationale*: Avoid situations in which the mempool contains non-bumped transactions below min relay
feerate (which we consider to have pay 0 fees and thus receiving free relay). While package
submission would ensure these transactions are bumped at the time of entry, it is not guaranteed
that the transaction will always be bumped. For example, a later transaction could replace the
fee-bumping child without still bumping the parent. These no-longer-bumped transactions should be
removed during a replacement, but we do not have a DoS-resistant way of removing them or enforcing a
limit on their quantity. Instead, prevent their entry into the mempool.
Implementation Note: Transactions within a package are always validated individually first, and Implementation Note: Transactions within a package are always validated individually first, and
package validation is used for the transactions that failed. Since package feerate is only package validation is used for the transactions that failed. Since package feerate is only
calculated using transactions that are not in the mempool, this implementation detail affects the calculated using transactions that are not in the mempool, this implementation detail affects the
outcome of package validation. outcome of package validation.
*Rationale*: It would be incorrect to use the fees of transactions that are already in the mempool, as
we do not want a transaction's fees to be double-counted.
*Rationale*: Packages are intended for incentive-compatible fee-bumping: transaction B is a *Rationale*: Packages are intended for incentive-compatible fee-bumping: transaction B is a
"legitimate" fee-bump for transaction A only if B is a descendant of A and has a *higher* feerate "legitimate" fee-bump for transaction A only if B is a descendant of A and has a *higher* feerate
than A. We want to prevent "parents pay for children" behavior; fees of parents should not help than A. We want to prevent "parents pay for children" behavior; fees of parents should not help
+14 -6
View File
@@ -671,10 +671,13 @@ BOOST_FIXTURE_TEST_CASE(package_cpfp_tests, TestChain100Setup)
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size); BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
const auto submit_cpfp_deprio = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, const auto submit_cpfp_deprio = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
package_cpfp, /*test_accept=*/ false); package_cpfp, /*test_accept=*/ false);
BOOST_CHECK_EQUAL(submit_cpfp_deprio.m_state.GetResult(), PackageValidationResult::PCKG_POLICY); BOOST_CHECK_EQUAL(submit_cpfp_deprio.m_state.GetResult(), PackageValidationResult::PCKG_TX);
BOOST_CHECK_MESSAGE(submit_cpfp_deprio.m_state.IsInvalid(), BOOST_CHECK(submit_cpfp_deprio.m_state.IsInvalid());
"Package validation unexpectedly succeeded: " << submit_cpfp_deprio.m_state.GetRejectReason()); BOOST_CHECK_EQUAL(submit_cpfp_deprio.m_tx_results.find(tx_parent->GetWitnessHash())->second.m_state.GetResult(),
BOOST_CHECK(submit_cpfp_deprio.m_tx_results.empty()); TxValidationResult::TX_MEMPOOL_POLICY);
BOOST_CHECK_EQUAL(submit_cpfp_deprio.m_tx_results.find(tx_child->GetWitnessHash())->second.m_state.GetResult(),
TxValidationResult::TX_MISSING_INPUTS);
BOOST_CHECK(submit_cpfp_deprio.m_tx_results.find(tx_parent->GetWitnessHash())->second.m_state.GetRejectReason() == "min relay fee not met");
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size); BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
const CFeeRate expected_feerate(0, GetVirtualTransactionSize(*tx_parent) + GetVirtualTransactionSize(*tx_child)); const CFeeRate expected_feerate(0, GetVirtualTransactionSize(*tx_parent) + GetVirtualTransactionSize(*tx_child));
} }
@@ -808,8 +811,8 @@ BOOST_FIXTURE_TEST_CASE(package_cpfp_tests, TestChain100Setup)
BOOST_CHECK_MESSAGE(submit_rich_parent.m_state.IsInvalid(), "Package validation unexpectedly succeeded"); BOOST_CHECK_MESSAGE(submit_rich_parent.m_state.IsInvalid(), "Package validation unexpectedly succeeded");
// The child would have been validated on its own and failed, then submitted as a "package" of 1. // The child would have been validated on its own and failed, then submitted as a "package" of 1.
BOOST_CHECK_EQUAL(submit_rich_parent.m_state.GetResult(), PackageValidationResult::PCKG_POLICY); BOOST_CHECK_EQUAL(submit_rich_parent.m_state.GetResult(), PackageValidationResult::PCKG_TX);
BOOST_CHECK_EQUAL(submit_rich_parent.m_state.GetRejectReason(), "package-fee-too-low"); BOOST_CHECK_EQUAL(submit_rich_parent.m_state.GetRejectReason(), "transaction failed");
auto it_parent = submit_rich_parent.m_tx_results.find(tx_parent_rich->GetWitnessHash()); auto it_parent = submit_rich_parent.m_tx_results.find(tx_parent_rich->GetWitnessHash());
BOOST_CHECK(it_parent != submit_rich_parent.m_tx_results.end()); BOOST_CHECK(it_parent != submit_rich_parent.m_tx_results.end());
@@ -818,6 +821,11 @@ BOOST_FIXTURE_TEST_CASE(package_cpfp_tests, TestChain100Setup)
BOOST_CHECK_MESSAGE(it_parent->second.m_base_fees.value() == high_parent_fee, BOOST_CHECK_MESSAGE(it_parent->second.m_base_fees.value() == high_parent_fee,
strprintf("rich parent: expected fee %s, got %s", high_parent_fee, it_parent->second.m_base_fees.value())); strprintf("rich parent: expected fee %s, got %s", high_parent_fee, it_parent->second.m_base_fees.value()));
BOOST_CHECK(it_parent->second.m_effective_feerate == CFeeRate(high_parent_fee, GetVirtualTransactionSize(*tx_parent_rich))); BOOST_CHECK(it_parent->second.m_effective_feerate == CFeeRate(high_parent_fee, GetVirtualTransactionSize(*tx_parent_rich)));
auto it_child = submit_rich_parent.m_tx_results.find(tx_child_poor->GetWitnessHash());
BOOST_CHECK(it_child != submit_rich_parent.m_tx_results.end());
BOOST_CHECK_EQUAL(it_child->second.m_result_type, MempoolAcceptResult::ResultType::INVALID);
BOOST_CHECK_EQUAL(it_child->second.m_state.GetResult(), TxValidationResult::TX_MEMPOOL_POLICY);
BOOST_CHECK(it_child->second.m_state.GetRejectReason() == "min relay fee not met");
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size); BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
BOOST_CHECK(m_node.mempool->exists(GenTxid::Txid(tx_parent_rich->GetHash()))); BOOST_CHECK(m_node.mempool->exists(GenTxid::Txid(tx_parent_rich->GetHash())));
+12 -3
View File
@@ -844,9 +844,18 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
return state.Invalid(TxValidationResult::TX_NOT_STANDARD, "bad-txns-too-many-sigops", return state.Invalid(TxValidationResult::TX_NOT_STANDARD, "bad-txns-too-many-sigops",
strprintf("%d", nSigOpsCost)); strprintf("%d", nSigOpsCost));
// No individual transactions are allowed below the min relay feerate and mempool min feerate except from // No individual transactions are allowed below the min relay feerate except from disconnected blocks.
// disconnected blocks and transactions in a package. Package transactions will be checked using // This requirement, unlike CheckFeeRate, cannot be bypassed using m_package_feerates because,
// package feerate later. // while a tx could be package CPFP'd when entering the mempool, we do not have a DoS-resistant
// method of ensuring the tx remains bumped. For example, the fee-bumping child could disappear
// due to a replacement.
if (!bypass_limits && ws.m_modified_fees < m_pool.m_min_relay_feerate.GetFee(ws.m_vsize)) {
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "min relay fee not met",
strprintf("%d < %d", ws.m_modified_fees, m_pool.m_min_relay_feerate.GetFee(ws.m_vsize)));
}
// No individual transactions are allowed below the mempool min feerate except from disconnected
// blocks and transactions in a package. Package transactions will be checked using package
// feerate later.
if (!bypass_limits && !args.m_package_feerates && !CheckFeeRate(ws.m_vsize, ws.m_modified_fees, state)) return false; if (!bypass_limits && !args.m_package_feerates && !CheckFeeRate(ws.m_vsize, ws.m_modified_fees, state)) return false;
ws.m_iters_conflicting = m_pool.GetIterSet(ws.m_conflicts); ws.m_iters_conflicting = m_pool.GetIterSet(ws.m_conflicts);