diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -599,6 +599,7 @@ node/ui_interface.cpp noui.cpp policy/fees.cpp + policy/packages.cpp policy/settings.cpp pow/aserti32d.cpp pow/daa.cpp diff --git a/src/policy/packages.h b/src/policy/packages.h --- a/src/policy/packages.h +++ b/src/policy/packages.h @@ -40,4 +40,14 @@ class PackageValidationState : public ValidationState { }; +/** + * Context-free package policy checks: + * 1. The number of transactions cannot exceed MAX_PACKAGE_COUNT. + * 2. The total virtual size cannot exceed MAX_PACKAGE_SIZE. + * 3. If any dependencies exist between transactions, parents must appear before + * children. + * 4. Transactions cannot conflict, i.e., spend the same inputs. + */ +bool CheckPackage(const Package &txns, PackageValidationState &state); + #endif // BITCOIN_POLICY_PACKAGES_H diff --git a/src/policy/packages.cpp b/src/policy/packages.cpp new file mode 100644 --- /dev/null +++ b/src/policy/packages.cpp @@ -0,0 +1,75 @@ +// Copyright (c) 2021 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include +#include +#include +#include + +#include +#include + +bool CheckPackage(const Package &txns, PackageValidationState &state) { + const size_t package_count = txns.size(); + + // These context-free package limits can be checked before taking the + // mempool lock. + if (package_count > MAX_PACKAGE_COUNT) { + return state.Invalid(PackageValidationResult::PCKG_POLICY, + "package-too-many-transactions"); + } + + const int64_t total_size = std::accumulate( + txns.cbegin(), txns.cend(), 0, [](int64_t sum, const auto &tx) { + return sum + GetVirtualTransactionSize(*tx); + }); + // If the package only contains 1 tx, it's better to report the policy + // violation on individual tx size. + if (package_count > 1 && total_size > MAX_PACKAGE_SIZE * 1000) { + return state.Invalid(PackageValidationResult::PCKG_POLICY, + "package-too-large"); + } + + // Require the package to be sorted in order of dependency, i.e. parents + // appear before children. + // An unsorted package will fail anyway on missing-inputs, but it's better + // to quit earlier and fail on something less ambiguous (missing-inputs + // could also be an orphan or trying to spend nonexistent coins). + std::unordered_set later_txids; + std::transform(txns.cbegin(), txns.cend(), + std::inserter(later_txids, later_txids.end()), + [](const auto &tx) { return tx->GetId(); }); + for (const auto &tx : txns) { + for (const auto &input : tx->vin) { + if (later_txids.find(input.prevout.GetTxId()) != + later_txids.end()) { + // The parent is a subsequent transaction in the package. + return state.Invalid(PackageValidationResult::PCKG_POLICY, + "package-not-sorted"); + } + } + later_txids.erase(tx->GetId()); + } + + // 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. + return state.Invalid(PackageValidationResult::PCKG_POLICY, + "conflict-in-package"); + } + } + // 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; }); + } + return true; +} diff --git a/src/validation.cpp b/src/validation.cpp --- a/src/validation.cpp +++ b/src/validation.cpp @@ -734,83 +734,24 @@ const std::vector &txns, ATMPArgs &args) { AssertLockHeld(cs_main); + // These context-free package limits can be done before taking the mempool + // lock. PackageValidationState package_state; - const size_t package_count = txns.size(); - - // These context-free package limits can be checked before taking the - // mempool lock. - if (package_count > MAX_PACKAGE_COUNT) { - package_state.Invalid(PackageValidationResult::PCKG_POLICY, - "package-too-many-transactions"); - return PackageMempoolAcceptResult(package_state, {}); - } - - const int64_t total_size = std::accumulate( - txns.cbegin(), txns.cend(), 0, [](int64_t sum, const auto &tx) { - return sum + GetVirtualTransactionSize(*tx); - }); - // If the package only contains 1 tx, it's better to report the policy - // violation on individual tx size. - if (package_count > 1 && total_size > MAX_PACKAGE_SIZE * 1000) { - package_state.Invalid(PackageValidationResult::PCKG_POLICY, - "package-too-large"); + if (!CheckPackage(txns, package_state)) { return PackageMempoolAcceptResult(package_state, {}); } - // Construct workspaces and check package policies. std::vector workspaces{}; - workspaces.reserve(package_count); - { - std::unordered_set later_txids; - std::transform(txns.cbegin(), txns.cend(), - std::inserter(later_txids, later_txids.end()), - [](const auto &tx) { return tx->GetId(); }); - // Require the package to be sorted in order of dependency, i.e. - // parents appear before children. - // An unsorted package will fail anyway on missing-inputs, but it's - // better to quit earlier and fail on something less ambiguous - // (missing-inputs could also be an orphan or trying to spend - // nonexistent coins). - for (const auto &tx : txns) { - for (const auto &input : tx->vin) { - if (later_txids.find(input.prevout.GetTxId()) != - later_txids.end()) { - // The parent is a subsequent transaction in the package. - package_state.Invalid(PackageValidationResult::PCKG_POLICY, - "package-not-sorted"); - return PackageMempoolAcceptResult(package_state, {}); - } - } - later_txids.erase(tx->GetId()); - workspaces.emplace_back( - tx, GetNextBlockScriptFlags( - args.m_config.GetChainParams().GetConsensus(), - m_active_chainstate.m_chain.Tip())); - } - } + workspaces.reserve(txns.size()); + std::transform(txns.cbegin(), txns.cend(), std::back_inserter(workspaces), + [&args, this](const auto &tx) { + return Workspace( + tx, + GetNextBlockScriptFlags( + args.m_config.GetChainParams().GetConsensus(), + m_active_chainstate.m_chain.Tip())); + }); 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);