Merge bitcoin/bitcoin#35482: fuzz: exercise the transaction-handling path in process_message(s)
What changed, and why it matters
This commit only changes Bitcoin Core's internal fuzz testing code. It makes the fuzz tests exercise more of the transaction-handling code path by toggling Initial Block Download mode and resetting the mempool between test runs. There is no change to production network, wallet, or consensus code, and no security vulnerability is being fixed or introduced.
No security action required. This is a test-quality improvement. Reviewers may verify that the new ResetChainmanAndMempool() helper behaves deterministically and that the global FakeNodeClock does not introduce unintended state leakage between fuzz targets.
Security signals we found
No production code modified
No consensus, validation, net_processing, or wallet logic changed
Only fuzz test harnesses and test utilities affected
No bug fix, bounds check, memory safety, or cryptographic change present
No CVE, advisory, or vendor security disclosure referenced
Evidence from the diff
The merge commit refactors fuzz test harnesses (process_message, process_messages, cmpctblock, p2p_handshake, utxo_snapshot) to share a single global FakeNodeClock and a common ResetChainmanAndMempool() helper. It enables the fuzz targets to leave IBD via JumpOutOfIbd() and to rebuild a deterministic mempool with P2WSH_OP_TRUE coinbases so that transaction messages can actually reach the mempool path. The diff is entirely within src/test/fuzz/ and src/test/util/.
Changed components
src/test/fuzz/process_message.cppsrc/test/fuzz/process_messages.cppsrc/test/fuzz/cmpctblock.cppsrc/test/fuzz/p2p_handshake.cppsrc/test/fuzz/utxo_snapshot.cppsrc/test/util/time.hsrc/test/util/validation.cppsrc/test/util/validation.hInspect captured patch +105 / −79
### src/test/fuzz/cmpctblock.cpp
@@ -14,7 +14,6 @@
#include <net_processing.h>
#include <netmessagemaker.h>
#include <node/blockstorage.h>
-#include <node/mining_types.h>
#include <policy/truc_policy.h>
#include <primitives/block.h>
#include <primitives/transaction.h>
@@ -26,20 +25,17 @@
#include <test/fuzz/fuzz.h>
#include <test/fuzz/util.h>
#include <test/fuzz/util/net.h>
-#include <test/util/mining.h>
#include <test/util/net.h>
#include <test/util/random.h>
#include <test/util/script.h>
#include <test/util/setup_common.h>
#include <test/util/time.h>
-#include <test/util/txmempool.h>
#include <test/util/validation.h>
#include <txmempool.h>
#include <uint256.h>
#include <util/check.h>
#include <util/task_runner.h>
#include <util/time.h>
-#include <util/translation.h>
#include <validation.h>
#include <validationinterface.h>
@@ -107,33 +103,6 @@ class FuzzedCBlockHeaderAndShortTxIDs : public CBlockHeaderAndShortTxIDs
}
};
-void ResetChainmanAndMempool(TestingSetup& setup)
-{
- SetMockTime(Params().GenesisBlock().Time());
-
- bilingual_str error{};
- setup.m_node.mempool.reset();
- setup.m_node.mempool = std::make_unique<CTxMemPool>(MemPoolOptionsForTest(setup.m_node), error);
- Assert(error.empty());
-
- setup.m_node.chainman.reset();
- setup.m_make_chainman();
- setup.LoadVerifyActivateChainstate();
-
- node::BlockCreateOptions options;
- options.coinbase_output_script = P2WSH_OP_TRUE;
-
- g_mature_coinbase.clear();
-
- for (int i = 0; i < 2 * COINBASE_MATURITY; ++i) {
- COutPoint prevout{MineBlock(setup.m_node, options)};
- if (i < COINBASE_MATURITY) {
- LOCK(cs_main);
- CAmount subsidy{setup.m_node.chainman->ActiveChainstate().CoinsTip().GetCoin(prevout)->out.nValue};
- g_mature_coinbase.emplace_back(prevout, subsidy);
- }
- }
-}
//! Used to run tasks in a std::thread to avoid DEBUG_LOCKORDER false positives.
class ImmediateBackgroundTaskRunner : public util::TaskRunnerInterface
@@ -155,15 +124,15 @@ void initialize_cmpctblock()
g_nBits = Params().GenesisBlock().nBits;
// Replace validation_signals before creating chainman and mempool so they use it.
testing_setup->m_node.validation_signals = std::make_unique<ValidationSignals>(std::make_unique<ImmediateBackgroundTaskRunner>());
- ResetChainmanAndMempool(*g_setup);
+ g_mature_coinbase = ResetChainmanAndMempool(*g_setup);
}
FUZZ_TARGET(cmpctblock, .init = initialize_cmpctblock)
{
SeedRandomStateForTest(SeedRand::ZEROS);
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
- FakeNodeClock clock{1610000000s};
+ GetFakeNodeClock().set(1610000000s);
FakeSteadyClock steady_clock;
auto setup = g_setup;
@@ -453,10 +422,10 @@ FUZZ_TARGET(cmpctblock, .init = initialize_cmpctblock)
[&]() {
// Set mock time randomly or to tip's time.
if (fuzzed_data_provider.ConsumeBool()) {
- clock.set(ConsumeTime(fuzzed_data_provider));
+ GetFakeNodeClock().set(ConsumeTime(fuzzed_data_provider));
} else {
const NodeSeconds tip_time = WITH_LOCK(::cs_main, return chainman.ActiveChain().Tip()->Time());
- clock.set(tip_time);
+ GetFakeNodeClock().set(tip_time);
}
sent_net_msg = false;
@@ -509,6 +478,6 @@ FUZZ_TARGET(cmpctblock, .init = initialize_cmpctblock)
if (initial_index_size != end_index_size || initial_sequence != end_sequence) {
MakeRandDeterministicDANGEROUS(uint256::ZERO);
- ResetChainmanAndMempool(*g_setup);
+ g_mature_coinbase = ResetChainmanAndMempool(*g_setup);
}
}
### src/test/fuzz/p2p_handshake.cpp
@@ -41,7 +41,7 @@ FUZZ_TARGET(p2p_handshake, .init = ::initialize)
auto& node{g_setup->m_node};
auto& connman{static_cast<ConnmanTestMsg&>(*node.connman)};
auto& chainman{static_cast<TestChainstateManager&>(*node.chainman)};
- FakeNodeClock clock{1610000000s}; // any time to successfully reset ibd
+ FakeNodeClock clock{1610000000s}; // 2021-01-07, arbitrary
FakeSteadyClock steady_clock;
chainman.ResetIbd();
@@ -72,6 +72,10 @@ FUZZ_TARGET(p2p_handshake, .init = ::initialize)
static_cast<ServiceFlags>(fuzzed_data_provider.ConsumeIntegral<uint64_t>()));
}
+ // Toggle IBD from within the loop, so that some messages may be processed
+ // under IBD and the rest after leaving it. JumpOutOfIbd() latches, so guard
+ // it to call at most once.
+ bool jump_out_of_ibd{false};
LIMITED_WHILE (fuzzed_data_provider.ConsumeBool(), 100) {
CNode& connection = *PickValue(fuzzed_data_provider, peers);
if (connection.fDisconnect || connection.fSuccessfullyConnected) {
@@ -80,6 +84,9 @@ FUZZ_TARGET(p2p_handshake, .init = ::initialize)
continue;
}
+ if (!jump_out_of_ibd) jump_out_of_ibd = fuzzed_data_provider.ConsumeBool();
+ if (jump_out_of_ibd && chainman.IsInitialBlockDownload()) chainman.JumpOutOfIbd();
+
clock += std::chrono::seconds{
fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(
-std::chrono::seconds{10min}.count(), // Allow mocktime to go backwards slightly
### src/test/fuzz/process_message.cpp
@@ -4,11 +4,9 @@
#include <addrman.h>
#include <banman.h>
-#include <consensus/consensus.h>
#include <kernel/chainparams.h>
#include <net.h>
#include <net_processing.h>
-#include <node/mining_types.h>
#include <primitives/block.h>
#include <primitives/transaction.h>
#include <protocol.h>
@@ -17,12 +15,12 @@
#include <test/fuzz/fuzz.h>
#include <test/fuzz/util.h>
#include <test/fuzz/util/net.h>
-#include <test/util/mining.h>
#include <test/util/net.h>
#include <test/util/random.h>
#include <test/util/setup_common.h>
#include <test/util/time.h>
#include <test/util/validation.h>
+#include <uint256.h>
#include <util/check.h>
#include <util/time.h>
#include <validation.h>
@@ -44,19 +42,10 @@ namespace {
TestingSetup* g_setup;
std::string_view LIMIT_TO_MESSAGE_TYPE{};
-void ResetChainman(TestingSetup& setup)
-{
- SetMockTime(setup.m_node.chainman->GetParams().GenesisBlock().Time());
- setup.m_node.chainman.reset();
- setup.m_make_chainman();
- setup.LoadVerifyActivateChainstate();
- for (int i = 0; i < 2 * COINBASE_MATURITY; i++) {
- node::BlockCreateOptions options;
- MineBlock(setup.m_node, options);
- }
-}
} // namespace
+extern void MakeRandDeterministicDANGEROUS(const uint256& seed) noexcept;
+
void initialize_process_message()
{
if (const auto val{std::getenv("LIMIT_TO_MESSAGE_TYPE")}) {
@@ -70,7 +59,7 @@ void initialize_process_message()
{}),
};
g_setup = testing_setup.get();
- ResetChainman(*g_setup);
+ ResetChainmanAndMempool(*g_setup);
}
FUZZ_TARGET(process_message, .init = initialize_process_message)
@@ -83,7 +72,8 @@ FUZZ_TARGET(process_message, .init = initialize_process_message)
connman.Reset();
auto& chainman{static_cast<TestChainstateManager&>(*node.chainman)};
const auto block_index_size{WITH_LOCK(chainman.GetMutex(), return chainman.BlockIndex().size())};
- FakeNodeClock clock{1610000000s}; // any time to successfully reset ibd
+ const auto initial_sequence{WITH_LOCK(node.mempool->cs, return node.mempool->GetSequence())};
+ GetFakeNodeClock().set(1610000000s); // 2021-01-07, arbitrary
FakeSteadyClock steady_clock;
chainman.ResetIbd();
chainman.DisableNextWrite();
@@ -117,7 +107,7 @@ FUZZ_TARGET(process_message, .init = initialize_process_message)
connman.AddTestNode(p2p_node);
FillNode(fuzzed_data_provider, connman, p2p_node);
- clock.set(ConsumeTime(fuzzed_data_provider));
+ GetFakeNodeClock().set(ConsumeTime(fuzzed_data_provider));
CSerializedNetMsg net_msg;
net_msg.m_type = random_message_type;
@@ -126,6 +116,10 @@ FUZZ_TARGET(process_message, .init = initialize_process_message)
connman.FlushSendBuffer(p2p_node);
(void)connman.ReceiveMsgFrom(p2p_node, std::move(net_msg));
+ if (fuzzed_data_provider.ConsumeBool()) {
+ chainman.JumpOutOfIbd();
+ }
+
bool more_work{true};
while (more_work) {
p2p_node.fPauseSend = false;
@@ -138,8 +132,10 @@ FUZZ_TARGET(process_message, .init = initialize_process_message)
node.validation_signals->SyncWithValidationInterfaceQueue();
node.validation_signals->UnregisterValidationInterface(node.peerman.get());
node.connman->StopNodes();
- if (block_index_size != WITH_LOCK(chainman.GetMutex(), return chainman.BlockIndex().size())) {
- // Reuse the global chainman, but reset it when it is dirty
- ResetChainman(*g_setup);
+ const auto end_sequence{WITH_LOCK(node.mempool->cs, return node.mempool->GetSequence())};
+ if (block_index_size != WITH_LOCK(chainman.GetMutex(), return chainman.BlockIndex().size()) || initial_sequence != end_sequence) {
+ // Reuse the global chainman and mempool, but reset them when dirty.
+ MakeRandDeterministicDANGEROUS(uint256::ZERO);
+ ResetChainmanAndMempool(*g_setup);
}
}
### src/test/fuzz/process_messages.cpp
@@ -4,11 +4,9 @@
#include <addrman.h>
#include <banman.h>
-#include <consensus/consensus.h>
#include <kernel/chainparams.h>
#include <net.h>
#include <net_processing.h>
-#include <node/mining_types.h>
#include <primitives/block.h>
#include <primitives/transaction.h>
#include <protocol.h>
@@ -17,12 +15,13 @@
#include <test/fuzz/fuzz.h>
#include <test/fuzz/util.h>
#include <test/fuzz/util/net.h>
-#include <test/util/mining.h>
#include <test/util/net.h>
#include <test/util/random.h>
#include <test/util/setup_common.h>
#include <test/util/time.h>
#include <test/util/validation.h>
+#include <uint256.h>
+#include <util/check.h>
#include <util/time.h>
#include <validation.h>
#include <validationinterface.h>
@@ -38,19 +37,10 @@
namespace {
TestingSetup* g_setup;
-void ResetChainman(TestingSetup& setup)
-{
- SetMockTime(setup.m_node.chainman->GetParams().GenesisBlock().Time());
- setup.m_node.chainman.reset();
- setup.m_make_chainman();
- setup.LoadVerifyActivateChainstate();
- node::BlockCreateOptions options;
- for (int i = 0; i < 2 * COINBASE_MATURITY; i++) {
- MineBlock(setup.m_node, options);
- }
-}
} // namespace
+extern void MakeRandDeterministicDANGEROUS(const uint256& seed) noexcept;
+
void initialize_process_messages()
{
static const auto testing_setup{
@@ -59,7 +49,7 @@ void initialize_process_messages()
{}),
};
g_setup = testing_setup.get();
- ResetChainman(*g_setup);
+ ResetChainmanAndMempool(*g_setup);
}
FUZZ_TARGET(process_messages, .init = initialize_process_messages)
@@ -72,7 +62,8 @@ FUZZ_TARGET(process_messages, .init = initialize_process_messages)
connman.Reset();
auto& chainman{static_cast<TestChainstateManager&>(*node.chainman)};
const auto block_index_size{WITH_LOCK(chainman.GetMutex(), return chainman.BlockIndex().size())};
- FakeNodeClock clock{1610000000s}; // any time to successfully reset ibd
+ const auto initial_sequence{WITH_LOCK(node.mempool->cs, return node.mempool->GetSequence())};
+ GetFakeNodeClock().set(1610000000s); // 2021-01-07, arbitrary
FakeSteadyClock steady_clock;
chainman.ResetIbd();
chainman.DisableNextWrite();
@@ -107,10 +98,16 @@ FUZZ_TARGET(process_messages, .init = initialize_process_messages)
connman.AddTestNode(p2p_node);
}
+ // Toggle IBD from within the loop, so that some messages may be processed
+ // under IBD and the rest after leaving it. JumpOutOfIbd() latches, so guard
+ // it to call at most once.
+ bool jump_out_of_ibd{false};
LIMITED_WHILE (fuzzed_data_provider.ConsumeBool(), 30) {
+ if (!jump_out_of_ibd) jump_out_of_ibd = fuzzed_data_provider.ConsumeBool();
+ if (jump_out_of_ibd && chainman.IsInitialBlockDownload()) chainman.JumpOutOfIbd();
const std::string random_message_type{fuzzed_data_provider.ConsumeBytesAsString(CMessageHeader::MESSAGE_TYPE_SIZE).c_str()};
- clock.set(ConsumeTime(fuzzed_data_provider));
+ GetFakeNodeClock().set(ConsumeTime(fuzzed_data_provider));
CSerializedNetMsg net_msg;
net_msg.m_type = random_message_type;
@@ -135,8 +132,10 @@ FUZZ_TARGET(process_messages, .init = initialize_process_messages)
node.validation_signals->SyncWithValidationInterfaceQueue();
node.validation_signals->UnregisterValidationInterface(node.peerman.get());
node.connman->StopNodes();
- if (block_index_size != WITH_LOCK(chainman.GetMutex(), return chainman.BlockIndex().size())) {
- // Reuse the global chainman, but reset it when it is dirty
- ResetChainman(*g_setup);
+ const auto end_sequence{WITH_LOCK(node.mempool->cs, return node.mempool->GetSequence())};
+ if (block_index_size != WITH_LOCK(chainman.GetMutex(), return chainman.BlockIndex().size()) || initial_sequence != end_sequence) {
+ // Reuse the global chainman and mempool, but reset them when dirty.
+ MakeRandDeterministicDANGEROUS(uint256::ZERO);
+ ResetChainmanAndMempool(*g_setup);
}
}
### src/test/fuzz/utxo_snapshot.cpp
@@ -73,7 +73,7 @@ void initialize_chain()
const auto params{CreateChainParams(ArgsManager{}, ChainType::REGTEST)};
static const auto chain{CreateBlockChain(2 * COINBASE_MATURITY, *params)};
g_chain = &chain;
- SetMockTime(chain.back()->Time());
+ GetFakeNodeClock().set(chain.back()->Time());
// Make sure we can generate a valid snapshot.
sanity_check_snapshot();
@@ -104,7 +104,7 @@ void utxo_snapshot_fuzz(FuzzBufferType buffer)
{
SeedRandomStateForTest(SeedRand::ZEROS);
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
- FakeNodeClock clock{ConsumeTime(fuzzed_data_provider, /*min=*/1296688602)}; // regtest genesis block timestamp
+ GetFakeNodeClock().set(ConsumeTime(fuzzed_data_provider, /*min=*/1296688602)); // regtest genesis block timestamp
auto& setup{*g_setup};
bool dirty_chainman{false}; // Reuse the global chainman, but reset it when it is dirty
auto& chainman{*setup.m_node.chainman};
### src/test/util/time.h
@@ -76,4 +76,10 @@ class FakeNodeClock : public LimitOne<FakeNodeClock>
void operator-=(std::chrono::seconds d) { set(m_t -= d); }
};
+inline FakeNodeClock& GetFakeNodeClock()
+{
+ static FakeNodeClock g_fake_node_clock{0s};
+ return g_fake_node_clock;
+}
+
#endif // BITCOIN_TEST_UTIL_TIME_H
### src/test/util/validation.cpp
@@ -4,12 +4,25 @@
#include <test/util/validation.h>
+#include <coins.h>
+#include <consensus/consensus.h>
#include <node/blockstorage.h>
+#include <node/mining_types.h>
+#include <test/util/mining.h>
+#include <test/util/script.h>
+#include <test/util/setup_common.h>
+#include <test/util/time.h>
+#include <test/util/txmempool.h>
+#include <txmempool.h>
#include <util/check.h>
#include <util/time.h>
#include <validation.h>
#include <validationinterface.h>
+#include <memory>
+#include <utility>
+#include <vector>
+
using kernel::ChainstateRole;
void TestBlockManager::CleanupForFuzzing()
@@ -91,3 +104,31 @@ void TestChainstateManager::ResetBestInvalid()
{
m_best_invalid = nullptr;
}
+
+std::vector<std::pair<COutPoint, CAmount>> ResetChainmanAndMempool(TestingSetup& setup)
+{
+ GetFakeNodeClock().set(setup.m_node.chainman->GetParams().GenesisBlock().Time());
+
+ bilingual_str error{};
+ setup.m_node.mempool.reset();
+ setup.m_node.mempool = std::make_unique<CTxMemPool>(MemPoolOptionsForTest(setup.m_node), error);
+ Assert(error.empty());
+
+ setup.m_node.chainman.reset();
+ setup.m_make_chainman();
+ setup.LoadVerifyActivateChainstate();
+
+ node::BlockCreateOptions options;
+ options.coinbase_output_script = P2WSH_OP_TRUE;
+
+ std::vector<std::pair<COutPoint, CAmount>> mature_coinbase;
+ for (int i = 0; i < 2 * COINBASE_MATURITY; ++i) {
+ COutPoint prevout{MineBlock(setup.m_node, options)};
+ if (i < COINBASE_MATURITY) {
+ LOCK(cs_main);
+ CAmount subsidy{setup.m_node.chainman->ActiveChainstate().CoinsTip().GetCoin(prevout)->out.nValue};
+ mature_coinbase.emplace_back(prevout, subsidy);
+ }
+ }
+ return mature_coinbase;
+}
### src/test/util/validation.h
@@ -5,12 +5,18 @@
#ifndef BITCOIN_TEST_UTIL_VALIDATION_H
#define BITCOIN_TEST_UTIL_VALIDATION_H
+#include <consensus/amount.h>
+#include <primitives/transaction.h>
#include <validation.h>
+#include <utility>
+#include <vector>
+
namespace node {
class BlockManager;
}
class CValidationInterface;
+struct TestingSetup;
struct TestBlockManager : public node::BlockManager {
/** Test-only method to clear internal state for fuzzing */
@@ -41,4 +47,6 @@ class ValidationInterfaceTest
const CBlockIndex* pindex);
};
+std::vector<std::pair<COutPoint, CAmount>> ResetChainmanAndMempool(TestingSetup& setup);
+
#endif // BITCOIN_TEST_UTIL_VALIDATION_HWhy 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.