Skip to content

Commit 2ef1879

Browse files
committed
[validation] package validation for test accepts
Only allow test accepts for now. Use the CoinsViewTemporary to keep track of coins created by each transaction so that subsequent transactions can spend them. Uncache all coins since we only ever do test accepts (Note this is different from ATMP which doesn't uncache for valid test_accepts) to minimize impact on the coins cache. Require that the input txns have no conflicts and be ordered topologically. This commit isn't able to detect unsorted packages.
1 parent 578148d commit 2ef1879

File tree

2 files changed

+136
-0
lines changed

2 files changed

+136
-0
lines changed

src/validation.cpp

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
#include <validationinterface.h>
5151
#include <warnings.h>
5252

53+
#include <numeric>
5354
#include <optional>
5455
#include <string>
5556

@@ -477,6 +478,13 @@ class MemPoolAccept
477478
// Single transaction acceptance
478479
MempoolAcceptResult AcceptSingleTransaction(const CTransactionRef& ptx, ATMPArgs& args) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
479480

481+
/**
482+
* Multiple transaction acceptance. Transactions may or may not be interdependent,
483+
* but must not conflict with each other. Parents must come before children if any
484+
* dependencies exist, otherwise a TX_MISSING_INPUTS error will be returned.
485+
*/
486+
PackageMempoolAcceptResult AcceptMultipleTransactions(const std::vector<CTransactionRef>& txns, ATMPArgs& args) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
487+
480488
private:
481489
// All the intermediate state that gets passed between the various levels
482490
// of checking a given transaction.
@@ -1064,6 +1072,76 @@ MempoolAcceptResult MemPoolAccept::AcceptSingleTransaction(const CTransactionRef
10641072
return MempoolAcceptResult::Success(std::move(ws.m_replaced_transactions), ws.m_base_fees);
10651073
}
10661074

1075+
PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std::vector<CTransactionRef>& txns, ATMPArgs& args)
1076+
{
1077+
AssertLockHeld(cs_main);
1078+
1079+
PackageValidationState package_state;
1080+
const unsigned int package_count = txns.size();
1081+
1082+
std::vector<Workspace> workspaces{};
1083+
workspaces.reserve(package_count);
1084+
std::transform(txns.cbegin(), txns.cend(), std::back_inserter(workspaces), [](const auto& tx) {
1085+
return Workspace(tx);
1086+
});
1087+
1088+
std::map<const uint256, const MempoolAcceptResult> results;
1089+
{
1090+
// Don't allow any conflicting transactions, i.e. spending the same inputs, in a package.
1091+
std::unordered_set<COutPoint, SaltedOutpointHasher> inputs_seen;
1092+
for (const auto& tx : txns) {
1093+
for (const auto& input : tx->vin) {
1094+
if (inputs_seen.find(input.prevout) != inputs_seen.end()) {
1095+
// This input is also present in another tx in the package.
1096+
package_state.Invalid(PackageValidationResult::PCKG_POLICY, "conflict-in-package");
1097+
return PackageMempoolAcceptResult(package_state, {});
1098+
}
1099+
}
1100+
// Batch-add all the inputs for a tx at a time. If we added them 1 at a time, we could
1101+
// catch duplicate inputs within a single tx. This is a more severe, consensus error,
1102+
// and we want to report that from CheckTransaction instead.
1103+
std::transform(tx->vin.cbegin(), tx->vin.cend(), std::inserter(inputs_seen, inputs_seen.end()),
1104+
[](const auto& input) { return input.prevout; });
1105+
}
1106+
}
1107+
1108+
LOCK(m_pool.cs);
1109+
1110+
// Do all PreChecks first and fail fast to avoid running expensive script checks when unnecessary.
1111+
for (Workspace& ws : workspaces) {
1112+
if (!PreChecks(args, ws)) {
1113+
package_state.Invalid(PackageValidationResult::PCKG_TX, "transaction failed");
1114+
// Exit early to avoid doing pointless work. Update the failed tx result; the rest are unfinished.
1115+
results.emplace(ws.m_ptx->GetWitnessHash(), MempoolAcceptResult::Failure(ws.m_state));
1116+
return PackageMempoolAcceptResult(package_state, std::move(results));
1117+
}
1118+
// Make the coins created by this transaction available for subsequent transactions in the
1119+
// package to spend. Since we already checked conflicts in the package and RBFs are
1120+
// impossible, we don't need to track the coins spent. Note that this logic will need to be
1121+
// updated if RBFs in packages are allowed in the future.
1122+
assert(args.disallow_mempool_conflicts);
1123+
m_viewmempool.PackageAddTransaction(ws.m_ptx);
1124+
}
1125+
1126+
for (Workspace& ws : workspaces) {
1127+
PrecomputedTransactionData txdata;
1128+
if (!PolicyScriptChecks(args, ws, txdata)) {
1129+
// Exit early to avoid doing pointless work. Update the failed tx result; the rest are unfinished.
1130+
package_state.Invalid(PackageValidationResult::PCKG_TX, "transaction failed");
1131+
results.emplace(ws.m_ptx->GetWitnessHash(), MempoolAcceptResult::Failure(ws.m_state));
1132+
return PackageMempoolAcceptResult(package_state, std::move(results));
1133+
}
1134+
if (args.m_test_accept) {
1135+
// When test_accept=true, transactions that pass PolicyScriptChecks are valid because there are
1136+
// no further mempool checks (passing PolicyScriptChecks implies passing ConsensusScriptChecks).
1137+
results.emplace(ws.m_ptx->GetWitnessHash(),
1138+
MempoolAcceptResult::Success(std::move(ws.m_replaced_transactions), ws.m_base_fees));
1139+
}
1140+
}
1141+
1142+
return PackageMempoolAcceptResult(package_state, std::move(results));
1143+
}
1144+
10671145
} // anon namespace
10681146

