Repository: bitcoin/bitcoin
File: src/validation.cpp (C++)

Code before:
        const auto single_res = AcceptSingleTransaction(tx, single_args);
        PackageValidationState package_state_wrapped;
        if (single_res.m_result_type != MempoolAcceptResult::ResultType::VALID) {
            package_state_wrapped.Invalid(PackageValidationResult::PCKG_TX, "transaction failed");
        }
        return PackageMempoolAcceptResult(package_state_wrapped, {{tx->GetWitnessHash(), single_res}});
    }();

    // Clean up m_view and m_viewmempool so that other subpackage evaluations don't have access to
    // coins they shouldn't. Keep some coins in order to minimize re-fetching coins from the UTXO set.
    // Clean up package feerate and rbf calculations
    ClearSubPackageState();

    return result;
}

PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package& package, ATMPArgs& args)
{
    AssertLockHeld(cs_main);
    // Used if returning a PackageMempoolAcceptResult directly from this function.
    PackageValidationState package_state_quit_early;

    // 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 (!IsWellFormedPackage(package, package_state_quit_early, /*require_sorted=*/true)) {
        return PackageMempoolAcceptResult(package_state_quit_early, {});
    }

    // 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_quit_early.Invalid(PackageValidationResult::PCKG_POLICY, "package-not-child-with-parents");
        return PackageMempoolAcceptResult(package_state_quit_early, {});
    }

    // IsChildWithParents() guarantees the package is > 1 transactions.
    assert(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.
    const auto& child = package.back();
    std::unordered_set<uint256, SaltedTxidHasher> unconfirmed_parent_txids;
    std::transform(package.cbegin(), package.cend() - 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

Review comment (suggestion):
Orthogonal to the changes here, but related and I think it would be good to add this assert + doc to `IsChildWithParentsTree()` too, if you force-push.

<details>
<summary>git diff on 92ed1bd586</summary>

```diff
diff --git a/src/policy/packages.cpp b/src/policy/packages.cpp
index 38f26a9c4b..e34f94cbb1 100644
--- a/src/policy/packages.cpp
+++ b/src/policy/packages.cpp
@@ -138,6 +138,7 @@ bool IsChildWithParentsTree(const Package& package)
 {
     if (!IsChildWithParents(package)) return false;
     std::unordered_set<uint256, SaltedTxidHasher> parent_txids;
+    assert(!package.empty()); // IsChildWithParents() guarantees the package is not empty.
     std::transform(package.cbegin(), package.cend() - 1, std::inserter(parent_txids, parent_txids.end()),
                    [](const auto& ptx) { return ptx->GetHash(); });
     // Each parent must not have an input who is one of the other parents.

```
</details>


Code after:
    return result;
}

PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package& package, ATMPArgs& args)
{
    Assert(!package.empty());
    AssertLockHeld(cs_main);
    // Used if returning a PackageMempoolAcceptResult directly from this function.
    PackageValidationState package_state_quit_early;

    // There are two topologies we are able to handle through this function:
    // (1) A single transaction
    // (2) A child-with-unconfirmed-parents package.
    // 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 (!IsWellFormedPackage(package, package_state_quit_early, /*require_sorted=*/true)) {
        return PackageMempoolAcceptResult(package_state_quit_early, {});
    }

    if (package.size() > 1) {
        // 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_quit_early.Invalid(PackageValidationResult::PCKG_POLICY, "package-not-child-with-parents");
            return PackageMempoolAcceptResult(package_state_quit_early, {});
        }

        // IsChildWithParents() guarantees the package is > 1 transactions.
        assert(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.
        const auto& child = package.back();
        std::unordered_set<uint256, SaltedTxidHasher> unconfirmed_parent_txids;
        std::transform(package.cbegin(), package.cend() - 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.