fuzz: send compact blocks in cmpctblock harness
What changed, and why it matters
This commit only adds new fuzz testing code for Bitcoin Core's compact block (CMPCTBLOCK) handling. It does not change any production network or consensus code, so it cannot introduce a runtime security vulnerability in the software users run. It is a test-harness improvement.
No security action required. Review as normal test code if desired.
Security signals we found
No production code changed
Only fuzz test harness modified
Assertions added are test-only invariants
No memory corruption, authentication bypass, or consensus change signals present
Evidence from the diff
The diff extends src/test/fuzz/cmpctblock.cpp to generate and send compact block messages during fuzzing. It adds a helper class FuzzedCBlockHeaderAndShortTxIDs to manipulate protected prefilledtxn and shorttxids fields, plus logic to randomly prefill transactions and assert invariants. No production P2P, mempool, consensus, or validation code is modified.
Changed components
src/test/fuzz/cmpctblock.cppInspect captured patch +105 / −0
diff --git a/src/test/fuzz/cmpctblock.cpp b/src/test/fuzz/cmpctblock.cpp
index de8cec7b..e14930a0 100644
--- a/src/test/fuzz/cmpctblock.cpp
+++ b/src/test/fuzz/cmpctblock.cpp
@@ -3,6 +3,7 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <addrman.h>
+#include <blockencodings.h>
#include <chain.h>
#include <chainparams.h>
#include <coins.h>
@@ -70,6 +71,40 @@ struct BlockInfo {
uint256 hash;
uint32_t height;
};
+//! Used to access prefilledtxn and shorttxids.
+class FuzzedCBlockHeaderAndShortTxIDs : public CBlockHeaderAndShortTxIDs
+{
+ using CBlockHeaderAndShortTxIDs::CBlockHeaderAndShortTxIDs;
+
+public:
+ void AddPrefilledTx(PrefilledTransaction&& prefilledtx)
+ {
+ prefilledtxn.push_back(std::move(prefilledtx));
+ }
+
+ void RemoveCoinbasePrefill()
+ {
+ prefilledtxn.erase(prefilledtxn.begin());
+ }
+
+ void InsertCoinbaseShortTxID(uint64_t shorttxid)
+ {
+ shorttxids.insert(shorttxids.begin(), shorttxid);
+ }
+
+ void EraseShortTxIDs(size_t index)
+ {
+ shorttxids.erase(shorttxids.begin() + index);
+ }
+
+ size_t PrefilledTxCount() {
+ return prefilledtxn.size();
+ }
+
+ size_t ShortTxIDCount() {
+ return shorttxids.size();
+ }
+};
void ResetChainmanAndMempool(TestingSetup& setup)
{
@@ -286,6 +321,64 @@ FUZZ_TARGET(cmpctblock, .init = initialize_cmpctblock)
CallOneOf(
fuzzed_data_provider,
+ [&]() {
+ // Send a compact block.
+ std::shared_ptr<CBlock> cblock;
+
+ // Pick an existing block or create a new block.
+ if (fuzzed_data_provider.ConsumeBool() && info.size() != 0) {
+ size_t index = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, info.size() - 1);
+ cblock = info[index].block;
+ } else {
+ BlockInfo block_info = create_block();
+ cblock = block_info.block;
+ info.push_back(block_info);
+ }
+
+ uint64_t nonce = fuzzed_data_provider.ConsumeIntegral<uint64_t>();
+ FuzzedCBlockHeaderAndShortTxIDs cmpctblock(*cblock, nonce);
+
+ if (fuzzed_data_provider.ConsumeBool()) {
+ CBlockHeaderAndShortTxIDs base_cmpctblock = cmpctblock;
+ net_msg = NetMsg::Make(NetMsgType::CMPCTBLOCK, base_cmpctblock);
+ return;
+ }
+
+ int prev_idx = 0;
+ size_t num_erased = 1;
+ size_t num_txs = cblock->vtx.size();
+
+ for (size_t i = 0; i < num_txs; ++i) {
+ if (i == 0) {
+ // Handle the coinbase specially. We either keep it prefilled or remove it.
+ if (fuzzed_data_provider.ConsumeBool()) continue;
+
+ // Remove the prefilled coinbase.
+ num_erased = 0;
+ uint64_t coinbase_shortid = cmpctblock.GetShortID(cblock->vtx[0]->GetWitnessHash());
+ cmpctblock.RemoveCoinbasePrefill();
+ cmpctblock.InsertCoinbaseShortTxID(coinbase_shortid);
+ continue;
+ }
+
+ if (fuzzed_data_provider.ConsumeBool()) continue;
+
+ uint16_t prefill_idx = num_erased == 0 ? i : i - prev_idx - 1;
+ prev_idx = i;
+ CTransactionRef txref = cblock->vtx[i];
+ PrefilledTransaction prefilledtx = {/*index=*/prefill_idx, txref};
+ cmpctblock.AddPrefilledTx(std::move(prefilledtx));
+
+ // Remove from shorttxids since we've prefilled. Subtract however many txs have been prefilled.
+ cmpctblock.EraseShortTxIDs(i - num_erased);
+ ++num_erased;
+ }
+
+ assert(cmpctblock.PrefilledTxCount() + cmpctblock.ShortTxIDCount() == num_txs);
+
+ CBlockHeaderAndShortTxIDs base_cmpctblock = cmpctblock;
+ net_msg = NetMsg::Make(NetMsgType::CMPCTBLOCK, base_cmpctblock);
+ },
[&]() {
// Send a headers message for an existing block (if one exists).
size_t num_blocks = info.size();
@@ -354,6 +447,18 @@ FUZZ_TARGET(cmpctblock, .init = initialize_cmpctblock)
std::vector<CNodeStats> stats;
connman.GetNodeStats(stats);
+ // We should have at maximum 3 HB peers.
+ int num_hb = 0;
+ for (const CNodeStats& stat : stats) {
+ if (stat.m_bip152_highbandwidth_to) {
+ // HB peers cannot be feelers or other "special" connections (besides addr-fetch).
+ CNode* hb_peer = peers[stat.nodeid];
+ if (!hb_peer->fDisconnect) num_hb += 1;
+ assert(hb_peer->IsInboundConn() || hb_peer->IsOutboundOrBlockRelayConn() || hb_peer->IsManualConn() || hb_peer->IsAddrFetchConn());
+ }
+ }
+ assert(num_hb <= 3);
+
if (sent_sendcmpct && !random_node.fDisconnect) {
// If the fuzzer sent SENDCMPCT with proper version, check the node's state matches what it sent.
const CNodeStats& random_node_stats = stats[random_node.GetId()];
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.