Bitcoin Core Fuzz Coverage Report

Coverage Report

Created: 2026-03-24 13:57

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/root/bitcoin/src/rpc/node.cpp
Line
Count
Source
1
// Copyright (c) 2010 Satoshi Nakamoto
2
// Copyright (c) 2009-present The Bitcoin Core developers
3
// Distributed under the MIT software license, see the accompanying
4
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
5
6
#include <bitcoin-build-config.h> // IWYU pragma: keep
7
8
#include <chainparams.h>
9
#include <httpserver.h>
10
#include <index/blockfilterindex.h>
11
#include <index/coinstatsindex.h>
12
#include <index/txindex.h>
13
#include <index/txospenderindex.h>
14
#include <interfaces/chain.h>
15
#include <interfaces/echo.h>
16
#include <interfaces/init.h>
17
#include <interfaces/ipc.h>
18
#include <kernel/cs_main.h>
19
#include <logging.h>
20
#include <node/context.h>
21
#include <rpc/server.h>
22
#include <rpc/server_util.h>
23
#include <rpc/util.h>
24
#include <scheduler.h>
25
#include <tinyformat.h>
26
#include <univalue.h>
27
#include <util/any.h>
28
#include <util/check.h>
29
#include <util/time.h>
30
31
#include <cstdint>
32
#ifdef HAVE_MALLOC_INFO
33
#include <malloc.h>
34
#endif
35
#include <string_view>
36
37
using node::NodeContext;
38
39
static RPCHelpMan setmocktime()
40
0
{
41
0
    return RPCHelpMan{
42
0
        "setmocktime",
43
0
        "Set the local time to given timestamp (-regtest only)\n",
44
0
        {
45
0
            {"timestamp", RPCArg::Type::NUM, RPCArg::Optional::NO, UNIX_EPOCH_TIME + "\n"
46
0
             "Pass 0 to go back to using the system time."},
47
0
        },
48
0
        RPCResult{RPCResult::Type::NONE, "", ""},
49
0
        RPCExamples{""},
50
0
        [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
51
0
{
52
0
    if (!Params().IsMockableChain()) {
53
0
        throw std::runtime_error("setmocktime is for regression testing (-regtest mode) only");
54
0
    }
55
56
    // For now, don't change mocktime if we're in the middle of validation, as
57
    // this could have an effect on mempool time-based eviction, as well as
58
    // IsCurrentForFeeEstimation() and IsInitialBlockDownload().
59
    // TODO: figure out the right way to synchronize around mocktime, and
60
    // ensure all call sites of GetTime() are accessing this safely.
61
0
    LOCK(cs_main);
Line
Count
Source
266
0
#define LOCK(cs) UniqueLock UNIQUE_NAME(criticalblock)(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__)
Line
Count
Source
11
0
#define UNIQUE_NAME(name) PASTE2(name, __COUNTER__)
Line
Count
Source
9
0
#define PASTE2(x, y) PASTE(x, y)
Line
Count
Source
8
0
#define PASTE(x, y) x ## y
62
63
0
    const int64_t time{request.params[0].getInt<int64_t>()};
64
0
    constexpr int64_t max_time{Ticks<std::chrono::seconds>(std::chrono::nanoseconds::max())};
65
0
    if (time < 0 || time > max_time) {
66
0
        throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Mocktime must be in the range [0, %s], not %s.", max_time, time));
Line
Count
Source
1172
0
#define strprintf tfm::format
67
0
    }
68
69
0
    SetMockTime(time);
70
0
    const NodeContext& node_context{EnsureAnyNodeContext(request.context)};
71
0
    for (const auto& chain_client : node_context.chain_clients) {
72
0
        chain_client->setMockTime(time);
73
0
    }
74
75
0
    return UniValue::VNULL;
76
0
},
77
0
    };
78
0
}
79
80
static RPCHelpMan mockscheduler()
81
0
{
82
0
    return RPCHelpMan{
83
0
        "mockscheduler",
84
0
        "Bump the scheduler into the future (-regtest only)\n",
85
0
        {
86
0
            {"delta_time", RPCArg::Type::NUM, RPCArg::Optional::NO, "Number of seconds to forward the scheduler into the future." },
87
0
        },
88
0
        RPCResult{RPCResult::Type::NONE, "", ""},
89
0
        RPCExamples{""},
90
0
        [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
91
0
{
92
0
    if (!Params().IsMockableChain()) {
93
0
        throw std::runtime_error("mockscheduler is for regression testing (-regtest mode) only");
94
0
    }
95
96
0
    int64_t delta_seconds = request.params[0].getInt<int64_t>();
97
0
    if (delta_seconds <= 0 || delta_seconds > 3600) {
98
0
        throw std::runtime_error("delta_time must be between 1 and 3600 seconds (1 hr)");
99
0
    }
100
101
0
    const NodeContext& node_context{EnsureAnyNodeContext(request.context)};
102
0
    CHECK_NONFATAL(node_context.scheduler)->MockForward(std::chrono::seconds{delta_seconds});
Line
Count
Source
110
0
    inline_check_non_fatal(condition, std::source_location::current(), #condition)
103
0
    CHECK_NONFATAL(node_context.validation_signals)->SyncWithValidationInterfaceQueue();
Line
Count
Source
110
0
    inline_check_non_fatal(condition, std::source_location::current(), #condition)
104
0
    for (const auto& chain_client : node_context.chain_clients) {
105
0
        chain_client->schedulerMockForward(std::chrono::seconds(delta_seconds));
106
0
    }
107
108
0
    return UniValue::VNULL;
109
0
},
110
0
    };
111
0
}
112
113
static UniValue RPCLockedMemoryInfo()
114
0
{
115
0
    LockedPool::Stats stats = LockedPoolManager::Instance().stats();
116
0
    UniValue obj(UniValue::VOBJ);
117
0
    obj.pushKV("used", stats.used);
118
0
    obj.pushKV("free", stats.free);
119
0
    obj.pushKV("total", stats.total);
120
0
    obj.pushKV("locked", stats.locked);
121
0
    obj.pushKV("chunks_used", stats.chunks_used);
122
0
    obj.pushKV("chunks_free", stats.chunks_free);
123
0
    return obj;
124
0
}
125
126
#ifdef HAVE_MALLOC_INFO
127
static std::string RPCMallocInfo()
128
0
{
129
0
    char *ptr = nullptr;
130
0
    size_t size = 0;
131
0
    FILE *f = open_memstream(&ptr, &size);
132
0
    if (f) {
133
0
        malloc_info(0, f);
134
0
        fclose(f);
135
0
        if (ptr) {
136
0
            std::string rv(ptr, size);
137
0
            free(ptr);
138
0
            return rv;
139
0
        }
140
0
    }
141
0
    return "";
142
0
}
143
#endif
144
145
static RPCHelpMan getmemoryinfo()
146
0
{
147
    /* Please, avoid using the word "pool" here in the RPC interface or help,
148
     * as users will undoubtedly confuse it with the other "memory pool"
149
     */
150
0
    return RPCHelpMan{"getmemoryinfo",
151
0
                "Returns an object containing information about memory usage.\n",
152
0
                {
153
0
                    {"mode", RPCArg::Type::STR, RPCArg::Default{"stats"}, "determines what kind of information is returned.\n"
154
0
            "  - \"stats\" returns general statistics about memory usage in the daemon.\n"
155
0
            "  - \"mallocinfo\" returns an XML string describing low-level heap state (only available if compiled with glibc)."},
156
0
                },
157
0
                {
158
0
                    RPCResult{"mode \"stats\"",
159
0
                        RPCResult::Type::OBJ, "", "",
160
0
                        {
161
0
                            {RPCResult::Type::OBJ, "locked", "Information about locked memory manager",
162
0
                            {
163
0
                                {RPCResult::Type::NUM, "used", "Number of bytes used"},
164
0
                                {RPCResult::Type::NUM, "free", "Number of bytes available in current arenas"},
165
0
                                {RPCResult::Type::NUM, "total", "Total number of bytes managed"},
166
0
                                {RPCResult::Type::NUM, "locked", "Amount of bytes that succeeded locking. If this number is smaller than total, locking pages failed at some point and key data could be swapped to disk."},
167
0
                                {RPCResult::Type::NUM, "chunks_used", "Number allocated chunks"},
168
0
                                {RPCResult::Type::NUM, "chunks_free", "Number unused chunks"},
169
0
                            }},
170
0
                        }
171
0
                    },
172
0
                    RPCResult{"mode \"mallocinfo\"",
173
0
                        RPCResult::Type::STR, "", "\"<malloc version=\"1\">...\""
174
0
                    },
175
0
                },
176
0
                RPCExamples{
177
0
                    HelpExampleCli("getmemoryinfo", "")
178
0
            + HelpExampleRpc("getmemoryinfo", "")
179
0
                },
180
0
        [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
181
0
{
182
0
    auto mode{self.Arg<std::string_view>("mode")};
183
0
    if (mode == "stats") {
184
0
        UniValue obj(UniValue::VOBJ);
185
0
        obj.pushKV("locked", RPCLockedMemoryInfo());
186
0
        return obj;
187
0
    } else if (mode == "mallocinfo") {
188
0
#ifdef HAVE_MALLOC_INFO
189
0
        return RPCMallocInfo();
190
#else
191
        throw JSONRPCError(RPC_INVALID_PARAMETER, "mallocinfo mode not available");
192
#endif
193
0
    } else {
194
0
        throw JSONRPCError(RPC_INVALID_PARAMETER, tfm::format("unknown mode %s", mode));
195
0
    }
196
0
},
197
0
    };
198
0
}
199
200
0
static void EnableOrDisableLogCategories(UniValue cats, bool enable) {
201
0
    cats = cats.get_array();
202
0
    for (unsigned int i = 0; i < cats.size(); ++i) {
203
0
        std::string cat = cats[i].get_str();
204
205
0
        bool success;
206
0
        if (enable) {
207
0
            success = LogInstance().EnableCategory(cat);
208
0
        } else {
209
0
            success = LogInstance().DisableCategory(cat);
210
0
        }
211
212
0
        if (!success) {
213
0
            throw JSONRPCError(RPC_INVALID_PARAMETER, "unknown logging category " + cat);
214
0
        }
215
0
    }
216
0
}
217
218
static RPCHelpMan logging()
219
0
{
220
0
    return RPCHelpMan{"logging",
221
0
            "Gets and sets the logging configuration.\n"
222
0
            "When called without an argument, returns the list of categories with status that are currently being debug logged or not.\n"
223
0
            "When called with arguments, adds or removes categories from debug logging and return the lists above.\n"
224
0
            "The arguments are evaluated in order \"include\", \"exclude\".\n"
225
0
            "If an item is both included and excluded, it will thus end up being excluded.\n"
226
0
            "The valid logging categories are: " + LogInstance().LogCategoriesString() + "\n"
227
0
            "In addition, the following are available as category names with special meanings:\n"
228
0
            "  - \"all\",  \"1\" : represent all logging categories.\n"
229
0
            ,
230
0
                {
231
0
                    {"include", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "The categories to add to debug logging",
232
0
                        {
233
0
                            {"include_category", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "the valid logging category"},
234
0
                        }},
235
0
                    {"exclude", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "The categories to remove from debug logging",
236
0
                        {
237
0
                            {"exclude_category", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "the valid logging category"},
238
0
                        }},
239
0
                },
240
0
                RPCResult{
241
0
                    RPCResult::Type::OBJ_DYN, "", "keys are the logging categories, and values indicates its status",
242
0
                    {
243
0
                        {RPCResult::Type::BOOL, "category", "if being debug logged or not. false:inactive, true:active"},
244
0
                    }
245
0
                },
246
0
                RPCExamples{
247
0
                    HelpExampleCli("logging", "\"[\\\"all\\\"]\" \"[\\\"http\\\"]\"")
248
0
            + HelpExampleRpc("logging", "[\"all\"], [\"libevent\"]")
249
0
                },
250
0
        [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
251
0
{
252
0
    BCLog::CategoryMask original_log_categories = LogInstance().GetCategoryMask();
253
0
    if (request.params[0].isArray()) {
254
0
        EnableOrDisableLogCategories(request.params[0], true);
255
0
    }
256
0
    if (request.params[1].isArray()) {
257
0
        EnableOrDisableLogCategories(request.params[1], false);
258
0
    }
259
0
    BCLog::CategoryMask updated_log_categories = LogInstance().GetCategoryMask();
260
0
    BCLog::CategoryMask changed_log_categories = original_log_categories ^ updated_log_categories;
261
262
    // Update libevent logging if BCLog::LIBEVENT has changed.
263
0
    if (changed_log_categories & BCLog::LIBEVENT) {
264
        // Currently no modules in the codebase produce libevent log messages.
265
        // To redirect libevent messages to our own logs see commit 8b2d6edaa9fbfb6344ca51edd0b3655b451cbcac
266
        // in https://github.com/bitcoin/bitcoin/pull/6695
267
0
    }
268
269
0
    UniValue result(UniValue::VOBJ);
270
0
    for (const auto& logCatActive : LogInstance().LogCategoriesList()) {
271
0
        result.pushKV(logCatActive.category, logCatActive.active);
272
0
    }
273
274
0
    return result;
275
0
},
276
0
    };
277
0
}
278
279
static RPCHelpMan echo(const std::string& name)
280
0
{
281
0
    return RPCHelpMan{
282
0
        name,
283
0
        "Simply echo back the input arguments. This command is for testing.\n"
284
0
                "\nIt will return an internal bug report when arg9='trigger_internal_bug' is passed.\n"
285
0
                "\nThe difference between echo and echojson is that echojson has argument conversion enabled in the client-side table in "
286
0
                "bitcoin-cli and the GUI. There is no server-side difference.",
287
0
        {
288
0
            {"arg0", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "", RPCArgOptions{.skip_type_check = true}},
289
0
            {"arg1", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "", RPCArgOptions{.skip_type_check = true}},
290
0
            {"arg2", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "", RPCArgOptions{.skip_type_check = true}},
291
0
            {"arg3", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "", RPCArgOptions{.skip_type_check = true}},
292
0
            {"arg4", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "", RPCArgOptions{.skip_type_check = true}},
293
0
            {"arg5", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "", RPCArgOptions{.skip_type_check = true}},
294
0
            {"arg6", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "", RPCArgOptions{.skip_type_check = true}},
295
0
            {"arg7", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "", RPCArgOptions{.skip_type_check = true}},
296
0
            {"arg8", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "", RPCArgOptions{.skip_type_check = true}},
297
0
            {"arg9", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "", RPCArgOptions{.skip_type_check = true}},
298
0
        },
299
0
                RPCResult{RPCResult::Type::ANY, "", "Returns whatever was passed in"},
300
0
                RPCExamples{""},
301
0
        [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
302
0
{
303
0
    if (request.params[9].isStr()) {
304
0
        CHECK_NONFATAL(request.params[9].get_str() != "trigger_internal_bug");
Line
Count
Source
110
0
    inline_check_non_fatal(condition, std::source_location::current(), #condition)
305
0
    }
306
307
0
    return request.params;
308
0
},
309
0
    };
310
0
}
311
312
0
static RPCHelpMan echo() { return echo("echo"); }
313
0
static RPCHelpMan echojson() { return echo("echojson"); }
314
315
static RPCHelpMan echoipc()
316
0
{
317
0
    return RPCHelpMan{
318
0
        "echoipc",
319
0
        "Echo back the input argument, passing it through a spawned process in a multiprocess build.\n"
320
0
        "This command is for testing.\n",
321
0
        {{"arg", RPCArg::Type::STR, RPCArg::Optional::NO, "The string to echo",}},
322
0
        RPCResult{RPCResult::Type::STR, "echo", "The echoed string."},
323
0
        RPCExamples{HelpExampleCli("echo", "\"Hello world\"") +
324
0
                    HelpExampleRpc("echo", "\"Hello world\"")},
325
0
        [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue {
326
0
            interfaces::Init& local_init = *EnsureAnyNodeContext(request.context).init;
327
0
            std::unique_ptr<interfaces::Echo> echo;
328
0
            if (interfaces::Ipc* ipc = local_init.ipc()) {
329
                // Spawn a new bitcoin-node process and call makeEcho to get a
330
                // client pointer to a interfaces::Echo instance running in
331
                // that process. This is just for testing. A slightly more
332
                // realistic test spawning a different executable instead of
333
                // the same executable would add a new bitcoin-echo executable,
334
                // and spawn bitcoin-echo below instead of bitcoin-node. But
335
                // using bitcoin-node avoids the need to build and install a
336
                // new executable just for this one test.
337
0
                auto init = ipc->spawnProcess("bitcoin-node");
338
0
                echo = init->makeEcho();
339
0
                ipc->addCleanup(*echo, [init = init.release()] { delete init; });
340
0
            } else {
341
                // IPC support is not available because this is a bitcoind
342
                // process not a bitcoind-node process, so just create a local
343
                // interfaces::Echo object and return it so the `echoipc` RPC
344
                // method will work, and the python test calling `echoipc`
345
                // can expect the same result.
346
0
                echo = local_init.makeEcho();
347
0
            }
348
0
            return echo->echo(request.params[0].get_str());
349
0
        },
350
0
    };
351
0
}
352
353
static UniValue SummaryToJSON(const IndexSummary&& summary, std::string index_name)
354
0
{
355
0
    UniValue ret_summary(UniValue::VOBJ);
356
0
    if (!index_name.empty() && index_name != summary.name) return ret_summary;
357
358
0
    UniValue entry(UniValue::VOBJ);
359
0
    entry.pushKV("synced", summary.synced);
360
0
    entry.pushKV("best_block_height", summary.best_block_height);
361
0
    ret_summary.pushKV(summary.name, std::move(entry));
362
0
    return ret_summary;
363
0
}
364
365
static RPCHelpMan getindexinfo()
366
0
{
367
0
    return RPCHelpMan{
368
0
        "getindexinfo",
369
0
        "Returns the status of one or all available indices currently running in the node.\n",
370
0
                {
371
0
                    {"index_name", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Filter results for an index with a specific name."},
372
0
                },
373
0
                RPCResult{
374
0
                    RPCResult::Type::OBJ_DYN, "", "", {
375
0
                        {
376
0
                            RPCResult::Type::OBJ, "name", "The name of the index",
377
0
                            {
378
0
                                {RPCResult::Type::BOOL, "synced", "Whether the index is synced or not"},
379
0
                                {RPCResult::Type::NUM, "best_block_height", "The block height to which the index is synced"},
380
0
                            }
381
0
                        },
382
0
                    },
383
0
                },
384
0
                RPCExamples{
385
0
                    HelpExampleCli("getindexinfo", "")
386
0
                  + HelpExampleRpc("getindexinfo", "")
387
0
                  + HelpExampleCli("getindexinfo", "txindex")
388
0
                  + HelpExampleRpc("getindexinfo", "txindex")
389
0
                },
390
0
                [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
391
0
{
392
0
    UniValue result(UniValue::VOBJ);
393
0
    const std::string index_name{self.MaybeArg<std::string_view>("index_name").value_or("")};
394
395
0
    if (g_txindex) {
396
0
        result.pushKVs(SummaryToJSON(g_txindex->GetSummary(), index_name));
397
0
    }
398
399
0
    if (g_coin_stats_index) {
400
0
        result.pushKVs(SummaryToJSON(g_coin_stats_index->GetSummary(), index_name));
401
0
    }
402
403
0
    if (g_txospenderindex) {
404
0
        result.pushKVs(SummaryToJSON(g_txospenderindex->GetSummary(), index_name));
405
0
    }
406
407
0
    ForEachBlockFilterIndex([&result, &index_name](const BlockFilterIndex& index) {
408
0
        result.pushKVs(SummaryToJSON(index.GetSummary(), index_name));
409
0
    });
410
411
0
    return result;
412
0
},
413
0
    };
414
0
}
415
416
void RegisterNodeRPCCommands(CRPCTable& t)
417
0
{
418
0
    static const CRPCCommand commands[]{
419
0
        {"control", &getmemoryinfo},
420
0
        {"control", &logging},
421
0
        {"util", &getindexinfo},
422
0
        {"hidden", &setmocktime},
423
0
        {"hidden", &mockscheduler},
424
0
        {"hidden", &echo},
425
0
        {"hidden", &echojson},
426
0
        {"hidden", &echoipc},
427
0
    };
428
0
    for (const auto& c : commands) {
429
0
        t.appendCommand(c.name, &c);
430
0
    }
431
0
}