diff --git a/src/consensus/tx_verify.cpp b/src/consensus/tx_verify.cpp index 3919a11b8b7..fbc7479683a 100644 --- a/src/consensus/tx_verify.cpp +++ b/src/consensus/tx_verify.cpp @@ -186,7 +186,8 @@ template int64_t GetTransactionSigOpCost>( template int64_t GetTransactionSigOpCost>>( const CTransaction& tx, const std::span> coins, uint32_t flags); -bool Consensus::CheckTxInputs(const CTransaction& tx, TxValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmount& txfee) +template +bool Consensus::CheckTxInputs(const CTransaction& tx, TxValidationState& state, const CCoinsViewCache& inputs, const T coins, int nSpendHeight, CAmount& txfee) { // are the actual inputs available? if (!inputs.HaveInputs(tx)) { @@ -195,15 +196,16 @@ bool Consensus::CheckTxInputs(const CTransaction& tx, TxValidationState& state, } CAmount nValueIn = 0; - for (unsigned int i = 0; i < tx.vin.size(); ++i) { - const COutPoint &prevout = tx.vin[i].prevout; - const Coin& coin = inputs.AccessCoin(prevout); + Assert(coins.size() == tx.vin.size()); + auto input_it = tx.vin.begin(); + for (auto it = coins.begin(); it != coins.end(); ++it, ++input_it) { + const Coin& coin{GetCoin(*it)}; assert(!coin.IsSpent()); // If prev is coinbase, check that it's matured if (coin.IsCoinBase() && nSpendHeight - coin.nHeight < COINBASE_MATURITY) { return state.Invalid(TxValidationResult::TX_PREMATURE_SPEND, "bad-txns-premature-spend-of-coinbase", - strprintf("tried to spend coinbase at depth %d", nSpendHeight - coin.nHeight)); + strprintf("tried to spend coinbase at depth %d", static_cast(nSpendHeight - coin.nHeight))); } // Check for negative or overflow input values @@ -228,3 +230,9 @@ bool Consensus::CheckTxInputs(const CTransaction& tx, TxValidationState& state, txfee = txfee_aux; return true; } + +template bool Consensus::CheckTxInputs>( + const CTransaction& tx, TxValidationState& state, const CCoinsViewCache& inputs, const std::span coins, int nSpendHeight, CAmount& txfee); + +template bool Consensus::CheckTxInputs>>( + const CTransaction& tx, TxValidationState& state, const CCoinsViewCache& inputs, const std::span> coins, int nSpendHeight, CAmount& txfee); diff --git a/src/consensus/tx_verify.h b/src/consensus/tx_verify.h index 5c9dae5a425..0333045aefa 100644 --- a/src/consensus/tx_verify.h +++ b/src/consensus/tx_verify.h @@ -25,7 +25,8 @@ namespace Consensus { * @param[out] txfee Set to the transaction fee if successful. * Preconditions: tx.IsCoinBase() is false. */ -[[nodiscard]] bool CheckTxInputs(const CTransaction& tx, TxValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmount& txfee); +template +[[nodiscard]] bool CheckTxInputs(const CTransaction& tx, TxValidationState& state, const CCoinsViewCache& inputs, const T coins, int nSpendHeight, CAmount& txfee); } // namespace Consensus /** Auxiliary functions for transaction validation (ideally should not be exposed) */ diff --git a/src/test/fuzz/coins_view.cpp b/src/test/fuzz/coins_view.cpp index ef5fbddba5d..e5e5fb45369 100644 --- a/src/test/fuzz/coins_view.cpp +++ b/src/test/fuzz/coins_view.cpp @@ -255,7 +255,10 @@ FUZZ_TARGET(coins_view, .init = initialize_coins_view) // It is not allowed to call CheckTxInputs if CheckTransaction failed return; } - if (Consensus::CheckTxInputs(transaction, state, coins_view_cache, fuzzed_data_provider.ConsumeIntegralInRange(0, std::numeric_limits::max()), tx_fee_out)) { + // are the actual inputs available? + if (!coins_view_cache.HaveInputs(transaction)) return; + auto coins{coins_view_cache.AccessCoins(transaction)}; + if (Consensus::CheckTxInputs(transaction, state, coins_view_cache, std::span{coins}, fuzzed_data_provider.ConsumeIntegralInRange(0, std::numeric_limits::max()), tx_fee_out)) { assert(MoneyRange(tx_fee_out)); } }, diff --git a/src/txmempool.cpp b/src/txmempool.cpp index 3a5a3fb306d..f100621fd19 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -777,7 +777,8 @@ void CTxMemPool::check(const CCoinsViewCache& active_coins_tip, int64_t spendhei TxValidationState dummy_state; // Not used. CheckTxInputs() should always pass CAmount txfee = 0; assert(!tx.IsCoinBase()); - assert(Consensus::CheckTxInputs(tx, dummy_state, mempoolDuplicate, spendheight, txfee)); + auto coins{mempoolDuplicate.AccessCoins(tx)}; + assert(Consensus::CheckTxInputs(tx, dummy_state, mempoolDuplicate, std::span{coins}, spendheight, txfee)); for (const auto& input: tx.vin) mempoolDuplicate.SpendCoin(input.prevout); AddCoins(mempoolDuplicate, tx, std::numeric_limits::max()); } diff --git a/src/validation.cpp b/src/validation.cpp index 1e6e9d90363..532c445f042 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -874,7 +874,8 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) } // The mempool holds txs for the next block, so pass height+1 to CheckTxInputs - if (!Consensus::CheckTxInputs(tx, state, m_view, m_active_chainstate.m_chain.Height() + 1, ws.m_base_fees)) { + auto coins{m_view.AccessCoins(tx)}; + if (!Consensus::CheckTxInputs(tx, state, m_view, std::span{coins}, m_active_chainstate.m_chain.Height() + 1, ws.m_base_fees)) { return false; // state filled in by CheckTxInputs } @@ -887,7 +888,6 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) return state.Invalid(TxValidationResult::TX_WITNESS_MUTATED, "bad-witness-nonstandard"); } - auto coins{m_view.AccessCoins(tx)}; int64_t nSigOpsCost = GetTransactionSigOpCost(tx, std::span{coins}, STANDARD_SCRIPT_VERIFY_FLAGS); // Keep track of transactions that spend a coinbase, which we re-scan @@ -2648,14 +2648,15 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state, { if (!state.IsValid()) break; const CTransaction &tx = *(block.vtx[i]); - nInputs += tx.vin.size(); if (!tx.IsCoinBase()) { CAmount txfee = 0; TxValidationState tx_state; - if (!Consensus::CheckTxInputs(tx, tx_state, view, pindex->nHeight, txfee)) { + + auto coins{view.AccessCoins(tx)}; + if (!Consensus::CheckTxInputs(tx, tx_state, view, std::span{coins}, pindex->nHeight, txfee)) { // Any transaction validation failure in ConnectBlock is a block consensus failure state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, tx_state.GetRejectReason(),