mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-04-29 14:59:39 -04:00
Compare commits
19 commits
daaaa1184e
...
f1da436246
Author | SHA1 | Date | |
---|---|---|---|
|
f1da436246 | ||
|
65714c162c | ||
|
a4eee6d50b | ||
|
af6cffa36d | ||
|
33e6538b30 | ||
|
fa48be3ba4 | ||
|
aaaa45399c | ||
|
cccc1f4e91 | ||
|
c7e2b9e264 | ||
|
fa58f40b89 | ||
|
fadf12a56c | ||
|
76a8f22c5c | ||
|
8221e89ac4 | ||
|
e1f88913b7 | ||
|
008748c3ae | ||
|
1eb575214d | ||
|
c1285a81de | ||
|
781668b6e8 | ||
|
a7e4132623 |
22 changed files with 544 additions and 273 deletions
|
@ -9,6 +9,7 @@
|
||||||
#include <script/interpreter.h>
|
#include <script/interpreter.h>
|
||||||
#include <sync.h>
|
#include <sync.h>
|
||||||
#include <test/util/setup_common.h>
|
#include <test/util/setup_common.h>
|
||||||
|
#include <undo.h>
|
||||||
#include <validation.h>
|
#include <validation.h>
|
||||||
|
|
||||||
#include <cassert>
|
#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
|
auto* pindex{chainman->m_blockman.AddToBlockIndex(test_block, chainman->m_best_header)}; // Doing this here doesn't impact the benchmark
|
||||||
CCoinsViewCache viewNew{&chainstate.CoinsTip()};
|
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));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
bool CCoinsViewCache::HaveCoin(const COutPoint &outpoint) const {
|
||||||
CCoinsMap::const_iterator it = FetchCoin(outpoint);
|
CCoinsMap::const_iterator it = FetchCoin(outpoint);
|
||||||
return (it != cacheCoins.end() && !it->second.coin.IsSpent());
|
return (it != cacheCoins.end() && !it->second.coin.IsSpent());
|
||||||
|
|
16
src/coins.h
16
src/coins.h
|
@ -415,6 +415,22 @@ public:
|
||||||
*/
|
*/
|
||||||
const Coin& AccessCoin(const COutPoint &output) const;
|
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
|
* Add a coin. Set possible_overwrite to true if an unspent version may
|
||||||
* already exist in the cache.
|
* already exist in the cache.
|
||||||
|
|
|
@ -14,6 +14,16 @@
|
||||||
#include <util/check.h>
|
#include <util/check.h>
|
||||||
#include <util/moneystr.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)
|
bool IsFinalTx(const CTransaction &tx, int nBlockHeight, int64_t nBlockTime)
|
||||||
{
|
{
|
||||||
if (tx.nLockTime == 0)
|
if (tx.nLockTime == 0)
|
||||||
|
@ -123,24 +133,33 @@ unsigned int GetLegacySigOpCount(const CTransaction& tx)
|
||||||
return nSigOps;
|
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())
|
if (tx.IsCoinBase())
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
unsigned int nSigOps = 0;
|
unsigned int nSigOps = 0;
|
||||||
for (unsigned int i = 0; i < tx.vin.size(); i++)
|
Assert(coins.size() == tx.vin.size());
|
||||||
{
|
auto input_it = tx.vin.begin();
|
||||||
const Coin& coin = inputs.AccessCoin(tx.vin[i].prevout);
|
for (auto it = coins.begin(); it != coins.end(); ++it, ++input_it) {
|
||||||
|
const Coin& coin{GetCoin(*it)};
|
||||||
assert(!coin.IsSpent());
|
assert(!coin.IsSpent());
|
||||||
const CTxOut &prevout = coin.out;
|
const CTxOut &prevout = coin.out;
|
||||||
if (prevout.scriptPubKey.IsPayToScriptHash())
|
if (prevout.scriptPubKey.IsPayToScriptHash())
|
||||||
nSigOps += prevout.scriptPubKey.GetSigOpCount(tx.vin[i].scriptSig);
|
nSigOps += prevout.scriptPubKey.GetSigOpCount(input_it->scriptSig);
|
||||||
}
|
}
|
||||||
return nSigOps;
|
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;
|
int64_t nSigOps = GetLegacySigOpCount(tx) * WITNESS_SCALE_FACTOR;
|
||||||
|
|
||||||
|
@ -148,37 +167,39 @@ int64_t GetTransactionSigOpCost(const CTransaction& tx, const CCoinsViewCache& i
|
||||||
return nSigOps;
|
return nSigOps;
|
||||||
|
|
||||||
if (flags & SCRIPT_VERIFY_P2SH) {
|
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++)
|
Assert(coins.size() == tx.vin.size());
|
||||||
{
|
auto input_it = tx.vin.begin();
|
||||||
const Coin& coin = inputs.AccessCoin(tx.vin[i].prevout);
|
for (auto it = coins.begin(); it != coins.end(); ++it, ++input_it) {
|
||||||
|
const Coin& coin{GetCoin(*it)};
|
||||||
assert(!coin.IsSpent());
|
assert(!coin.IsSpent());
|
||||||
const CTxOut &prevout = coin.out;
|
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;
|
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;
|
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
|
||||||
|
@ -203,3 +224,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 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);
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#ifndef BITCOIN_CONSENSUS_TX_VERIFY_H
|
#ifndef BITCOIN_CONSENSUS_TX_VERIFY_H
|
||||||
#define BITCOIN_CONSENSUS_TX_VERIFY_H
|
#define BITCOIN_CONSENSUS_TX_VERIFY_H
|
||||||
|
|
||||||
|
#include <coins.h>
|
||||||
#include <consensus/amount.h>
|
#include <consensus/amount.h>
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
@ -24,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 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) */
|
||||||
|
@ -39,20 +41,22 @@ unsigned int GetLegacySigOpCount(const CTransaction& tx);
|
||||||
/**
|
/**
|
||||||
* Count ECDSA signature operations in pay-to-script-hash inputs.
|
* 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
|
* @return maximum number of sigops required to validate this transaction's inputs
|
||||||
* @see CTransaction::FetchInputs
|
* @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.
|
* Compute total signature operation cost of a transaction.
|
||||||
* @param[in] tx Transaction for which we are computing the cost
|
* @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] coins Sorted span of Coins containing previous transaction outputs we're spending
|
||||||
* @param[in] flags Script verification flags
|
* @param[in] flags Script verification flags
|
||||||
* @return Total signature operation cost of tx
|
* @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
|
* Check if transaction is final and can be included in a block with the
|
||||||
|
|
|
@ -137,7 +137,8 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx)
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
CTransaction ctx = CTransaction(mtx);
|
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;
|
result.estimated_vsize = size;
|
||||||
// Estimate fee rate
|
// Estimate fee rate
|
||||||
CFeeRate feerate(fee, size);
|
CFeeRate feerate(fee, size);
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#include <test/util/index.h>
|
#include <test/util/index.h>
|
||||||
#include <test/util/setup_common.h>
|
#include <test/util/setup_common.h>
|
||||||
#include <test/util/validation.h>
|
#include <test/util/validation.h>
|
||||||
|
#include <undo.h>
|
||||||
#include <validation.h>
|
#include <validation.h>
|
||||||
|
|
||||||
#include <boost/test/unit_test.hpp>
|
#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(CheckBlock(block, state, params.GetConsensus()));
|
||||||
BOOST_CHECK(m_node.chainman->AcceptBlock(new_block, state, &new_block_index, true, nullptr, nullptr, true));
|
BOOST_CHECK(m_node.chainman->AcceptBlock(new_block, state, &new_block_index, true, nullptr, nullptr, true));
|
||||||
CCoinsViewCache view(&chainstate.CoinsTip());
|
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
|
// Send block connected notification, then stop the index without
|
||||||
// sending a chainstate flushed notification. Prior to #24138, this
|
// sending a chainstate flushed notification. Prior to #24138, this
|
||||||
|
|
|
@ -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, 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));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -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.
|
// consensus/tx_verify.cpp:130: unsigned int GetP2SHSigOpCount(const CTransaction &, const CCoinsViewCache &): Assertion `!coin.IsSpent()' failed.
|
||||||
return;
|
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};
|
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.
|
// script/interpreter.cpp:1705: size_t CountWitnessSigOps(const CScript &, const CScript &, const CScriptWitness *, unsigned int): Assertion `(flags & SCRIPT_VERIFY_P2SH) != 0' failed.
|
||||||
return;
|
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);
|
(void)IsWitnessStandard(CTransaction{random_mutable_transaction}, coins_view_cache);
|
||||||
|
|
|
@ -362,7 +362,8 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard)
|
||||||
|
|
||||||
BOOST_CHECK(::AreInputsStandard(CTransaction(txTo), coins));
|
BOOST_CHECK(::AreInputsStandard(CTransaction(txTo), coins));
|
||||||
// 22 P2SH sigops for all inputs (1 for vin[0], 6 for vin[3], 15 for vin[4]
|
// 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;
|
CMutableTransaction txToNonStd1;
|
||||||
txToNonStd1.vout.resize(1);
|
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());
|
txToNonStd1.vin[0].scriptSig << std::vector<unsigned char>(sixteenSigops.begin(), sixteenSigops.end());
|
||||||
|
|
||||||
BOOST_CHECK(!::AreInputsStandard(CTransaction(txToNonStd1), coins));
|
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;
|
CMutableTransaction txToNonStd2;
|
||||||
txToNonStd2.vout.resize(1);
|
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());
|
txToNonStd2.vin[0].scriptSig << std::vector<unsigned char>(twentySigops.begin(), twentySigops.end());
|
||||||
|
|
||||||
BOOST_CHECK(!::AreInputsStandard(CTransaction(txToNonStd2), coins));
|
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()
|
BOOST_AUTO_TEST_SUITE_END()
|
||||||
|
|
|
@ -131,13 +131,15 @@ BOOST_AUTO_TEST_CASE(GetTxSigOpCost)
|
||||||
CScript scriptSig = CScript() << OP_0 << OP_0;
|
CScript scriptSig = CScript() << OP_0 << OP_0;
|
||||||
|
|
||||||
BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, CScriptWitness());
|
BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, CScriptWitness());
|
||||||
|
auto spending_coins{coins.AccessCoins(CTransaction(spendingTx))};
|
||||||
// Legacy counting only includes signature operations in scriptSigs and scriptPubKeys
|
// Legacy counting only includes signature operations in scriptSigs and scriptPubKeys
|
||||||
// of a transaction and does not take the actual executed sig operations into account.
|
// of a transaction and does not take the actual executed sig operations into account.
|
||||||
// spendingTx in itself does not contain a signature operation.
|
// 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
|
// creationTx contains two signature operations in its scriptPubKey, but legacy counting
|
||||||
// is not accurate.
|
// 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.
|
// Sanity check: script verification fails because of an invalid signature.
|
||||||
assert(VerifyWithFlag(CTransaction(creationTx), spendingTx, flags) == SCRIPT_ERR_CHECKMULTISIGVERIFY);
|
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);
|
CScript scriptSig = CScript() << OP_0 << OP_0 << ToByteVector(redeemScript);
|
||||||
|
|
||||||
BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, CScriptWitness());
|
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);
|
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);
|
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.
|
// 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);
|
assert(VerifyWithFlag(CTransaction(creationTx), spendingTx, flags) == SCRIPT_ERR_EQUALVERIFY);
|
||||||
|
|
||||||
// The sig op cost for witness version != 0 is zero.
|
// The sig op cost for witness version != 0 is zero.
|
||||||
assert(scriptPubKey[0] == 0x00);
|
assert(scriptPubKey[0] == 0x00);
|
||||||
scriptPubKey[0] = 0x51;
|
scriptPubKey[0] = 0x51;
|
||||||
BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, scriptWitness);
|
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;
|
scriptPubKey[0] = 0x00;
|
||||||
BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, scriptWitness);
|
BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, scriptWitness);
|
||||||
|
|
||||||
// The witness of a coinbase transaction is not taken into account.
|
// The witness of a coinbase transaction is not taken into account.
|
||||||
spendingTx.vin[0].prevout.SetNull();
|
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
|
// P2WPKH nested in P2SH
|
||||||
|
@ -191,7 +197,8 @@ BOOST_AUTO_TEST_CASE(GetTxSigOpCost)
|
||||||
scriptWitness.stack.emplace_back(0);
|
scriptWitness.stack.emplace_back(0);
|
||||||
|
|
||||||
BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, scriptWitness);
|
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);
|
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());
|
scriptWitness.stack.emplace_back(witnessScript.begin(), witnessScript.end());
|
||||||
|
|
||||||
BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, scriptWitness);
|
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), coins, flags & ~SCRIPT_VERIFY_WITNESS) == 0);
|
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);
|
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());
|
scriptWitness.stack.emplace_back(witnessScript.begin(), witnessScript.end());
|
||||||
|
|
||||||
BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, scriptWitness);
|
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);
|
assert(VerifyWithFlag(CTransaction(creationTx), spendingTx, flags) == SCRIPT_ERR_CHECKMULTISIGVERIFY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ struct Dersig100Setup : public TestChain100Setup {
|
||||||
};
|
};
|
||||||
|
|
||||||
bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,
|
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,
|
bool cacheFullScriptStore, PrecomputedTransactionData& txdata,
|
||||||
ValidationCache& validation_cache,
|
ValidationCache& validation_cache,
|
||||||
std::vector<CScriptCheck>* pvChecks) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
|
std::vector<CScriptCheck>* pvChecks) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
|
||||||
|
@ -124,6 +124,9 @@ static void ValidateCheckInputsForAllFlags(const CTransaction &tx, uint32_t fail
|
||||||
{
|
{
|
||||||
PrecomputedTransactionData txdata;
|
PrecomputedTransactionData txdata;
|
||||||
|
|
||||||
|
std::vector<CTxOut> spent_outputs{active_coins_tip.GetUnspentOutputs(tx)};
|
||||||
|
txdata.Init(tx, std::move(spent_outputs));
|
||||||
|
|
||||||
FastRandomContext insecure_rand(true);
|
FastRandomContext insecure_rand(true);
|
||||||
|
|
||||||
for (int count = 0; count < 10000; ++count) {
|
for (int count = 0; count < 10000; ++count) {
|
||||||
|
@ -142,7 +145,7 @@ static void ValidateCheckInputsForAllFlags(const CTransaction &tx, uint32_t fail
|
||||||
// WITNESS requires P2SH
|
// WITNESS requires P2SH
|
||||||
test_flags |= SCRIPT_VERIFY_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
|
// CheckInputScripts should succeed iff test_flags doesn't intersect with
|
||||||
// failing_flags
|
// failing_flags
|
||||||
bool expected_return_value = !(test_flags & 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) {
|
if (ret && add_to_cache) {
|
||||||
// Check that we get a cache hit if the tx was valid
|
// Check that we get a cache hit if the tx was valid
|
||||||
std::vector<CScriptCheck> scriptchecks;
|
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());
|
BOOST_CHECK(scriptchecks.empty());
|
||||||
} else {
|
} else {
|
||||||
// Check that we get script executions to check, if the transaction
|
// Check that we get script executions to check, if the transaction
|
||||||
// was invalid, or we didn't add to cache.
|
// was invalid, or we didn't add to cache.
|
||||||
std::vector<CScriptCheck> scriptchecks;
|
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());
|
BOOST_CHECK_EQUAL(scriptchecks.size(), tx.vin.size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -216,13 +219,16 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, Dersig100Setup)
|
||||||
TxValidationState state;
|
TxValidationState state;
|
||||||
PrecomputedTransactionData ptd_spend_tx;
|
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
|
// If we call again asking for scriptchecks (as happens in
|
||||||
// ConnectBlock), we should add a script check object for this -- we're
|
// ConnectBlock), we should add a script check object for this -- we're
|
||||||
// not caching invalidity (if that changes, delete this test case).
|
// not caching invalidity (if that changes, delete this test case).
|
||||||
std::vector<CScriptCheck> scriptchecks;
|
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);
|
BOOST_CHECK_EQUAL(scriptchecks.size(), 1U);
|
||||||
|
|
||||||
// Test that CheckInputScripts returns true iff DERSIG-enforcing flags are
|
// 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;
|
invalid_with_cltv_tx.vin[0].scriptSig = CScript() << vchSig << 100;
|
||||||
TxValidationState state;
|
TxValidationState state;
|
||||||
PrecomputedTransactionData txdata;
|
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
|
// TEST CHECKSEQUENCEVERIFY
|
||||||
|
@ -312,7 +321,10 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, Dersig100Setup)
|
||||||
invalid_with_csv_tx.vin[0].scriptSig = CScript() << vchSig << 100;
|
invalid_with_csv_tx.vin[0].scriptSig = CScript() << vchSig << 100;
|
||||||
TxValidationState state;
|
TxValidationState state;
|
||||||
PrecomputedTransactionData txdata;
|
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
|
// TODO: add tests for remaining script flags
|
||||||
|
@ -373,13 +385,16 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, Dersig100Setup)
|
||||||
|
|
||||||
TxValidationState state;
|
TxValidationState state;
|
||||||
PrecomputedTransactionData txdata;
|
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.
|
// 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;
|
std::vector<CScriptCheck> scriptchecks;
|
||||||
// Make sure this transaction was not cached (ie because the first
|
// Make sure this transaction was not cached (ie because the first
|
||||||
// input was valid)
|
// 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.
|
// Should get 2 script checks back -- caching is on a whole-transaction basis.
|
||||||
BOOST_CHECK_EQUAL(scriptchecks.size(), 2U);
|
BOOST_CHECK_EQUAL(scriptchecks.size(), 2U);
|
||||||
}
|
}
|
||||||
|
|
|
@ -777,7 +777,9 @@ 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));
|
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);
|
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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -137,11 +137,11 @@ const CBlockIndex* Chainstate::FindForkInGlobalIndex(const CBlockLocator& locato
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,
|
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,
|
bool cacheFullScriptStore, PrecomputedTransactionData& txdata,
|
||||||
ValidationCache& validation_cache,
|
ValidationCache& validation_cache,
|
||||||
std::vector<CScriptCheck>* pvChecks = nullptr)
|
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)
|
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.
|
// 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 {
|
namespace {
|
||||||
|
@ -873,8 +875,15 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
||||||
return state.Invalid(TxValidationResult::TX_PREMATURE_SPEND, "non-BIP68-final");
|
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
|
// 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
|
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");
|
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
|
// Keep track of transactions that spend a coinbase, which we re-scan
|
||||||
// during reorgs to ensure COINBASE_MATURITY is still met.
|
// 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;
|
constexpr unsigned int scriptVerifyFlags = STANDARD_SCRIPT_VERIFY_FLAGS;
|
||||||
|
|
||||||
|
std::vector<CTxOut> spent_outputs{m_view.GetUnspentOutputs(tx)};
|
||||||
|
|
||||||
// Check input scripts and signatures.
|
// Check input scripts and signatures.
|
||||||
// This is done last to help prevent CPU exhaustion denial-of-service attacks.
|
// 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
|
// SCRIPT_VERIFY_CLEANSTACK requires SCRIPT_VERIFY_WITNESS, so we
|
||||||
// need to turn both off, and compare against just turning off CLEANSTACK
|
// need to turn both off, and compare against just turning off CLEANSTACK
|
||||||
// to see if the failure is specifically due to witness validation.
|
// to see if the failure is specifically due to witness validation.
|
||||||
TxValidationState state_dummy; // Want reported failures to be from first CheckInputScripts
|
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()) &&
|
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, m_view, scriptVerifyFlags & ~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.
|
// Only the witness is missing, so the transaction itself may be fine.
|
||||||
state.Invalid(TxValidationResult::TX_WITNESS_STRIPPED,
|
state.Invalid(TxValidationResult::TX_WITNESS_STRIPPED,
|
||||||
state.GetRejectReason(), state.GetDebugMessage());
|
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
|
* Non-static (and redeclared) in src/test/txvalidationcache_tests.cpp
|
||||||
*/
|
*/
|
||||||
bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,
|
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,
|
bool cacheFullScriptStore, PrecomputedTransactionData& txdata,
|
||||||
ValidationCache& validation_cache,
|
ValidationCache& validation_cache,
|
||||||
std::vector<CScriptCheck>* pvChecks)
|
std::vector<CScriptCheck>* pvChecks)
|
||||||
|
@ -2186,15 +2197,6 @@ bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!txdata.m_spent_outputs_ready) {
|
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));
|
txdata.Init(tx, std::move(spent_outputs));
|
||||||
}
|
}
|
||||||
assert(txdata.m_spent_outputs.size() == tx.vin.size());
|
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.
|
/** 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()
|
* Validity checks that depend on the UTXO set are also done; ConnectBlock()
|
||||||
* can fail if those validity checks fail (among other reasons). */
|
* can fail if those validity checks fail (among other reasons). */
|
||||||
bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state, CBlockIndex* pindex,
|
bool Chainstate::ConnectBlock(const CBlock& block, const uint256& block_hash, const CBlockUndo& blockundo, BlockValidationState& state,
|
||||||
CCoinsViewCache& view, bool fJustCheck)
|
CBlockIndex* pindex, bool fJustCheck)
|
||||||
{
|
{
|
||||||
AssertLockHeld(cs_main);
|
AssertLockHeld(cs_main);
|
||||||
assert(pindex);
|
assert(pindex);
|
||||||
|
Assume(block.GetHash() == block_hash);
|
||||||
uint256 block_hash{block.GetHash()};
|
|
||||||
assert(*pindex->phashBlock == block_hash);
|
assert(*pindex->phashBlock == block_hash);
|
||||||
const bool parallel_script_checks{m_chainman.GetCheckQueue().HasThreads()};
|
const bool parallel_script_checks{m_chainman.GetCheckQueue().HasThreads()};
|
||||||
|
|
||||||
|
@ -2474,17 +2475,11 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state,
|
||||||
return false;
|
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++;
|
m_chainman.num_blocks_total++;
|
||||||
|
|
||||||
// Special case for the genesis block, skipping connection of its transactions
|
// Special case for the genesis block, skipping connection of its transactions
|
||||||
// (its coinbase is unspendable)
|
// (its coinbase is unspendable)
|
||||||
if (block_hash == params.GetConsensus().hashGenesisBlock) {
|
if (block_hash == params.GetConsensus().hashGenesisBlock) {
|
||||||
if (!fJustCheck)
|
|
||||||
view.SetBestBlock(pindex->GetBlockHash());
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2526,92 +2521,6 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state,
|
||||||
Ticks<SecondsDouble>(m_chainman.time_check),
|
Ticks<SecondsDouble>(m_chainman.time_check),
|
||||||
Ticks<MillisecondsDouble>(m_chainman.time_check) / m_chainman.num_blocks_total);
|
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)
|
// Enforce BIP68 (sequence locks)
|
||||||
int nLockTimeFlags = 0;
|
int nLockTimeFlags = 0;
|
||||||
if (DeploymentActiveAt(*pindex, m_chainman, Consensus::DEPLOYMENT_CSV)) {
|
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<SecondsDouble>(m_chainman.time_forks),
|
||||||
Ticks<MillisecondsDouble>(m_chainman.time_forks) / m_chainman.num_blocks_total);
|
Ticks<MillisecondsDouble>(m_chainman.time_forks) / m_chainman.num_blocks_total);
|
||||||
|
|
||||||
CBlockUndo blockundo;
|
|
||||||
|
|
||||||
// Precomputed transaction data pointers must not be invalidated
|
// Precomputed transaction data pointers must not be invalidated
|
||||||
// until after `control` has run the script checks (potentially
|
// until after `control` has run the script checks (potentially
|
||||||
// in multiple threads). Preallocate the vector size so a new allocation
|
// 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;
|
CAmount nFees = 0;
|
||||||
int nInputs = 0;
|
int nInputs = 0;
|
||||||
int64_t nSigOpsCost = 0;
|
int64_t nSigOpsCost = 0;
|
||||||
blockundo.vtxundo.reserve(block.vtx.size() - 1);
|
|
||||||
for (unsigned int i = 0; i < block.vtx.size(); i++)
|
for (unsigned int i = 0; i < block.vtx.size(); i++)
|
||||||
{
|
{
|
||||||
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())
|
||||||
{
|
{
|
||||||
|
auto spent_coins = std::span<const Coin>{blockundo.vtxundo[i - 1].vprevout};
|
||||||
CAmount txfee = 0;
|
CAmount txfee = 0;
|
||||||
TxValidationState tx_state;
|
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
|
// 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(),
|
||||||
|
@ -2673,7 +2580,7 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state,
|
||||||
// be in ConnectBlock because they require the UTXO set
|
// be in ConnectBlock because they require the UTXO set
|
||||||
prevheights.resize(tx.vin.size());
|
prevheights.resize(tx.vin.size());
|
||||||
for (size_t j = 0; j < tx.vin.size(); j++) {
|
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)) {
|
if (!SequenceLocks(tx, nLockTimeFlags, prevheights, *pindex)) {
|
||||||
|
@ -2687,7 +2594,12 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state,
|
||||||
// * legacy (always)
|
// * legacy (always)
|
||||||
// * p2sh (when P2SH enabled in flags and excludes coinbase)
|
// * p2sh (when P2SH enabled in flags and excludes coinbase)
|
||||||
// * witness (when witness 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) {
|
if (nSigOpsCost > MAX_BLOCK_SIGOPS_COST) {
|
||||||
state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-blk-sigops", "too many sigops");
|
state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-blk-sigops", "too many sigops");
|
||||||
break;
|
break;
|
||||||
|
@ -2698,7 +2610,17 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state,
|
||||||
std::vector<CScriptCheck> vChecks;
|
std::vector<CScriptCheck> vChecks;
|
||||||
bool fCacheResults = fJustCheck; /* Don't cache results if we're actually connecting blocks (still consult the cache, though) */
|
bool fCacheResults = fJustCheck; /* Don't cache results if we're actually connecting blocks (still consult the cache, though) */
|
||||||
TxValidationState tx_state;
|
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
|
// 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.GetDebugMessage());
|
tx_state.GetRejectReason(), tx_state.GetDebugMessage());
|
||||||
|
@ -2706,12 +2628,6 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state,
|
||||||
}
|
}
|
||||||
control.Add(std::move(vChecks));
|
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()};
|
const auto time_3{SteadyClock::now()};
|
||||||
m_chainman.time_connect += time_3 - time_2;
|
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);
|
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()};
|
const auto time_6{SteadyClock::now()};
|
||||||
m_chainman.time_index += time_6 - time_5;
|
m_chainman.time_index += time_6 - time_5;
|
||||||
LogDebug(BCLog::BENCH, " - Index writing: %.2fms [%.2fs (%.2fms/blk)]\n",
|
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) : "");
|
!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)
|
void Chainstate::UpdateTip(const CBlockIndex* pindexNew)
|
||||||
{
|
{
|
||||||
AssertLockHeld(::cs_main);
|
AssertLockHeld(::cs_main);
|
||||||
|
@ -3197,7 +3258,18 @@ bool Chainstate::ConnectTip(BlockValidationState& state, CBlockIndex* pindexNew,
|
||||||
Ticks<MillisecondsDouble>(time_2 - time_1));
|
Ticks<MillisecondsDouble>(time_2 - time_1));
|
||||||
{
|
{
|
||||||
CCoinsViewCache view(&CoinsTip());
|
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) {
|
if (m_chainman.m_options.signals) {
|
||||||
m_chainman.m_options.signals->BlockChecked(blockConnecting, state);
|
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());
|
LogError("%s: ConnectBlock %s failed, %s\n", __func__, pindexNew->GetBlockHash().ToString(), state.ToString());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
// add this block to the view's block chain
|
||||||
|
view.SetBestBlock(block_hash);
|
||||||
time_3 = SteadyClock::now();
|
time_3 = SteadyClock::now();
|
||||||
m_chainman.time_connect_total += time_3 - time_2;
|
m_chainman.time_connect_total += time_3 - time_2;
|
||||||
assert(m_chainman.num_blocks_total > 0);
|
assert(m_chainman.num_blocks_total > 0);
|
||||||
|
@ -4678,7 +4752,11 @@ bool TestBlockValidity(BlockValidationState& state,
|
||||||
LogError("%s: Consensus::ContextualCheckBlock: %s\n", __func__, state.ToString());
|
LogError("%s: Consensus::ContextualCheckBlock: %s\n", __func__, state.ToString());
|
||||||
return false;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
assert(state.IsValid());
|
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());
|
LogPrintf("Verification error: ReadBlock failed at %d, hash=%s\n", pindex->nHeight, pindex->GetBlockHash().ToString());
|
||||||
return VerifyDBResult::CORRUPTED_BLOCK_DB;
|
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());
|
LogPrintf("Verification error: found unconnectable block at %d, hash=%s (%s)\n", pindex->nHeight, pindex->GetBlockHash().ToString(), state.ToString());
|
||||||
return VerifyDBResult::CORRUPTED_BLOCK_DB;
|
return VerifyDBResult::CORRUPTED_BLOCK_DB;
|
||||||
}
|
}
|
||||||
|
coins.SetBestBlock(block_hash);
|
||||||
if (chainstate.m_chainman.m_interrupt) return VerifyDBResult::INTERRUPTED;
|
if (chainstate.m_chainman.m_interrupt) return VerifyDBResult::INTERRUPTED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -706,8 +706,36 @@ public:
|
||||||
// Block (dis)connection on a given view:
|
// Block (dis)connection on a given view:
|
||||||
DisconnectResult DisconnectBlock(const CBlock& block, const CBlockIndex* pindex, CCoinsViewCache& view)
|
DisconnectResult DisconnectBlock(const CBlock& block, const CBlockIndex* pindex, CCoinsViewCache& view)
|
||||||
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
|
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.
|
// 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);
|
bool DisconnectTip(BlockValidationState& state, DisconnectedBlockTransactions* disconnectpool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_mempool->cs);
|
||||||
|
|
|
@ -111,7 +111,7 @@ class PSBTTest(BitcoinTestFramework):
|
||||||
# Mine a transaction that credits the offline address
|
# Mine a transaction that credits the offline address
|
||||||
offline_addr = offline_node.getnewaddress(address_type="bech32m")
|
offline_addr = offline_node.getnewaddress(address_type="bech32m")
|
||||||
online_addr = w2.getnewaddress(address_type="bech32m")
|
online_addr = w2.getnewaddress(address_type="bech32m")
|
||||||
wonline.importaddress(offline_addr, "", False)
|
wonline.importaddress(offline_addr, label="", rescan=False)
|
||||||
mining_wallet = mining_node.get_wallet_rpc(self.default_wallet_name)
|
mining_wallet = mining_node.get_wallet_rpc(self.default_wallet_name)
|
||||||
mining_wallet.sendtoaddress(address=offline_addr, amount=1.0)
|
mining_wallet.sendtoaddress(address=offline_addr, amount=1.0)
|
||||||
self.generate(mining_node, nblocks=1, sync_fun=lambda: self.sync_all([online_node, mining_node]))
|
self.generate(mining_node, nblocks=1, sync_fun=lambda: self.sync_all([online_node, mining_node]))
|
||||||
|
@ -312,9 +312,9 @@ class PSBTTest(BitcoinTestFramework):
|
||||||
wmulti = self.nodes[2].get_wallet_rpc('wmulti')
|
wmulti = self.nodes[2].get_wallet_rpc('wmulti')
|
||||||
|
|
||||||
# Create all the addresses
|
# Create all the addresses
|
||||||
p2sh = wmulti.addmultisigaddress(2, [pubkey0, pubkey1, pubkey2], "", "legacy")['address']
|
p2sh = wmulti.addmultisigaddress(2, [pubkey0, pubkey1, pubkey2], label="", address_type="legacy")["address"]
|
||||||
p2wsh = wmulti.addmultisigaddress(2, [pubkey0, pubkey1, pubkey2], "", "bech32")['address']
|
p2wsh = wmulti.addmultisigaddress(2, [pubkey0, pubkey1, pubkey2], label="", address_type="bech32")["address"]
|
||||||
p2sh_p2wsh = wmulti.addmultisigaddress(2, [pubkey0, pubkey1, pubkey2], "", "p2sh-segwit")['address']
|
p2sh_p2wsh = wmulti.addmultisigaddress(2, [pubkey0, pubkey1, pubkey2], label="", address_type="p2sh-segwit")["address"]
|
||||||
p2wpkh = self.nodes[1].getnewaddress("", "bech32")
|
p2wpkh = self.nodes[1].getnewaddress("", "bech32")
|
||||||
p2pkh = self.nodes[1].getnewaddress("", "legacy")
|
p2pkh = self.nodes[1].getnewaddress("", "legacy")
|
||||||
p2sh_p2wpkh = self.nodes[1].getnewaddress("", "p2sh-segwit")
|
p2sh_p2wpkh = self.nodes[1].getnewaddress("", "p2sh-segwit")
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# Copyright (c) 2017-2022 The Bitcoin Core developers
|
# Copyright (c) 2017-present The Bitcoin Core developers
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
"""Class for bitcoind node under test"""
|
"""Class for bitcoind node under test"""
|
||||||
|
@ -209,7 +209,7 @@ class TestNode():
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
"""Dispatches any unrecognised messages to the RPC connection or a CLI instance."""
|
"""Dispatches any unrecognised messages to the RPC connection or a CLI instance."""
|
||||||
if self.use_cli:
|
if self.use_cli:
|
||||||
return getattr(RPCOverloadWrapper(self.cli, True), name)
|
return getattr(RPCOverloadWrapper(self.cli), name)
|
||||||
else:
|
else:
|
||||||
assert self.rpc_connected and self.rpc is not None, self._node_msg("Error: no RPC connection")
|
assert self.rpc_connected and self.rpc is not None, self._node_msg("Error: no RPC connection")
|
||||||
return getattr(RPCOverloadWrapper(self.rpc), name)
|
return getattr(RPCOverloadWrapper(self.rpc), name)
|
||||||
|
@ -374,7 +374,7 @@ class TestNode():
|
||||||
|
|
||||||
def get_wallet_rpc(self, wallet_name):
|
def get_wallet_rpc(self, wallet_name):
|
||||||
if self.use_cli:
|
if self.use_cli:
|
||||||
return RPCOverloadWrapper(self.cli("-rpcwallet={}".format(wallet_name)), True)
|
return RPCOverloadWrapper(self.cli("-rpcwallet={}".format(wallet_name)))
|
||||||
else:
|
else:
|
||||||
assert self.rpc_connected and self.rpc, self._node_msg("RPC not connected")
|
assert self.rpc_connected and self.rpc, self._node_msg("RPC not connected")
|
||||||
wallet_path = "wallet/{}".format(urllib.parse.quote(wallet_name))
|
wallet_path = "wallet/{}".format(urllib.parse.quote(wallet_name))
|
||||||
|
@ -925,17 +925,13 @@ class TestNodeCLI():
|
||||||
return cli_stdout.rstrip("\n")
|
return cli_stdout.rstrip("\n")
|
||||||
|
|
||||||
class RPCOverloadWrapper():
|
class RPCOverloadWrapper():
|
||||||
def __init__(self, rpc, cli=False):
|
def __init__(self, rpc):
|
||||||
self.rpc = rpc
|
self.rpc = rpc
|
||||||
self.is_cli = cli
|
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
return getattr(self.rpc, name)
|
return getattr(self.rpc, name)
|
||||||
|
|
||||||
def createwallet_passthrough(self, *args, **kwargs):
|
def importprivkey(self, privkey, *, label=None, rescan=None):
|
||||||
return self.__getattr__("createwallet")(*args, **kwargs)
|
|
||||||
|
|
||||||
def importprivkey(self, privkey, label=None, rescan=None):
|
|
||||||
wallet_info = self.getwalletinfo()
|
wallet_info = self.getwalletinfo()
|
||||||
if 'descriptors' not in wallet_info or ('descriptors' in wallet_info and not wallet_info['descriptors']):
|
if 'descriptors' not in wallet_info or ('descriptors' in wallet_info and not wallet_info['descriptors']):
|
||||||
return self.__getattr__('importprivkey')(privkey, label, rescan)
|
return self.__getattr__('importprivkey')(privkey, label, rescan)
|
||||||
|
@ -943,13 +939,13 @@ class RPCOverloadWrapper():
|
||||||
req = [{
|
req = [{
|
||||||
'desc': desc,
|
'desc': desc,
|
||||||
'timestamp': 0 if rescan else 'now',
|
'timestamp': 0 if rescan else 'now',
|
||||||
'label': label if label else ''
|
'label': label if label else '',
|
||||||
}]
|
}]
|
||||||
import_res = self.importdescriptors(req)
|
import_res = self.importdescriptors(req)
|
||||||
if not import_res[0]['success']:
|
if not import_res[0]['success']:
|
||||||
raise JSONRPCException(import_res[0]['error'])
|
raise JSONRPCException(import_res[0]['error'])
|
||||||
|
|
||||||
def addmultisigaddress(self, nrequired, keys, label=None, address_type=None):
|
def addmultisigaddress(self, nrequired, keys, *, label=None, address_type=None):
|
||||||
wallet_info = self.getwalletinfo()
|
wallet_info = self.getwalletinfo()
|
||||||
if 'descriptors' not in wallet_info or ('descriptors' in wallet_info and not wallet_info['descriptors']):
|
if 'descriptors' not in wallet_info or ('descriptors' in wallet_info and not wallet_info['descriptors']):
|
||||||
return self.__getattr__('addmultisigaddress')(nrequired, keys, label, address_type)
|
return self.__getattr__('addmultisigaddress')(nrequired, keys, label, address_type)
|
||||||
|
@ -957,14 +953,14 @@ class RPCOverloadWrapper():
|
||||||
req = [{
|
req = [{
|
||||||
'desc': cms['descriptor'],
|
'desc': cms['descriptor'],
|
||||||
'timestamp': 0,
|
'timestamp': 0,
|
||||||
'label': label if label else ''
|
'label': label if label else '',
|
||||||
}]
|
}]
|
||||||
import_res = self.importdescriptors(req)
|
import_res = self.importdescriptors(req)
|
||||||
if not import_res[0]['success']:
|
if not import_res[0]['success']:
|
||||||
raise JSONRPCException(import_res[0]['error'])
|
raise JSONRPCException(import_res[0]['error'])
|
||||||
return cms
|
return cms
|
||||||
|
|
||||||
def importpubkey(self, pubkey, label=None, rescan=None):
|
def importpubkey(self, pubkey, *, label=None, rescan=None):
|
||||||
wallet_info = self.getwalletinfo()
|
wallet_info = self.getwalletinfo()
|
||||||
if 'descriptors' not in wallet_info or ('descriptors' in wallet_info and not wallet_info['descriptors']):
|
if 'descriptors' not in wallet_info or ('descriptors' in wallet_info and not wallet_info['descriptors']):
|
||||||
return self.__getattr__('importpubkey')(pubkey, label, rescan)
|
return self.__getattr__('importpubkey')(pubkey, label, rescan)
|
||||||
|
@ -972,13 +968,13 @@ class RPCOverloadWrapper():
|
||||||
req = [{
|
req = [{
|
||||||
'desc': desc,
|
'desc': desc,
|
||||||
'timestamp': 0 if rescan else 'now',
|
'timestamp': 0 if rescan else 'now',
|
||||||
'label': label if label else ''
|
'label': label if label else '',
|
||||||
}]
|
}]
|
||||||
import_res = self.importdescriptors(req)
|
import_res = self.importdescriptors(req)
|
||||||
if not import_res[0]['success']:
|
if not import_res[0]['success']:
|
||||||
raise JSONRPCException(import_res[0]['error'])
|
raise JSONRPCException(import_res[0]['error'])
|
||||||
|
|
||||||
def importaddress(self, address, label=None, rescan=None, p2sh=None):
|
def importaddress(self, address, *, label=None, rescan=None, p2sh=None):
|
||||||
wallet_info = self.getwalletinfo()
|
wallet_info = self.getwalletinfo()
|
||||||
if 'descriptors' not in wallet_info or ('descriptors' in wallet_info and not wallet_info['descriptors']):
|
if 'descriptors' not in wallet_info or ('descriptors' in wallet_info and not wallet_info['descriptors']):
|
||||||
return self.__getattr__('importaddress')(address, label, rescan, p2sh)
|
return self.__getattr__('importaddress')(address, label, rescan, p2sh)
|
||||||
|
@ -992,13 +988,13 @@ class RPCOverloadWrapper():
|
||||||
reqs = [{
|
reqs = [{
|
||||||
'desc': desc,
|
'desc': desc,
|
||||||
'timestamp': 0 if rescan else 'now',
|
'timestamp': 0 if rescan else 'now',
|
||||||
'label': label if label else ''
|
'label': label if label else '',
|
||||||
}]
|
}]
|
||||||
if is_hex and p2sh:
|
if is_hex and p2sh:
|
||||||
reqs.append({
|
reqs.append({
|
||||||
'desc': descsum_create('p2sh(raw(' + address + '))'),
|
'desc': descsum_create('p2sh(raw(' + address + '))'),
|
||||||
'timestamp': 0 if rescan else 'now',
|
'timestamp': 0 if rescan else 'now',
|
||||||
'label': label if label else ''
|
'label': label if label else '',
|
||||||
})
|
})
|
||||||
import_res = self.importdescriptors(reqs)
|
import_res = self.importdescriptors(reqs)
|
||||||
for res in import_res:
|
for res in import_res:
|
||||||
|
|
|
@ -344,7 +344,7 @@ class AddressTypeTest(BitcoinTestFramework):
|
||||||
self.test_address(3, self.nodes[3].getrawchangeaddress(), multisig=False, typ='bech32')
|
self.test_address(3, self.nodes[3].getrawchangeaddress(), multisig=False, typ='bech32')
|
||||||
|
|
||||||
self.log.info('test invalid address type arguments')
|
self.log.info('test invalid address type arguments')
|
||||||
assert_raises_rpc_error(-5, "Unknown address type ''", self.nodes[3].addmultisigaddress, 2, [compressed_1, compressed_2], None, '')
|
assert_raises_rpc_error(-5, "Unknown address type ''", self.nodes[3].addmultisigaddress, 2, [compressed_1, compressed_2], address_type="")
|
||||||
assert_raises_rpc_error(-5, "Unknown address type ''", self.nodes[3].getnewaddress, None, '')
|
assert_raises_rpc_error(-5, "Unknown address type ''", self.nodes[3].getnewaddress, None, '')
|
||||||
assert_raises_rpc_error(-5, "Unknown address type ''", self.nodes[3].getrawchangeaddress, '')
|
assert_raises_rpc_error(-5, "Unknown address type ''", self.nodes[3].getrawchangeaddress, '')
|
||||||
assert_raises_rpc_error(-5, "Unknown address type 'bech23'", self.nodes[3].getrawchangeaddress, 'bech23')
|
assert_raises_rpc_error(-5, "Unknown address type 'bech23'", self.nodes[3].getrawchangeaddress, 'bech23')
|
||||||
|
|
|
@ -26,11 +26,12 @@ from test_framework.util import (
|
||||||
assert_raises_rpc_error,
|
assert_raises_rpc_error,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
LAST_KEYPOOL_INDEX = 9 # Index of the last derived address with the keypool size of 10
|
||||||
|
|
||||||
class BackwardsCompatibilityTest(BitcoinTestFramework):
|
class BackwardsCompatibilityTest(BitcoinTestFramework):
|
||||||
def set_test_params(self):
|
def set_test_params(self):
|
||||||
self.setup_clean_chain = True
|
self.setup_clean_chain = True
|
||||||
self.num_nodes = 10
|
self.num_nodes = 8
|
||||||
# Add new version after each release:
|
# Add new version after each release:
|
||||||
self.extra_args = [
|
self.extra_args = [
|
||||||
["-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # Pre-release: use to mine blocks. noban for immediate tx relay
|
["-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # Pre-release: use to mine blocks. noban for immediate tx relay
|
||||||
|
@ -38,11 +39,9 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
|
||||||
["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # v25.0
|
["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # v25.0
|
||||||
["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # v24.0.1
|
["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # v24.0.1
|
||||||
["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # v23.0
|
["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # v23.0
|
||||||
["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # v22.0
|
["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1", f"-keypool={LAST_KEYPOOL_INDEX + 1}"], # v22.0
|
||||||
["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # v0.21.0
|
["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # v0.21.0
|
||||||
["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # v0.20.1
|
["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # v0.20.1
|
||||||
["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # v0.19.1
|
|
||||||
["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=127.0.0.1"], # v0.18.1
|
|
||||||
]
|
]
|
||||||
self.wallet_names = [self.default_wallet_name]
|
self.wallet_names = [self.default_wallet_name]
|
||||||
|
|
||||||
|
@ -60,8 +59,6 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
|
||||||
220000,
|
220000,
|
||||||
210000,
|
210000,
|
||||||
200100,
|
200100,
|
||||||
190100,
|
|
||||||
180100,
|
|
||||||
])
|
])
|
||||||
|
|
||||||
self.start_nodes()
|
self.start_nodes()
|
||||||
|
@ -85,21 +82,98 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
|
||||||
node_major, _, _ = self.split_version(node)
|
node_major, _, _ = self.split_version(node)
|
||||||
return node_major >= major
|
return node_major >= major
|
||||||
|
|
||||||
|
def test_v22_inactivehdchain_path(self):
|
||||||
|
self.log.info("Testing inactive hd chain bad derivation path cleanup")
|
||||||
|
# 0.21.x and 22.x would both produce bad derivation paths when topping up an inactive hd chain
|
||||||
|
# Make sure that this is being automatically cleaned up by migration
|
||||||
|
node_master = self.nodes[1]
|
||||||
|
node_v22 = self.nodes[self.num_nodes - 5]
|
||||||
|
wallet_name = "bad_deriv_path"
|
||||||
|
node_v22.createwallet(wallet_name=wallet_name, descriptors=False)
|
||||||
|
bad_deriv_wallet = node_v22.get_wallet_rpc(wallet_name)
|
||||||
|
|
||||||
|
# Make a dump of the wallet to get an unused address
|
||||||
|
dump_path = node_v22.wallets_path / f"{wallet_name}.dump"
|
||||||
|
bad_deriv_wallet.dumpwallet(dump_path)
|
||||||
|
addr = None
|
||||||
|
seed = None
|
||||||
|
with open(dump_path, encoding="utf8") as f:
|
||||||
|
for line in f:
|
||||||
|
if f"hdkeypath=m/0'/0'/{LAST_KEYPOOL_INDEX}'" in line:
|
||||||
|
addr = line.split(" ")[4].split("=")[1]
|
||||||
|
elif " hdseed=1 " in line:
|
||||||
|
seed = line.split(" ")[0]
|
||||||
|
assert addr is not None
|
||||||
|
assert seed is not None
|
||||||
|
# Rotate seed and unload
|
||||||
|
bad_deriv_wallet.sethdseed()
|
||||||
|
bad_deriv_wallet.unloadwallet()
|
||||||
|
# Receive at addr to trigger inactive chain topup on next load
|
||||||
|
self.nodes[0].sendtoaddress(addr, 1)
|
||||||
|
self.generate(self.nodes[0], 1, sync_fun=self.no_op)
|
||||||
|
self.sync_all(nodes=[self.nodes[0], node_master, node_v22])
|
||||||
|
node_v22.loadwallet(wallet_name)
|
||||||
|
|
||||||
|
# Dump again to find bad hd keypath
|
||||||
|
bad_deriv_path = f"m/0'/0'/{LAST_KEYPOOL_INDEX}'/0'/0'/{LAST_KEYPOOL_INDEX + 1}'"
|
||||||
|
good_deriv_path = f"m/0h/0h/{LAST_KEYPOOL_INDEX + 1}h"
|
||||||
|
os.unlink(dump_path)
|
||||||
|
bad_deriv_wallet.dumpwallet(dump_path)
|
||||||
|
bad_path_addr = None
|
||||||
|
with open(dump_path, encoding="utf8") as f:
|
||||||
|
for line in f:
|
||||||
|
if f"hdkeypath={bad_deriv_path}" in line:
|
||||||
|
bad_path_addr = line.split(" ")[4].split("=")[1]
|
||||||
|
assert bad_path_addr is not None
|
||||||
|
assert_equal(bad_deriv_wallet.getaddressinfo(bad_path_addr)["hdkeypath"], bad_deriv_path)
|
||||||
|
|
||||||
|
# Verify that this bad derivation path addr is actually at m/0'/0'/10' by making a new wallet with the same seed but larger keypool
|
||||||
|
node_v22.createwallet(wallet_name="path_verify", descriptors=False, blank=True)
|
||||||
|
verify_wallet = node_v22.get_wallet_rpc("path_verify")
|
||||||
|
verify_wallet.sethdseed(True, seed)
|
||||||
|
# Bad addr is after keypool, so need to generate it by refilling
|
||||||
|
verify_wallet.keypoolrefill(LAST_KEYPOOL_INDEX + 2)
|
||||||
|
assert_equal(verify_wallet.getaddressinfo(bad_path_addr)["hdkeypath"], good_deriv_path.replace("h", "'"))
|
||||||
|
|
||||||
|
# Migrate with master
|
||||||
|
# Since all keymeta records are now deleted after migration, the derivation path
|
||||||
|
# should now be correct as it is derived on-the-fly from the inactive hd chain's descriptor
|
||||||
|
backup_path = node_v22.wallets_path / f"{wallet_name}.bak"
|
||||||
|
bad_deriv_wallet.backupwallet(backup_path)
|
||||||
|
wallet_dir_master = os.path.join(node_master.wallets_path, wallet_name)
|
||||||
|
os.makedirs(wallet_dir_master, exist_ok=True)
|
||||||
|
shutil.copy(backup_path, os.path.join(wallet_dir_master, "wallet.dat"))
|
||||||
|
node_master.migratewallet(wallet_name)
|
||||||
|
bad_deriv_wallet_master = node_master.get_wallet_rpc(wallet_name)
|
||||||
|
assert_equal(bad_deriv_wallet_master.getaddressinfo(bad_path_addr)["hdkeypath"], good_deriv_path)
|
||||||
|
bad_deriv_wallet_master.unloadwallet()
|
||||||
|
|
||||||
|
# If we have sqlite3, verify that there are no keymeta records
|
||||||
|
try:
|
||||||
|
import sqlite3
|
||||||
|
wallet_db = node_master.wallets_path / wallet_name / "wallet.dat"
|
||||||
|
conn = sqlite3.connect(wallet_db)
|
||||||
|
with conn:
|
||||||
|
# Retrieve all records that have the "keymeta" prefix. The remaining key data varies for each record.
|
||||||
|
keymeta_rec = conn.execute("SELECT value FROM main where key >= x'076b65796d657461' AND key < x'076b65796d657462'").fetchone()
|
||||||
|
assert_equal(keymeta_rec, None)
|
||||||
|
conn.close()
|
||||||
|
except ImportError:
|
||||||
|
self.log.warning("sqlite3 module not available, skipping lack of keymeta records check")
|
||||||
|
|
||||||
def run_test(self):
|
def run_test(self):
|
||||||
node_miner = self.nodes[0]
|
node_miner = self.nodes[0]
|
||||||
node_master = self.nodes[1]
|
node_master = self.nodes[1]
|
||||||
node_v21 = self.nodes[self.num_nodes - 4]
|
node_v21 = self.nodes[self.num_nodes - 2]
|
||||||
node_v18 = self.nodes[self.num_nodes - 1]
|
node_v20 = self.nodes[self.num_nodes - 1] # bdb only
|
||||||
|
|
||||||
legacy_nodes = self.nodes[2:] # Nodes that support legacy wallets
|
legacy_nodes = self.nodes[2:] # Nodes that support legacy wallets
|
||||||
legacy_only_nodes = self.nodes[-3:] # Nodes that only support legacy wallets
|
descriptors_nodes = self.nodes[2:-1] # Nodes that support descriptor wallets
|
||||||
descriptors_nodes = self.nodes[2:-3] # Nodes that support descriptor wallets
|
|
||||||
|
|
||||||
self.generatetoaddress(node_miner, COINBASE_MATURITY + 1, node_miner.getnewaddress())
|
self.generatetoaddress(node_miner, COINBASE_MATURITY + 1, node_miner.getnewaddress())
|
||||||
|
|
||||||
# Sanity check the test framework:
|
# Sanity check the test framework:
|
||||||
res = node_v18.getblockchaininfo()
|
assert_equal(node_v20.getblockchaininfo()["blocks"], COINBASE_MATURITY + 1)
|
||||||
assert_equal(res['blocks'], COINBASE_MATURITY + 1)
|
|
||||||
|
|
||||||
self.log.info("Test wallet backwards compatibility...")
|
self.log.info("Test wallet backwards compatibility...")
|
||||||
# Create a number of wallets and open them in older versions:
|
# Create a number of wallets and open them in older versions:
|
||||||
|
@ -206,13 +280,11 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check that descriptor wallets don't work on legacy only nodes
|
# Check that descriptor wallets don't work on legacy only nodes
|
||||||
self.log.info("Test descriptor wallet incompatibility on:")
|
self.log.info("Test descriptor wallet incompatibility on v0.20")
|
||||||
for node in legacy_only_nodes:
|
# Descriptor wallets appear to be corrupted wallets to old software
|
||||||
self.log.info(f"- {node.version}")
|
assert self.major_version_equals(node_v20, 20)
|
||||||
# Descriptor wallets appear to be corrupted wallets to old software
|
for wallet_name in ["w1", "w2", "w3"]:
|
||||||
assert self.major_version_less_than(node, 21)
|
assert_raises_rpc_error(-4, "Wallet file verification failed: wallet.dat corrupt, salvage failed", node_v20.loadwallet, wallet_name)
|
||||||
for wallet_name in ["w1", "w2", "w3"]:
|
|
||||||
assert_raises_rpc_error(-4, "Wallet file verification failed: wallet.dat corrupt, salvage failed", node.loadwallet, wallet_name)
|
|
||||||
|
|
||||||
# w1 cannot be opened by 0.21 since it contains a taproot descriptor
|
# w1 cannot be opened by 0.21 since it contains a taproot descriptor
|
||||||
self.log.info("Test that 0.21 cannot open wallet containing tr() descriptors")
|
self.log.info("Test that 0.21 cannot open wallet containing tr() descriptors")
|
||||||
|
@ -308,5 +380,7 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
|
||||||
# Legacy wallets are no longer supported. Trying to load these should result in an error
|
# Legacy wallets are no longer supported. Trying to load these should result in an error
|
||||||
assert_raises_rpc_error(-18, "The wallet appears to be a Legacy wallet, please use the wallet migration tool (migratewallet RPC)", node_master.restorewallet, wallet_name, backup_path)
|
assert_raises_rpc_error(-18, "The wallet appears to be a Legacy wallet, please use the wallet migration tool (migratewallet RPC)", node_master.restorewallet, wallet_name, backup_path)
|
||||||
|
|
||||||
|
self.test_v22_inactivehdchain_path()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
BackwardsCompatibilityTest(__file__).main()
|
BackwardsCompatibilityTest(__file__).main()
|
||||||
|
|
|
@ -192,7 +192,7 @@ class RawTransactionsTest(BitcoinTestFramework):
|
||||||
watchonly_address = self.nodes[0].getnewaddress()
|
watchonly_address = self.nodes[0].getnewaddress()
|
||||||
watchonly_pubkey = self.nodes[0].getaddressinfo(watchonly_address)["pubkey"]
|
watchonly_pubkey = self.nodes[0].getaddressinfo(watchonly_address)["pubkey"]
|
||||||
self.watchonly_amount = Decimal(200)
|
self.watchonly_amount = Decimal(200)
|
||||||
wwatch.importpubkey(watchonly_pubkey, "", True)
|
wwatch.importpubkey(watchonly_pubkey, label="", rescan=True)
|
||||||
self.watchonly_utxo = self.create_outpoints(self.nodes[0], outputs=[{watchonly_address: self.watchonly_amount}])[0]
|
self.watchonly_utxo = self.create_outpoints(self.nodes[0], outputs=[{watchonly_address: self.watchonly_amount}])[0]
|
||||||
|
|
||||||
# Lock UTXO so nodes[0] doesn't accidentally spend it
|
# Lock UTXO so nodes[0] doesn't accidentally spend it
|
||||||
|
|
|
@ -49,7 +49,7 @@ class WalletLabelsTest(BitcoinTestFramework):
|
||||||
assert_equal(response[0]['error']['message'], "Invalid label name")
|
assert_equal(response[0]['error']['message'], "Invalid label name")
|
||||||
|
|
||||||
for rpc_call in rpc_calls:
|
for rpc_call in rpc_calls:
|
||||||
assert_raises_rpc_error(-11, "Invalid label name", *rpc_call, "*")
|
assert_raises_rpc_error(-11, "Invalid label name", *rpc_call, label="*")
|
||||||
|
|
||||||
def run_test(self):
|
def run_test(self):
|
||||||
# Check that there's no UTXO on the node
|
# Check that there's no UTXO on the node
|
||||||
|
|
|
@ -24,34 +24,7 @@ SHA256_SUMS = {
|
||||||
"d86fc90824a85c38b25c8488115178d5785dbc975f5ff674f9f5716bc8ad6e65": {"tag": "v0.14.3", "tarball": "bitcoin-0.14.3-arm-linux-gnueabihf.tar.gz"},
|
"d86fc90824a85c38b25c8488115178d5785dbc975f5ff674f9f5716bc8ad6e65": {"tag": "v0.14.3", "tarball": "bitcoin-0.14.3-arm-linux-gnueabihf.tar.gz"},
|
||||||
"1b0a7408c050e3d09a8be8e21e183ef7ee570385dc41216698cc3ab392a484e7": {"tag": "v0.14.3", "tarball": "bitcoin-0.14.3-osx64.tar.gz"},
|
"1b0a7408c050e3d09a8be8e21e183ef7ee570385dc41216698cc3ab392a484e7": {"tag": "v0.14.3", "tarball": "bitcoin-0.14.3-osx64.tar.gz"},
|
||||||
"706e0472dbc933ed2757650d54cbcd780fd3829ebf8f609b32780c7eedebdbc9": {"tag": "v0.14.3", "tarball": "bitcoin-0.14.3-x86_64-linux-gnu.tar.gz"},
|
"706e0472dbc933ed2757650d54cbcd780fd3829ebf8f609b32780c7eedebdbc9": {"tag": "v0.14.3", "tarball": "bitcoin-0.14.3-x86_64-linux-gnu.tar.gz"},
|
||||||
#
|
|
||||||
"d40f18b4e43c6e6370ef7db9131f584fbb137276ec2e3dba67a4b267f81cb644": {"tag": "v0.15.2", "tarball": "bitcoin-0.15.2-aarch64-linux-gnu.tar.gz"},
|
|
||||||
"54fb877a148a6ad189a1e1ab1ff8b11181e58ff2aaf430da55b3fd46ae549a6b": {"tag": "v0.15.2", "tarball": "bitcoin-0.15.2-arm-linux-gnueabihf.tar.gz"},
|
|
||||||
"87e9340ff3d382d543b2b69112376077f0c8b4f7450d372e83b68f5a1e22b2df": {"tag": "v0.15.2", "tarball": "bitcoin-0.15.2-osx64.tar.gz"},
|
|
||||||
"566be44190fd76daa01f13d428939dadfb8e3daacefc8fa17f433cad28f73bd5": {"tag": "v0.15.2", "tarball": "bitcoin-0.15.2-x86_64-linux-gnu.tar.gz"},
|
|
||||||
#
|
|
||||||
"0768c6c15caffbaca6524824c9563b42c24f70633c681c2744649158aa3fd484": {"tag": "v0.16.3", "tarball": "bitcoin-0.16.3-aarch64-linux-gnu.tar.gz"},
|
|
||||||
"fb2818069854a6ad20ea03b28b55dbd35d8b1f7d453e90b83eace5d0098a2a87": {"tag": "v0.16.3", "tarball": "bitcoin-0.16.3-arm-linux-gnueabihf.tar.gz"},
|
|
||||||
"78c3bff3b619a19aed575961ea43cc9e142959218835cf51aede7f0b764fc25d": {"tag": "v0.16.3", "tarball": "bitcoin-0.16.3-osx64.tar.gz"},
|
|
||||||
"5d422a9d544742bc0df12427383f9c2517433ce7b58cf672b9a9b17c2ef51e4f": {"tag": "v0.16.3", "tarball": "bitcoin-0.16.3-x86_64-linux-gnu.tar.gz"},
|
|
||||||
#
|
|
||||||
"5a6b35d1a348a402f2d2d6ab5aed653a1a1f13bc63aaaf51605e3501b0733b7a": {"tag": "v0.17.2", "tarball": "bitcoin-0.17.2-aarch64-linux-gnu.tar.gz"},
|
|
||||||
"d1913a5d19c8e8da4a67d1bd5205d03c8614dfd2e02bba2fe3087476643a729e": {"tag": "v0.17.2", "tarball": "bitcoin-0.17.2-arm-linux-gnueabihf.tar.gz"},
|
|
||||||
"a783ba20706dbfd5b47fbedf42165fce70fbbc7d78003305d964f6b3da14887f": {"tag": "v0.17.2", "tarball": "bitcoin-0.17.2-osx64.tar.gz"},
|
|
||||||
"943f9362b9f11130177839116f48f809d83478b4c28591d486ee9a7e35179da6": {"tag": "v0.17.2", "tarball": "bitcoin-0.17.2-x86_64-linux-gnu.tar.gz"},
|
|
||||||
#
|
|
||||||
"88f343af72803b851c7da13874cc5525026b0b55e63e1b5e1298390c4688adc6": {"tag": "v0.18.1", "tarball": "bitcoin-0.18.1-aarch64-linux-gnu.tar.gz"},
|
|
||||||
"cc7d483e4b20c5dabd4dcaf304965214cf4934bcc029ca99cbc9af00d3771a1f": {"tag": "v0.18.1", "tarball": "bitcoin-0.18.1-arm-linux-gnueabihf.tar.gz"},
|
|
||||||
"b7bbcee7a7540f711b171d6981f939ca8482005fde22689bc016596d80548bb1": {"tag": "v0.18.1", "tarball": "bitcoin-0.18.1-osx64.tar.gz"},
|
|
||||||
"425ee5ec631ae8da71ebc1c3f5c0269c627cf459379b9b030f047107a28e3ef8": {"tag": "v0.18.1", "tarball": "bitcoin-0.18.1-riscv64-linux-gnu.tar.gz"},
|
|
||||||
"600d1db5e751fa85903e935a01a74f5cc57e1e7473c15fd3e17ed21e202cfe5a": {"tag": "v0.18.1", "tarball": "bitcoin-0.18.1-x86_64-linux-gnu.tar.gz"},
|
|
||||||
#
|
|
||||||
"3a80431717842672df682bdb619e66523b59541483297772a7969413be3502ff": {"tag": "v0.19.1", "tarball": "bitcoin-0.19.1-aarch64-linux-gnu.tar.gz"},
|
|
||||||
"657f28213823d240dd3324d14829702f9ad6f0710f8bdd1c379cb3c447197f48": {"tag": "v0.19.1", "tarball": "bitcoin-0.19.1-arm-linux-gnueabihf.tar.gz"},
|
|
||||||
"1ae1b87de26487075cd2fd22e0d4ead87d969bd55c44f2f1d873ecdc6147ebb3": {"tag": "v0.19.1", "tarball": "bitcoin-0.19.1-osx64.tar.gz"},
|
|
||||||
"aa7a9563b48aa79252c8e7b6a41c07a5441bd9f14c5e4562cc72720ea6cb0ee5": {"tag": "v0.19.1", "tarball": "bitcoin-0.19.1-riscv64-linux-gnu.tar.gz"},
|
|
||||||
"5fcac9416e486d4960e1a946145566350ca670f9aaba99de6542080851122e4c": {"tag": "v0.19.1", "tarball": "bitcoin-0.19.1-x86_64-linux-gnu.tar.gz"},
|
|
||||||
#
|
|
||||||
"60c93e3462c303eb080be7cf623f1a7684b37fd47a018ad3848bc23e13c84e1c": {"tag": "v0.20.1", "tarball": "bitcoin-0.20.1-aarch64-linux-gnu.tar.gz"},
|
"60c93e3462c303eb080be7cf623f1a7684b37fd47a018ad3848bc23e13c84e1c": {"tag": "v0.20.1", "tarball": "bitcoin-0.20.1-aarch64-linux-gnu.tar.gz"},
|
||||||
"55b577e0fb306fb429d4be6c9316607753e8543e5946b542d75d876a2f08654c": {"tag": "v0.20.1", "tarball": "bitcoin-0.20.1-arm-linux-gnueabihf.tar.gz"},
|
"55b577e0fb306fb429d4be6c9316607753e8543e5946b542d75d876a2f08654c": {"tag": "v0.20.1", "tarball": "bitcoin-0.20.1-arm-linux-gnueabihf.tar.gz"},
|
||||||
"b9024dde373ea7dad707363e07ec7e265383204127539ae0c234bff3a61da0d1": {"tag": "v0.20.1", "tarball": "bitcoin-0.20.1-osx64.tar.gz"},
|
"b9024dde373ea7dad707363e07ec7e265383204127539ae0c234bff3a61da0d1": {"tag": "v0.20.1", "tarball": "bitcoin-0.20.1-osx64.tar.gz"},
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# Copyright 2014 BitPay Inc.
|
# Copyright 2014 BitPay Inc.
|
||||||
# Copyright 2016-2017 The Bitcoin Core developers
|
# Copyright 2016-present The Bitcoin Core developers
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
"""Test framework for bitcoin utils.
|
"""Test framework for bitcoin utils.
|
||||||
|
@ -155,15 +155,16 @@ def bctest(testDir, testObj, buildenv):
|
||||||
|
|
||||||
if "error_txt" in testObj:
|
if "error_txt" in testObj:
|
||||||
want_error = testObj["error_txt"]
|
want_error = testObj["error_txt"]
|
||||||
# Compare error text
|
# A partial match instead of an exact match makes writing tests easier
|
||||||
# TODO: ideally, we'd compare the strings exactly and also assert
|
# and should be sufficient.
|
||||||
# That stderr is empty if no errors are expected. However, bitcoin-tx
|
|
||||||
# emits DISPLAY errors when running as a windows application on
|
|
||||||
# linux through wine. Just assert that the expected error text appears
|
|
||||||
# somewhere in stderr.
|
|
||||||
if want_error not in res.stderr:
|
if want_error not in res.stderr:
|
||||||
logging.error(f"Error mismatch:\nExpected: {want_error}\nReceived: {res.stderr.rstrip()}\nres: {str(res)}")
|
logging.error(f"Error mismatch:\nExpected: {want_error}\nReceived: {res.stderr.rstrip()}\nres: {str(res)}")
|
||||||
raise Exception
|
raise Exception
|
||||||
|
else:
|
||||||
|
if res.stderr:
|
||||||
|
logging.error(f"Unexpected error received: {res.stderr.rstrip()}\nres: {str(res)}")
|
||||||
|
raise Exception
|
||||||
|
|
||||||
|
|
||||||
def parse_output(a, fmt):
|
def parse_output(a, fmt):
|
||||||
"""Parse the output according to specified format.
|
"""Parse the output according to specified format.
|
||||||
|
|
Loading…
Add table
Reference in a new issue