Coverage Report

Created: 2026-08-14 17:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/root/bitcoin/src/test/fuzz/p2p_headers_presync.cpp
Line
Count
Source
1
// Copyright (c) 2024-present The Bitcoin Core developers
2
// Distributed under the MIT software license, see the accompanying
3
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5
#include <arith_uint256.h>
6
#include <blockencodings.h>
7
#include <net.h>
8
#include <net_processing.h>
9
#include <netmessagemaker.h>
10
#include <node/peerman_args.h>
11
#include <test/fuzz/FuzzedDataProvider.h>
12
#include <test/fuzz/fuzz.h>
13
#include <test/fuzz/util.h>
14
#include <test/util/net.h>
15
#include <test/util/script.h>
16
#include <test/util/setup_common.h>
17
#include <test/util/time.h>
18
#include <uint256.h>
19
#include <validation.h>
20
21
namespace {
22
constexpr uint32_t FUZZ_MAX_HEADERS_RESULTS{16};
23
24
class HeadersSyncSetup : public TestingSetup
25
{
26
    std::vector<CNode*> m_connections;
27
28
public:
29
0
    HeadersSyncSetup(const ChainType chain_type, TestOpts opts) : TestingSetup(chain_type, opts)
30
0
    {
31
0
        PeerManager::Options peerman_opts;
32
0
        node::ApplyArgsManOptions(*m_node.args, peerman_opts);
33
0
        peerman_opts.max_headers_result = FUZZ_MAX_HEADERS_RESULTS;
34
        // The peerman's rng is a global that is reused, so it will be reused
35
        // and may cause non-determinism between runs. This may even influence
36
        // the global RNG, because seeding may be done from the global one. For
37
        // now, avoid it influencing the global RNG, and initialize it with a
38
        // constant instead.
39
0
        peerman_opts.deterministic_rng = true;
40
        // No txs are relayed. Disable irrelevant and possibly
41
        // non-deterministic code paths.
42
0
        peerman_opts.ignore_incoming_txs = true;
43
0
        m_node.peerman = PeerManager::make(*m_node.connman, *m_node.addrman,
44
0
                                           m_node.banman.get(), *m_node.chainman,
45
0
                                           *m_node.mempool, *m_node.warnings, peerman_opts);
46
47
0
        CConnman::Options options;
48
0
        options.m_msgproc = m_node.peerman.get();
49
0
        m_node.connman->Init(options);
50
0
    }
51
52
    void ResetAndInitialize() EXCLUSIVE_LOCKS_REQUIRED(NetEventsInterface::g_msgproc_mutex);
53
    void SendMessage(FuzzedDataProvider& fuzzed_data_provider, CSerializedNetMsg&& msg)
54
        EXCLUSIVE_LOCKS_REQUIRED(NetEventsInterface::g_msgproc_mutex);
55
};
56
57
void HeadersSyncSetup::ResetAndInitialize()
58
0
{
59
0
    m_connections.clear();
60
0
    auto& connman = static_cast<ConnmanTestMsg&>(*m_node.connman);
61
0
    connman.StopNodes();
62
63
0
    static NodeId id{0};
64
0
    std::vector<ConnectionType> conn_types = {
65
0
        ConnectionType::OUTBOUND_FULL_RELAY,
66
0
        ConnectionType::BLOCK_RELAY,
67
0
        ConnectionType::INBOUND
68
0
    };
69
70
0
    for (auto conn_type : conn_types) {
  Branch (70:25): [True: 0, False: 0]
71
0
        CAddress addr{};
72
0
        m_connections.push_back(new CNode(id++, nullptr, addr, 0, 0, addr, "", conn_type, false, 0));
73
0
        CNode& p2p_node = *m_connections.back();
74
75
0
        connman.Handshake(
76
0
            /*node=*/p2p_node,
77
0
            /*successfully_connected=*/true,
78
0
            /*remote_services=*/ServiceFlags(NODE_NETWORK | NODE_WITNESS),
79
0
            /*local_services=*/ServiceFlags(NODE_NETWORK | NODE_WITNESS),
80
0
            /*version=*/PROTOCOL_VERSION,
81
0
            /*relay_txs=*/true);
82
83
0
        connman.AddTestNode(p2p_node);
84
0
    }
85
0
}
86
87
void HeadersSyncSetup::SendMessage(FuzzedDataProvider& fuzzed_data_provider, CSerializedNetMsg&& msg)
88
0
{
89
0
    auto& connman = static_cast<ConnmanTestMsg&>(*m_node.connman);
90
0
    CNode& connection = *PickValue(fuzzed_data_provider, m_connections);
91
92
0
    connman.FlushSendBuffer(connection);
93
0
    (void)connman.ReceiveMsgFrom(connection, std::move(msg));
94
0
    connection.fPauseSend = false;
95
0
    try {
96
0
        connman.ProcessMessagesOnce(connection);
97
0
    } catch (const std::ios_base::failure&) {
98
0
    }
99
0
    m_node.peerman->SendMessages(connection);
100
0
}
101
102
CBlockHeader ConsumeHeader(FuzzedDataProvider& fuzzed_data_provider, const uint256& prev_hash, uint32_t prev_nbits)
103
0
{
104
0
    CBlockHeader header;
105
0
    header.nNonce = 0;
106
    // Either use the previous difficulty or let the fuzzer choose. The upper target in the
107
    // range comes from the bits value of the genesis block, which is 0x1d00ffff. The lower
108
    // target comes from the bits value of mainnet block 840000, which is 0x17034219.
109
    // Calling lower_target.SetCompact(0x17034219) and upper_target.SetCompact(0x1d00ffff)
110
    // should return the values below.
111
    //
112
    // RPC commands to verify:
113
    // getblockheader 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f
114
    // getblockheader 0000000000000000000320283a032748cef8227873ff4872689bf23f1cda83a5
115
0
    if (fuzzed_data_provider.ConsumeBool()) {
  Branch (115:9): [True: 0, False: 0]
116
0
        header.nBits = prev_nbits;
117
0
    } else {
118
0
        arith_uint256 lower_target = UintToArith256(uint256{"0000000000000000000342190000000000000000000000000000000000000000"});
119
0
        arith_uint256 upper_target = UintToArith256(uint256{"00000000ffff0000000000000000000000000000000000000000000000000000"});
120
0
        arith_uint256 target = ConsumeArithUInt256InRange(fuzzed_data_provider, lower_target, upper_target);
121
0
        header.nBits = target.GetCompact();
122
0
    }
123
0
    header.nTime = TicksSinceEpoch<std::chrono::seconds>(ConsumeTime(fuzzed_data_provider));
124
0
    header.hashPrevBlock = prev_hash;
125
0
    header.nVersion = fuzzed_data_provider.ConsumeIntegral<int32_t>();
126
0
    return header;
127
0
}
128
129
CBlock ConsumeBlock(FuzzedDataProvider& fuzzed_data_provider, const uint256& prev_hash, uint32_t prev_nbits)
130
0
{
131
0
    auto header = ConsumeHeader(fuzzed_data_provider, prev_hash, prev_nbits);
132
    // In order to reach the headers acceptance logic, the block is
133
    // constructed in a way that will pass the mutation checks.
134
0
    CBlock block{header};
135
0
    CMutableTransaction tx;
136
0
    tx.vin.resize(1);
137
0
    tx.vout.resize(1);
138
0
    tx.vout[0].nValue = 0;
139
0
    tx.vin[0].scriptSig.resize(2);
140
0
    block.vtx.push_back(MakeTransactionRef(tx));
141
0
    block.hashMerkleRoot = block.vtx[0]->GetHash().ToUint256();
142
0
    return block;
143
0
}
144
145
// Global setup works for this test as state modification (specifically in the
146
// block index) would indicate a bug.
147
HeadersSyncSetup* g_testing_setup;
148
149
void initialize()
150
0
{
151
0
    static auto setup{
152
0
        MakeNoLogFileContext<HeadersSyncSetup>(ChainType::MAIN,
153
0
                                               {
154
0
                                                   .setup_validation_interface = false,
155
0
                                               }),
156
0
    };
157
0
    g_testing_setup = setup.get();
158
0
}
159
} // namespace
160
161
FUZZ_TARGET(p2p_headers_presync, .init = initialize)
162
0
{
163
0
    SeedRandomStateForTest(SeedRand::ZEROS);
164
0
    FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
165
    // The steady clock is currently only used for logging, so a constant
166
    // time-point seems acceptable for now.
167
0
    FakeSteadyClock steady_ctx{};
168
169
0
    ChainstateManager& chainman = *g_testing_setup->m_node.chainman;
170
0
    CBlockHeader base{chainman.GetParams().GenesisBlock()};
171
0
    const FakeNodeClock clock{base.Time()};
172
173
0
    LOCK(NetEventsInterface::g_msgproc_mutex);
174
175
0
    g_testing_setup->ResetAndInitialize();
176
177
    // The chain is just a single block, so this is equal to 1
178
0
    size_t original_index_size{WITH_LOCK(cs_main, return chainman.m_blockman.m_block_index.size())};
179
0
    arith_uint256 total_work{WITH_LOCK(cs_main, return chainman.m_best_header->nChainWork)};
180
181
0
    std::vector<CBlockHeader> all_headers;
182
183
0
    LIMITED_WHILE (fuzzed_data_provider.ConsumeBool(), 100) {
184
0
        auto finalized_block = [&]() {
185
0
            CBlock block = ConsumeBlock(fuzzed_data_provider, base.GetHash(), base.nBits);
186
0
            FinalizeHeader(block, chainman);
187
0
            return block;
188
0
        };
189
190
        // Send low-work headers, compact blocks, and blocks
191
0
        CallOneOf(
192
0
            fuzzed_data_provider,
193
0
            [&]() NO_THREAD_SAFETY_ANALYSIS {
194
                // Send FUZZ_MAX_HEADERS_RESULTS headers
195
0
                std::vector<CBlock> headers;
196
0
                headers.resize(FUZZ_MAX_HEADERS_RESULTS);
197
0
                for (CBlock& header : headers) {
  Branch (197:37): [True: 0, False: 0]
198
0
                    header = ConsumeHeader(fuzzed_data_provider, base.GetHash(), base.nBits);
199
0
                    FinalizeHeader(header, chainman);
200
0
                    base = header;
201
0
                }
202
203
0
                all_headers.insert(all_headers.end(), headers.begin(), headers.end());
204
205
0
                auto headers_msg = NetMsg::Make(NetMsgType::HEADERS, TX_WITH_WITNESS(headers));
206
0
                g_testing_setup->SendMessage(fuzzed_data_provider, std::move(headers_msg));
207
0
            },
208
0
            [&]() NO_THREAD_SAFETY_ANALYSIS {
209
                // Send a compact block
210
0
                auto block = finalized_block();
211
0
                CBlockHeaderAndShortTxIDs cmpct_block{block, fuzzed_data_provider.ConsumeIntegral<uint64_t>()};
212
213
0
                all_headers.push_back(block);
214
215
0
                auto headers_msg = NetMsg::Make(NetMsgType::CMPCTBLOCK, TX_WITH_WITNESS(cmpct_block));
216
0
                g_testing_setup->SendMessage(fuzzed_data_provider, std::move(headers_msg));
217
0
            },
218
0
            [&]() NO_THREAD_SAFETY_ANALYSIS {
219
                // Send a block
220
0
                auto block = finalized_block();
221
222
0
                all_headers.push_back(block);
223
224
0
                auto headers_msg = NetMsg::Make(NetMsgType::BLOCK, TX_WITH_WITNESS(block));
225
0
                g_testing_setup->SendMessage(fuzzed_data_provider, std::move(headers_msg));
226
0
            });
227
0
    }
228
229
    // This is a conservative overestimate, as base is only moved forward when sending headers. In theory,
230
    // the longest chain generated by this test is 1600 (FUZZ_MAX_HEADERS_RESULTS * 100) headers. In that case,
231
    // this variable will accurately reflect the chain's total work.
232
0
    total_work += CalculateClaimedHeadersWork(all_headers);
233
234
    // This test should never create a chain with more work than MinimumChainWork.
235
0
    assert(total_work < chainman.MinimumChainWork());
  Branch (235:5): [True: 0, False: 0]
236
237
    // The headers/blocks sent in this test should never be stored, as the chains don't have the work required
238
    // to meet the anti-DoS work threshold. So, if at any point the block index grew in size, then there's a bug
239
    // in the headers pre-sync logic.
240
0
    assert(WITH_LOCK(cs_main, return chainman.m_blockman.m_block_index.size()) == original_index_size);
  Branch (240:5): [True: 0, False: 0]
241
0
}