consensus: Use Coin span in GetP2SHSigOpCount

Move the responsibility of retrieving coins from GetP2SHSigOpCount to
its caller.

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 11:09:34 +02:00
parent d91a746815
commit a7e4132623
No known key found for this signature in database
GPG key ID: 9B79B45691DB4173
6 changed files with 56 additions and 12 deletions

View file

@ -160,6 +160,16 @@ const Coin& CCoinsViewCache::AccessCoin(const COutPoint &outpoint) const {
}
}
std::vector<std::reference_wrapper<const Coin>> CCoinsViewCache::AccessCoins(const CTransaction& tx) const
{
std::vector<std::reference_wrapper<const Coin>> coins;
coins.reserve(tx.vin.size());
for (const CTxIn& input : tx.vin) {
coins.emplace_back(AccessCoin(input.prevout));
}
return coins;
}
bool CCoinsViewCache::HaveCoin(const COutPoint &outpoint) const {
CCoinsMap::const_iterator it = FetchCoin(outpoint);
return (it != cacheCoins.end() && !it->second.coin.IsSpent());

View file

@ -415,6 +415,15 @@ public:
*/
const Coin& AccessCoin(const COutPoint &output) const;
/**
* Return a vector of references to Coins in the cache, or coinEmpty if
* the Coin is not found.
*
* Generally, this should only be held for a short scope. The coins should
* generally not be held through any other calls to this cache.
*/
std::vector<std::reference_wrapper<const Coin>> AccessCoins(const CTransaction& tx) const;
/**
* Add a coin. Set possible_overwrite to true if an unspent version may
* already exist in the cache.

View file

@ -14,6 +14,16 @@
#include <util/check.h>
#include <util/moneystr.h>
template <typename T>
static constexpr const Coin& GetCoin(const T& item)
{
if constexpr (std::is_same_v<T, std::reference_wrapper<const Coin>>) {
return item.get();
} else {
return item;
}
}
bool IsFinalTx(const CTransaction &tx, int nBlockHeight, int64_t nBlockTime)
{
if (tx.nLockTime == 0)
@ -123,23 +133,31 @@ unsigned int GetLegacySigOpCount(const CTransaction& tx)
return nSigOps;
}
unsigned int GetP2SHSigOpCount(const CTransaction& tx, const CCoinsViewCache& inputs)
template <typename T>
unsigned int GetP2SHSigOpCount(const CTransaction& tx, const T coins)
{
if (tx.IsCoinBase())
return 0;
unsigned int nSigOps = 0;
for (unsigned int i = 0; i < tx.vin.size(); i++)
{
const Coin& coin = inputs.AccessCoin(tx.vin[i].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());
const CTxOut &prevout = coin.out;
if (prevout.scriptPubKey.IsPayToScriptHash())
nSigOps += prevout.scriptPubKey.GetSigOpCount(tx.vin[i].scriptSig);
nSigOps += prevout.scriptPubKey.GetSigOpCount(input_it->scriptSig);
}
return nSigOps;
}
template unsigned int GetP2SHSigOpCount<std::span<const Coin>>(
const CTransaction& tx, const std::span<const Coin>);
template unsigned int GetP2SHSigOpCount<std::span<std::reference_wrapper<const Coin>>>(
const CTransaction& tx, const std::span<std::reference_wrapper<const Coin>>);
int64_t GetTransactionSigOpCost(const CTransaction& tx, const CCoinsViewCache& inputs, uint32_t flags)
{
int64_t nSigOps = GetLegacySigOpCount(tx) * WITNESS_SCALE_FACTOR;
@ -148,7 +166,8 @@ int64_t GetTransactionSigOpCost(const CTransaction& tx, const CCoinsViewCache& i
return nSigOps;
if (flags & SCRIPT_VERIFY_P2SH) {
nSigOps += GetP2SHSigOpCount(tx, inputs) * WITNESS_SCALE_FACTOR;
auto coins{inputs.AccessCoins(tx)};
nSigOps += GetP2SHSigOpCount(tx, std::span{coins}) * WITNESS_SCALE_FACTOR;
}
for (unsigned int i = 0; i < tx.vin.size(); i++)

View file

@ -5,6 +5,7 @@
#ifndef BITCOIN_CONSENSUS_TX_VERIFY_H
#define BITCOIN_CONSENSUS_TX_VERIFY_H
#include <coins.h>
#include <consensus/amount.h>
#include <stdint.h>
@ -39,11 +40,12 @@ unsigned int GetLegacySigOpCount(const CTransaction& tx);
/**
* Count ECDSA signature operations in pay-to-script-hash inputs.
*
* @param[in] mapInputs Map of previous transactions that have outputs we're spending
* @param[in] coins Sorted span of Coins containing previous transaction outputs we're spending
* @return maximum number of sigops required to validate this transaction's inputs
* @see CTransaction::FetchInputs
*/
unsigned int GetP2SHSigOpCount(const CTransaction& tx, const CCoinsViewCache& mapInputs);
template <typename T>
unsigned int GetP2SHSigOpCount(const CTransaction& tx, const T coins);
/**
* Compute total signature operation cost of a transaction.

View file

@ -266,7 +266,8 @@ FUZZ_TARGET(coins_view, .init = initialize_coins_view)
// consensus/tx_verify.cpp:130: unsigned int GetP2SHSigOpCount(const CTransaction &, const CCoinsViewCache &): Assertion `!coin.IsSpent()' failed.
return;
}
(void)GetP2SHSigOpCount(transaction, coins_view_cache);
auto coins{coins_view_cache.AccessCoins(transaction)};
(void)GetP2SHSigOpCount(transaction, std::span{coins});
},
[&] {
const CTransaction transaction{random_mutable_transaction};

View file

@ -362,7 +362,8 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard)
BOOST_CHECK(::AreInputsStandard(CTransaction(txTo), coins));
// 22 P2SH sigops for all inputs (1 for vin[0], 6 for vin[3], 15 for vin[4]
BOOST_CHECK_EQUAL(GetP2SHSigOpCount(CTransaction(txTo), coins), 22U);
auto coinsTxTo{coins.AccessCoins(CTransaction(txTo))};
BOOST_CHECK_EQUAL(GetP2SHSigOpCount(CTransaction(txTo), std::span{coinsTxTo}), 22U);
CMutableTransaction txToNonStd1;
txToNonStd1.vout.resize(1);
@ -374,7 +375,8 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard)
txToNonStd1.vin[0].scriptSig << std::vector<unsigned char>(sixteenSigops.begin(), sixteenSigops.end());
BOOST_CHECK(!::AreInputsStandard(CTransaction(txToNonStd1), coins));
BOOST_CHECK_EQUAL(GetP2SHSigOpCount(CTransaction(txToNonStd1), coins), 16U);
auto coinsTxToNonStd1{coins.AccessCoins(CTransaction(txToNonStd1))};
BOOST_CHECK_EQUAL(GetP2SHSigOpCount(CTransaction(txToNonStd1), std::span{coinsTxToNonStd1}), 16U);
CMutableTransaction txToNonStd2;
txToNonStd2.vout.resize(1);
@ -386,7 +388,8 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard)
txToNonStd2.vin[0].scriptSig << std::vector<unsigned char>(twentySigops.begin(), twentySigops.end());
BOOST_CHECK(!::AreInputsStandard(CTransaction(txToNonStd2), coins));
BOOST_CHECK_EQUAL(GetP2SHSigOpCount(CTransaction(txToNonStd2), coins), 20U);
auto coinsTxToNonStd2{coins.AccessCoins(CTransaction(txToNonStd2))};
BOOST_CHECK_EQUAL(GetP2SHSigOpCount(CTransaction(txToNonStd2), std::span{coinsTxToNonStd2}), 20U);
}
BOOST_AUTO_TEST_SUITE_END()