10691147
/** (try to) add transaction to memory pool with a specified acceptance time **/
@@ -1101,6 +1179,29 @@ MempoolAcceptResult AcceptToMemoryPool(CChainState& active_chainstate, CTxMemPoo
11011179
return AcceptToMemoryPoolWithTime(Params(), pool, active_chainstate, tx, GetTime(), bypass_limits, test_accept);
11021180
}
11031181

1182+
PackageMempoolAcceptResult ProcessNewPackage(CChainState& active_chainstate, CTxMemPool& pool,
1183+
const Package& package, bool test_accept)
1184+
{
1185+
AssertLockHeld(cs_main);
1186+
assert(test_accept); // Only allow package accept dry-runs (testmempoolaccept RPC).
1187+
assert(!package.empty());
1188+
assert(std::all_of(package.cbegin(), package.cend(), [](const auto& tx){return tx != nullptr;}));
1189+
1190+
std::vector<COutPoint> coins_to_uncache;
1191+
const CChainParams& chainparams = Params();
1192+
MemPoolAccept::ATMPArgs args { chainparams, GetTime(), /* bypass_limits */ false, coins_to_uncache,
1193+
test_accept, /* disallow_mempool_conflicts */ true };
1194+
assert(std::addressof(::ChainstateActive()) == std::addressof(active_chainstate));
1195+
const PackageMempoolAcceptResult result = MemPoolAccept(pool, active_chainstate).AcceptMultipleTransactions(package, args);
1196+
1197+
// Uncache coins pertaining to transactions that were not submitted to the mempool.
1198+
// Ensure the cache is still within its size limits.
1199+
for (const COutPoint& hashTx : coins_to_uncache) {
1200+
active_chainstate.CoinsTip().Uncache(hashTx);
1201+
}
1202+
return result;
1203+
}
1204+
11041205
CTransactionRef GetTransaction(const CBlockIndex* const block_index, const CTxMemPool* const mempool, const uint256& hash, const Consensus::Params& consensusParams, uint256& hashBlock)
11051206
{
11061207
LOCK(cs_main);

src/validation.h

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include <fs.h>
1919
#include <node/utxo_snapshot.h>
2020
#include <policy/feerate.h>
21+
#include <policy/packages.h>
2122
#include <protocol.h> // For CMessageHeader::MessageStartChars
2223
#include <script/script_error.h>
2324
#include <sync.h>
@@ -204,6 +205,28 @@ struct MempoolAcceptResult {
204205
m_replaced_transactions(std::move(replaced_txns)), m_base_fees(fees) {}
205206
};
206207

208+
/**
209+
* Validation result for package mempool acceptance.
210+
*/
211+
struct PackageMempoolAcceptResult
212+
{
213+
const PackageValidationState m_state;
214+
/**
215+
* Map from wtxid to finished MempoolAcceptResults. The client is responsible
216+
* for keeping track of the transaction objects themselves. If a result is not
217+
* present, it means validation was unfinished for that transaction.
218+
*/
219+
std::map<const uint256, const MempoolAcceptResult> m_tx_results;
220+
221+
explicit PackageMempoolAcceptResult(PackageValidationState state,
222+
std::map<const uint256, const MempoolAcceptResult>&& results)
223+
: m_state{state}, m_tx_results(std::move(results)) {}
224+
225+
/** Constructor to create a PackageMempoolAcceptResult from a single MempoolAcceptResult */
226+
explicit PackageMempoolAcceptResult(const uint256& wtxid, const MempoolAcceptResult& result)
227+
: m_tx_results{ {wtxid, result} } {}
228+
};
229+
207230
/**
208231
* (Try to) add a transaction to the memory pool.
209232
* @param[in] bypass_limits When true, don't enforce mempool fee limits.
@@ -212,6 +235,18 @@ struct MempoolAcceptResult {
212235
MempoolAcceptResult AcceptToMemoryPool(CChainState& active_chainstate, CTxMemPool& pool, const CTransactionRef& tx,
213236
bool bypass_limits, bool test_accept=false) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
214237

238+
/**
239+
* Atomically test acceptance of a package. If the package only contains one tx, package rules still apply.
240+
* @param[in] txns Group of transactions which may be independent or contain
241+
* parent-child dependencies. The transactions must not conflict, i.e.
242+
* must not spend the same inputs, even if it would be a valid BIP125
243+
* replace-by-fee. Parents must appear before children.
244+
* @returns a PackageMempoolAcceptResult which includes a MempoolAcceptResult for each transaction.
245+
* If a transaction fails, validation will exit early and some results may be missing.
246+
*/
247+
PackageMempoolAcceptResult ProcessNewPackage(CChainState& active_chainstate, CTxMemPool& pool,
248+
const Package& txns, bool test_accept)
249+
EXCLUSIVE_LOCKS_REQUIRED(cs_main);
215250

216251
/** Apply the effects of this transaction on the UTXO set represented by view */
217252
void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, int nHeight);

0 commit comments

Comments
 (0)