From 2ef187941db439c5b3e529f08b6ab153ff061fc5 Mon Sep 17 00:00:00 2001 From: glozow Date: Mon, 5 Apr 2021 10:12:58 -0700 Subject: [PATCH] [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. --- src/validation.cpp | 101 +++++++++++++++++++++++++++++++++++++++++++++ src/validation.h | 35 ++++++++++++++++ 2 files changed, 136 insertions(+) diff --git a/src/validation.cpp b/src/validation.cpp index 9165a1f1b33..ad0f636554f 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -50,6 +50,7 @@ #include #include +#include #include #include @@ -477,6 +478,13 @@ public: // Single transaction acceptance MempoolAcceptResult AcceptSingleTransaction(const CTransactionRef& ptx, ATMPArgs& args) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + /** + * Multiple transaction acceptance. Transactions may or may not be interdependent, + * but must not conflict with each other. Parents must come before children if any + * dependencies exist, otherwise a TX_MISSING_INPUTS error will be returned. + */ + PackageMempoolAcceptResult AcceptMultipleTransactions(const std::vector& txns, ATMPArgs& args) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + private: // All the intermediate state that gets passed between the various levels // of checking a given transaction. @@ -1064,6 +1072,76 @@ MempoolAcceptResult MemPoolAccept::AcceptSingleTransaction(const CTransactionRef return MempoolAcceptResult::Success(std::move(ws.m_replaced_transactions), ws.m_base_fees); } +PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std::vector& txns, ATMPArgs& args) +{ + AssertLockHeld(cs_main); + + PackageValidationState package_state; + const unsigned int package_count = txns.size(); + + std::vector workspaces{}; + workspaces.reserve(package_count); + std::transform(txns.cbegin(), txns.cend(), std::back_inserter(workspaces), [](const auto& tx) { + return Workspace(tx); + }); + + std::map results; + { + // Don't allow any conflicting transactions, i.e. spending the same inputs, in a package. + std::unordered_set inputs_seen; + for (const auto& tx : txns) { + for (const auto& input : tx->vin) { + if (inputs_seen.find(input.prevout) != inputs_seen.end()) { + // This input is also present in another tx in the package. + package_state.Invalid(PackageValidationResult::PCKG_POLICY, "conflict-in-package"); + return PackageMempoolAcceptResult(package_state, {}); + } + } + // Batch-add all the inputs for a tx at a time. If we added them 1 at a time, we could + // catch duplicate inputs within a single tx. This is a more severe, consensus error, + // and we want to report that from CheckTransaction instead. + std::transform(tx->vin.cbegin(), tx->vin.cend(), std::inserter(inputs_seen, inputs_seen.end()), + [](const auto& input) { return input.prevout; }); + } + } + + LOCK(m_pool.cs); + + // Do all PreChecks first and fail fast to avoid running expensive script checks when unnecessary. + for (Workspace& ws : workspaces) { + if (!PreChecks(args, ws)) { + package_state.Invalid(PackageValidationResult::PCKG_TX, "transaction failed"); + // Exit early to avoid doing pointless work. Update the failed tx result; the rest are unfinished. + results.emplace(ws.m_ptx->GetWitnessHash(), MempoolAcceptResult::Failure(ws.m_state)); + return PackageMempoolAcceptResult(package_state, std::move(results)); + } + // Make the coins created by this transaction available for subsequent transactions in the + // package to spend. Since we already checked conflicts in the package and RBFs are + // impossible, we don't need to track the coins spent. Note that this logic will need to be + // updated if RBFs in packages are allowed in the future. + assert(args.disallow_mempool_conflicts); + m_viewmempool.PackageAddTransaction(ws.m_ptx); + } + + for (Workspace& ws : workspaces) { + PrecomputedTransactionData txdata; + if (!PolicyScriptChecks(args, ws, txdata)) { + // Exit early to avoid doing pointless work. Update the failed tx result; the rest are unfinished. + package_state.Invalid(PackageValidationResult::PCKG_TX, "transaction failed"); + results.emplace(ws.m_ptx->GetWitnessHash(), MempoolAcceptResult::Failure(ws.m_state)); + return PackageMempoolAcceptResult(package_state, std::move(results)); + } + if (args.m_test_accept) { + // When test_accept=true, transactions that pass PolicyScriptChecks are valid because there are + // no further mempool checks (passing PolicyScriptChecks implies passing ConsensusScriptChecks). + results.emplace(ws.m_ptx->GetWitnessHash(), + MempoolAcceptResult::Success(std::move(ws.m_replaced_transactions), ws.m_base_fees)); + } + } + + return PackageMempoolAcceptResult(package_state, std::move(results)); +} + } // anon namespace /** (try to) add transaction to memory pool with a specified acceptance time **/ @@ -1101,6 +1179,29 @@ MempoolAcceptResult AcceptToMemoryPool(CChainState& active_chainstate, CTxMemPoo return AcceptToMemoryPoolWithTime(Params(), pool, active_chainstate, tx, GetTime(), bypass_limits, test_accept); } +PackageMempoolAcceptResult ProcessNewPackage(CChainState& active_chainstate, CTxMemPool& pool, + const Package& package, bool test_accept) +{ + AssertLockHeld(cs_main); + assert(test_accept); // Only allow package accept dry-runs (testmempoolaccept RPC). + assert(!package.empty()); + assert(std::all_of(package.cbegin(), package.cend(), [](const auto& tx){return tx != nullptr;})); + + std::vector coins_to_uncache; + const CChainParams& chainparams = Params(); + MemPoolAccept::ATMPArgs args { chainparams, GetTime(), /* bypass_limits */ false, coins_to_uncache, + test_accept, /* disallow_mempool_conflicts */ true }; + assert(std::addressof(::ChainstateActive()) == std::addressof(active_chainstate)); + const PackageMempoolAcceptResult result = MemPoolAccept(pool, active_chainstate).AcceptMultipleTransactions(package, args); + + // Uncache coins pertaining to transactions that were not submitted to the mempool. + // Ensure the cache is still within its size limits. + for (const COutPoint& hashTx : coins_to_uncache) { + active_chainstate.CoinsTip().Uncache(hashTx); + } + return result; +} + CTransactionRef GetTransaction(const CBlockIndex* const block_index, const CTxMemPool* const mempool, const uint256& hash, const Consensus::Params& consensusParams, uint256& hashBlock) { LOCK(cs_main); diff --git a/src/validation.h b/src/validation.h index 231f55d8273..3f09e59dab6 100644 --- a/src/validation.h +++ b/src/validation.h @@ -18,6 +18,7 @@ #include #include #include +#include #include // For CMessageHeader::MessageStartChars #include