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