Compare commits

...

9 commits

Author SHA1 Message Date
Reproducibility Matters
daaaa1184e
Merge 76a8f22c5c into 3a29ba33dc 2025-04-28 18:13:02 +02:00
TheCharlatan
76a8f22c5c
doc: Add docstrings for ConnectBlock and SpendBlock 2025-04-21 21:50:03 +02:00
TheCharlatan
8221e89ac4
validation: Move coin existence and spend check to SpendBlock
Move the remaining UTXO-related operations from ConnectBlock to
SpendBlock. This includes moving the existence check, the UpdateCoins
call, and CBlockUndo generation.

ConnectBlock now takes a pre-populated CBlockUndo as an argument and no
longer accesses or manipulates the UTXO set.
2025-04-21 21:47:54 +02:00
TheCharlatan
e1f88913b7
validation: Move SetBestBlock out of ConnectBlock
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.
2025-04-21 21:18:21 +02:00
TheCharlatan
008748c3ae
validation: Add SpendBlock function
Move the BIP30 checks from ConnectBlock to a new SpendBlock method.
This is a move-only change, more content to SpendBlock is added in the
next commits. The goal is to move logic requiring access to the
CCoinsViewCache out of ConnectBlock and to the new SpendBlock method.
SpendBlock will in future handle all UTXO set interactions that
previously took place in ConnectBlock.

By returning early in case of a BIP30 validation failure, an
additional failure check for an invalid block reward in case of its
failure is left out.

Callers of ConnectBlock now also need to call SpendBlock before. This is
enforced in a future commit by adding a CBlockUndo argument that needs
to be passed to both.

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.
2025-04-21 21:18:20 +02:00
TheCharlatan
1eb575214d
validation: Use vector of outputs instead of CCoinsViewCache in CheckInputScripts
Move the responsibility of retrieving coins from the CCoinsViewCache
in CheckInputScripts to its caller.

Add a helper method in CCoinsViewCache to collect all Coin's outputs
spent by a transaction's inputs.

Callers of CCoinsViewCache are updated to either pre-fetch the spent
outputs, or pass in an empty vector if the precomputed transaction data
has already been initialized with the required outputs.

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.
2025-04-21 21:18:18 +02:00
TheCharlatan
c1285a81de
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.
2025-04-21 21:18:14 +02:00
TheCharlatan
781668b6e8
consensus: Use Coin span in GetTransactionSigOpCost
Move the responsibility of retrieving coins from GetTransactionSigOpCost
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.
2025-04-21 18:14:24 +02:00
TheCharlatan
a7e4132623
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.
2025-04-21 18:14:22 +02:00
14 changed files with 422 additions and 195 deletions

View file

@ -9,6 +9,7 @@
#include <script/interpreter.h>
#include <sync.h>
#include <test/util/setup_common.h>
#include <undo.h>
#include <validation.h>
#include <cassert>
@ -100,7 +101,10 @@ void BenchmarkConnectBlock(benchmark::Bench& bench, std::vector<CKey>& keys, std
auto* pindex{chainman->m_blockman.AddToBlockIndex(test_block, chainman->m_best_header)}; // Doing this here doesn't impact the benchmark
CCoinsViewCache viewNew{&chainstate.CoinsTip()};
assert(chainstate.ConnectBlock(test_block, test_block_state, pindex, viewNew));
CBlockUndo blockundo;
const auto block_hash{test_block.GetHash()};
assert(chainstate.SpendBlock(test_block, pindex, block_hash, viewNew, test_block_state, blockundo));
assert(chainstate.ConnectBlock(test_block, block_hash, blockundo, test_block_state, pindex));
});
}

View file

