Skip to content

Commit be3ff15

Browse files
committed
[validation] full package accept + mempool submission
1 parent 144a290 commit be3ff15

File tree

2 files changed

+106
-9
lines changed

2 files changed

+106
-9
lines changed

src/validation.cpp

Lines changed: 103 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,11 @@ class MemPoolAccept
451451
* any transaction spending the same inputs as a transaction in the mempool is considered
452452
* a conflict. */
453453
const bool m_allow_bip125_replacement;
454+
/** When true, the mempool will not be trimmed when individual transactions are submitted in
455+
* Finalize(). Instead, limits should be enforced at the end to ensure the package is not
456+
* partially submitted.
457+
*/
458+
const bool m_package_submission;
454459

455460
/** Parameters for single transaction mempool validation. */
456461
static ATMPArgs SingleAccept(const CChainParams& chainparams, int64_t accept_time,
@@ -462,6 +467,7 @@ class MemPoolAccept
462467
/* m_coins_to_uncache */ coins_to_uncache,
463468
/* m_test_accept */ test_accept,
464469
/* m_allow_bip125_replacement */ true,
470+
/* m_package_submission */ false,
465471
};
466472
}
467473

@@ -474,6 +480,7 @@ class MemPoolAccept
474480
/* m_coins_to_uncache */ coins_to_uncache,
475481
/* m_test_accept */ true,
476482
/* m_allow_bip125_replacement */ false,
483+
/* m_package_submission */ false, // not submitting to mempool
477484
};
478485
}
479486

@@ -486,6 +493,7 @@ class MemPoolAccept
486493
/* m_coins_to_uncache */ coins_to_uncache,
487494
/* m_test_accept */ false,
488495
/* m_allow_bip125_replacement */ false,
496+
/* m_package_submission */ true,
489497
};
490498
}
491499
// No default ctor to avoid exposing details to clients and allowing the possibility of
@@ -497,9 +505,9 @@ class MemPoolAccept
497505
MempoolAcceptResult AcceptSingleTransaction(const CTransactionRef& ptx, ATMPArgs& args) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
498506

499507
/**
500-
* Multiple transaction acceptance. Transactions may or may not be interdependent,
501-
* but must not conflict with each other. Parents must come before children if any
502-
* dependencies exist.
508+
* Multiple transaction acceptance. Transactions may or may not be interdependent, but must not
509+
* conflict with each other, and the transactions cannot already be in the mempool. Parents must
510+
* come before children if any dependencies exist.
503511
*/
504512
PackageMempoolAcceptResult AcceptMultipleTransactions(const std::vector<CTransactionRef>& txns, ATMPArgs& args) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
505513

@@ -581,6 +589,14 @@ class MemPoolAccept
581589
// limiting is performed, false otherwise.
582590
bool Finalize(const ATMPArgs& args, Workspace& ws) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
583591

592+
// Submit all transactions to the mempool and call ConsensusScriptChecks to add to the script
593+
// cache - should only be called after successful validation of all transactions in the package.
594+
// The package may end up partially-submitted after size limitting; returns true if all
595+
// transactions are successfully added to the mempool, false otherwise.
596+
bool FinalizePackage(const ATMPArgs& args, std::vector<Workspace>& workspaces, PackageValidationState& package_state,
597+
std::map<const uint256, const MempoolAcceptResult>& results)
598+
EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
599+
584600
// Compare a package's feerate against minimum allowed.
585601
bool CheckFeeRate(size_t package_size, CAmount package_fee, TxValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs)
586602
{
@@ -992,20 +1008,88 @@ bool MemPoolAccept::Finalize(const ATMPArgs& args, Workspace& ws)
9921008
// - it's not being re-added during a reorg which bypasses typical mempool fee limits
9931009
// - the node is not behind
9941010
// - the transaction is not dependent on any other transactions in the mempool
995-
bool validForFeeEstimation = !bypass_limits && IsCurrentForFeeEstimation(m_active_chainstate) && m_pool.HasNoInputsOf(tx);
1011+
// - it's not part of a package. Since package relay is not currently supported, this
1012+
// transaction has not necessarily been accepted to miners' mempools.
1013+
bool validForFeeEstimation = !bypass_limits && !args.m_package_submission && IsCurrentForFeeEstimation(m_active_chainstate) && m_pool.HasNoInputsOf(tx);
9961014

9971015
// Store transaction in memory
9981016
m_pool.addUnchecked(*entry, ws.m_ancestors, validForFeeEstimation);
9991017

10001018
// trim mempool and check if tx was trimmed
1001-
if (!bypass_limits) {
1019+
// If we are validating a package, don't trim here because we could evict a previous transaction
1020+
// in the package. LimitMempoolSize() should be called at the very end to make sure the mempool
1021+
// is still within limits and package submission happens atomically.
1022+
if (!args.m_package_submission && !bypass_limits) {
10021023
LimitMempoolSize(m_pool, m_active_chainstate.CoinsTip(), gArgs.GetIntArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000, std::chrono::hours{gArgs.GetIntArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY)});
10031024
if (!m_pool.exists(GenTxid::Txid(hash)))
10041025
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "mempool full");
10051026
}
10061027
return true;
10071028
}
10081029

