#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 <util/chaintype.h>
#include <util/time.h>
#include <validation.h>
using node::BlockAssembler;
FUZZ_TARGET(utxo_total_supply)
{
SeedRandomStateForTest(SeedRand::ZEROS);
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
SetMockTime(ConsumeTime(fuzzed_data_provider, 1296688602));
ChainTestingSetup test_setup{
ChainType::REGTEST,
{
.extra_args = {
"-testactivationheight=bip34@2",
},
},
};
test_setup.LoadVerifyActivateChainstate();
auto& node{test_setup.m_node};
auto& chainman{*Assert(test_setup.m_node.chainman)};
const auto ActiveHeight = [&]() {
LOCK(chainman.GetMutex());
return chainman.ActiveHeight();
};
BlockAssembler::Options options;
options.coinbase_output_script = CScript() << OP_FALSE;
const auto PrepareNextBlock = [&]() {
auto block = PrepareBlock(node, options);
{
CMutableTransaction tx{*block->vtx.back()};
tx.nLockTime = 0; tx.vout.at(0).scriptPubKey = CScript{} << OP_TRUE;
block->vtx.back() = MakeTransactionRef(tx);
}
return block;
};
auto current_block = PrepareNextBlock();
std::vector<std::pair<COutPoint, CTxOut>> txos;
kernel::CCoinsStats utxo_stats;
CAmount circulation{0};
const auto StoreLastTxo = [&]() {
const CTransaction& tx = *current_block->vtx.back();
const uint32_t i = tx.vout.size() - 1;
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) {
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); };
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, {})));
assert(circulation == utxo_stats.total_amount);
};
StoreLastTxo();
UpdateUtxoStats();
assert(ActiveHeight() == 0);
int64_t duplicate_coinbase_height = fuzzed_data_provider.ConsumeIntegralInRange(0, 300);
const CScript duplicate_coinbase_script = CScript() << duplicate_coinbase_height << OP_0;
current_block = PrepareNextBlock();
StoreLastTxo();
{
CMutableTransaction tx{*current_block->vtx.front()};
tx.vin.at(0).scriptSig = duplicate_coinbase_script;
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(), 2'00)
{
CallOneOf(
fuzzed_data_provider,
[&] {
CMutableTransaction tx{*current_block->vtx.back()};
AppendRandomTxo(tx);
current_block->vtx.back() = MakeTransactionRef(tx);
StoreLastTxo();
},
[&] {
CMutableTransaction tx{};
AppendRandomTxo(tx);
current_block->vtx.push_back(MakeTransactionRef(tx));
StoreLastTxo();
},
[&] {
node::RegenerateCommitments(*current_block, chainman);
const bool was_valid = !MineBlock(node, current_block).IsNull();
const auto prev_utxo_stats = utxo_stats;
if (was_valid) {
if (duplicate_coinbase_height == ActiveHeight()) {
assert(current_block->vtx.at(0)->vin.at(0).scriptSig == duplicate_coinbase_script);
}
circulation += GetBlockSubsidy(ActiveHeight(), Params().GetConsensus());
}
UpdateUtxoStats();
if (!was_valid) {
assert(prev_utxo_stats.hashSerialized == utxo_stats.hashSerialized);
}
current_block = PrepareNextBlock();
StoreLastTxo();
});
}
}