From b11456386b266b8c0a319b6fad3481b8eeb155cf Mon Sep 17 00:00:00 2001 From: Hao Xu Date: Fri, 12 Jun 2026 19:30:21 +0800 Subject: [PATCH] fuzz: let the test input toggle IBD in the p2p fuzz targets process_message, process_messages and p2p_handshake put the node in IBD via ResetIbd(), so net_processing returns early at the IsInitialBlockDownload() check and almost never exercises the non-IBD transaction-handling code paths behind it. Only ConnectTip() latches the node back out, through UpdateIBDStatus(). p2p_handshake connects no block, and in process_message the single message is the whole iteration, so for those two the non-IBD paths are out of reach entirely. process_messages could reach them if one of its messages carried a block building on the tip, but that is unlikely. For process_message(s), leaving IBD used to be controllable from the fuzz input via a jump_out_of_ibd bool that called JumpOutOfIbd(). Commit fa0a864b (#20908) dropped that toggle because mocktime made it redundant: back then IsInitialBlockDownload() evaluated the tip timestamp against the current mocked time on every call, so SetMockTime(ConsumeTime(...)) alone could drive the node in and out of IBD. That stopped working in #34253, which turned IsInitialBlockDownload() into a lock-free read of the cached m_cached_is_ibd flag, latched only by UpdateIBDStatus() on chain activation; mocktime no longer affects it. p2p_handshake never had such a toggle. Restore the toggle. In process_message (a single message), the bool is consumed last. In process_messages and p2p_handshake, the toggle lives inside the message loop, as it did before fa0a864b. A latched bool decides before each message whether to call JumpOutOfIbd(), so some messages can be handled under IBD and the rest after leaving it. Fixes: #34253 --- src/test/fuzz/p2p_handshake.cpp | 9 ++++++++- src/test/fuzz/process_message.cpp | 6 +++++- src/test/fuzz/process_messages.cpp | 8 +++++++- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/test/fuzz/p2p_handshake.cpp b/src/test/fuzz/p2p_handshake.cpp index a71a61b85c9..9b657d68360 100644 --- a/src/test/fuzz/p2p_handshake.cpp +++ b/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(*node.connman)}; auto& chainman{static_cast(*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(fuzzed_data_provider.ConsumeIntegral())); } + // 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( -std::chrono::seconds{10min}.count(), // Allow mocktime to go backwards slightly diff --git a/src/test/fuzz/process_message.cpp b/src/test/fuzz/process_message.cpp index b4a08cfdd29..932a4c2fa15 100644 --- a/src/test/fuzz/process_message.cpp +++ b/src/test/fuzz/process_message.cpp @@ -83,7 +83,7 @@ FUZZ_TARGET(process_message, .init = initialize_process_message) connman.Reset(); auto& chainman{static_cast(*node.chainman)}; const auto block_index_size{WITH_LOCK(chainman.GetMutex(), return chainman.BlockIndex().size())}; - GetFakeNodeClock().set(1610000000s); // any time to successfully reset ibd + GetFakeNodeClock().set(1610000000s); // 2021-01-07, arbitrary FakeSteadyClock steady_clock; chainman.ResetIbd(); chainman.DisableNextWrite(); @@ -126,6 +126,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; diff --git a/src/test/fuzz/process_messages.cpp b/src/test/fuzz/process_messages.cpp index cec2bc3fb5d..094c956f777 100644 --- a/src/test/fuzz/process_messages.cpp +++ b/src/test/fuzz/process_messages.cpp @@ -72,7 +72,7 @@ FUZZ_TARGET(process_messages, .init = initialize_process_messages) connman.Reset(); auto& chainman{static_cast(*node.chainman)}; const auto block_index_size{WITH_LOCK(chainman.GetMutex(), return chainman.BlockIndex().size())}; - GetFakeNodeClock().set(1610000000s); // any time to successfully reset ibd + GetFakeNodeClock().set(1610000000s); // 2021-01-07, arbitrary FakeSteadyClock steady_clock; chainman.ResetIbd(); chainman.DisableNextWrite(); @@ -107,7 +107,13 @@ 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()}; GetFakeNodeClock().set(ConsumeTime(fuzzed_data_provider));