mirror of https://github.com/bitcoin/bitcoin
parent
faae7d5c00
commit
fa2d8b61f9
@ -0,0 +1,165 @@
|
|||||||
|
// Copyright (c) 2020 The Bitcoin Core developers
|
||||||
|
// Distributed under the MIT software license, see the accompanying
|
||||||
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
#include <chainparams.h>
|
||||||
|
#include <consensus/consensus.h>
|
||||||
|
#include <consensus/merkle.h>
|
||||||
|
#include <kernel/coinstats.h>
|
||||||
|
#include <node/miner.h>
|
||||||
|
#include <script/interpreter.h>
|
||||||
|
#include <streams.h>
|
||||||
|
#include <test/fuzz/FuzzedDataProvider.h>
|
||||||
|
#include <test/fuzz/fuzz.h>
|
||||||
|
#include <test/fuzz/util.h>
|
||||||
|
#include <test/util/mining.h>
|
||||||
|
#include <test/util/setup_common.h>
|
||||||
|
#include <validation.h>
|
||||||
|
#include <version.h>
|
||||||
|
|
||||||
|
FUZZ_TARGET(utxo_total_supply)
|
||||||
|
{
|
||||||
|
/** The testing setup that creates a chainman only (no chainstate) */
|
||||||
|
ChainTestingSetup test_setup{
|
||||||
|
CBaseChainParams::REGTEST,
|
||||||
|
{
|
||||||
|
"-testactivationheight=bip34@2",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
// Create chainstate
|
||||||
|
test_setup.LoadVerifyActivateChainstate();
|
||||||
|
auto& node{test_setup.m_node};
|
||||||
|
auto& chainman{*Assert(test_setup.m_node.chainman)};
|
||||||
|
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
|
||||||
|
|
||||||
|
const auto ActiveHeight = [&]() {
|
||||||
|
LOCK(chainman.GetMutex());
|
||||||
|
return chainman.ActiveHeight();
|
||||||
|
};
|
||||||
|
const auto PrepareNextBlock = [&]() {
|
||||||
|
// Use OP_FALSE to avoid BIP30 check from hitting early
|
||||||
|
auto block = PrepareBlock(node, CScript{} << OP_FALSE);
|
||||||
|
// Replace OP_FALSE with OP_TRUE
|
||||||
|
{
|
||||||
|
CMutableTransaction tx{*block->vtx.back()};
|
||||||
|
tx.vout.at(0).scriptPubKey = CScript{} << OP_TRUE;
|
||||||
|
block->vtx.back() = MakeTransactionRef(tx);
|
||||||
|
}
|
||||||
|
return block;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** The block template this fuzzer is working on */
|
||||||
|
auto current_block = PrepareNextBlock();
|
||||||
|
/** Append-only set of tx outpoints, entries are not removed when spent */
|
||||||
|
std::vector<std::pair<COutPoint, CTxOut>> txos;
|
||||||
|
/** The utxo stats at the chain tip */
|
||||||
|
kernel::CCoinsStats utxo_stats;
|
||||||
|
/** The total amount of coins in the utxo set */
|
||||||
|
CAmount circulation{0};
|
||||||
|
|
||||||
|
|
||||||
|
// Store the tx out in the txo map
|
||||||
|
const auto StoreLastTxo = [&]() {
|
||||||
|
// get last tx
|
||||||
|
const CTransaction& tx = *current_block->vtx.back();
|
||||||
|
// get last out
|
||||||
|
const uint32_t i = tx.vout.size() - 1;
|
||||||
|
// store it
|
||||||
|
txos.emplace_back(COutPoint{tx.GetHash(), i}, tx.vout.at(i));
|
||||||
|
if (current_block->vtx.size() == 1 && tx.vout.at(i).scriptPubKey[0] == OP_RETURN) {
|
||||||
|
// also store coinbase
|
||||||
|
const uint32_t i = tx.vout.size() - 2;
|
||||||
|
txos.emplace_back(COutPoint{tx.GetHash(), i}, tx.vout.at(i));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const auto AppendRandomTxo = [&](CMutableTransaction& tx) {
|
||||||
|
const auto& txo = txos.at(fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, txos.size() - 1));
|
||||||
|
tx.vin.emplace_back(txo.first);
|
||||||
|
tx.vout.emplace_back(txo.second.nValue, txo.second.scriptPubKey); // "Forward" coin with no fee
|
||||||
|
};
|
||||||
|
const auto UpdateUtxoStats = [&]() {
|
||||||
|
LOCK(chainman.GetMutex());
|
||||||
|
chainman.ActiveChainstate().ForceFlushStateToDisk();
|
||||||
|
utxo_stats = std::move(
|
||||||
|
*Assert(kernel::ComputeUTXOStats(kernel::CoinStatsHashType::NONE, &chainman.ActiveChainstate().CoinsDB(), chainman.m_blockman, {})));
|
||||||
|
// Check that miner can't print more money than they are allowed to
|
||||||
|
assert(circulation == utxo_stats.total_amount);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Update internal state to chain tip
|
||||||
|
StoreLastTxo();
|
||||||
|
UpdateUtxoStats();
|
||||||
|
assert(ActiveHeight() == 0);
|
||||||
|
// Get at which height we duplicate the coinbase
|
||||||
|
// Assuming that the fuzzer will mine relatively short chains (less than 200 blocks), we want the duplicate coinbase to be not too high.
|
||||||
|
// Up to 2000 seems reasonable.
|
||||||
|
int64_t duplicate_coinbase_height = fuzzed_data_provider.ConsumeIntegralInRange(0, 20 * COINBASE_MATURITY);
|
||||||
|
// Always pad with OP_0 at the end to avoid bad-cb-length error
|
||||||
|
const CScript duplicate_coinbase_script = CScript() << duplicate_coinbase_height << OP_0;
|
||||||
|
// Mine the first block with this duplicate
|
||||||
|
current_block = PrepareNextBlock();
|
||||||
|
StoreLastTxo();
|
||||||
|
|
||||||
|
{
|
||||||
|
// Create duplicate (CScript should match exact format as in CreateNewBlock)
|
||||||
|
CMutableTransaction tx{*current_block->vtx.front()};
|
||||||
|
tx.vin.at(0).scriptSig = duplicate_coinbase_script;
|
||||||
|
|
||||||
|
// Mine block and create next block template
|
||||||
|
current_block->vtx.front() = MakeTransactionRef(tx);
|
||||||
|
}
|
||||||
|
current_block->hashMerkleRoot = BlockMerkleRoot(*current_block);
|
||||||
|
assert(!MineBlock(node, current_block).IsNull());
|
||||||
|
circulation += GetBlockSubsidy(ActiveHeight(), Params().GetConsensus());
|
||||||
|
|
||||||
|
assert(ActiveHeight() == 1);
|
||||||
|
UpdateUtxoStats();
|
||||||
|
current_block = PrepareNextBlock();
|
||||||
|
StoreLastTxo();
|
||||||
|
|
||||||
|
LIMITED_WHILE(fuzzed_data_provider.remaining_bytes(), 100'000)
|
||||||
|
{
|
||||||
|
CallOneOf(
|
||||||
|
fuzzed_data_provider,
|
||||||
|
[&] {
|
||||||
|
// Append an input-output pair to the last tx in the current block
|
||||||
|
CMutableTransaction tx{*current_block->vtx.back()};
|
||||||
|
AppendRandomTxo(tx);
|
||||||
|
current_block->vtx.back() = MakeTransactionRef(tx);
|
||||||
|
StoreLastTxo();
|
||||||
|
},
|
||||||
|
[&] {
|
||||||
|
// Append a tx to the list of txs in the current block
|
||||||
|
CMutableTransaction tx{};
|
||||||
|
AppendRandomTxo(tx);
|
||||||
|
current_block->vtx.push_back(MakeTransactionRef(tx));
|
||||||
|
StoreLastTxo();
|
||||||
|
},
|
||||||
|
[&] {
|
||||||
|
// Append the current block to the active chain
|
||||||
|
node::RegenerateCommitments(*current_block, chainman);
|
||||||
|
const bool was_valid = !MineBlock(node, current_block).IsNull();
|
||||||
|
|
||||||
|
const auto prev_utxo_stats = utxo_stats;
|
||||||
|
if (was_valid) {
|
||||||
|
circulation += GetBlockSubsidy(ActiveHeight(), Params().GetConsensus());
|
||||||
|
|
||||||
|
if (duplicate_coinbase_height == ActiveHeight()) {
|
||||||
|
// we mined the duplicate coinbase
|
||||||
|
assert(current_block->vtx.at(0)->vin.at(0).scriptSig == duplicate_coinbase_script);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateUtxoStats();
|
||||||
|
|
||||||
|
if (!was_valid) {
|
||||||
|
// utxo stats must not change
|
||||||
|
assert(prev_utxo_stats.hashSerialized == utxo_stats.hashSerialized);
|
||||||
|
}
|
||||||
|
|
||||||
|
current_block = PrepareNextBlock();
|
||||||
|
StoreLastTxo();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue