Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Makefile.test.include
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,7 @@ test_fuzz_fuzz_SOURCES = \
test/fuzz/tx_out.cpp \
test/fuzz/tx_pool.cpp \
test/fuzz/utxo_snapshot.cpp \
test/fuzz/utxo_total_supply.cpp \
test/fuzz/validation_load_mempool.cpp \
test/fuzz/versionbits.cpp
endif # ENABLE_FUZZ_BINARY
Expand Down
2 changes: 1 addition & 1 deletion src/bench/block_assemble.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ static void AssembleBlock(benchmark::Bench& bench)
std::array<CTransactionRef, NUM_BLOCKS - COINBASE_MATURITY + 1> txs;
for (size_t b{0}; b < NUM_BLOCKS; ++b) {
CMutableTransaction tx;
tx.vin.push_back(MineBlock(test_setup->m_node, SCRIPT_PUB));
tx.vin.push_back(CTxIn{MineBlock(test_setup->m_node, SCRIPT_PUB)});
tx.vin.back().scriptSig = scriptSig;
tx.vout.emplace_back(1337, SCRIPT_PUB);
if (NUM_BLOCKS - b >= COINBASE_MATURITY)
Expand Down
2 changes: 1 addition & 1 deletion src/node/coinstats.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ static bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats&
uint256 prevkey;
std::map<uint32_t, Coin> outputs;
while (pcursor->Valid()) {
interruption_point();
if (interruption_point) interruption_point();
COutPoint key;
Coin coin;
if (pcursor->GetKey(key) && pcursor->GetValue(coin)) {
Expand Down
6 changes: 3 additions & 3 deletions src/test/fuzz/tx_pool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,12 @@ void initialize_tx_pool()
g_setup = testing_setup.get();

for (int i = 0; i < 2 * COINBASE_MATURITY; ++i) {
CTxIn in = MineBlock(g_setup->m_node, CScript() << OP_TRUE);
// Remember the txids to avoid expensive disk acess later on
COutPoint prevout{MineBlock(g_setup->m_node, CScript() << OP_TRUE)};
// Remember the txids to avoid expensive disk access later on
Comment thread
PastaPastaPasta marked this conversation as resolved.
auto& outpoints = i < COINBASE_MATURITY ?
g_outpoints_coinbase_init_mature :
g_outpoints_coinbase_init_immature;
outpoints.push_back(in.prevout);
outpoints.push_back(prevout);
}
SyncWithValidationInterfaceQueue();
}
Expand Down
167 changes: 167 additions & 0 deletions src/test/fuzz/utxo_total_supply.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// 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 <node/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
// In Dash, chainstate is loaded in TestingSetup constructor
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(cs_main);
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 */
std::unique_ptr<node::CCoinsStats> utxo_stats = std::make_unique<node::CCoinsStats>(node::CoinStatsHashType::NONE);
/** 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(cs_main);
chainman.ActiveChainstate().ForceFlushStateToDisk();
node::CCoinsStats new_stats{node::CoinStatsHashType::NONE};
[[maybe_unused]] bool success = node::GetUTXOStats(&chainman.ActiveChainstate().CoinsDB(), chainman.m_blockman, new_stats, {});
assert(success);
utxo_stats = std::make_unique<node::CCoinsStats>(std::move(new_stats));
// 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 += GetBlockSubsidyInner(current_block->nBits, ActiveHeight(), Params().GetConsensus(), false);

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
current_block->hashMerkleRoot = BlockMerkleRoot(*current_block);
const bool was_valid = !MineBlock(node, current_block).IsNull();

const auto prev_hash = utxo_stats->hashSerialized;
if (was_valid) {
circulation += GetBlockSubsidyInner(current_block->nBits, ActiveHeight(), Params().GetConsensus(), false);

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_hash == utxo_stats->hashSerialized);
}

current_block = PrepareNextBlock();
StoreLastTxo();
});
}
}
48 changes: 42 additions & 6 deletions src/test/util/mining.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,25 @@

#include <chainparams.h>
#include <consensus/merkle.h>
#include <consensus/validation.h>
#include <evo/evodb.h>
#include <key_io.h>
#include <node/context.h>
#include <node/miner.h>
#include <pow.h>
#include <primitives/transaction.h>
#include <script/standard.h>
#include <spork.h>
#include <test/util/script.h>
#include <util/check.h>
#include <validation.h>
#include <validationinterface.h>
#include <versionbits.h>

using node::BlockAssembler;
using node::NodeContext;

CTxIn generatetoaddress(const NodeContext& node, const std::string& address)
COutPoint generatetoaddress(const NodeContext& node, const std::string& address)
{
const auto dest = DecodeDestination(address);
assert(IsValidDestination(dest));
Expand Down Expand Up @@ -61,19 +64,52 @@ std::vector<std::shared_ptr<CBlock>> CreateBlockChain(size_t total_height, const
return ret;
}

CTxIn MineBlock(const NodeContext& node, const CScript& coinbase_scriptPubKey)
COutPoint MineBlock(const NodeContext& node, const CScript& coinbase_scriptPubKey)
{
auto block = PrepareBlock(node, coinbase_scriptPubKey);
auto valid = MineBlock(node, block);
assert(!valid.IsNull());
return valid;
}

struct BlockValidationStateCatcher : public CValidationInterface {
const uint256 m_hash;
std::optional<BlockValidationState> m_state;

BlockValidationStateCatcher(const uint256& hash)
: m_hash{hash},
m_state{} {}

protected:
void BlockChecked(const CBlock& block, const BlockValidationState& state) override
{
if (block.GetHash() != m_hash) return;
m_state = state;
}
};

COutPoint MineBlock(const NodeContext& node, std::shared_ptr<CBlock>& block)
{
while (!CheckProofOfWork(block->GetHash(), block->nBits, Params().GetConsensus())) {
++block->nNonce;
assert(block->nNonce);
}

bool processed{Assert(node.chainman)->ProcessNewBlock(Params(), block, true, nullptr)};
assert(processed);

return CTxIn{block->vtx[0]->GetHash(), 0};
auto& chainman{*Assert(node.chainman)};
const auto old_height = WITH_LOCK(cs_main, return chainman.ActiveHeight());
bool new_block;
BlockValidationStateCatcher bvsc{block->GetHash()};
RegisterValidationInterface(&bvsc);
const bool processed{chainman.ProcessNewBlock(Params(), block, true, &new_block)};
const bool duplicate{!new_block && processed};
assert(!duplicate);
UnregisterValidationInterface(&bvsc);
SyncWithValidationInterfaceQueue();
const bool was_valid{bvsc.m_state && bvsc.m_state->IsValid()};
assert(old_height + was_valid == WITH_LOCK(cs_main, return chainman.ActiveHeight()));

if (was_valid) return {block->vtx[0]->GetHash(), 0};
return {};
}

std::shared_ptr<CBlock> PrepareBlock(const NodeContext& node, const CScript& coinbase_scriptPubKey)
Expand Down
12 changes: 9 additions & 3 deletions src/test/util/mining.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@

class CBlock;
class CChainParams;
class COutPoint;
class CScript;
class CTxIn;
namespace node {
struct NodeContext;
} // namespace node
Expand All @@ -21,12 +21,18 @@ struct NodeContext;
std::vector<std::shared_ptr<CBlock>> CreateBlockChain(size_t total_height, const CChainParams& params);

/** Returns the generated coin */
CTxIn MineBlock(const node::NodeContext&, const CScript& coinbase_scriptPubKey);
COutPoint MineBlock(const node::NodeContext&, const CScript& coinbase_scriptPubKey);

/**
* Returns the generated coin (or Null if the block was invalid).
* It is recommended to call RegenerateCommitments before mining the block to avoid merkle tree mismatches.
**/
COutPoint MineBlock(const node::NodeContext&, std::shared_ptr<CBlock>& block);

/** Prepare a block to be mined */
std::shared_ptr<CBlock> PrepareBlock(const node::NodeContext&, const CScript& coinbase_scriptPubKey);

/** RPC-like helper function, returns the generated coin */
CTxIn generatetoaddress(const node::NodeContext&, const std::string& address);
COutPoint generatetoaddress(const node::NodeContext&, const std::string& address);

#endif // BITCOIN_TEST_UTIL_MINING_H
6 changes: 6 additions & 0 deletions src/test/util/setup_common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,12 @@ ChainTestingSetup::~ChainTestingSetup()
m_node.chainman.reset();
}

void ChainTestingSetup::LoadVerifyActivateChainstate()
{
// In Dash, chainstate is loaded in TestingSetup constructor
// This function exists for Bitcoin compatibility but is not needed
}

TestingSetup::TestingSetup(const std::string& chainName, const std::vector<const char*>& extra_args)
: ChainTestingSetup(chainName, extra_args)
{
Expand Down
5 changes: 5 additions & 0 deletions src/test/util/setup_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,14 @@ struct BasicTestingSetup {
*/
struct ChainTestingSetup : public BasicTestingSetup {
node::CacheSizes m_cache_sizes{};
bool m_coins_db_in_memory{true};
bool m_block_tree_db_in_memory{true};

explicit ChainTestingSetup(const std::string& chainName = CBaseChainParams::MAIN, const std::vector<const char*>& extra_args = {});
~ChainTestingSetup();

// Supplies a chainstate, if one is needed
void LoadVerifyActivateChainstate();
};

/** Testing setup that configures a complete environment.
Expand Down