@ -160,6 +160,29 @@ 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;
}
std::vector<CTxOut> CCoinsViewCache::GetUnspentOutputs(const CTransaction& tx) const
{
std::vector<CTxOut> spent_outputs;
spent_outputs.reserve(tx.vin.size());
for (const auto& txin : tx.vin) {
const COutPoint& prevout = txin.prevout;
const Coin& coin = AccessCoin(prevout);
assert(!coin.IsSpent());
spent_outputs.emplace_back(coin.out);
}
return spent_outputs;
}
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,22 @@ 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;
/**
* Return a vector of unspent outputs of coins in the cache that are spent
* by the provided transaction. The coins they belong to must be unspent in
* the cache.
*/
std::vector<CTxOut> GetUnspentOutputs(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,24 +133,33 @@ 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;
}
int64_t GetTransactionSigOpCost(const CTransaction& tx, const CCoinsViewCache& inputs, uint32_t flags)
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>>);
template <typename T>
int64_t GetTransactionSigOpCost(const CTransaction& tx, const T coins, uint32_t flags)
{
int64_t nSigOps = GetLegacySigOpCount(tx) * WITNESS_SCALE_FACTOR;
@ -148,37 +167,39 @@ int64_t GetTransactionSigOpCost(const CTransaction& tx, const CCoinsViewCache& i
return nSigOps;
if (flags & SCRIPT_VERIFY_P2SH) {
nSigOps += GetP2SHSigOpCount(tx, inputs) * WITNESS_SCALE_FACTOR;
nSigOps += GetP2SHSigOpCount(tx, coins) * WITNESS_SCALE_FACTOR;
}
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;
nSigOps += CountWitnessSigOps(tx.vin[i].scriptSig, prevout.scriptPubKey, &tx.vin[i].scriptWitness, flags);
nSigOps += CountWitnessSigOps(input_it->scriptSig, prevout.scriptPubKey, &input_it->scriptWitness, flags);
}
return nSigOps;
}
template int64_t GetTransactionSigOpCost<std::span<const Coin>>(
const CTransaction& tx, std::span<const Coin> coins, uint32_t flags);
bool Consensus::CheckTxInputs(const CTransaction& tx, TxValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmount& txfee)
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);
template <typename T>
bool Consensus::CheckTxInputs(const CTransaction& tx, TxValidationState& state, const T coins, int nSpendHeight, CAmount& txfee)
{
// are the actual inputs available?
if (!inputs.HaveInputs(tx)) {
return state.Invalid(TxValidationResult::TX_MISSING_INPUTS, "bad-txns-inputs-missingorspent",
strprintf("%s: inputs missing/spent", __func__));
}
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<int>(nSpendHeight - coin.nHeight)));
}
// Check for negative or overflow input values
@ -203,3 +224,9 @@ bool Consensus::CheckTxInputs(const CTransaction& tx, TxValidationState& state,
txfee = txfee_aux;
return true;
}
template bool Consensus::CheckTxInputs<std::span<const Coin>>(
const CTransaction& tx, TxValidationState& state, 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 std::span<std::reference_wrapper<const Coin>> coins, int nSpendHeight, CAmount& txfee);

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>
@ -24,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 <typename T>
[[nodiscard]] bool CheckTxInputs(const CTransaction& tx, TxValidationState& state, const T coins, int nSpendHeight, CAmount& txfee);
} // namespace Consensus
/** Auxiliary functions for transaction validation (ideally should not be exposed) */
@ -39,20 +41,22 @@ 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.
* @param[in] tx Transaction for which we are computing the cost
* @param[in] inputs Map of previous transactions that have outputs we're spending
* @param[in] tx Transaction for which we are computing the cost
* @param[in] coins Sorted span of Coins containing previous transaction outputs we're spending
* @param[in] flags Script verification flags
* @return Total signature operation cost of tx
*/
int64_t GetTransactionSigOpCost(const CTransaction& tx, const CCoinsViewCache& inputs, uint32_t flags);
template <typename T>
int64_t GetTransactionSigOpCost(const CTransaction& tx, const T coins, uint32_t flags);
/**
* Check if transaction is final and can be included in a block with the

View file

@ -137,7 +137,8 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx)
if (success) {
CTransaction ctx = CTransaction(mtx);
size_t size(GetVirtualTransactionSize(ctx, GetTransactionSigOpCost(ctx, view, STANDARD_SCRIPT_VERIFY_FLAGS), ::nBytesPerSigOp));
auto coins{view.AccessCoins(ctx)};
size_t size(GetVirtualTransactionSize(ctx, GetTransactionSigOpCost(ctx, std::span{coins}, STANDARD_SCRIPT_VERIFY_FLAGS), ::nBytesPerSigOp));
result.estimated_vsize = size;
// Estimate fee rate
CFeeRate feerate(fee, size);

View file

@ -9,6 +9,7 @@
#include <test/util/index.h>
#include <test/util/setup_common.h>
#include <test/util/validation.h>
#include <undo.h>
#include <validation.h>
#include <boost/test/unit_test.hpp>
@ -100,7 +101,10 @@ BOOST_FIXTURE_TEST_CASE(coinstatsindex_unclean_shutdown, TestChain100Setup)
BOOST_CHECK(CheckBlock(block, state, params.GetConsensus()));
BOOST_CHECK(m_node.chainman->AcceptBlock(new_block, state, &new_block_index, true, nullptr, nullptr, true));
CCoinsViewCache view(&chainstate.CoinsTip());
BOOST_CHECK(chainstate.ConnectBlock(block, state, new_block_index, view));
CBlockUndo blockundo;
const auto block_hash{block.GetHash()};
BOOST_CHECK(chainstate.SpendBlock(block, new_block_index, block_hash, view, state, blockundo));
BOOST_CHECK(chainstate.ConnectBlock(block, block_hash, blockundo, state, new_block_index));
}
// Send block connected notification, then stop the index without
// sending a chainstate flushed notification. Prior to #24138, this

View file

@ -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<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, std::span{coins}, fuzzed_data_provider.ConsumeIntegralInRange<int>(0, std::numeric_limits<int>::max()), tx_fee_out)) {
assert(MoneyRange(tx_fee_out));
}
},
@ -266,7 +269,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};
@ -281,7 +285,8 @@ FUZZ_TARGET(coins_view, .init = initialize_coins_view)
// script/interpreter.cpp:1705: size_t CountWitnessSigOps(const CScript &, const CScript &, const CScriptWitness *, unsigned int): Assertion `(flags & SCRIPT_VERIFY_P2SH) != 0' failed.
return;
}
(void)GetTransactionSigOpCost(transaction, coins_view_cache, flags);
auto coins{coins_view_cache.AccessCoins(transaction)};
(void)GetTransactionSigOpCost(transaction, std::span{coins}, flags);
},
[&] {
(void)IsWitnessStandard(CTransaction{random_mutable_transaction}, coins_view_cache);

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()

View file

@ -131,13 +131,15 @@ BOOST_AUTO_TEST_CASE(GetTxSigOpCost)
CScript scriptSig = CScript() << OP_0 << OP_0;
BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, CScriptWitness());
auto spending_coins{coins.AccessCoins(CTransaction(spendingTx))};
// Legacy counting only includes signature operations in scriptSigs and scriptPubKeys
// of a transaction and does not take the actual executed sig operations into account.
// spendingTx in itself does not contain a signature operation.
assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 0);
assert(GetTransactionSigOpCost(CTransaction(spendingTx), std::span{spending_coins}, flags) == 0);
// creationTx contains two signature operations in its scriptPubKey, but legacy counting
// is not accurate.
assert(GetTransactionSigOpCost(CTransaction(creationTx), coins, flags) == MAX_PUBKEYS_PER_MULTISIG * WITNESS_SCALE_FACTOR);
auto creation_coins{coins.AccessCoins(CTransaction(creationTx))};
assert(GetTransactionSigOpCost(CTransaction(creationTx), std::span{creation_coins}, flags) == MAX_PUBKEYS_PER_MULTISIG * WITNESS_SCALE_FACTOR);
// Sanity check: script verification fails because of an invalid signature.
assert(VerifyWithFlag(CTransaction(creationTx), spendingTx, flags) == SCRIPT_ERR_CHECKMULTISIGVERIFY);
}
@ -149,7 +151,8 @@ BOOST_AUTO_TEST_CASE(GetTxSigOpCost)
CScript scriptSig = CScript() << OP_0 << OP_0 << ToByteVector(redeemScript);
BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, CScriptWitness());
assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 2 * WITNESS_SCALE_FACTOR);
auto spending_coins{coins.AccessCoins(CTransaction(spendingTx))};
assert(GetTransactionSigOpCost(CTransaction(spendingTx), std::span{spending_coins}, flags) == 2 * WITNESS_SCALE_FACTOR);
assert(VerifyWithFlag(CTransaction(creationTx), spendingTx, flags) == SCRIPT_ERR_CHECKMULTISIGVERIFY);
}
@ -163,22 +166,25 @@ BOOST_AUTO_TEST_CASE(GetTxSigOpCost)
BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, scriptWitness);
assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 1);
auto spending_coins{coins.AccessCoins(CTransaction(spendingTx))};
assert(GetTransactionSigOpCost(CTransaction(spendingTx), std::span{spending_coins}, flags) == 1);
// No signature operations if we don't verify the witness.
assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags & ~SCRIPT_VERIFY_WITNESS) == 0);
assert(GetTransactionSigOpCost(CTransaction(spendingTx), std::span{spending_coins}, flags & ~SCRIPT_VERIFY_WITNESS) == 0);
assert(VerifyWithFlag(CTransaction(creationTx), spendingTx, flags) == SCRIPT_ERR_EQUALVERIFY);
// The sig op cost for witness version != 0 is zero.
assert(scriptPubKey[0] == 0x00);
scriptPubKey[0] = 0x51;
BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, scriptWitness);
assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 0);
spending_coins = coins.AccessCoins(CTransaction(spendingTx));
assert(GetTransactionSigOpCost(CTransaction(spendingTx), std::span{spending_coins}, flags) == 0);
scriptPubKey[0] = 0x00;
BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, scriptWitness);
// The witness of a coinbase transaction is not taken into account.
spendingTx.vin[0].prevout.SetNull();
assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 0);
spending_coins = coins.AccessCoins(CTransaction(spendingTx));
assert(GetTransactionSigOpCost(CTransaction(spendingTx), std::span{spending_coins}, flags) == 0);
}
// P2WPKH nested in P2SH
@ -191,7 +197,8 @@ BOOST_AUTO_TEST_CASE(GetTxSigOpCost)
scriptWitness.stack.emplace_back(0);
BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, scriptWitness);
assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 1);
auto spending_coins{coins.AccessCoins(CTransaction(spendingTx))};
assert(GetTransactionSigOpCost(CTransaction(spendingTx), std::span{spending_coins}, flags) == 1);
assert(VerifyWithFlag(CTransaction(creationTx), spendingTx, flags) == SCRIPT_ERR_EQUALVERIFY);
}
@ -206,8 +213,9 @@ BOOST_AUTO_TEST_CASE(GetTxSigOpCost)
scriptWitness.stack.emplace_back(witnessScript.begin(), witnessScript.end());
BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, scriptWitness);
assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 2);
assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags & ~SCRIPT_VERIFY_WITNESS) == 0);
auto spending_coins{coins.AccessCoins(CTransaction(spendingTx))};
assert(GetTransactionSigOpCost(CTransaction(spendingTx), std::span{spending_coins}, flags) == 2);
assert(GetTransactionSigOpCost(CTransaction(spendingTx), std::span{spending_coins}, flags & ~SCRIPT_VERIFY_WITNESS) == 0);
assert(VerifyWithFlag(CTransaction(creationTx), spendingTx, flags) == SCRIPT_ERR_CHECKMULTISIGVERIFY);
}
@ -223,7 +231,8 @@ BOOST_AUTO_TEST_CASE(GetTxSigOpCost)
scriptWitness.stack.emplace_back(witnessScript.begin(), witnessScript.end());
BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, scriptWitness);
assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 2);
auto spending_coins{coins.AccessCoins(CTransaction(spendingTx))};
assert(GetTransactionSigOpCost(CTransaction(spendingTx), std::span{spending_coins}, flags) == 2);
assert(VerifyWithFlag(CTransaction(creationTx), spendingTx, flags) == SCRIPT_ERR_CHECKMULTISIGVERIFY);
}
}

View file

@ -21,7 +21,7 @@ struct Dersig100Setup : public TestChain100Setup {
};
bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,
const CCoinsViewCache& inputs, unsigned int flags, bool cacheSigStore,
std::vector<CTxOut>&& spent_outputs, unsigned int flags, bool cacheSigStore,
bool cacheFullScriptStore, PrecomputedTransactionData& txdata,
ValidationCache& validation_cache,
std::vector<CScriptCheck>* pvChecks) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
@ -124,6 +124,9 @@ static void ValidateCheckInputsForAllFlags(const CTransaction &tx, uint32_t fail
{
PrecomputedTransactionData txdata;
std::vector<CTxOut> spent_outputs{active_coins_tip.GetUnspentOutputs(tx)};
txdata.Init(tx, std::move(spent_outputs));
FastRandomContext insecure_rand(true);
for (int count = 0; count < 10000; ++count) {
@ -142,7 +145,7 @@ static void ValidateCheckInputsForAllFlags(const CTransaction &tx, uint32_t fail
// WITNESS requires P2SH
test_flags |= SCRIPT_VERIFY_P2SH;
}
bool ret = CheckInputScripts(tx, state, &active_coins_tip, test_flags, true, add_to_cache, txdata, validation_cache, nullptr);
bool ret = CheckInputScripts(tx, state, {}, test_flags, true, add_to_cache, txdata, validation_cache, nullptr);
// CheckInputScripts should succeed iff test_flags doesn't intersect with
// failing_flags
bool expected_return_value = !(test_flags & failing_flags);
@ -152,13 +155,13 @@ static void ValidateCheckInputsForAllFlags(const CTransaction &tx, uint32_t fail
if (ret && add_to_cache) {
// Check that we get a cache hit if the tx was valid
std::vector<CScriptCheck> scriptchecks;
BOOST_CHECK(CheckInputScripts(tx, state, &active_coins_tip, test_flags, true, add_to_cache, txdata, validation_cache, &scriptchecks));
BOOST_CHECK(CheckInputScripts(tx, state, {}, test_flags, true, add_to_cache, txdata, validation_cache, &scriptchecks));
BOOST_CHECK(scriptchecks.empty());
} else {
// Check that we get script executions to check, if the transaction
// was invalid, or we didn't add to cache.
std::vector<CScriptCheck> scriptchecks;
BOOST_CHECK(CheckInputScripts(tx, state, &active_coins_tip, test_flags, true, add_to_cache, txdata, validation_cache, &scriptchecks));
BOOST_CHECK(CheckInputScripts(tx, state, {}, test_flags, true, add_to_cache, txdata, validation_cache, &scriptchecks));
BOOST_CHECK_EQUAL(scriptchecks.size(), tx.vin.size());
}
}
@ -216,13 +219,16 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, Dersig100Setup)
TxValidationState state;
PrecomputedTransactionData ptd_spend_tx;
BOOST_CHECK(!CheckInputScripts(CTransaction(spend_tx), state, &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_DERSIG, true, true, ptd_spend_tx, m_node.chainman->m_validation_cache, nullptr));
std::vector<CTxOut> spent_outputs{m_node.chainman->ActiveChainstate().CoinsTip().GetUnspentOutputs(CTransaction(spend_tx))};
ptd_spend_tx.Init(spend_tx, std::move(spent_outputs));
BOOST_CHECK(!CheckInputScripts(CTransaction(spend_tx), state, {}, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_DERSIG, true, true, ptd_spend_tx, m_node.chainman->m_validation_cache, nullptr));
// If we call again asking for scriptchecks (as happens in
// ConnectBlock), we should add a script check object for this -- we're
// not caching invalidity (if that changes, delete this test case).
std::vector<CScriptCheck> scriptchecks;
BOOST_CHECK(CheckInputScripts(CTransaction(spend_tx), state, &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_DERSIG, true, true, ptd_spend_tx, m_node.chainman->m_validation_cache, &scriptchecks));
BOOST_CHECK(CheckInputScripts(CTransaction(spend_tx), state, {}, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_DERSIG, true, true, ptd_spend_tx, m_node.chainman->m_validation_cache, &scriptchecks));
BOOST_CHECK_EQUAL(scriptchecks.size(), 1U);
// Test that CheckInputScripts returns true iff DERSIG-enforcing flags are
@ -284,7 +290,10 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, Dersig100Setup)
invalid_with_cltv_tx.vin[0].scriptSig = CScript() << vchSig << 100;
TxValidationState state;
PrecomputedTransactionData txdata;
BOOST_CHECK(CheckInputScripts(CTransaction(invalid_with_cltv_tx), state, m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY, true, true, txdata, m_node.chainman->m_validation_cache, nullptr));
std::vector<CTxOut> spent_outputs{m_node.chainman->ActiveChainstate().CoinsTip().GetUnspentOutputs(CTransaction(invalid_with_cltv_tx))};
txdata.Init(invalid_with_cltv_tx, std::move(spent_outputs));
BOOST_CHECK(CheckInputScripts(CTransaction(invalid_with_cltv_tx), state, {}, SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY, true, true, txdata, m_node.chainman->m_validation_cache, nullptr));
}
// TEST CHECKSEQUENCEVERIFY
@ -312,7 +321,10 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, Dersig100Setup)
invalid_with_csv_tx.vin[0].scriptSig = CScript() << vchSig << 100;
TxValidationState state;
PrecomputedTransactionData txdata;
BOOST_CHECK(CheckInputScripts(CTransaction(invalid_with_csv_tx), state, &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_CHECKSEQUENCEVERIFY, true, true, txdata, m_node.chainman->m_validation_cache, nullptr));
std::vector<CTxOut> spent_outputs{m_node.chainman->ActiveChainstate().CoinsTip().GetUnspentOutputs(CTransaction(invalid_with_csv_tx))};
txdata.Init(invalid_with_csv_tx, std::move(spent_outputs));
BOOST_CHECK(CheckInputScripts(CTransaction(invalid_with_csv_tx), state, {}, SCRIPT_VERIFY_CHECKSEQUENCEVERIFY, true, true, txdata, m_node.chainman->m_validation_cache, nullptr));
}
// TODO: add tests for remaining script flags
@ -373,13 +385,16 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, Dersig100Setup)
TxValidationState state;
PrecomputedTransactionData txdata;
std::vector<CTxOut> spent_outputs{m_node.chainman->ActiveChainstate().CoinsTip().GetUnspentOutputs(CTransaction(tx))};
txdata.Init(tx, std::move(spent_outputs));
// This transaction is now invalid under segwit, because of the second input.
BOOST_CHECK(!CheckInputScripts(CTransaction(tx), state, &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, true, true, txdata, m_node.chainman->m_validation_cache, nullptr));
BOOST_CHECK(!CheckInputScripts(CTransaction(tx), state, {}, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, true, true, txdata, m_node.chainman->m_validation_cache, nullptr));
std::vector<CScriptCheck> scriptchecks;
// Make sure this transaction was not cached (ie because the first
// input was valid)
BOOST_CHECK(CheckInputScripts(CTransaction(tx), state, &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, true, true, txdata, m_node.chainman->m_validation_cache, &scriptchecks));
BOOST_CHECK(CheckInputScripts(CTransaction(tx), state, {}, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, true, true, txdata, m_node.chainman->m_validation_cache, &scriptchecks));
// Should get 2 script checks back -- caching is on a whole-transaction basis.
BOOST_CHECK_EQUAL(scriptchecks.size(), 2U);
}

View file

@ -777,7 +777,9 @@ 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));
assert(mempoolDuplicate.HaveInputs(tx));
auto coins{mempoolDuplicate.AccessCoins(tx)};
assert(Consensus::CheckTxInputs(tx, dummy_state, std::span{coins}, spendheight, txfee));
for (const auto& input: tx.vin) mempoolDuplicate.SpendCoin(input.prevout);
AddCoins(mempoolDuplicate, tx, std::numeric_limits<int>::max());
}

View file

@ -137,11 +137,11 @@ const CBlockIndex* Chainstate::FindForkInGlobalIndex(const CBlockLocator& locato
}
bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,
const CCoinsViewCache& inputs, unsigned int flags, bool cacheSigStore,
std::vector<CTxOut>&& spent_outputs, unsigned int flags, bool cacheSigStore,
bool cacheFullScriptStore, PrecomputedTransactionData& txdata,
ValidationCache& validation_cache,
std::vector<CScriptCheck>* pvChecks = nullptr)
EXCLUSIVE_LOCKS_REQUIRED(cs_main);
EXCLUSIVE_LOCKS_REQUIRED(cs_main);
bool CheckFinalTxAtTip(const CBlockIndex& active_chain_tip, const CTransaction& tx)
{
@ -428,8 +428,10 @@ static bool CheckInputsFromMempoolAndCache(const CTransaction& tx, TxValidationS
}
}
std::vector<CTxOut> spent_outputs{view.GetUnspentOutputs(tx)};
// Call CheckInputScripts() to cache signature and script validity against current tip consensus rules.
return CheckInputScripts(tx, state, view, flags, /* cacheSigStore= */ true, /* cacheFullScriptStore= */ true, txdata, validation_cache);
return CheckInputScripts(tx, state, std::move(spent_outputs), flags, /* cacheSigStore= */ true, /* cacheFullScriptStore= */ true, txdata, validation_cache);
}
namespace {
@ -873,8 +875,15 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
return state.Invalid(TxValidationResult::TX_PREMATURE_SPEND, "non-BIP68-final");
}
// are the actual inputs available?
if (!m_view.HaveInputs(tx)) {
return state.Invalid(TxValidationResult::TX_MISSING_INPUTS, "bad-txns-inputs-missingorspent",
strprintf("%s: inputs missing/spent", __func__));
}
// 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, std::span{coins}, m_active_chainstate.m_chain.Height() + 1, ws.m_base_fees)) {
return false; // state filled in by CheckTxInputs
}
@ -887,7 +896,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
return state.Invalid(TxValidationResult::TX_WITNESS_MUTATED, "bad-witness-nonstandard");
}
int64_t nSigOpsCost = GetTransactionSigOpCost(tx, m_view, 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
// during reorgs to ensure COINBASE_MATURITY is still met.
@ -1233,15 +1242,17 @@ bool MemPoolAccept::PolicyScriptChecks(const ATMPArgs& args, Workspace& ws)
constexpr unsigned int scriptVerifyFlags = STANDARD_SCRIPT_VERIFY_FLAGS;
std::vector<CTxOut> spent_outputs{m_view.GetUnspentOutputs(tx)};
// Check input scripts and signatures.
// This is done last to help prevent CPU exhaustion denial-of-service attacks.
if (!CheckInputScripts(tx, state, m_view, scriptVerifyFlags, true, false, ws.m_precomputed_txdata, GetValidationCache())) {
if (!CheckInputScripts(tx, state, std::move(spent_outputs), scriptVerifyFlags, true, false, ws.m_precomputed_txdata, GetValidationCache())) {
// SCRIPT_VERIFY_CLEANSTACK requires SCRIPT_VERIFY_WITNESS, so we
// need to turn both off, and compare against just turning off CLEANSTACK
// to see if the failure is specifically due to witness validation.
TxValidationState state_dummy; // Want reported failures to be from first CheckInputScripts
if (!tx.HasWitness() && CheckInputScripts(tx, state_dummy, m_view, scriptVerifyFlags & ~(SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_CLEANSTACK), true, false, ws.m_precomputed_txdata, GetValidationCache()) &&
!CheckInputScripts(tx, state_dummy, m_view, scriptVerifyFlags & ~SCRIPT_VERIFY_CLEANSTACK, true, false, ws.m_precomputed_txdata, GetValidationCache())) {
if (!tx.HasWitness() && CheckInputScripts(tx, state_dummy, {}, scriptVerifyFlags & ~(SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_CLEANSTACK), true, false, ws.m_precomputed_txdata, GetValidationCache()) &&
!CheckInputScripts(tx, state_dummy, {}, scriptVerifyFlags & ~SCRIPT_VERIFY_CLEANSTACK, true, false, ws.m_precomputed_txdata, GetValidationCache())) {
// Only the witness is missing, so the transaction itself may be fine.
state.Invalid(TxValidationResult::TX_WITNESS_STRIPPED,
state.GetRejectReason(), state.GetDebugMessage());
@ -2161,7 +2172,7 @@ ValidationCache::ValidationCache(const size_t script_execution_cache_bytes, cons
* Non-static (and redeclared) in src/test/txvalidationcache_tests.cpp
*/
bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,
const CCoinsViewCache& inputs, unsigned int flags, bool cacheSigStore,
std::vector<CTxOut>&& spent_outputs, unsigned int flags, bool cacheSigStore,
bool cacheFullScriptStore, PrecomputedTransactionData& txdata,
ValidationCache& validation_cache,
std::vector<CScriptCheck>* pvChecks)
@ -2186,15 +2197,6 @@ bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,
}
if (!txdata.m_spent_outputs_ready) {
std::vector<CTxOut> spent_outputs;
spent_outputs.reserve(tx.vin.size());
for (const auto& txin : tx.vin) {
const COutPoint& prevout = txin.prevout;
const Coin& coin = inputs.AccessCoin(prevout);
assert(!coin.IsSpent());
spent_outputs.emplace_back(coin.out);
}
txdata.Init(tx, std::move(spent_outputs));
}
assert(txdata.m_spent_outputs.size() == tx.vin.size());
@ -2437,13 +2439,12 @@ static unsigned int GetBlockScriptFlags(const CBlockIndex& block_index, const Ch
/** Apply the effects of this block (with given index) on the UTXO set represented by coins.
* Validity checks that depend on the UTXO set are also done; ConnectBlock()
* can fail if those validity checks fail (among other reasons). */
bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state, CBlockIndex* pindex,
CCoinsViewCache& view, bool fJustCheck)
bool Chainstate::ConnectBlock(const CBlock& block, const uint256& block_hash, const CBlockUndo& blockundo, BlockValidationState& state,
CBlockIndex* pindex, bool fJustCheck)
{
AssertLockHeld(cs_main);
assert(pindex);
uint256 block_hash{block.GetHash()};
Assume(block.GetHash() == block_hash);
assert(*pindex->phashBlock == block_hash);
const bool parallel_script_checks{m_chainman.GetCheckQueue().HasThreads()};
@ -2474,17 +2475,11 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state,
return false;
}
// verify that the view's current state corresponds to the previous block
uint256 hashPrevBlock = pindex->pprev == nullptr ? uint256() : pindex->pprev->GetBlockHash();
assert(hashPrevBlock == view.GetBestBlock());
m_chainman.num_blocks_total++;
// Special case for the genesis block, skipping connection of its transactions
// (its coinbase is unspendable)
if (block_hash == params.GetConsensus().hashGenesisBlock) {
if (!fJustCheck)
view.SetBestBlock(pindex->GetBlockHash());
return true;
}
@ -2526,92 +2521,6 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state,
Ticks<SecondsDouble>(m_chainman.time_check),
Ticks<MillisecondsDouble>(m_chainman.time_check) / m_chainman.num_blocks_total);
// Do not allow blocks that contain transactions which 'overwrite' older transactions,
// unless those are already completely spent.
// If such overwrites are allowed, coinbases and transactions depending upon those
// can be duplicated to remove the ability to spend the first instance -- even after
// being sent to another address.
// See BIP30, CVE-2012-1909, and http://r6.ca/blog/20120206T005236Z.html for more information.
// This rule was originally applied to all blocks with a timestamp after March 15, 2012, 0:00 UTC.
// Now that the whole chain is irreversibly beyond that time it is applied to all blocks except the
// two in the chain that violate it. This prevents exploiting the issue against nodes during their
// initial block download.
bool fEnforceBIP30 = !IsBIP30Repeat(*pindex);
// Once BIP34 activated it was not possible to create new duplicate coinbases and thus other than starting
// with the 2 existing duplicate coinbase pairs, not possible to create overwriting txs. But by the
// time BIP34 activated, in each of the existing pairs the duplicate coinbase had overwritten the first
// before the first had been spent. Since those coinbases are sufficiently buried it's no longer possible to create further
// duplicate transactions descending from the known pairs either.
// If we're on the known chain at height greater than where BIP34 activated, we can save the db accesses needed for the BIP30 check.
// BIP34 requires that a block at height X (block X) has its coinbase
// scriptSig start with a CScriptNum of X (indicated height X). The above
// logic of no longer requiring BIP30 once BIP34 activates is flawed in the
// case that there is a block X before the BIP34 height of 227,931 which has
// an indicated height Y where Y is greater than X. The coinbase for block
// X would also be a valid coinbase for block Y, which could be a BIP30
// violation. An exhaustive search of all mainnet coinbases before the
// BIP34 height which have an indicated height greater than the block height
// reveals many occurrences. The 3 lowest indicated heights found are
// 209,921, 490,897, and 1,983,702 and thus coinbases for blocks at these 3
// heights would be the first opportunity for BIP30 to be violated.
// The search reveals a great many blocks which have an indicated height
// greater than 1,983,702, so we simply remove the optimization to skip
// BIP30 checking for blocks at height 1,983,702 or higher. Before we reach
// that block in another 25 years or so, we should take advantage of a
// future consensus change to do a new and improved version of BIP34 that
// will actually prevent ever creating any duplicate coinbases in the
// future.
static constexpr int BIP34_IMPLIES_BIP30_LIMIT = 1983702;
// There is no potential to create a duplicate coinbase at block 209,921
// because this is still before the BIP34 height and so explicit BIP30
// checking is still active.
// The final case is block 176,684 which has an indicated height of
// 490,897. Unfortunately, this issue was not discovered until about 2 weeks
// before block 490,897 so there was not much opportunity to address this
// case other than to carefully analyze it and determine it would not be a
// problem. Block 490,897 was, in fact, mined with a different coinbase than
// block 176,684, but it is important to note that even if it hadn't been or
// is remined on an alternate fork with a duplicate coinbase, we would still
// not run into a BIP30 violation. This is because the coinbase for 176,684
// is spent in block 185,956 in transaction
// d4f7fbbf92f4a3014a230b2dc70b8058d02eb36ac06b4a0736d9d60eaa9e8781. This
// spending transaction can't be duplicated because it also spends coinbase
// 0328dd85c331237f18e781d692c92de57649529bd5edf1d01036daea32ffde29. This
// coinbase has an indicated height of over 4.2 billion, and wouldn't be
// duplicatable until that height, and it's currently impossible to create a
// chain that long. Nevertheless we may wish to consider a future soft fork
// which retroactively prevents block 490,897 from creating a duplicate
// coinbase. The two historical BIP30 violations often provide a confusing
// edge case when manipulating the UTXO and it would be simpler not to have
// another edge case to deal with.
// testnet3 has no blocks before the BIP34 height with indicated heights
// post BIP34 before approximately height 486,000,000. After block
// 1,983,702 testnet3 starts doing unnecessary BIP30 checking again.
assert(pindex->pprev);
CBlockIndex* pindexBIP34height = pindex->pprev->GetAncestor(params.GetConsensus().BIP34Height);
//Only continue to enforce if we're below BIP34 activation height or the block hash at that height doesn't correspond.
fEnforceBIP30 = fEnforceBIP30 && (!pindexBIP34height || !(pindexBIP34height->GetBlockHash() == params.GetConsensus().BIP34Hash));
// TODO: Remove BIP30 checking from block height 1,983,702 on, once we have a
// consensus change that ensures coinbases at those heights cannot
// duplicate earlier coinbases.
if (fEnforceBIP30 || pindex->nHeight >= BIP34_IMPLIES_BIP30_LIMIT) {
for (const auto& tx : block.vtx) {
for (size_t o = 0; o < tx->vout.size(); o++) {
if (view.HaveCoin(COutPoint(tx->GetHash(), o))) {
state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-txns-BIP30",
"tried to overwrite transaction");
}
}
}
}
// Enforce BIP68 (sequence locks)
int nLockTimeFlags = 0;
if (DeploymentActiveAt(*pindex, m_chainman, Consensus::DEPLOYMENT_CSV)) {
@ -2628,8 +2537,6 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state,
Ticks<SecondsDouble>(m_chainman.time_forks),
Ticks<MillisecondsDouble>(m_chainman.time_forks) / m_chainman.num_blocks_total);
CBlockUndo blockundo;
// Precomputed transaction data pointers must not be invalidated
// until after `control` has run the script checks (potentially
// in multiple threads). Preallocate the vector size so a new allocation
@ -2642,19 +2549,19 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state,
CAmount nFees = 0;
int nInputs = 0;
int64_t nSigOpsCost = 0;
blockundo.vtxundo.reserve(block.vtx.size() - 1);
for (unsigned int i = 0; i < block.vtx.size(); i++)
{
if (!state.IsValid()) break;
const CTransaction &tx = *(block.vtx[i]);
nInputs += tx.vin.size();
if (!tx.IsCoinBase())
{
auto spent_coins = std::span<const Coin>{blockundo.vtxundo[i - 1].vprevout};
CAmount txfee = 0;
TxValidationState tx_state;
if (!Consensus::CheckTxInputs(tx, tx_state, view, pindex->nHeight, txfee)) {
if (!Consensus::CheckTxInputs(tx, tx_state, spent_coins, pindex->nHeight, txfee)) {
// Any transaction validation failure in ConnectBlock is a block consensus failure
state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,
tx_state.GetRejectReason(),
@ -2673,7 +2580,7 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state,
// be in ConnectBlock because they require the UTXO set
prevheights.resize(tx.vin.size());
for (size_t j = 0; j < tx.vin.size(); j++) {
prevheights[j] = view.AccessCoin(tx.vin[j].prevout).nHeight;
prevheights[j] = spent_coins[j].nHeight;
}
if (!SequenceLocks(tx, nLockTimeFlags, prevheights, *pindex)) {
@ -2687,7 +2594,12 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state,
// * legacy (always)
// * p2sh (when P2SH enabled in flags and excludes coinbase)
// * witness (when witness enabled in flags and excludes coinbase)
nSigOpsCost += GetTransactionSigOpCost(tx, view, flags);
if (!tx.IsCoinBase()) {
nSigOpsCost += GetTransactionSigOpCost(tx, std::span<const Coin>{blockundo.vtxundo[i - 1].vprevout}, flags);
} else {
std::vector<Coin> coin;
nSigOpsCost += GetTransactionSigOpCost(tx, std::span<const Coin>{coin}, flags);
}
if (nSigOpsCost > MAX_BLOCK_SIGOPS_COST) {
state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-blk-sigops", "too many sigops");
break;
@ -2698,7 +2610,17 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state,
std::vector<CScriptCheck> vChecks;
bool fCacheResults = fJustCheck; /* Don't cache results if we're actually connecting blocks (still consult the cache, though) */
TxValidationState tx_state;
if (fScriptChecks && !CheckInputScripts(tx, tx_state, view, flags, fCacheResults, fCacheResults, txsdata[i], m_chainman.m_validation_cache, parallel_script_checks ? &vChecks : nullptr)) {
const std::vector<Coin>& spent_coins = blockundo.vtxundo[i - 1].vprevout;
std::vector<CTxOut> spent_outputs;
spent_outputs.reserve(tx.vin.size());
std::transform(spent_coins.begin(), spent_coins.end(),
std::back_inserter(spent_outputs),
[](const Coin& coin) -> CTxOut {
return coin.out; // Assuming Coin has a CTxOut member named 'out'
});
if (fScriptChecks && !CheckInputScripts(tx, tx_state, std::move(spent_outputs), flags, fCacheResults, fCacheResults, txsdata[i], m_chainman.m_validation_cache, parallel_script_checks ? &vChecks : nullptr)) {
// Any transaction validation failure in ConnectBlock is a block consensus failure
state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,
tx_state.GetRejectReason(), tx_state.GetDebugMessage());
@ -2706,12 +2628,6 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state,
}
control.Add(std::move(vChecks));
}
CTxUndo undoDummy;
if (i > 0) {
blockundo.vtxundo.emplace_back();
}
UpdateCoins(tx, view, i == 0 ? undoDummy : blockundo.vtxundo.back(), pindex->nHeight);
}
const auto time_3{SteadyClock::now()};
m_chainman.time_connect += time_3 - time_2;
@ -2763,9 +2679,6 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state,
m_blockman.m_dirty_blockindex.insert(pindex);
}
// add this block to the view's block chain
view.SetBestBlock(pindex->GetBlockHash());
const auto time_6{SteadyClock::now()};
m_chainman.time_index += time_6 - time_5;
LogDebug(BCLog::BENCH, " - Index writing: %.2fms [%.2fs (%.2fms/blk)]\n",
@ -3006,6 +2919,154 @@ static void UpdateTipLog(
!warning_messages.empty() ? strprintf(" warning='%s'", warning_messages) : "");
}
bool Chainstate::SpendBlock(
const CBlock& block,
const CBlockIndex* pindex,
const uint256& block_hash,
CCoinsViewCache& view,
BlockValidationState& state,
CBlockUndo& blockundo)
{
AssertLockHeld(cs_main);
assert(pindex);
Assume(block.GetHash() == block_hash);
assert(*pindex->phashBlock == block_hash);
const CChainParams& params{m_chainman.GetParams()};
// Special case for the genesis block, skipping connection of its transactions
// (its coinbase is unspendable)
if (block_hash == params.GetConsensus().hashGenesisBlock) {
return true;
}
// verify that the view's current state corresponds to the previous block
uint256 hashPrevBlock = pindex->pprev == nullptr ? uint256() : pindex->pprev->GetBlockHash();
assert(hashPrevBlock == view.GetBestBlock());
const auto time_start{SteadyClock::now()};
// Do not allow blocks that contain transactions which 'overwrite' older transactions,
// unless those are already completely spent.
// If such overwrites are allowed, coinbases and transactions depending upon those
// can be duplicated to remove the ability to spend the first instance -- even after
// being sent to another address.
// See BIP30, CVE-2012-1909, and http://r6.ca/blog/20120206T005236Z.html for more information.
// This rule was originally applied to all blocks with a timestamp after March 15, 2012, 0:00 UTC.
// Now that the whole chain is irreversibly beyond that time it is applied to all blocks except the
// two in the chain that violate it. This prevents exploiting the issue against nodes during their
// initial block download.
bool fEnforceBIP30 = !IsBIP30Repeat(*pindex);
// Once BIP34 activated it was not possible to create new duplicate coinbases and thus other than starting
// with the 2 existing duplicate coinbase pairs, not possible to create overwriting txs. But by the
// time BIP34 activated, in each of the existing pairs the duplicate coinbase had overwritten the first
// before the first had been spent. Since those coinbases are sufficiently buried it's no longer possible to create further
// duplicate transactions descending from the known pairs either.
// If we're on the known chain at height greater than where BIP34 activated, we can save the db accesses needed for the BIP30 check.
// BIP34 requires that a block at height X (block X) has its coinbase
// scriptSig start with a CScriptNum of X (indicated height X). The above
// logic of no longer requiring BIP30 once BIP34 activates is flawed in the
// case that there is a block X before the BIP34 height of 227,931 which has
// an indicated height Y where Y is greater than X. The coinbase for block
// X would also be a valid coinbase for block Y, which could be a BIP30
// violation. An exhaustive search of all mainnet coinbases before the
// BIP34 height which have an indicated height greater than the block height
// reveals many occurrences. The 3 lowest indicated heights found are
// 209,921, 490,897, and 1,983,702 and thus coinbases for blocks at these 3
// heights would be the first opportunity for BIP30 to be violated.
// The search reveals a great many blocks which have an indicated height
// greater than 1,983,702, so we simply remove the optimization to skip
// BIP30 checking for blocks at height 1,983,702 or higher. Before we reach
// that block in another 25 years or so, we should take advantage of a
// future consensus change to do a new and improved version of BIP34 that
// will actually prevent ever creating any duplicate coinbases in the
// future.
static constexpr int BIP34_IMPLIES_BIP30_LIMIT = 1983702;
// There is no potential to create a duplicate coinbase at block 209,921
// because this is still before the BIP34 height and so explicit BIP30
// checking is still active.
// The final case is block 176,684 which has an indicated height of
// 490,897. Unfortunately, this issue was not discovered until about 2 weeks
// before block 490,897 so there was not much opportunity to address this
// case other than to carefully analyze it and determine it would not be a
// problem. Block 490,897 was, in fact, mined with a different coinbase than
// block 176,684, but it is important to note that even if it hadn't been or
// is remined on an alternate fork with a duplicate coinbase, we would still
// not run into a BIP30 violation. This is because the coinbase for 176,684
// is spent in block 185,956 in transaction
// d4f7fbbf92f4a3014a230b2dc70b8058d02eb36ac06b4a0736d9d60eaa9e8781. This
// spending transaction can't be duplicated because it also spends coinbase
// 0328dd85c331237f18e781d692c92de57649529bd5edf1d01036daea32ffde29. This
// coinbase has an indicated height of over 4.2 billion, and wouldn't be
// duplicatable until that height, and it's currently impossible to create a
// chain that long. Nevertheless we may wish to consider a future soft fork
// which retroactively prevents block 490,897 from creating a duplicate
// coinbase. The two historical BIP30 violations often provide a confusing
// edge case when manipulating the UTXO and it would be simpler not to have
// another edge case to deal with.
// testnet3 has no blocks before the BIP34 height with indicated heights
// post BIP34 before approximately height 486,000,000. After block
// 1,983,702 testnet3 starts doing unnecessary BIP30 checking again.
assert(pindex->pprev);
CBlockIndex* pindexBIP34height = pindex->pprev->GetAncestor(params.GetConsensus().BIP34Height);
//Only continue to enforce if we're below BIP34 activation height or the block hash at that height doesn't correspond.
fEnforceBIP30 = fEnforceBIP30 && (!pindexBIP34height || !(pindexBIP34height->GetBlockHash() == params.GetConsensus().BIP34Hash));
// TODO: Remove BIP30 checking from block height 1,983,702 on, once we have a
// consensus change that ensures coinbases at those heights cannot
// duplicate earlier coinbases.
if (fEnforceBIP30 || pindex->nHeight >= BIP34_IMPLIES_BIP30_LIMIT) {
for (const auto& tx : block.vtx) {
for (size_t o = 0; o < tx->vout.size(); o++) {
if (view.HaveCoin(COutPoint(tx->GetHash(), o))) {
return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-txns-BIP30",
"tried to overwrite transaction");
}
}
}
}
int nInputs = 0;
blockundo.vtxundo.reserve(block.vtx.size() - 1);
for (const CTransactionRef& tx : block.vtx) {
nInputs += tx->vin.size();
// are the actual inputs available?
if (!view.HaveInputs(*tx)) {
state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-txns-inputs-missingorspent",
strprintf("%s: inputs missing/spent in transaction %s", __func__, tx->GetHash().ToString()));
break;
}
if (!tx->IsCoinBase()) {
blockundo.vtxundo.emplace_back();
}
CTxUndo dummy;
UpdateCoins(*tx, view, tx->IsCoinBase() ? dummy : blockundo.vtxundo.back(), pindex->nHeight);
}
const auto time_1{SteadyClock::now()};
m_chainman.time_check += time_1 - time_start;
LogDebug(BCLog::BENCH, " - Spend Block processed %u transactions: %.2fms (%.3fms/tx, %.3fms/txin) [%.2fs (%.2fms/blk)]\n", (unsigned)block.vtx.size(),
Ticks<MillisecondsDouble>(time_1 - time_start),
block.vtx.size() == 0 ? 0 : Ticks<MillisecondsDouble>(time_1 - time_start) / block.vtx.size(),
nInputs <= 1 ? 0 : Ticks<MillisecondsDouble>(time_1 - time_start) / (nInputs - 1),
Ticks<SecondsDouble>(m_chainman.time_connect),
m_chainman.num_blocks_total == 0 ? 0 : Ticks<MillisecondsDouble>(m_chainman.time_connect) / m_chainman.num_blocks_total);
if (!state.IsValid()) {
LogInfo("Block validation error: %s", state.ToString());
return false;
}
assert(blockundo.vtxundo.size() == block.vtx.size() - 1);
return true;
}
void Chainstate::UpdateTip(const CBlockIndex* pindexNew)
{
AssertLockHeld(::cs_main);
@ -3197,7 +3258,18 @@ bool Chainstate::ConnectTip(BlockValidationState& state, CBlockIndex* pindexNew,
Ticks<MillisecondsDouble>(time_2 - time_1));
{
CCoinsViewCache view(&CoinsTip());
bool rv = ConnectBlock(blockConnecting, state, pindexNew, view);
CBlockUndo blockundo;
const auto block_hash{blockConnecting.GetHash()};
if (!SpendBlock(blockConnecting, pindexNew, block_hash, view, state, blockundo)) {
assert(state.IsInvalid());
if (m_chainman.m_options.signals) {
m_chainman.m_options.signals->BlockChecked(blockConnecting, state);
}
InvalidBlockFound(pindexNew, state);
LogError("%s: SpendBlock %s failed, %s\n", __func__, pindexNew->GetBlockHash().ToString(), state.ToString());
return false;
}
bool rv = ConnectBlock(blockConnecting, block_hash, blockundo, state, pindexNew);
if (m_chainman.m_options.signals) {
m_chainman.m_options.signals->BlockChecked(blockConnecting, state);
}
@ -3207,6 +3279,8 @@ bool Chainstate::ConnectTip(BlockValidationState& state, CBlockIndex* pindexNew,
LogError("%s: ConnectBlock %s failed, %s\n", __func__, pindexNew->GetBlockHash().ToString(), state.ToString());
return false;
}
// add this block to the view's block chain
view.SetBestBlock(block_hash);
time_3 = SteadyClock::now();
m_chainman.time_connect_total += time_3 - time_2;
assert(m_chainman.num_blocks_total > 0);
@ -4678,7 +4752,11 @@ bool TestBlockValidity(BlockValidationState& state,
LogError("%s: Consensus::ContextualCheckBlock: %s\n", __func__, state.ToString());
return false;
}
if (!chainstate.ConnectBlock(block, state, &indexDummy, viewNew, true)) {
CBlockUndo blockundo;
if (!chainstate.SpendBlock(block, &indexDummy, block_hash, viewNew, state, blockundo)) {
return false;
}
if (!chainstate.ConnectBlock(block, block_hash, blockundo, state, &indexDummy, true)) {
return false;
}
assert(state.IsValid());
@ -4862,10 +4940,18 @@ VerifyDBResult CVerifyDB::VerifyDB(
LogPrintf("Verification error: ReadBlock failed at %d, hash=%s\n", pindex->nHeight, pindex->GetBlockHash().ToString());
return VerifyDBResult::CORRUPTED_BLOCK_DB;
}
if (!chainstate.ConnectBlock(block, state, pindex, coins)) {
CBlockUndo blockundo;
const auto block_hash{block.GetHash()};
if (!chainstate.SpendBlock(block, pindex, block_hash, coins, state, blockundo)) {
LogError("SpendBlock failed %s\n", state.ToString());
return VerifyDBResult::CORRUPTED_BLOCK_DB;
}
if (!chainstate.ConnectBlock(block, block_hash, blockundo, state, pindex)) {
LogPrintf("Verification error: found unconnectable block at %d, hash=%s (%s)\n", pindex->nHeight, pindex->GetBlockHash().ToString(), state.ToString());
return VerifyDBResult::CORRUPTED_BLOCK_DB;
}
coins.SetBestBlock(block_hash);
if (chainstate.m_chainman.m_interrupt) return VerifyDBResult::INTERRUPTED;
}
}

View file

@ -706,8 +706,36 @@ public:
// Block (dis)connection on a given view:
DisconnectResult DisconnectBlock(const CBlock& block, const CBlockIndex* pindex, CCoinsViewCache& view)
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
bool ConnectBlock(const CBlock& block, BlockValidationState& state, CBlockIndex* pindex,
CCoinsViewCache& view, bool fJustCheck = false) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
/**
* Validates the block against the coins in the blockundo data, writes the
* undo data, and raises the block validity in the block index.
*
* @param[in] block The block to be validated
* @param[in] block_hash Hash of block
* @param[in] blockundo Has to contain all coins spent by block. Written to disk on successful validation
* @param[out] state This may be set to an Error state if any error occurred validating block
* @param[out] pindex Points to the block map entry associated with block. On successful validation, raises the block validity
* @param[in] fJustCheck If set, no data is written to disk and pindex's validity is not raised
*/
bool ConnectBlock(const CBlock& block, const uint256& block_hash, const CBlockUndo& blockundo, BlockValidationState& state,
CBlockIndex* pindex, bool fJustCheck = false) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
/**
* Spends the coins associated with a block. First checks that the block
* contains no duplicate transactions (BIP30), then for each transaction in
* the block checks that the outputs it is spending exist, spends them, and
* populates the CBlockUndo data structure.
*
* @param[in] block The block to be spent
* @param[in] pindex Points to the block map entry associated with block
* @param[in] block_hash Hash of block
* @param[out] view Its coins are spent and used to populate CBlockUndo during its execution
* @param[out] state This may be set to an Error state if any error occurred processing them
* @param[out] blockundo Coins consumed by the block are added to it.
*/
bool SpendBlock(const CBlock& block, const CBlockIndex* pindex, const uint256& block_hash,
CCoinsViewCache& view, BlockValidationState& state, CBlockUndo& blockundo) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
// Apply the effects of a block disconnection on the UTXO set.
bool DisconnectTip(BlockValidationState& state, DisconnectedBlockTransactions* disconnectpool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_mempool->cs);