1030+
bool MemPoolAccept::FinalizePackage(const ATMPArgs& args, std::vector<Workspace>& workspaces,
1031+
PackageValidationState& package_state,
1032+
std::map<const uint256, const MempoolAcceptResult>& results)
1033+
{
1034+
AssertLockHeld(cs_main);
1035+
AssertLockHeld(m_pool.cs);
1036+
bool all_submitted = true;
1037+
// ConsensusScriptChecks adds to the script cache and is therefore consensus-critical;
1038+
// CheckInputsFromMempoolAndCache asserts that transactions only spend coins available from the
1039+
// mempool or UTXO set. Submit each transaction to the mempool immediately after calling
1040+
// ConsensusScriptChecks to make the outputs available for subsequent transactions.
1041+
for (Workspace& ws : workspaces) {
1042+
if (!ConsensusScriptChecks(args, ws)) {
1043+
results.emplace(ws.m_ptx->GetWitnessHash(), MempoolAcceptResult::Failure(ws.m_state));
1044+
// Since PolicyScriptChecks() passed, this should never fail.
1045+
all_submitted = Assume(false);
1046+
}
1047+
1048+
// Re-calculate mempool ancestors to call addUnchecked(). They may have changed since the
1049+
// last calculation done in PreChecks, since package ancestors have already been submitted.
1050+
std::string err_string;
1051+
if(!m_pool.CalculateMemPoolAncestors(*ws.m_entry, ws.m_ancestors, m_limit_ancestors,
1052+
m_limit_ancestor_size, m_limit_descendants,
1053+
m_limit_descendant_size, err_string)) {
1054+
results.emplace(ws.m_ptx->GetWitnessHash(), MempoolAcceptResult::Failure(ws.m_state));
1055+
// Since PreChecks() and PackageMempoolChecks() both enforce limits, this should never fail.
1056+
all_submitted = Assume(false);
1057+
}
1058+
// If we call LimitMempoolSize() for each individual Finalize(), the mempool will not take
1059+
// the transaction's descendant feerate into account because it hasn't seen them yet. Also,
1060+
// we risk evicting a transaction that a subsequent package transaction depends on. Instead,
1061+
// allow the mempool to temporarily bypass limits, the maximum package size) while
1062+
// submitting transactions individually and then trim at the very end.
1063+
if (!Finalize(args, ws)) {
1064+
results.emplace(ws.m_ptx->GetWitnessHash(), MempoolAcceptResult::Failure(ws.m_state));
1065+
// Since LimitMempoolSize() won't be called, this should never fail.
1066+
all_submitted = Assume(false);
1067+
}
1068+
}
1069+
1070+
// It may or may not be the case that all the transactions made it into the mempool. Regardless,
1071+
// make sure we haven't exceeded max mempool size.
1072+
LimitMempoolSize(m_pool, m_active_chainstate.CoinsTip(),
1073+
gArgs.GetIntArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000,
1074+
std::chrono::hours{gArgs.GetIntArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY)});
1075+
if (!all_submitted) return false;
1076+
1077+
// Find the wtxids of the transactions that made it into the mempool. Allow partial submission,
1078+
// but don't report success unless they all made it into the mempool.
1079+
for (Workspace& ws : workspaces) {
1080+
if (m_pool.exists(GenTxid::Wtxid(ws.m_ptx->GetWitnessHash()))) {
1081+
results.emplace(ws.m_ptx->GetWitnessHash(),
1082+
MempoolAcceptResult::Success(std::move(ws.m_replaced_transactions), ws.m_vsize, ws.m_base_fees));
1083+
GetMainSignals().TransactionAddedToMempool(ws.m_ptx, m_pool.GetAndIncrementSequence());
1084+
} else {
1085+
all_submitted = false;
1086+
ws.m_state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "mempool full");
1087+
results.emplace(ws.m_ptx->GetWitnessHash(), MempoolAcceptResult::Failure(ws.m_state));
1088+
}
1089+
}
1090+
return all_submitted;
1091+
}
1092+
10091093
MempoolAcceptResult MemPoolAccept::AcceptSingleTransaction(const CTransactionRef& ptx, ATMPArgs& args)
10101094
{
10111095
AssertLockHeld(cs_main);
@@ -1091,6 +1175,13 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std::
10911175
}
10921176
}
10931177

