Coverage Report

Created: 2025-06-10 13:21

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