diff --git a/src/validation.cpp b/src/validation.cpp index 9a3375fea9..08d37aec0c 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -477,6 +477,17 @@ public: }; } + /** Parameters for child-with-unconfirmed-parents package validation. */ + static ATMPArgs PackageChildWithParents(const CChainParams& chainparams, int64_t accept_time, + std::vector& coins_to_uncache) { + return ATMPArgs{/* m_chainparams */ chainparams, + /* m_accept_time */ accept_time, + /* m_bypass_limits */ false, + /* m_coins_to_uncache */ coins_to_uncache, + /* m_test_accept */ false, + /* m_allow_bip125_replacement */ false, + }; + } // No default ctor to avoid exposing details to clients and allowing the possibility of // mixing up the order of the arguments. Use static functions above instead. ATMPArgs() = delete; @@ -492,6 +503,12 @@ public: */ PackageMempoolAcceptResult AcceptMultipleTransactions(const std::vector& txns, ATMPArgs& args) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + /** + * Package (more specific than just multiple transactions) acceptance. Package must be a child + * with all of its unconfirmed parents, and topologically sorted. + */ + PackageMempoolAcceptResult AcceptPackage(const Package& package, ATMPArgs& args) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + private: // All the intermediate state that gets passed between the various levels // of checking a given transaction. @@ -1077,6 +1094,62 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std:: return PackageMempoolAcceptResult(package_state, std::move(results)); } +PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package& package, ATMPArgs& args) +{ + AssertLockHeld(cs_main); + PackageValidationState package_state; + + // Check that the package is well-formed. If it isn't, we won't try to validate any of the + // transactions and thus won't return any MempoolAcceptResults, just a package-wide error. + + // Context-free package checks. + if (!CheckPackage(package, package_state)) return PackageMempoolAcceptResult(package_state, {}); + + // All transactions in the package must be a parent of the last transaction. This is just an + // opportunity for us to fail fast on a context-free check without taking the mempool lock. + if (!IsChildWithParents(package)) { + package_state.Invalid(PackageValidationResult::PCKG_POLICY, "package-not-child-with-parents"); + return PackageMempoolAcceptResult(package_state, {}); + } + + const auto& child = package[package.size() - 1]; + // The package must be 1 child with all of its unconfirmed parents. The package is expected to + // be sorted, so the last transaction is the child. + std::unordered_set unconfirmed_parent_txids; + std::transform(package.cbegin(), package.end() - 1, + std::inserter(unconfirmed_parent_txids, unconfirmed_parent_txids.end()), + [](const auto& tx) { return tx->GetHash(); }); + + // All child inputs must refer to a preceding package transaction or a confirmed UTXO. The only + // way to verify this is to look up the child's inputs in our current coins view (not including + // mempool), and enforce that all parents not present in the package be available at chain tip. + // Since this check can bring new coins into the coins cache, keep track of these coins and + // uncache them if we don't end up submitting this package to the mempool. + const CCoinsViewCache& coins_tip_cache = m_active_chainstate.CoinsTip(); + for (const auto& input : child->vin) { + if (!coins_tip_cache.HaveCoinInCache(input.prevout)) { + args.m_coins_to_uncache.push_back(input.prevout); + } + } + // Using the MemPoolAccept m_view cache allows us to look up these same coins faster later. + // This should be connecting directly to CoinsTip, not to m_viewmempool, because we specifically + // require inputs to be confirmed if they aren't in the package. + m_view.SetBackend(m_active_chainstate.CoinsTip()); + const auto package_or_confirmed = [this, &unconfirmed_parent_txids](const auto& input) { + return unconfirmed_parent_txids.count(input.prevout.hash) > 0 || m_view.HaveCoin(input.prevout); + }; + if (!std::all_of(child->vin.cbegin(), child->vin.cend(), package_or_confirmed)) { + package_state.Invalid(PackageValidationResult::PCKG_POLICY, "package-not-child-with-unconfirmed-parents"); + return PackageMempoolAcceptResult(package_state, {}); + } + // Protect against bugs where we pull more inputs from disk that miss being added to + // coins_to_uncache. The backend will be connected again when needed in PreChecks. + m_view.SetBackend(m_dummy); + + LOCK(m_pool.cs); + return AcceptMultipleTransactions(package, args); +} + } // anon namespace /** (try to) add transaction to memory pool with a specified acceptance time **/ @@ -1120,8 +1193,16 @@ PackageMempoolAcceptResult ProcessNewPackage(CChainState& active_chainstate, CTx std::vector coins_to_uncache; const CChainParams& chainparams = Params(); - auto args = MemPoolAccept::ATMPArgs::PackageTestAccept(chainparams, GetTime(), coins_to_uncache); - const PackageMempoolAcceptResult result = MemPoolAccept(pool, active_chainstate).AcceptMultipleTransactions(package, args); + const auto result = [&]() EXCLUSIVE_LOCKS_REQUIRED(cs_main) { + AssertLockHeld(cs_main); + if (test_accept) { + auto args = MemPoolAccept::ATMPArgs::PackageTestAccept(chainparams, GetTime(), coins_to_uncache); + return MemPoolAccept(pool, active_chainstate).AcceptMultipleTransactions(package, args); + } else { + auto args = MemPoolAccept::ATMPArgs::PackageChildWithParents(chainparams, GetTime(), coins_to_uncache); + return MemPoolAccept(pool, active_chainstate).AcceptPackage(package, args); + } + }(); // Uncache coins pertaining to transactions that were not submitted to the mempool. for (const COutPoint& hashTx : coins_to_uncache) {