1178+
if (args.m_test_accept) return PackageMempoolAcceptResult(package_state, std::move(results));
1179+
1180+
if (!FinalizePackage(args, workspaces, package_state, results)) {
1181+
package_state.Invalid(PackageValidationResult::PCKG_TX, "submission failed");
1182+
return PackageMempoolAcceptResult(package_state, std::move(results));
1183+
}
1184+
10941185
return PackageMempoolAcceptResult(package_state, std::move(results));
10951186
}
10961187

@@ -1187,7 +1278,6 @@ PackageMempoolAcceptResult ProcessNewPackage(CChainState& active_chainstate, CTx
11871278
const Package& package, bool test_accept)
11881279
{
11891280
AssertLockHeld(cs_main);
1190-
assert(test_accept); // Only allow package accept dry-runs (testmempoolaccept RPC).
11911281
assert(!package.empty());
11921282
assert(std::all_of(package.cbegin(), package.cend(), [](const auto& tx){return tx != nullptr;}));
11931283

@@ -1205,9 +1295,14 @@ PackageMempoolAcceptResult ProcessNewPackage(CChainState& active_chainstate, CTx
12051295
}();
12061296

12071297
// Uncache coins pertaining to transactions that were not submitted to the mempool.
1208-
for (const COutPoint& hashTx : coins_to_uncache) {
1209-
active_chainstate.CoinsTip().Uncache(hashTx);
1298+
// Ensure the coins cache is still within limits.
1299+
if (test_accept || result.m_state.IsInvalid()) {
1300+
for (const COutPoint& hashTx : coins_to_uncache) {
1301+
active_chainstate.CoinsTip().Uncache(hashTx);
1302+
}
12101303
}
1304+
BlockValidationState state_dummy;
1305+
active_chainstate.FlushStateToDisk(state_dummy, FlushStateMode::PERIODIC);
12111306
return result;
12121307
}
12131308

src/validation.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,8 +218,10 @@ MempoolAcceptResult AcceptToMemoryPool(CChainState& active_chainstate, CTxMemPoo
218218
/**
219219
* Validate (and maybe submit) a package to the mempool. See doc/policy/packages.md for full details
220220
* on package validation rules.
221+
* @param[in] test_accept When true, run validation checks but don't submit to mempool.
221222
* @returns a PackageMempoolAcceptResult which includes a MempoolAcceptResult for each transaction.
222-
* If a transaction fails, validation will exit early and some results may be missing.
223+
* If a transaction fails, validation will exit early and some results may be missing. It is also
224+
* possible for the package to be partially submitted.
223225
*/
224226
PackageMempoolAcceptResult ProcessNewPackage(CChainState& active_chainstate, CTxMemPool& pool,
225227
const Package& txns, bool test_accept)

0 commit comments

Comments
 (0)