diff --git a/src/Makefile.am b/src/Makefile.am index 8f0a279423e9b..28aa3bad7186f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -484,6 +484,7 @@ libbitcoin_server_a_SOURCES = \ node/ui_interface.cpp \ noui.cpp \ policy/fees.cpp \ + policy/packages.cpp \ policy/policy.cpp \ policy/settings.cpp \ pow.cpp \ diff --git a/src/policy/packages.cpp b/src/policy/packages.cpp new file mode 100644 index 0000000000000..cfd05399655d0 --- /dev/null +++ b/src/policy/packages.cpp @@ -0,0 +1,62 @@ +// 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 +#include + +bool CheckPackage(const Package& txns, PackageValidationState& state) +{ + const unsigned int package_count = txns.size(); + + 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->GetHash(); }); + for (const auto& tx : txns) { + for (const auto& input : tx->vin) { + if (later_txids.find(input.prevout.hash) != 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->GetHash()); + } + + // 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/policy/packages.h b/src/policy/packages.h index 4b1463dcb34eb..bb2e4b3b47066 100644 --- a/src/policy/packages.h +++ b/src/policy/packages.h @@ -6,6 +6,7 @@ #define BITCOIN_POLICY_PACKAGES_H #include +#include #include #include @@ -14,6 +15,7 @@ static constexpr uint32_t MAX_PACKAGE_COUNT{25}; /** Default maximum total virtual size of transactions in a package in KvB. */ static constexpr uint32_t MAX_PACKAGE_SIZE{101}; +static_assert(MAX_PACKAGE_SIZE * 1000 >= MAX_STANDARD_TX_SIZE); /** A "reason" why a package was invalid. It may be that one or more of the included * transactions is invalid or the package itself violates our rules. @@ -31,4 +33,12 @@ using Package = std::vector; 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/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 1d2aae46fcf25..b879e8b4455bc 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -1159,7 +1159,7 @@ static UniValue testmempoolaccept(const JSONRPCRequest& request) "\nReturns result of mempool acceptance tests indicating if raw transaction (serialized, hex-encoded) would be accepted by mempool.\n" "\nIf multiple transactions are passed in, parents must come before children and package policies apply: the transactions cannot conflict with any mempool transactions or each other.\n" "\nIf one transaction fails, other transactions may not be fully validated (the 'allowed' key will be blank).\n" - "\nThe maximum number of transactions allowed is 25 (MAX_PACKAGE_COUNT)\n" + "\nThe maximum number of transactions allowed is " + ToString(MAX_PACKAGE_COUNT) + ".\n" "\nThis checks if transactions violate the consensus or policy rules.\n" "\nSee sendrawtransaction call.\n", { @@ -1174,7 +1174,7 @@ static UniValue testmempoolaccept(const JSONRPCRequest& request) RPCResult{ RPCResult::Type::ARR, "", "The result of the mempool acceptance test for each raw transaction in the input array.\n" "Returns results for each transaction in the same order they were passed in.\n" - "It is possible for transactions to not be fully validated ('allowed' unset) if an earlier transaction failed.\n", + "It is possible for transactions to not be fully validated ('allowed' unset) if another transaction failed.\n", { {RPCResult::Type::OBJ, "", "", { @@ -1207,7 +1207,6 @@ static UniValue testmempoolaccept(const JSONRPCRequest& request) UniValue::VARR, UniValueType(), // VNUM or VSTR, checked inside AmountFromValue() }); - const UniValue raw_transactions = request.params[0].get_array(); if (raw_transactions.size() < 1 || raw_transactions.size() > MAX_PACKAGE_COUNT) { throw JSONRPCError(RPC_INVALID_PARAMETER, @@ -1219,6 +1218,7 @@ static UniValue testmempoolaccept(const JSONRPCRequest& request) CFeeRate(AmountFromValue(request.params[1])); std::vector txns; + txns.reserve(raw_transactions.size()); for (const auto& rawtx : raw_transactions.getValues()) { CMutableTransaction mtx; if (!DecodeHexTx(mtx, rawtx.get_str())) { @@ -1239,8 +1239,8 @@ static UniValue testmempoolaccept(const JSONRPCRequest& request) }(); UniValue rpc_result(UniValue::VARR); - // We will check transaction fees we iterate through txns in order. If any transaction fee - // exceeds maxfeerate, we will keave the rest of the validation results blank, because it + // We will check transaction fees while we iterate through txns in order. If any transaction fee + // exceeds maxfeerate, we will leave the rest of the validation results blank, because it // doesn't make sense to return a validation result for a transaction if its ancestor(s) would // not be submitted. bool exit_early{false}; diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index 2a48ddc83095e..be5a682931afe 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -35,8 +35,8 @@ struct MinerTestingSetup : public TestingSetup { void TestPackageSelection(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector& txFirst) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_node.mempool->cs); bool TestSequenceLocks(const CTransaction& tx, int flags) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_node.mempool->cs) { - CCoinsViewMemPool viewMempool(&m_node.chainman->ActiveChainstate().CoinsTip(), *m_node.mempool); - return CheckSequenceLocks(m_node.chainman->ActiveChain().Tip(), viewMempool, tx, flags); + CCoinsViewMemPool view_mempool(&m_node.chainman->ActiveChainstate().CoinsTip(), *m_node.mempool); + return CheckSequenceLocks(m_node.chainman->ActiveChain().Tip(), view_mempool, tx, flags); } BlockAssembler AssemblerForTest(const CChainParams& params); }; diff --git a/src/txmempool.cpp b/src/txmempool.cpp index dd8fc092636a2..1dc59fe84ae29 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -728,9 +728,9 @@ void CTxMemPool::removeForReorg(CChainState& active_chainstate, int flags) EXCLU LockPoints lp = it->GetLockPoints(); assert(std::addressof(::ChainstateActive()) == std::addressof(active_chainstate)); bool validLP = TestLockPointValidity(active_chainstate.m_chain, &lp); - CCoinsViewMemPool viewMempool(&active_chainstate.CoinsTip(), *this); + CCoinsViewMemPool view_mempool(&active_chainstate.CoinsTip(), *this); if (!CheckFinalTx(active_chainstate.m_chain.Tip(), tx, flags) - || !CheckSequenceLocks(active_chainstate.m_chain.Tip(), viewMempool, tx, flags, &lp, validLP)) { + || !CheckSequenceLocks(active_chainstate.m_chain.Tip(), view_mempool, tx, flags, &lp, validLP)) { // Note if CheckSequenceLocks fails the LockPoints may still be invalid // So it's critical that we remove the tx and not depend on the LockPoints. txToRemove.insert(it); diff --git a/src/txmempool.h b/src/txmempool.h index 60b756a84f979..cbf5fb8e12d57 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -859,7 +859,8 @@ class CCoinsViewMemPool : public CCoinsViewBacked public: CCoinsViewMemPool(CCoinsView* baseIn, const CTxMemPool& mempoolIn); bool GetCoin(const COutPoint &outpoint, Coin &coin) const override; - /** Add the coins created by this transaction. */ + /** Add the coins created by this transaction. These coins are only temporarily stored in + * m_temp_added and cannot be flushed to the back end. Only used for package validation. */ void PackageAddTransaction(const CTransactionRef& tx); }; diff --git a/src/validation.cpp b/src/validation.cpp index 6a69574627c4b..dc1f5242676df 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -559,7 +559,7 @@ class MemPoolAccept /** * 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. + * dependencies exist. */ PackageMempoolAcceptResult AcceptMultipleTransactions(const std::vector& txns, ATMPArgs& args) EXCLUSIVE_LOCKS_REQUIRED(cs_main); @@ -982,65 +982,15 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std:: { AssertLockHeld(cs_main); + // These context-free package limits can be done before taking the mempool lock. PackageValidationState package_state; - const unsigned int package_count = txns.size(); + if (!CheckPackage(txns, package_state)) return PackageMempoolAcceptResult(package_state, {}); - // 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"); - 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->GetHash(); }); - // 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.hash) != 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->GetHash()); - workspaces.emplace_back(Workspace(tx)); - } - } + workspaces.reserve(txns.size()); + 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); @@ -1127,7 +1077,6 @@ PackageMempoolAcceptResult ProcessNewPackage(CChainState& active_chainstate, CTx 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); } diff --git a/src/validation.h b/src/validation.h index 9fefb34f047aa..0f1b61e170021 100644 --- a/src/validation.h +++ b/src/validation.h @@ -291,10 +291,12 @@ MempoolAcceptResult AcceptToMemoryPool(CChainState& active_chainstate, CTxMemPoo bool bypass_limits, bool test_accept=false) EXCLUSIVE_LOCKS_REQUIRED(cs_main); /** -* Atomically test acceptance of a package. If the package only contains one tx, package rules still apply. +* Atomically test acceptance of a package. If the package only contains one tx, package rules still +* apply. The transaction(s) cannot spend the same inputs as any transaction in the mempool. * @param[in] txns Group of transactions which may be independent or contain -* parent-child dependencies. The transactions must not conflict, i.e. -* must not spend the same inputs. Parents must appear before children. +* parent-child dependencies. The transactions must not conflict +* with each other, i.e., must not spend the same inputs. If any +* dependencies exist, parents must appear before children. * @returns a PackageMempoolAcceptResult which includes a MempoolAcceptResult for each transaction. * If a transaction fails, validation will exit early and some results may be missing. */ @@ -329,9 +331,13 @@ bool TestLockPointValidity(CChain& active_chain, const LockPoints* lp) EXCLUSIVE * Check if transaction will be BIP68 final in the next block to be created on top of tip. * @param[in] tip Chain tip to check tx sequence locks against. For example, * the tip of the current active chain. - * @param[in] coins_view Any CCoinsView that provides access to the relevant coins - * for checking sequence locks. Any CCoinsView can be passed in; - * it is assumed to be consistent with the tip. + * @param[in] coins_view Any CCoinsView that provides access to the relevant coins for + * checking sequence locks. For example, it can be a CCoinsViewCache + * that isn't connected to anything but contains all the relevant + * coins, or a CCoinsViewMemPool that is connected to the + * mempool and chainstate UTXO set. In the latter case, the caller is + * responsible for holding the appropriate locks to ensure that + * calls to GetCoin() return correct coins. * Simulates calling SequenceLocks() with data from the tip passed in. * Optionally stores in LockPoints the resulting height and time calculated and the hash * of the block needed for calculation or skips the calculation and uses the LockPoints