0
0
Fork 0
mirror of https://github.com/bitcoin/bitcoin.git synced 2025-02-08 10:31:50 -05:00

[validation] add AcceptSubPackage to delegate Accept* calls and clean up m_view

(1) Call AcceptSingleTransaction when there is only 1 transaction in the
  subpackage. This avoids calling PackageMempoolChecks() which enforces
rules that don't need to be applied for a single transaction, i.e.
disabling CPFP carve out.
There is a slight change in the error type returned, as shown in the
txpackage_tests change.  When a transaction is the last one left in the
package and its fee is too low, this returns a PCKG_TX instead of
PCKG_POLICY. This interface is clearer; "package-fee-too-low" for 1
transaction would be a bit misleading.

(2) Clean up m_view and m_viewmempool so that coins created in this
sub-package evaluation are not available for other sub-package
evaluations. The contents of the mempool may change, so coins that are
available now might not be later.
This commit is contained in:
glozow 2022-12-15 18:00:04 +00:00
parent 3f01a3dab1
commit 03b87c11ca
2 changed files with 77 additions and 14 deletions

View file

@ -821,18 +821,20 @@ BOOST_FIXTURE_TEST_CASE(package_cpfp_tests, TestChain100Setup)
expected_pool_size += 1;
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.
BOOST_CHECK_EQUAL(submit_rich_parent.m_state.GetResult(), PackageValidationResult::PCKG_TX);
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_child = submit_rich_parent.m_tx_results.find(tx_child_poor->GetWitnessHash());
BOOST_CHECK(it_parent != submit_rich_parent.m_tx_results.end());
BOOST_CHECK(it_child != submit_rich_parent.m_tx_results.end());
BOOST_CHECK(it_parent->second.m_result_type == MempoolAcceptResult::ResultType::VALID);
BOOST_CHECK(it_child->second.m_result_type == MempoolAcceptResult::ResultType::INVALID);
BOOST_CHECK(it_parent->second.m_state.GetRejectReason() == "");
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()));
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);

View file

