Compare commits

...

19 commits

Author SHA1 Message Date
Reproducibility Matters
f1da436246
Merge 76a8f22c5c into 65714c162c 2025-04-29 01:15:41 +02:00
Ava Chow
65714c162c
Merge bitcoin/bitcoin#32327: test: Add missing check for empty stderr in util tester
Some checks are pending
CI / test each commit (push) Waiting to run
CI / macOS 14 native, arm64, no depends, sqlite only, gui (push) Waiting to run
CI / macOS 14 native, arm64, fuzz (push) Waiting to run
CI / Windows native, VS 2022 (push) Waiting to run
CI / Windows native, fuzz, VS 2022 (push) Waiting to run
CI / Linux->Windows cross, no tests (push) Waiting to run
CI / Windows, test cross-built (push) Blocked by required conditions
CI / ASan + LSan + UBSan + integer, no depends, USDT (push) Waiting to run
fadf12a56c test: Add missing check for empty stderr in util tester (MarcoFalke)

Pull request description:

  Now that wine support was removed from the CI in 25b56fd9b4, it can probably be removed from the util tester as well.

  If someone really needs this, they can comment the new check out, or submit a patch to add an option/env var to silence the new check.

ACKs for top commit:
  achow101:
    ACK fadf12a56c
  i-am-yuvi:
    tACK fadf12a56c
  BrandonOdiwuor:
    Code Review ACK fadf12a56c
  ismaelsadeeq:
    Tested ACK fadf12a56c

Tree-SHA512: d9e4d7a7f724e114391070ea7f17b585a7e4c4f3221c3bf510eeb11df6ccd089b881ab5654adfef8d3a1f8fa7ec6bf5e3a3feeb0cdfe724a8f3e5a146c388e66
2025-04-28 14:43:32 -07:00
merge-script
a4eee6d50b
Merge bitcoin/bitcoin#29124: test: Test that migration automatically repairs corrupted metadata with doubled derivation path
c7e2b9e264 tests: Test migration cleans up bad inactive chain derivation path (Ava Chow)

Pull request description:

  A bug in 0.21.x and 22.x resulted in some wallets having invalid derivation paths that are the concatenation of two derivation paths. These appear only when inactive hd chains are topped up.

  Since key metadata is a legacy wallet only record, migrating legacy wallets to descriptor wallets will fix this issue as all key metadata records are deleted. The derivation path information is derived on-the-fly from the descriptor that is produced for the inactive hd chain.

  Thus we only need a test to verify that the derivation paths are good, and that all key metadata records are deleted from the migrated wallet.

ACKs for top commit:
  murchandamus:
    re-ACK c7e2b9e264 via range-diff:
  rkrux:
    re-ACK c7e2b9e264
  furszy:
    utACK c7e2b9e264

Tree-SHA512: 3117c4a43798972109fe2d3539341a8b69db70c6457fcabdd019e6044834dc4b17212abbc006d7b8008f560dce4b7856142b057981b9404f406d58fa0955cbd9
2025-04-28 17:13:42 -04:00
Ava Chow
af6cffa36d
Merge bitcoin/bitcoin#32350: test: Slim down previous releases bdb check
fa58f40b89 test: Slim down previous releases bdb check (MarcoFalke)

Pull request description:

  The check iterates over several previous BDB-only releases to check that descriptor wallets are considered "corrupt" when loading. It is unclear why this needs to be done for more than one release.

  Avoid the confusion by removing the unused releases from the test and from the download script.

ACKs for top commit:
  achow101:
    ACK fa58f40b89
  rkrux:
    ACK fa58f40b89

Tree-SHA512: 8084392481bfe1fba9b80bb865ffbdfa454e9e6e14e02c39fa3f61c1a596b1def2c531c5da1c7566e5fddb77ac7e56f19feabaaf9b5af043fa6c230d9e2370b5
2025-04-28 12:56:22 -07:00
Ava Chow
33e6538b30
Merge bitcoin/bitcoin#32360: test: Force named args for RPCOverloadWrapper optional args
fa48be3ba4 test: Force named args for RPCOverloadWrapper optional args (MarcoFalke)
aaaa45399c test: Remove unused createwallet_passthrough (MarcoFalke)
cccc1f4e91 test: Remove unused RPCOverloadWrapper is_cli field (MarcoFalke)

