Skip to content

Commit 03b87c1

Browse files
committed
[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.
1 parent 3f01a3d commit 03b87c1

File tree

2 files changed

+77
-14
lines changed

2 files changed

+77
-14
lines changed

src/test/txpackage_tests.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -821,18 +821,20 @@ BOOST_FIXTURE_TEST_CASE(package_cpfp_tests, TestChain100Setup)
821821
expected_pool_size += 1;
822822
BOOST_CHECK_MESSAGE(submit_rich_parent.m_state.IsInvalid(), "Package validation unexpectedly succeeded");
823823

824-
// The child would have been validated on its own and failed, then submitted as a "package" of 1.
824+
// The child would have been validated on its own and failed.
825825
BOOST_CHECK_EQUAL(submit_rich_parent.m_state.GetResult(), PackageValidationResult::PCKG_TX);
826826
BOOST_CHECK_EQUAL(submit_rich_parent.m_state.GetRejectReason(), "transaction failed");
827827

828828
auto it_parent = submit_rich_parent.m_tx_results.find(tx_parent_rich->GetWitnessHash());
829+
auto it_child = submit_rich_parent.m_tx_results.find(tx_child_poor->GetWitnessHash());
829830
BOOST_CHECK(it_parent != submit_rich_parent.m_tx_results.end());
831+
BOOST_CHECK(it_child != submit_rich_parent.m_tx_results.end());
830832
BOOST_CHECK(it_parent->second.m_result_type == MempoolAcceptResult::ResultType::VALID);
833+
BOOST_CHECK(it_child->second.m_result_type == MempoolAcceptResult::ResultType::INVALID);
831834
BOOST_CHECK(it_parent->second.m_state.GetRejectReason() == "");
832835
BOOST_CHECK_MESSAGE(it_parent->second.m_base_fees.value() == high_parent_fee,
833836
strprintf("rich parent: expected fee %s, got %s", high_parent_fee, it_parent->second.m_base_fees.value()));
834837
BOOST_CHECK(it_parent->second.m_effective_feerate == CFeeRate(high_parent_fee, GetVirtualTransactionSize(*tx_parent_rich)));
835-
auto it_child = submit_rich_parent.m_tx_results.find(tx_child_poor->GetWitnessHash());
836838
BOOST_CHECK(it_child != submit_rich_parent.m_tx_results.end());
837839
BOOST_CHECK_EQUAL(it_child->second.m_result_type, MempoolAcceptResult::ResultType::INVALID);
838840
BOOST_CHECK_EQUAL(it_child->second.m_state.GetResult(), TxValidationResult::TX_MEMPOOL_POLICY);

src/validation.cpp

Lines changed: 73 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,19 @@ class MemPoolAccept
554554
*/
555555
PackageMempoolAcceptResult AcceptMultipleTransactions(const std::vector<CTransactionRef>& txns, ATMPArgs& args) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
556556

557+
/**
558+
* Submission of a subpackage.
559+
* If subpackage size == 1, calls AcceptSingleTransaction() with adjusted ATMPArgs to avoid
560+
* package policy restrictions like no CPFP carve out (PackageMempoolChecks) and disabled RBF
561+
* (m_allow_replacement), and creates a PackageMempoolAcceptResult wrapping the result.
562+
*
563+
* If subpackage size > 1, calls AcceptMultipleTransactions() with the provided ATMPArgs.
564+
*
565+
* Also cleans up all non-chainstate coins from m_view at the end.
566+
*/
567+
PackageMempoolAcceptResult AcceptSubPackage(const std::vector<CTransactionRef>& subpackage, ATMPArgs& args)
568+
EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
569+
557570
/**
558571
* Package (more specific than just multiple transactions) acceptance. Package must be a child
559572
* with all of its unconfirmed parents, and topologically sorted.
@@ -1326,6 +1339,54 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std::
13261339
return PackageMempoolAcceptResult(package_state, std::move(results));
13271340
}
13281341

1342+
PackageMempoolAcceptResult MemPoolAccept::AcceptSubPackage(const std::vector<CTransactionRef>& subpackage, ATMPArgs& args)
1343+
{
1344+
AssertLockHeld(::cs_main);
1345+
AssertLockHeld(m_pool.cs);
1346+
auto result = [&]() EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_pool.cs) {
1347+
if (subpackage.size() > 1) {
1348+
return AcceptMultipleTransactions(subpackage, args);
1349+
}
1350+
const auto& tx = subpackage.front();
1351+
ATMPArgs single_args = ATMPArgs::SingleInPackageAccept(args);
1352+
const auto single_res = AcceptSingleTransaction(tx, single_args);
1353+
PackageValidationState package_state_wrapped;
1354+
if (single_res.m_result_type != MempoolAcceptResult::ResultType::VALID) {
1355+
package_state_wrapped.Invalid(PackageValidationResult::PCKG_TX, "transaction failed");
1356+
}
1357+
return PackageMempoolAcceptResult(package_state_wrapped, {{tx->GetWitnessHash(), single_res}});
1358+
}();
1359+
// Clean up m_view and m_viewmempool so that other subpackage evaluations don't have access to
1360+
// coins they shouldn't. Keep some coins in order to minimize re-fetching coins from the UTXO set.
1361+
//
1362+
// There are 3 kinds of coins in m_view:
1363+
// (1) Temporary coins from the transactions in subpackage, constructed by m_viewmempool.
1364+
// (2) Mempool coins from transactions in the mempool, constructed by m_viewmempool.
1365+
// (3) Confirmed coins fetched from our current UTXO set.
1366+
//
1367+
// (1) Temporary coins need to be removed, regardless of whether the transaction was submitted.
1368+
// If the transaction was submitted to the mempool, m_viewmempool will be able to fetch them from
1369+
// there. If it wasn't submitted to mempool, it is incorrect to keep them - future calls may try
1370+
// to spend those coins that don't actually exist.
1371+
// (2) Mempool coins also need to be removed. If the mempool contents have changed as a result
1372+
// of submitting or replacing transactions, coins previously fetched from mempool may now be
1373+
// spent or nonexistent. Those coins need to be deleted from m_view.
1374+
// (3) Confirmed coins don't need to be removed. The chainstate has not changed (we are
1375+
// holding cs_main and no blocks have been processed) so the confirmed tx cannot disappear like
1376+
// a mempool tx can. The coin may now be spent after we submitted a tx to mempool, but
1377+
// we have already checked that the package does not have 2 transactions spending the same coin.
1378+
// Keeping them in m_view is an optimization to not re-fetch confirmed coins if we later look up
1379+
// inputs for this transaction again.
1380+
for (const auto& outpoint : m_viewmempool.GetNonBaseCoins()) {
1381+
// In addition to resetting m_viewmempool, we also need to manually delete these coins from
1382+
// m_view because it caches copies of the coins it fetched from m_viewmempool previously.
1383+
m_view.Uncache(outpoint);
1384+
}
1385+
// This deletes the temporary and mempool coins.
1386+
m_viewmempool.Reset();
1387+
return result;
1388+
}
1389+
13291390
PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package& package, ATMPArgs& args)
13301391
{
13311392
AssertLockHeld(cs_main);
@@ -1384,15 +1445,6 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package& package,
13841445
LOCK(m_pool.cs);
13851446
// Stores final results that won't change
13861447
std::map<const uint256, const MempoolAcceptResult> results_final;
1387-
// Node operators are free to set their mempool policies however they please, nodes may receive
1388-
// transactions in different orders, and malicious counterparties may try to take advantage of
1389-
// policy differences to pin or delay propagation of transactions. As such, it's possible for
1390-
// some package transaction(s) to already be in the mempool, and we don't want to reject the
1391-
// entire package in that case (as that could be a censorship vector). De-duplicate the
1392-
// transactions that are already in the mempool, and only call AcceptMultipleTransactions() with
1393-
// the new transactions. This ensures we don't double-count transaction counts and sizes when
1394-
// checking ancestor/descendant limits, or double-count transaction fees for fee-related policy.
1395-
ATMPArgs single_args = ATMPArgs::SingleInPackageAccept(args);
13961448
// Results from individual validation. "Nonfinal" because if a transaction fails by itself but
13971449
// succeeds later (i.e. when evaluated with a fee-bumping child), the result changes (though not
13981450
// 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,
14081460
// we know is that the inputs aren't available.
14091461
if (m_pool.exists(GenTxid::Wtxid(wtxid))) {
14101462
// Exact transaction already exists in the mempool.
1463+
// Node operators are free to set their mempool policies however they please, nodes may receive
1464+
// transactions in different orders, and malicious counterparties may try to take advantage of
1465+
// policy differences to pin or delay propagation of transactions. As such, it's possible for
1466+
// some package transaction(s) to already be in the mempool, and we don't want to reject the
1467+
// entire package in that case (as that could be a censorship vector). De-duplicate the
1468+
// transactions that are already in the mempool, and only call AcceptMultipleTransactions() with
1469+
// the new transactions. This ensures we don't double-count transaction counts and sizes when
1470+
// checking ancestor/descendant limits, or double-count transaction fees for fee-related policy.
14111471
auto iter = m_pool.GetIter(txid);
14121472
assert(iter != std::nullopt);
14131473
results_final.emplace(wtxid, MempoolAcceptResult::MempoolTx(iter.value()->GetTxSize(), iter.value()->GetFee()));
@@ -1426,7 +1486,8 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package& package,
14261486
} else {
14271487
// Transaction does not already exist in the mempool.
14281488
// Try submitting the transaction on its own.
1429-
const auto single_res = AcceptSingleTransaction(tx, single_args);
1489+
const auto single_package_res = AcceptSubPackage({tx}, args);
1490+
const auto& single_res = single_package_res.m_tx_results.at(wtxid);
14301491
if (single_res.m_result_type == MempoolAcceptResult::ResultType::VALID) {
14311492
// The transaction succeeded on its own and is now in the mempool. Don't include it
14321493
// in package validation, because its fees should only be "used" once.
@@ -1464,15 +1525,15 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package& package,
14641525
}
14651526
// Validate the (deduplicated) transactions as a package. Note that submission_result has its
14661527
// own PackageValidationState; package_state_quit_early is unused past this point.
1467-
auto submission_result = AcceptMultipleTransactions(txns_package_eval, args);
1528+
auto submission_result = AcceptSubPackage(txns_package_eval, args);
14681529
// Include already-in-mempool transaction results in the final result.
14691530
for (const auto& [wtxid, mempoolaccept_res] : results_final) {
14701531
Assume(submission_result.m_tx_results.emplace(wtxid, mempoolaccept_res).second);
14711532
Assume(mempoolaccept_res.m_result_type != MempoolAcceptResult::ResultType::INVALID);
14721533
}
14731534
if (submission_result.m_state.GetResult() == PackageValidationResult::PCKG_TX) {
14741535
// Package validation failed because one or more transactions failed. Provide a result for
1475-
// each transaction; if AcceptMultipleTransactions() didn't return a result for a tx,
1536+
// each transaction; if a transaction doesn't have an entry in submission_result,
14761537
// include the previous individual failure reason.
14771538
submission_result.m_tx_results.insert(individual_results_nonfinal.cbegin(),
14781539
individual_results_nonfinal.cend());

0 commit comments

Comments
 (0)