fuzz: mine blocks and send headers for them in cmpctblock harness
What changed, and why it matters
This commit only changes Bitcoin Core's internal fuzz testing code. It expands a fuzz harness so that the test can mine fake blocks and send headers for them, improving test coverage for compact block processing. There is no change to production network code, wallets, consensus rules, or RPC interfaces, so it does not introduce a security vulnerability in the software users run.
No action required. This is a test-only improvement and does not affect production security posture.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The diff modifies src/test/fuzz/cmpctblock.cpp, src/test/fuzz/p2p_headers_presync.cpp, and src/test/fuzz/util.h. It adds helper logic to create CBlock objects with valid proof-of-work headers, optionally include mempool and non-mempool transactions, and send HEADERS network messages during fuzzing. FinalizeHeader is moved from p2p_headers_presync.cpp to util.h so both harnesses can share it. All changes are confined to test/fuzz targets and do not alter net_processing, validation, mempool, or consensus behavior in release builds.
Changed components
src/test/fuzz/cmpctblock.cppsrc/test/fuzz/p2p_headers_presync.cppsrc/test/fuzz/util.hInspect captured patch +130 / −10
diff --git a/src/test/fuzz/cmpctblock.cpp b/src/test/fuzz/cmpctblock.cpp
index a4ad037d..de8cec7b 100644
--- a/src/test/fuzz/cmpctblock.cpp
+++ b/src/test/fuzz/cmpctblock.cpp
@@ -8,9 +8,11 @@
#include <coins.h>
#include <consensus/amount.h>
#include <consensus/consensus.h>
+#include <consensus/merkle.h>
#include <net.h>
#include <net_processing.h>
#include <netmessagemaker.h>
+#include <node/blockstorage.h>
#include <node/miner.h>
#include <policy/truc_policy.h>
#include <primitives/block.h>
@@ -60,6 +62,14 @@ TestingSetup* g_setup;
const CAmount AMOUNT_FEE{1000};
//! Cached coinbases that each iteration can copy and use.
std::vector<std::pair<COutPoint, CAmount>> g_mature_coinbase;
+//! Constant value used to create valid headers.
+uint32_t g_nBits;
+//! One for each block the fuzzer generates.
+struct BlockInfo {
+ std::shared_ptr<CBlock> block;
+ uint256 hash;
+ uint32_t height;
+};
void ResetChainmanAndMempool(TestingSetup& setup)
{
@@ -98,6 +108,7 @@ void initialize_cmpctblock()
{
static const auto testing_setup = MakeNoLogFileContext<TestingSetup>();
g_setup = testing_setup.get();
+ g_nBits = Params().GenesisBlock().nBits;
ResetChainmanAndMempool(*g_setup);
}
@@ -113,6 +124,7 @@ FUZZ_TARGET(cmpctblock, .init = initialize_cmpctblock)
auto& chainman = static_cast<TestChainstateManager&>(*setup->m_node.chainman);
chainman.ResetIbd();
chainman.DisableNextWrite();
+ const size_t initial_index_size{WITH_LOCK(chainman.GetMutex(), return chainman.BlockIndex().size())};
AddrMan addrman{*setup->m_node.netgroupman, /*deterministic=*/true, /*consistency_check_ratio=*/0};
auto& connman = *static_cast<ConnmanTestMsg*>(setup->m_node.connman.get());
@@ -137,6 +149,9 @@ FUZZ_TARGET(cmpctblock, .init = initialize_cmpctblock)
connman.AddTestNode(p2p_node);
}
+ // Stores blocks generated this iteration.
+ std::vector<BlockInfo> info;
+
// Coinbase UTXOs for this iteration.
std::vector<std::pair<COutPoint, CAmount>> mature_coinbase = g_mature_coinbase;
@@ -147,7 +162,7 @@ FUZZ_TARGET(cmpctblock, .init = initialize_cmpctblock)
tx_mut.version = fuzzed_data_provider.ConsumeBool() ? CTransaction::CURRENT_VERSION : TRUC_VERSION;
tx_mut.nLockTime = fuzzed_data_provider.ConsumeBool() ? 0 : fuzzed_data_provider.ConsumeIntegral<uint32_t>();
- // If the mempool is non-empty, sometimes choose a mempool outpoint. Otherwise, choose a coinbase.
+ // Choose an outpoint from the mempool, created blocks, or coinbases.
CAmount amount_in;
COutPoint outpoint;
unsigned long mempool_size = mempool.size();
@@ -156,6 +171,14 @@ FUZZ_TARGET(cmpctblock, .init = initialize_cmpctblock)
CTransactionRef tx = WITH_LOCK(mempool.cs, return mempool.txns_randomized[random_idx].second->GetSharedTx(););
outpoint = COutPoint(tx->GetHash(), 0);
amount_in = tx->vout[0].nValue;
+ } else if (info.size() != 0 && fuzzed_data_provider.ConsumeBool()) {
+ // These blocks (and txs) may be invalid, use a spent output, or not be in the main chain.
+ auto info_it = info.begin();
+ std::advance(info_it, fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, info.size() - 1));
+ auto tx_it = info_it->block->vtx.begin();
+ std::advance(tx_it, fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, info_it->block->vtx.size() - 1));
+ outpoint = COutPoint(tx_it->get()->GetHash(), 0);
+ amount_in = tx_it->get()->vout[0].nValue;
} else {
auto coinbase_it = mature_coinbase.begin();
std::advance(coinbase_it, fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, mature_coinbase.size() - 1));
@@ -181,6 +204,78 @@ FUZZ_TARGET(cmpctblock, .init = initialize_cmpctblock)
return tx;
};
+ auto create_block = [&]() {
+ uint256 prev;
+ uint32_t height;
+
+ if (info.size() == 0 || fuzzed_data_provider.ConsumeBool()) {
+ LOCK(cs_main);
+ prev = chainman.ActiveChain().Tip()->GetBlockHash();
+ height = chainman.ActiveChain().Height() + 1;
+ } else {
+ size_t index = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, info.size() - 1);
+ prev = info[index].hash;
+ height = info[index].height + 1;
+ }
+
+ const auto new_time = WITH_LOCK(::cs_main, return chainman.ActiveChain().Tip()->GetMedianTimePast() + 1);
+
+ CBlockHeader header;
+ header.nNonce = 0;
+ header.hashPrevBlock = prev;
+ header.nBits = g_nBits;
+ header.nTime = new_time;
+ header.nVersion = fuzzed_data_provider.ConsumeIntegral<int32_t>();
+
+ std::shared_ptr<CBlock> block = std::make_shared<CBlock>();
+ *block = header;
+
+ CMutableTransaction coinbase_tx;
+ coinbase_tx.vin.resize(1);
+ coinbase_tx.vin[0].prevout.SetNull();
+ coinbase_tx.vin[0].scriptSig = CScript() << height << OP_0;
+ coinbase_tx.vout.resize(1);
+ coinbase_tx.vout[0].scriptPubKey = CScript() << OP_TRUE;
+ coinbase_tx.vout[0].nValue = COIN;
+ block->vtx.push_back(MakeTransactionRef(coinbase_tx));
+
+ const auto mempool_size = mempool.size();
+ if (fuzzed_data_provider.ConsumeBool() && mempool_size != 0) {
+ // Add txns from the mempool. Since we do not include parents, it may be an invalid block.
+ size_t num_txns = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, mempool_size);
+ size_t random_idx = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, mempool_size - 1);
+
+ LOCK(mempool.cs);
+ for (size_t i = random_idx; i < random_idx + num_txns; ++i) {
+ CTransactionRef mempool_tx = mempool.txns_randomized[i % mempool_size].second->GetSharedTx();
+ block->vtx.push_back(mempool_tx);
+ }
+ }
+
+ // Create and add (possibly invalid) txns that are not in the mempool.
+ if (fuzzed_data_provider.ConsumeBool()) {
+ size_t new_txns = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, 10);
+ for (size_t i = 0; i < new_txns; ++i) {
+ CTransactionRef non_mempool_tx = create_tx();
+ block->vtx.push_back(non_mempool_tx);
+ }
+ }
+
+ CBlockIndex* pindexPrev{WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(prev))};
+ chainman.GenerateCoinbaseCommitment(*block, pindexPrev);
+
+ bool mutated;
+ block->hashMerkleRoot = BlockMerkleRoot(*block, &mutated);
+ FinalizeHeader(*block, chainman);
+
+ BlockInfo block_info;
+ block_info.block = block;
+ block_info.hash = block->GetHash();
+ block_info.height = height;
+
+ return block_info;
+ };
+
LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 1000)
{
CSerializedNetMsg net_msg;
@@ -191,6 +286,23 @@ FUZZ_TARGET(cmpctblock, .init = initialize_cmpctblock)
CallOneOf(
fuzzed_data_provider,
+ [&]() {
+ // Send a headers message for an existing block (if one exists).
+ size_t num_blocks = info.size();
+ if (num_blocks == 0) {
+ sent_net_msg = false;
+ return;
+ }
+
+ // Choose an existing block and send a HEADERS message for it.
+ size_t index = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, num_blocks - 1);
+ CBlock block = *info[index].block;
+ block.vtx.clear(); // No tx in HEADERS.
+ std::vector<CBlock> headers;
+ headers.emplace_back(block);
+
+ net_msg = NetMsg::Make(NetMsgType::HEADERS, TX_WITH_WITNESS(headers));
+ },
[&]() {
// Send a sendcmpct message, optionally setting hb mode.
bool hb = fuzzed_data_provider.ConsumeBool();
@@ -200,6 +312,12 @@ FUZZ_TARGET(cmpctblock, .init = initialize_cmpctblock)
sent_sendcmpct = true;
valid_sendcmpct = version == CMPCTBLOCKS_VERSION;
},
+ [&]() {
+ // Mine a block, but don't send it.
+ BlockInfo block_info = create_block();
+ info.push_back(block_info);
+ sent_net_msg = false;
+ },
[&]() {
// Send a transaction.
CTransactionRef tx = create_tx();
@@ -247,9 +365,10 @@ FUZZ_TARGET(cmpctblock, .init = initialize_cmpctblock)
setup->m_node.validation_signals->UnregisterAllValidationInterfaces();
connman.StopNodes();
+ const size_t end_index_size{WITH_LOCK(chainman.GetMutex(), return chainman.BlockIndex().size())};
const uint64_t end_sequence{WITH_LOCK(mempool.cs, return mempool.GetSequence())};
- if (initial_sequence != end_sequence) {
+ if (initial_index_size != end_index_size || initial_sequence != end_sequence) {
MakeRandDeterministicDANGEROUS(uint256::ZERO);
ResetChainmanAndMempool(*g_setup);
}
diff --git a/src/test/fuzz/p2p_headers_presync.cpp b/src/test/fuzz/p2p_headers_presync.cpp
index 704e447f..0e60cb2a 100644
--- a/src/test/fuzz/p2p_headers_presync.cpp
+++ b/src/test/fuzz/p2p_headers_presync.cpp
@@ -8,7 +8,6 @@
#include <net_processing.h>
#include <netmessagemaker.h>
#include <node/peerman_args.h>
-#include <pow.h>
#include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h>
#include <test/fuzz/util.h>
@@ -143,13 +142,6 @@ CBlock ConsumeBlock(FuzzedDataProvider& fuzzed_data_provider, const uint256& pre
return block;
}
-void FinalizeHeader(CBlockHeader& header, const ChainstateManager& chainman)
-{
- while (!CheckProofOfWork(header.GetHash(), header.nBits, chainman.GetParams().GetConsensus())) {
- ++(header.nNonce);
- }
-}
-
// Global setup works for this test as state modification (specifically in the
// block index) would indicate a bug.
HeadersSyncSetup* g_testing_setup;
diff --git a/src/test/fuzz/util.h b/src/test/fuzz/util.h
index aa1eed8b..6fb8267b 100644
--- a/src/test/fuzz/util.h
+++ b/src/test/fuzz/util.h
@@ -13,6 +13,7 @@
#include <consensus/consensus.h>
#include <key.h>
#include <merkleblock.h>
+#include <pow.h>
#include <primitives/transaction.h>
#include <script/script.h>
#include <serialize.h>
@@ -20,6 +21,7 @@
#include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h>
#include <uint256.h>
+#include <validation.h>
#include <algorithm>
#include <array>
@@ -336,4 +338,11 @@ void ReadFromStream(FuzzedDataProvider& fuzzed_data_provider, Stream& stream) no
}
}
+inline void FinalizeHeader(CBlockHeader& header, const ChainstateManager& chainman)
+{
+ while (!CheckProofOfWork(header.GetHash(), header.nBits, chainman.GetParams().GetConsensus())) {
+ ++(header.nNonce);
+ }
+}
+
#endif // BITCOIN_TEST_FUZZ_UTIL_H
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.