consensus: Use Coin span in CheckTxInputs

Move the responsibility of retrieving coins from CheckTxInputs
to its caller. The missing inputs check will be moved in an upcoming
commit.

This is a part of a series of commits for removing access to the
CCoinsViewCache in consensus verification functions. The goal is to
allow calling verification functions with pre-fetched, or a user-defined
set of coins.

Define two explicit template specializations for both a span of
references to coins and a span of coins. This allows using it for both
Coin entries referenced from the CCoinsViewCache, and from contiguous
memory, like the vector in CBlockUndo.
This commit is contained in:
TheCharlatan 2025-04-18 14:59:25 +02:00
parent 781668b6e8
commit c1285a81de
No known key found for this signature in database
GPG key ID: 9B79B45691DB4173
5 changed files with 26 additions and 12 deletions

View file

@ -186,7 +186,8 @@ template int64_t GetTransactionSigOpCost<std::span<const Coin>>(
template int64_t GetTransactionSigOpCost<std::span<std::reference_wrapper<const Coin>>>( template int64_t GetTransactionSigOpCost<std::span<std::reference_wrapper<const Coin>>>(
const CTransaction& tx, const std::span<std::reference_wrapper<const Coin>> coins, uint32_t flags); const CTransaction& tx, const std::span<std::reference_wrapper<const Coin>> coins, uint32_t flags);
bool Consensus::CheckTxInputs(const CTransaction& tx, TxValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmount& txfee) template <typename T>
bool Consensus::CheckTxInputs(const CTransaction& tx, TxValidationState& state, const CCoinsViewCache& inputs, const T coins, int nSpendHeight, CAmount& txfee)
{ {
// are the actual inputs available? // are the actual inputs available?
if (!inputs.HaveInputs(tx)) { if (!inputs.HaveInputs(tx)) {
@ -195,15 +196,16 @@ bool Consensus::CheckTxInputs(const CTransaction& tx, TxValidationState& state,
} }
CAmount nValueIn = 0; CAmount nValueIn = 0;
for (unsigned int i = 0; i < tx.vin.size(); ++i) { Assert(coins.size() == tx.vin.size());
const COutPoint &prevout = tx.vin[i].prevout; auto input_it = tx.vin.begin();
const Coin& coin = inputs.AccessCoin(prevout); for (auto it = coins.begin(); it != coins.end(); ++it, ++input_it) {
const Coin& coin{GetCoin(*it)};
assert(!coin.IsSpent()); assert(!coin.IsSpent());
// If prev is coinbase, check that it's matured // If prev is coinbase, check that it's matured
if (coin.IsCoinBase() && nSpendHeight - coin.nHeight < COINBASE_MATURITY) { if (coin.IsCoinBase() && nSpendHeight - coin.nHeight < COINBASE_MATURITY) {
return state.Invalid(TxValidationResult::TX_PREMATURE_SPEND, "bad-txns-premature-spend-of-coinbase", 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<int>(nSpendHeight - coin.nHeight)));
} }
// Check for negative or overflow input values // Check for negative or overflow input values
@ -228,3 +230,9 @@ bool Consensus::CheckTxInputs(const CTransaction& tx, TxValidationState& state,
txfee = txfee_aux; txfee = txfee_aux;
return true; return true;
} }
template bool Consensus::CheckTxInputs<std::span<const Coin>>(
const CTransaction& tx, TxValidationState& state, const CCoinsViewCache& inputs, const std::span<const Coin> coins, int nSpendHeight, CAmount& txfee);
template bool Consensus::CheckTxInputs<std::span<std::reference_wrapper<const Coin>>>(
const CTransaction& tx, TxValidationState& state, const CCoinsViewCache& inputs, const std::span<std::reference_wrapper<const Coin>> coins, int nSpendHeight, CAmount& txfee);

View file

@ -25,7 +25,8 @@ namespace Consensus {
* @param[out] txfee Set to the transaction fee if successful. * @param[out] txfee Set to the transaction fee if successful.
* Preconditions: tx.IsCoinBase() is false. * Preconditions: tx.IsCoinBase() is false.
*/ */
[[nodiscard]] bool CheckTxInputs(const CTransaction& tx, TxValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmount& txfee); template <typename T>
[[nodiscard]] bool CheckTxInputs(const CTransaction& tx, TxValidationState& state, const CCoinsViewCache& inputs, const T coins, int nSpendHeight, CAmount& txfee);
} // namespace Consensus } // namespace Consensus
/** Auxiliary functions for transaction validation (ideally should not be exposed) */ /** Auxiliary functions for transaction validation (ideally should not be exposed) */

View file

@ -255,7 +255,10 @@ FUZZ_TARGET(coins_view, .init = initialize_coins_view)
// It is not allowed to call CheckTxInputs if CheckTransaction failed // It is not allowed to call CheckTxInputs if CheckTransaction failed
return; return;
} }
if (Consensus::CheckTxInputs(transaction, state, coins_view_cache, fuzzed_data_provider.ConsumeIntegralInRange<int>(0, std::numeric_limits<int>::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<int>(0, std::numeric_limits<int>::max()), tx_fee_out)) {
assert(MoneyRange(tx_fee_out)); assert(MoneyRange(tx_fee_out));
} }
}, },

View file

@ -777,7 +777,8 @@ void CTxMemPool::check(const CCoinsViewCache& active_coins_tip, int64_t spendhei
TxValidationState dummy_state; // Not used. CheckTxInputs() should always pass TxValidationState dummy_state; // Not used. CheckTxInputs() should always pass
CAmount txfee = 0; CAmount txfee = 0;
assert(!tx.IsCoinBase()); 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); for (const auto& input: tx.vin) mempoolDuplicate.SpendCoin(input.prevout);
AddCoins(mempoolDuplicate, tx, std::numeric_limits<int>::max()); AddCoins(mempoolDuplicate, tx, std::numeric_limits<int>::max());
} }

View file

@ -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 // 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 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"); 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); int64_t nSigOpsCost = GetTransactionSigOpCost(tx, std::span{coins}, STANDARD_SCRIPT_VERIFY_FLAGS);
// Keep track of transactions that spend a coinbase, which we re-scan // 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; if (!state.IsValid()) break;
const CTransaction &tx = *(block.vtx[i]); const CTransaction &tx = *(block.vtx[i]);
nInputs += tx.vin.size(); nInputs += tx.vin.size();
if (!tx.IsCoinBase()) if (!tx.IsCoinBase())
{ {
CAmount txfee = 0; CAmount txfee = 0;
TxValidationState tx_state; 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 // Any transaction validation failure in ConnectBlock is a block consensus failure
state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,
tx_state.GetRejectReason(), tx_state.GetRejectReason(),