Pull request description:

  This can avoid bugs and makes the test code easier to read, because the
  order of positional args does not have to be known or assumed.

  Also, contains two commits to remove dead code.

ACKs for top commit:
  achow101:
    ACK fa48be3ba4
  rkrux:
    tACK fa48be3ba4
  janb84:
    tACK [fa48be3](fa48be3ba4)

Tree-SHA512: d938fbc18be5035ad0d0e1ad2bf7297b2b66ede3bb2d3f10b8d27aa2a19d27a897b024a5f5a2a1cceca467837890729c26054928cb06acbe282b9e9eea94ae69
2025-04-28 12:41:00 -07:00
MarcoFalke
fa48be3ba4
test: Force named args for RPCOverloadWrapper optional args
This can avoid bugs and makes the test code easier to read, because the
order of positional args does not have to be known or assumed.
2025-04-28 15:15:05 +02:00
MarcoFalke
aaaa45399c
test: Remove unused createwallet_passthrough 2025-04-28 15:14:52 +02:00
MarcoFalke
cccc1f4e91
test: Remove unused RPCOverloadWrapper is_cli field 2025-04-28 10:18:59 +02:00
Ava Chow
c7e2b9e264 tests: Test migration cleans up bad inactive chain derivation path
A bug in 0.21.x and 22.x resulted in some wallets having invalid
derivation paths that are the concatenation of two derivation paths.
These appear only when inactive hd chains are topped up.

Since key metadata is a legacy wallet only record, migrating legacy
wallets to descriptor wallets will fix this issue as all key metadata
records are deleted. The derivation path information is derived
on-the-fly from the descriptor that is produced for the inactive hd
chain.

Thus we only need a test to verify that the derivation paths are good,
and that all key metadata records are deleted from the migrated wallet.
2025-04-25 09:12:53 -07:00
MarcoFalke
fa58f40b89
test: Slim down previous releases bdb check 2025-04-25 16:04:07 +02:00
MarcoFalke
fadf12a56c
test: Add missing check for empty stderr in util tester 2025-04-22 17:54:38 +02:00
TheCharlatan
76a8f22c5c
doc: Add docstrings for ConnectBlock and SpendBlock 2025-04-21 21:50:03 +02:00
TheCharlatan
8221e89ac4
validation: Move coin existence and spend check to SpendBlock
Move the remaining UTXO-related operations from ConnectBlock to
SpendBlock. This includes moving the existence check, the UpdateCoins
call, and CBlockUndo generation.

ConnectBlock now takes a pre-populated CBlockUndo as an argument and no
longer accesses or manipulates the UTXO set.
2025-04-21 21:47:54 +02:00
TheCharlatan
e1f88913b7
validation: Move SetBestBlock out of ConnectBlock
This is a part of a series of commits for removing access to the
CCoinsViewCache in consensus verification functions. The goal is to
allow calling verification functions with pre-fetched, or a user-defined
set of coins.
2025-04-21 21:18:21 +02:00
TheCharlatan
008748c3ae
validation: Add SpendBlock function
Move the BIP30 checks from ConnectBlock to a new SpendBlock method.
This is a move-only change, more content to SpendBlock is added in the
next commits. The goal is to move logic requiring access to the
CCoinsViewCache out of ConnectBlock and to the new SpendBlock method.
SpendBlock will in future handle all UTXO set interactions that
previously took place in ConnectBlock.

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

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

This is a part of a series of commits for removing access to the
CCoinsViewCache in consensus verification functions. The goal is to
allow calling verification functions with pre-fetched, or a user-defined
set of coins.
2025-04-21 21:18:20 +02:00
TheCharlatan
1eb575214d
validation: Use vector of outputs instead of CCoinsViewCache in CheckInputScripts
Move the responsibility of retrieving coins from the CCoinsViewCache
in CheckInputScripts to its caller.

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

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