@ -554,6 +554,19 @@ public:
*/
PackageMempoolAcceptResult AcceptMultipleTransactions(const std::vector<CTransactionRef>& txns, ATMPArgs& args) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
/**
* Submission of a subpackage.
* If subpackage size == 1, calls AcceptSingleTransaction() with adjusted ATMPArgs to avoid
* package policy restrictions like no CPFP carve out (PackageMempoolChecks) and disabled RBF
* (m_allow_replacement), and creates a PackageMempoolAcceptResult wrapping the result.
*
* If subpackage size > 1, calls AcceptMultipleTransactions() with the provided ATMPArgs.
*
* Also cleans up all non-chainstate coins from m_view at the end.
*/
PackageMempoolAcceptResult AcceptSubPackage(const std::vector<CTransactionRef>& subpackage, ATMPArgs& args)
EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
/**
* Package (more specific than just multiple transactions) acceptance. Package must be a child
* with all of its unconfirmed parents, and topologically sorted.
@ -1326,6 +1339,54 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std::
return PackageMempoolAcceptResult(package_state, std::move(results));
}
PackageMempoolAcceptResult MemPoolAccept::AcceptSubPackage(const std::vector<CTransactionRef>& subpackage, ATMPArgs& args)
{
AssertLockHeld(::cs_main);
AssertLockHeld(m_pool.cs);
auto result = [&]() EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_pool.cs) {
if (subpackage.size() > 1) {
return AcceptMultipleTransactions(subpackage, args);
}
const auto& tx = subpackage.front();
ATMPArgs single_args = ATMPArgs::SingleInPackageAccept(args);
const auto single_res = AcceptSingleTransaction(tx, single_args);
PackageValidationState package_state_wrapped;
if (single_res.m_result_type != MempoolAcceptResult::ResultType::VALID) {
package_state_wrapped.Invalid(PackageValidationResult::PCKG_TX, "transaction failed");
}
return PackageMempoolAcceptResult(package_state_wrapped, {{tx->GetWitnessHash(), single_res}});
}();
// Clean up m_view and m_viewmempool so that other subpackage evaluations don't have access to
// coins they shouldn't. Keep some coins in order to minimize re-fetching coins from the UTXO set.
//
// There are 3 kinds of coins in m_view:
// (1) Temporary coins from the transactions in subpackage, constructed by m_viewmempool.
// (2) Mempool coins from transactions in the mempool, constructed by m_viewmempool.
// (3) Confirmed coins fetched from our current UTXO set.
//
// (1) Temporary coins need to be removed, regardless of whether the transaction was submitted.
// If the transaction was submitted to the mempool, m_viewmempool will be able to fetch them from
// there. If it wasn't submitted to mempool, it is incorrect to keep them - future calls may try
// to spend those coins that don't actually exist.
// (2) Mempool coins also need to be removed. If the mempool contents have changed as a result
// of submitting or replacing transactions, coins previously fetched from mempool may now be
// spent or nonexistent. Those coins need to be deleted from m_view.
// (3) Confirmed coins don't need to be removed. The chainstate has not changed (we are
// holding cs_main and no blocks have been processed) so the confirmed tx cannot disappear like
// a mempool tx can. The coin may now be spent after we submitted a tx to mempool, but
// we have already checked that the package does not have 2 transactions spending the same coin.
// Keeping them in m_view is an optimization to not re-fetch confirmed coins if we later look up
// inputs for this transaction again.
for (const auto& outpoint : m_viewmempool.GetNonBaseCoins()) {
// In addition to resetting m_viewmempool, we also need to manually delete these coins from
// m_view because it caches copies of the coins it fetched from m_viewmempool previously.
m_view.Uncache(outpoint);
}
// This deletes the temporary and mempool coins.
m_viewmempool.Reset();
return result;
}
PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package& package, ATMPArgs& args)
{
AssertLockHeld(cs_main);
@ -1384,15 +1445,6 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package& package,
LOCK(m_pool.cs);
// Stores final results that won't change
std::map<const uint256, const MempoolAcceptResult> results_final;
// Node operators are free to set their mempool policies however they please, nodes may receive
// transactions in different orders, and malicious counterparties may try to take advantage of
// policy differences to pin or delay propagation of transactions. As such, it's possible for
// some package transaction(s) to already be in the mempool, and we don't want to reject the
// entire package in that case (as that could be a censorship vector). De-duplicate the
// transactions that are already in the mempool, and only call AcceptMultipleTransactions() with
// the new transactions. This ensures we don't double-count transaction counts and sizes when
// checking ancestor/descendant limits, or double-count transaction fees for fee-related policy.
ATMPArgs single_args = ATMPArgs::SingleInPackageAccept(args);
// Results from individual validation. "Nonfinal" because if a transaction fails by itself but
// succeeds later (i.e. when evaluated with a fee-bumping child), the result changes (though not
// reflected in this map). If a transaction fails more than once, we want to return the first
@ -1408,6 +1460,14 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package& package,
// we know is that the inputs aren't available.
if (m_pool.exists(GenTxid::Wtxid(wtxid))) {
// Exact transaction already exists in the mempool.
// Node operators are free to set their mempool policies however they please, nodes may receive
// transactions in different orders, and malicious counterparties may try to take advantage of
// policy differences to pin or delay propagation of transactions. As such, it's possible for
// some package transaction(s) to already be in the mempool, and we don't want to reject the
// entire package in that case (as that could be a censorship vector). De-duplicate the
// transactions that are already in the mempool, and only call AcceptMultipleTransactions() with
// the new transactions. This ensures we don't double-count transaction counts and sizes when
// checking ancestor/descendant limits, or double-count transaction fees for fee-related policy.
auto iter = m_pool.GetIter(txid);
assert(iter != std::nullopt);
results_final.emplace(wtxid, MempoolAcceptResult::MempoolTx(iter.value()->GetTxSize(), iter.value()->GetFee()));
@ -1426,7 +1486,8 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package& package,
} else {
// Transaction does not already exist in the mempool.
// Try submitting the transaction on its own.
const auto single_res = AcceptSingleTransaction(tx, single_args);
const auto single_package_res = AcceptSubPackage({tx}, args);
const auto& single_res = single_package_res.m_tx_results.at(wtxid);
if (single_res.m_result_type == MempoolAcceptResult::ResultType::VALID) {
// The transaction succeeded on its own and is now in the mempool. Don't include it
// in package validation, because its fees should only be "used" once.
@ -1464,7 +1525,7 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package& package,
}
// Validate the (deduplicated) transactions as a package. Note that submission_result has its
// own PackageValidationState; package_state_quit_early is unused past this point.
auto submission_result = AcceptMultipleTransactions(txns_package_eval, args);
auto submission_result = AcceptSubPackage(txns_package_eval, args);
// Include already-in-mempool transaction results in the final result.
for (const auto& [wtxid, mempoolaccept_res] : results_final) {
Assume(submission_result.m_tx_results.emplace(wtxid, mempoolaccept_res).second);
@ -1472,7 +1533,7 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package& package,
}
if (submission_result.m_state.GetResult() == PackageValidationResult::PCKG_TX) {
// Package validation failed because one or more transactions failed. Provide a result for
// each transaction; if AcceptMultipleTransactions() didn't return a result for a tx,
// each transaction; if a transaction doesn't have an entry in submission_result,
// include the previous individual failure reason.
submission_result.m_tx_results.insert(individual_results_nonfinal.cbegin(),
individual_results_nonfinal.cend());