From 8e5fc7507b7cb4d606b7b6a8df710d3a6c89e1ae Mon Sep 17 00:00:00 2001 From: TheCharlatan Date: Fri, 18 Apr 2025 11:09:34 +0200 Subject: [PATCH 1/9] 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. --- src/coins.h | 15 +++++++++++++++ src/consensus/tx_verify.cpp | 22 ++++++++++++++++------ src/consensus/tx_verify.h | 12 ++++++++++-- src/test/fuzz/coins_view.cpp | 2 +- src/test/script_p2sh_tests.cpp | 14 +++++++++----- src/test/transaction_tests.cpp | 8 +++++--- 6 files changed, 56 insertions(+), 17 deletions(-) diff --git a/src/coins.h b/src/coins.h index ae7f34f46581..c005b2f6fea6 100644 --- a/src/coins.h +++ b/src/coins.h @@ -458,6 +458,21 @@ class CCoinsViewCache : public CCoinsViewBacked */ const Coin& AccessCoin(const COutPoint &output) const; + /** + * Retrieves a vector of references to Coins in the cache in order they get + * consumed by the tx's inputs, and passes it to the invoked callback. + */ + template + auto AccessCoins(const CTransaction& tx, Callable&& callback) const + { + std::vector> coins; + coins.reserve(tx.vin.size()); + for (const CTxIn& input : tx.vin) { + coins.emplace_back(AccessCoin(input.prevout)); + } + return callback(std::span{coins}); + } + /** * Add a coin. Set possible_overwrite to true if an unspent version may * already exist in the cache. diff --git a/src/consensus/tx_verify.cpp b/src/consensus/tx_verify.cpp index 4efed70fd411..140c4b522dc0 100644 --- a/src/consensus/tx_verify.cpp +++ b/src/consensus/tx_verify.cpp @@ -14,6 +14,8 @@ #include #include +#include + bool IsFinalTx(const CTransaction &tx, int nBlockHeight, int64_t nBlockTime) { if (tx.nLockTime == 0) @@ -123,23 +125,31 @@ unsigned int GetLegacySigOpCount(const CTransaction& tx) return nSigOps; } -unsigned int GetP2SHSigOpCount(const CTransaction& tx, const CCoinsViewCache& inputs) +template +unsigned int GetP2SHSigOpCount(const CTransaction& tx, const std::span 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 = *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( + const CTransaction& tx, const std::span); + +template unsigned int GetP2SHSigOpCount>( + const CTransaction& tx, const std::span>); + int64_t GetTransactionSigOpCost(const CTransaction& tx, const CCoinsViewCache& inputs, script_verify_flags flags) { int64_t nSigOps = GetLegacySigOpCount(tx) * WITNESS_SCALE_FACTOR; @@ -148,7 +158,7 @@ int64_t GetTransactionSigOpCost(const CTransaction& tx, const CCoinsViewCache& i return nSigOps; if (flags & SCRIPT_VERIFY_P2SH) { - nSigOps += GetP2SHSigOpCount(tx, inputs) * WITNESS_SCALE_FACTOR; + nSigOps += inputs.AccessCoins(tx, [&tx](auto&& coins) { return GetP2SHSigOpCount(tx, coins); }) * WITNESS_SCALE_FACTOR; } for (unsigned int i = 0; i < tx.vin.size(); i++) diff --git a/src/consensus/tx_verify.h b/src/consensus/tx_verify.h index ed44d435c1b9..48cad6730bb1 100644 --- a/src/consensus/tx_verify.h +++ b/src/consensus/tx_verify.h @@ -5,10 +5,13 @@ #ifndef BITCOIN_CONSENSUS_TX_VERIFY_H #define BITCOIN_CONSENSUS_TX_VERIFY_H +#include #include #include