This is a part of a series of commits for removing access to the
CCoinsViewCache in consensus verification functions. The goal is to
allow calling verification functions with pre-fetched, or a user-defined
set of coins.
2025-04-21 21:18:18 +02:00
TheCharlatan
c1285a81de
consensus: Use Coin span in CheckTxInputs
Move the responsibility of retrieving coins from CheckTxInputs
to its caller. The missing inputs check will be moved in an upcoming
commit.

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

Define two explicit template specializations for both a span of
references to coins and a span of coins. This allows using it for both
Coin entries referenced from the CCoinsViewCache, and from contiguous
memory, like the vector in CBlockUndo.
2025-04-21 21:18:14 +02:00
TheCharlatan
781668b6e8
consensus: Use Coin span in GetTransactionSigOpCost
Move the responsibility of retrieving coins from GetTransactionSigOpCost
to its caller.

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

Define two explicit template specializations for both a span of
references to coins and a span of coins. This allows using it for both
Coin entries referenced from the CCoinsViewCache, and from contiguous
memory, like the vector in CBlockUndo.
2025-04-21 18:14:24 +02:00
TheCharlatan
a7e4132623
consensus: Use Coin span in GetP2SHSigOpCount
Move the responsibility of retrieving coins from GetP2SHSigOpCount to
its caller.

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

Define two explicit template specializations for both a span of
references to coins and a span of coins. This allows using it for both
Coin entries referenced from the CCoinsViewCache, and from contiguous
memory, like the vector in CBlockUndo.
2025-04-21 18:14:22 +02:00
22 changed files with 544 additions and 273 deletions

View file

@ -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));
}); });
} }

View file

@ -160,6 +160,29 @@ const Coin& CCoinsViewCache::AccessCoin(const COutPoint &outpoint) const {
} }
} }
std::vector<std::reference_wrapper<const Coin>> CCoinsViewCache::AccessCoins(const CTransaction& tx) const
{
std::vector<std::reference_wrapper<const Coin>> coins;
coins.reserve(tx.vin.size());
for (const CTxIn& input : tx.vin) {
coins.emplace_back(AccessCoin(input.prevout));
}
return coins;
}
std::vector<CTxOut> CCoinsViewCache::GetUnspentOutputs(const CTransaction& tx) const
{
std::vector<CTxOut> spent_outputs;
spent_outputs.reserve(tx.vin.size());
for (const auto& txin : tx.vin) {
const COutPoint& prevout = txin.prevout;
const Coin& coin = AccessCoin(prevout);
assert(!coin.IsSpent());
spent_outputs.emplace_back(coin.out);
}
return spent_outputs;
}
bool CCoinsViewCache::HaveCoin(const COutPoint &outpoint) const { 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());

View file

@ -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.

View file

@ -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);

View file

@ -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

View file

@ -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);

View file

@ -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

View file

@ -255,7 +255,10 @@ FUZZ_TARGET(coins_view, .init = initialize_coins_view)
// It is not allowed to call CheckTxInputs if CheckTransaction failed // It is not allowed to call CheckTxInputs if CheckTransaction failed
return; return;
} }
if (Consensus::CheckTxInputs(transaction, state, coins_view_cache, fuzzed_data_provider.ConsumeIntegralInRange<int>(0, std::numeric_limits<int>::max()), tx_fee_out)) { // are the actual inputs available?
if (!coins_view_cache.HaveInputs(transaction)) return;
auto coins{coins_view_cache.AccessCoins(transaction)};
if (Consensus::CheckTxInputs(transaction, state, 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);

View file

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

View file

@ -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);
} }
} }

View file

@ -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);
} }

View file

@ -777,7 +777,9 @@ void CTxMemPool::check(const CCoinsViewCache& active_coins_tip, int64_t spendhei
TxValidationState dummy_state; // Not used. CheckTxInputs() should always pass 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());
} }

View file

@ -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;
} }
} }

View file

@ -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);

View file

@ -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")

View file

@ -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:

View file

@ -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')

View file

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

View file

@ -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

View file

@ -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

View file

@ -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"},

View file

@ -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.