diff options
Diffstat (limited to 'src/validation.cpp')
-rw-r--r-- | src/validation.cpp | 224 |
1 files changed, 178 insertions, 46 deletions
diff --git a/src/validation.cpp b/src/validation.cpp index f591e64fd4..5e3d429c2e 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -42,6 +42,7 @@ #include <uint256.h> #include <undo.h> #include <util/check.h> // For NDEBUG compile time check +#include <util/hasher.h> #include <util/moneystr.h> #include <util/rbf.h> #include <util/strencodings.h> @@ -50,6 +51,7 @@ #include <validationinterface.h> #include <warnings.h> +#include <numeric> #include <optional> #include <string> @@ -245,18 +247,13 @@ bool TestLockPointValidity(CChain& active_chain, const LockPoints* lp) return true; } -bool CheckSequenceLocks(CChainState& active_chainstate, - const CTxMemPool& pool, +bool CheckSequenceLocks(CBlockIndex* tip, + const CCoinsView& coins_view, const CTransaction& tx, int flags, LockPoints* lp, bool useExistingLockPoints) { - AssertLockHeld(cs_main); - AssertLockHeld(pool.cs); - assert(std::addressof(::ChainstateActive()) == std::addressof(active_chainstate)); - - CBlockIndex* tip = active_chainstate.m_chain.Tip(); assert(tip != nullptr); CBlockIndex index; @@ -276,14 +273,12 @@ bool CheckSequenceLocks(CChainState& active_chainstate, lockPair.second = lp->time; } else { - // CoinsTip() contains the UTXO set for active_chainstate.m_chain.Tip() - CCoinsViewMemPool viewMemPool(&active_chainstate.CoinsTip(), pool); std::vector<int> prevheights; prevheights.resize(tx.vin.size()); for (size_t txinIndex = 0; txinIndex < tx.vin.size(); txinIndex++) { const CTxIn& txin = tx.vin[txinIndex]; Coin coin; - if (!viewMemPool.GetCoin(txin.prevout, coin)) { + if (!coins_view.GetCoin(txin.prevout, coin)) { return error("%s: Missing input", __func__); } if (coin.nHeight == MEMPOOL_HEIGHT) { @@ -477,11 +472,20 @@ public: */ std::vector<COutPoint>& m_coins_to_uncache; const bool m_test_accept; + /** Disable BIP125 RBFing; disallow all conflicts with mempool transactions. */ + const bool disallow_mempool_conflicts; }; // 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<CTransactionRef>& 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. @@ -625,10 +629,13 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) // is for the sake of multi-party protocols, where we don't // want a single party to be able to disable replacement. // - // The opt-out ignores descendants as anyone relying on - // first-seen mempool behavior should be checking all - // unconfirmed ancestors anyway; doing otherwise is hopelessly - // insecure. + // Transactions that don't explicitly signal replaceability are + // *not* replaceable with the current logic, even if one of their + // unconfirmed ancestors signals replaceability. This diverges + // from BIP125's inherited signaling description (see CVE-2021-31876). + // Applications relying on first-seen mempool behavior should + // check all unconfirmed ancestors; otherwise an opt-in ancestor + // might be replaced, causing removal of this descendant. bool fReplacementOptOut = true; for (const CTxIn &_txin : ptxConflicting->vin) { @@ -638,7 +645,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) break; } } - if (fReplacementOptOut) { + if (fReplacementOptOut || args.disallow_mempool_conflicts) { return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "txn-mempool-conflict"); } @@ -686,10 +693,10 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) // Only accept BIP68 sequence locked transactions that can be mined in the next // block; we don't want our mempool filled up with transactions that can't // be mined yet. - // Must keep pool.cs for this unless we change CheckSequenceLocks to take a - // CoinsViewCache instead of create its own + // Pass in m_view which has all of the relevant inputs cached. Note that, since m_view's + // backend was removed, it no longer pulls coins from the mempool. assert(std::addressof(::ChainstateActive()) == std::addressof(m_active_chainstate)); - if (!CheckSequenceLocks(m_active_chainstate, m_pool, tx, STANDARD_LOCKTIME_VERIFY_FLAGS, &lp)) + if (!CheckSequenceLocks(m_active_chainstate.m_chain.Tip(), m_view, tx, STANDARD_LOCKTIME_VERIFY_FLAGS, &lp)) return state.Invalid(TxValidationResult::TX_PREMATURE_SPEND, "non-BIP68-final"); assert(std::addressof(g_chainman.m_blockman) == std::addressof(m_active_chainstate.m_blockman)); @@ -1045,7 +1052,7 @@ MempoolAcceptResult MemPoolAccept::AcceptSingleTransaction(const CTransactionRef Workspace ws(ptx); - if (!PreChecks(args, ws)) return MempoolAcceptResult(ws.m_state); + if (!PreChecks(args, ws)) return MempoolAcceptResult::Failure(ws.m_state); // Only compute the precomputed transaction data if we need to verify // scripts (ie, other policy checks pass). We perform the inexpensive @@ -1053,20 +1060,121 @@ MempoolAcceptResult MemPoolAccept::AcceptSingleTransaction(const CTransactionRef // checks pass, to mitigate CPU exhaustion denial-of-service attacks. PrecomputedTransactionData txdata; - if (!PolicyScriptChecks(args, ws, txdata)) return MempoolAcceptResult(ws.m_state); + if (!PolicyScriptChecks(args, ws, txdata)) return MempoolAcceptResult::Failure(ws.m_state); - if (!ConsensusScriptChecks(args, ws, txdata)) return MempoolAcceptResult(ws.m_state); + if (!ConsensusScriptChecks(args, ws, txdata)) return MempoolAcceptResult::Failure(ws.m_state); // Tx was accepted, but not added if (args.m_test_accept) { - return MempoolAcceptResult(std::move(ws.m_replaced_transactions), ws.m_base_fees); + return MempoolAcceptResult::Success(std::move(ws.m_replaced_transactions), ws.m_base_fees); } - if (!Finalize(args, ws)) return MempoolAcceptResult(ws.m_state); + if (!Finalize(args, ws)) return MempoolAcceptResult::Failure(ws.m_state); GetMainSignals().TransactionAddedToMempool(ptx, m_pool.GetAndIncrementSequence()); - return MempoolAcceptResult(std::move(ws.m_replaced_transactions), ws.m_base_fees); + return MempoolAcceptResult::Success(std::move(ws.m_replaced_transactions), ws.m_base_fees); +} + +PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std::vector<CTransactionRef>& txns, ATMPArgs& args) +{ + AssertLockHeld(cs_main); + + PackageValidationState package_state; + const unsigned int 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"); + return PackageMempoolAcceptResult(package_state, {}); + } + + // Construct workspaces and check package policies. + std::vector<Workspace> workspaces{}; + workspaces.reserve(package_count); + { + std::unordered_set<uint256, SaltedTxidHasher> 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)); + } + } + std::map<const uint256, const MempoolAcceptResult> results; + { + // Don't allow any conflicting transactions, i.e. spending the same inputs, in a package. + std::unordered_set<COutPoint, SaltedOutpointHasher> 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 @@ -1079,7 +1187,8 @@ static MempoolAcceptResult AcceptToMemoryPoolWithTime(const CChainParams& chainp EXCLUSIVE_LOCKS_REQUIRED(cs_main) { std::vector<COutPoint> coins_to_uncache; - MemPoolAccept::ATMPArgs args { chainparams, nAcceptTime, bypass_limits, coins_to_uncache, test_accept }; + MemPoolAccept::ATMPArgs args { chainparams, nAcceptTime, bypass_limits, coins_to_uncache, + test_accept, /* disallow_mempool_conflicts */ false }; assert(std::addressof(::ChainstateActive()) == std::addressof(active_chainstate)); const MempoolAcceptResult result = MemPoolAccept(pool, active_chainstate).AcceptSingleTransaction(tx, args); @@ -1105,6 +1214,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<COutPoint> 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); @@ -1272,8 +1404,9 @@ void CChainState::InvalidChainFound(CBlockIndex* pindexNew) } // Same as InvalidChainFound, above, except not called directly from InvalidateBlock, -// which does its own setBlockIndexCandidates manageent. -void CChainState::InvalidBlockFound(CBlockIndex *pindex, const BlockValidationState &state) { +// which does its own setBlockIndexCandidates management. +void CChainState::InvalidBlockFound(CBlockIndex* pindex, const BlockValidationState& state) +{ if (state.GetResult() != BlockValidationResult::BLOCK_MUTATED) { pindex->nStatus |= BLOCK_FAILED_VALID; m_blockman.m_failed_blocks.insert(pindex); @@ -1690,8 +1823,8 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, // may have let in a block that violates the rule prior to updating the // software, and we would NOT be enforcing the rule here. Fully solving // upgrade from one software version to the next after a consensus rule - // change is potentially tricky and issue-specific (see RewindBlockIndex() - // for one general approach that was used for BIP 141 deployment). + // change is potentially tricky and issue-specific (see NeedsRedownload() + // for one approach that was used for BIP 141 deployment). // Also, currently the rule against blocks more than 2 hours in the future // is enforced in ContextualCheckBlockHeader(); we wouldn't want to // re-enforce that rule here (at least until we make it impossible for @@ -3462,29 +3595,32 @@ bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, Block return true; } -bool ChainstateManager::ProcessNewBlock(const CChainParams& chainparams, const std::shared_ptr<const CBlock> pblock, bool fForceProcessing, bool* fNewBlock) +bool ChainstateManager::ProcessNewBlock(const CChainParams& chainparams, const std::shared_ptr<const CBlock>& block, bool force_processing, bool* new_block) { AssertLockNotHeld(cs_main); assert(std::addressof(::ChainstateActive()) == std::addressof(ActiveChainstate())); { CBlockIndex *pindex = nullptr; - if (fNewBlock) *fNewBlock = false; + if (new_block) *new_block = false; BlockValidationState state; // CheckBlock() does not support multi-threaded block validation because CBlock::fChecked can cause data race. // Therefore, the following critical section must include the CheckBlock() call as well. LOCK(cs_main); - // Ensure that CheckBlock() passes before calling AcceptBlock, as - // belt-and-suspenders. - bool ret = CheckBlock(*pblock, state, chainparams.GetConsensus()); + // Skipping AcceptBlock() for CheckBlock() failures means that we will never mark a block as invalid if + // CheckBlock() fails. This is protective against consensus failure if there are any unknown forms of block + // malleability that cause CheckBlock() to fail; see e.g. CVE-2012-2459 and + // https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2019-February/016697.html. Because CheckBlock() is + // not very expensive, the anti-DoS benefits of caching failure (of a definitely-invalid block) are not substantial. + bool ret = CheckBlock(*block, state, chainparams.GetConsensus()); if (ret) { // Store to disk - ret = ActiveChainstate().AcceptBlock(pblock, state, chainparams, &pindex, fForceProcessing, nullptr, fNewBlock); + ret = ActiveChainstate().AcceptBlock(block, state, chainparams, &pindex, force_processing, nullptr, new_block); } if (!ret) { - GetMainSignals().BlockChecked(*pblock, state); + GetMainSignals().BlockChecked(*block, state); return error("%s: AcceptBlock FAILED (%s)", __func__, state.ToString()); } } @@ -3492,7 +3628,7 @@ bool ChainstateManager::ProcessNewBlock(const CChainParams& chainparams, const s NotifyHeaderTip(ActiveChainstate()); BlockValidationState state; // Only used to report errors, not invalidity - ignore it - if (!ActiveChainstate().ActivateBestChain(state, chainparams, pblock)) + if (!ActiveChainstate().ActivateBestChain(state, chainparams, block)) return error("%s: ActivateBestChain failed (%s)", __func__, state.ToString()); return true; @@ -4950,24 +5086,20 @@ bool ChainstateManager::PopulateAndValidateSnapshot( LOCK(::cs_main); // Fake various pieces of CBlockIndex state: - // - // - nChainTx: so that we accurately report IBD-to-tip progress - // - nTx: so that LoadBlockIndex() loads assumed-valid CBlockIndex entries - // (among other things) - // - nStatus & BLOCK_OPT_WITNESS: so that RewindBlockIndex() doesn't zealously - // unwind the assumed-valid chain. - // CBlockIndex* index = nullptr; for (int i = 0; i <= snapshot_chainstate.m_chain.Height(); ++i) { index = snapshot_chainstate.m_chain[i]; + // Fake nTx so that LoadBlockIndex() loads assumed-valid CBlockIndex + // entries (among other things) if (!index->nTx) { index->nTx = 1; } + // Fake nChainTx so that GuessVerificationProgress reports accurately index->nChainTx = index->pprev ? index->pprev->nChainTx + index->nTx : 1; - // We need to fake this flag so that CChainState::RewindBlockIndex() - // won't try to rewind the entire assumed-valid chain on startup. + // Fake BLOCK_OPT_WITNESS so that CChainState::NeedsRedownload() + // won't ask to rewind the entire assumed-valid chain on startup. if (index->pprev && ::IsWitnessEnabled(index->pprev, ::Params().GetConsensus())) { index->nStatus |= BLOCK_OPT_WITNESS; } |