Coverage Report

Created: 2026-07-23 20:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/tmp/bitcoin/src/rpc/server.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 <rpc/server.h>
9
10
#include <common/args.h>
11
#include <common/system.h>
12
#include <logging.h>
13
#include <node/context.h>
14
#include <node/kernel_notifications.h>
15
#include <rpc/server_util.h>
16
#include <rpc/util.h>
17
#include <sync.h>
18
#include <util/signalinterrupt.h>
19
#include <util/strencodings.h>
20
#include <util/string.h>
21
#include <util/time.h>
22
#include <validation.h>
23
24
#include <algorithm>
25
#include <cassert>
26
#include <chrono>
27
#include <memory>
28
#include <mutex>
29
#include <span>
30
#include <string_view>
31
#include <unordered_set>
32
#include <unordered_map>
33
#include <variant>
34
35
using util::SplitString;
36
37
static GlobalMutex g_rpc_warmup_mutex;
38
static std::atomic<bool> g_rpc_running{false};
39
static bool fRPCInWarmup GUARDED_BY(g_rpc_warmup_mutex) = true;
40
static std::string rpcWarmupStatus GUARDED_BY(g_rpc_warmup_mutex) = "RPC server started";
41
static bool ExecuteCommand(const CRPCCommand& command, const JSONRPCRequest& request, UniValue& result, bool last_handler);
42
43
struct RPCCommandExecutionInfo
44
{
45
    std::string method;
46
    SteadyClock::time_point start;
47
};
48
49
struct RPCServerInfo
50
{
51
    Mutex mutex;
52
    std::list<RPCCommandExecutionInfo> active_commands GUARDED_BY(mutex);
53
};
54
55
static RPCServerInfo g_rpc_server_info;
56
57
struct RPCCommandExecution
58
{
59
    std::list<RPCCommandExecutionInfo>::iterator it;
60
    explicit RPCCommandExecution(const std::string& method)
61
196k
    {
62
196k
        LOCK(g_rpc_server_info.mutex);
63
196k
        it = g_rpc_server_info.active_commands.insert(g_rpc_server_info.active_commands.end(), {method, SteadyClock::now()});
64
196k
    }
65
    ~RPCCommandExecution()
66
196k
    {
67
196k
        LOCK(g_rpc_server_info.mutex);
68
196k
        g_rpc_server_info.active_commands.erase(it);
69
196k
    }
70
};
71
72
std::string CRPCTable::help(std::string_view strCommand, const JSONRPCRequest& helpreq) const
73
178
{
74
178
    std::string strRet;
75
178
    std::string category;
76
178
    std::set<intptr_t> setDone;
77
178
    std::vector<std::pair<std::string, const CRPCCommand*> > vCommands;
78
178
    vCommands.reserve(mapCommands.size());
79
80
178
    for (const auto& entry : mapCommands)
81
29.9k
        vCommands.emplace_back(entry.second.front()->category + entry.first, entry.second.front());
82
178
    std::ranges::sort(vCommands);
83
84
178
    JSONRPCRequest jreq = helpreq;
85
178
    jreq.mode = JSONRPCRequest::GET_HELP;
86
178
    jreq.params = UniValue();
87
88
29.9k
    for (const auto& [_, pcmd] : vCommands) {
89
29.9k
        std::string strMethod = pcmd->name;
90
29.9k
        if ((strCommand != "" || pcmd->category == "hidden") && strMethod != strCommand)
91
28.8k
            continue;
92
1.06k
        jreq.strMethod = strMethod;
93
1.06k
        try
94
1.06k
        {
95
1.06k
            UniValue unused_result;
96
1.06k
            if (setDone.insert(pcmd->unique_id).second)
97
1.06k
                pcmd->actor(jreq, unused_result, /*last_handler=*/true);
98
1.06k
        } catch (const HelpResult& e) {
99
1.06k
            std::string strHelp{e.what()};
100
1.06k
            if (strCommand == "")
101
892
            {
102
892
                if (strHelp.find('\n') != std::string::npos)
103
892
                    strHelp = strHelp.substr(0, strHelp.find('\n'));
104
105
892
                if (category != pcmd->category)
106
58
                {
107
58
                    if (!category.empty())
108
50
                        strRet += "\n";
109
58
                    category = pcmd->category;
110
58
                    strRet += "== " + Capitalize(category) + " ==\n";
111
58
                }
112
892
            }
113
1.06k
            strRet += strHelp + "\n";
114
1.06k
        }
115
1.06k
    }
116
178
    if (strRet == "")
117
1
        strRet = strprintf("help: unknown command: %s\n", strCommand);
118
178
    strRet = strRet.substr(0,strRet.size()-1);
119
178
    return strRet;
120
178
}
121
122
static RPCMethod help()
123
2.91k
{
124
2.91k
    return RPCMethod{
125
2.91k
        "help",
126
2.91k
        "List all commands, or get help for a specified command.\n",
127
2.91k
                {
128
2.91k
                    {"command", RPCArg::Type::STR, RPCArg::DefaultHint{"all commands"}, "The command to get help on"},
129
2.91k
                },
130
2.91k
                {
131
2.91k
                    RPCResult{RPCResult::Type::STR, "", "The help text"},
132
2.91k
                    RPCResult{RPCResult::Type::ANY, "", ""},
133
2.91k
                },
134
2.91k
                RPCExamples{""},
135
2.91k
        [](const RPCMethod& self, const JSONRPCRequest& jsonRequest) -> UniValue
136
2.91k
{
137
180
    auto command{self.MaybeArg<std::string_view>("command")};
138
180
    if (command == "dump_all_command_conversions") {
139
        // Used for testing only, undocumented
140
2
        return tableRPC.dumpArgMap(jsonRequest);
141
2
    }
142
143
178
    return tableRPC.help(command.value_or(""), jsonRequest);
144
180
},
145
2.91k
    };
146
2.91k
}
147
148
static RPCMethod stop()
149
3.72k
{
150
3.72k
    static const std::string RESULT{CLIENT_NAME " stopping"};
151
3.72k
    return RPCMethod{
152
3.72k
        "stop",
153
    // Also accept the hidden 'wait' integer argument (milliseconds)
154
    // For instance, 'stop 1000' makes the call wait 1 second before returning
155
    // to the client (intended for testing)
156
3.72k
        "Request a graceful shutdown of " CLIENT_NAME ".",
157
3.72k
                {
158
3.72k
                    {"wait", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "how long to wait in ms", RPCArgOptions{.hidden=true}},
159
3.72k
                },
160
3.72k
                RPCResult{RPCResult::Type::STR, "", "A string with the content '" + RESULT + "'"},
161
3.72k
                RPCExamples{""},
162
3.72k
        [](const RPCMethod& self, const JSONRPCRequest& jsonRequest) -> UniValue
163
3.72k
{
164
    // Event loop will exit after current HTTP requests have been handled, so
165
    // this reply will get back to the client.
166
985
    CHECK_NONFATAL((CHECK_NONFATAL(EnsureAnyNodeContext(jsonRequest.context).shutdown_request))());
167
985
    if (jsonRequest.params[0].isNum()) {
168
984
        UninterruptibleSleep(std::chrono::milliseconds{jsonRequest.params[0].getInt<int>()});
169
984
    }
170
985
    return RESULT;
171
985
},
172
3.72k
    };
173
3.72k
}
174
175
static RPCMethod uptime()
176
2.73k
{
177
2.73k
    return RPCMethod{
178
2.73k
        "uptime",
179
2.73k
        "Returns the total uptime of the server.\n",
180
2.73k
                            {},
181
2.73k
                            RPCResult{
182
2.73k
                                RPCResult::Type::NUM, "", "The number of seconds that the server has been running"
183
2.73k
                            },
184
2.73k
                RPCExamples{
185
2.73k
                    HelpExampleCli("uptime", "")
186
2.73k
                + HelpExampleRpc("uptime", "")
187
2.73k
                },
188
2.73k
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
189
2.73k
{
190
2
    return TicksSeconds(GetUptime());
191
2
}
192
2.73k
    };
193
2.73k
}
194
195
static RPCMethod getrpcinfo()
196
2.73k
{
197
2.73k
    return RPCMethod{
198
2.73k
        "getrpcinfo",
199
2.73k
        "Returns details of the RPC server.\n",
200
2.73k
                {},
201
2.73k
                RPCResult{
202
2.73k
                    RPCResult::Type::OBJ, "", "",
203
2.73k
                    {
204
2.73k
                        {RPCResult::Type::ARR, "active_commands", "All active commands",
205
2.73k
                        {
206
2.73k
                            {RPCResult::Type::OBJ, "", "Information about an active command",
207
2.73k
                            {
208
2.73k
                                 {RPCResult::Type::STR, "method", "The name of the RPC command"},
209
2.73k
                                 {RPCResult::Type::NUM, "duration", "The running time in microseconds"},
210
2.73k
                            }},
211
2.73k
                        }},
212
2.73k
                        {RPCResult::Type::STR, "logpath", "The complete file path to the debug log"},
213
2.73k
                    }
214
2.73k
                },
215
2.73k
                RPCExamples{
216
2.73k
                    HelpExampleCli("getrpcinfo", "")
217
2.73k
                + HelpExampleRpc("getrpcinfo", "")},
218
2.73k
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
219
2.73k
{
220
3
    LOCK(g_rpc_server_info.mutex);
221
3
    UniValue active_commands(UniValue::VARR);
222
5
    for (const RPCCommandExecutionInfo& info : g_rpc_server_info.active_commands) {
223
5
        UniValue entry(UniValue::VOBJ);
224
5
        entry.pushKV("method", info.method);
225
5
        entry.pushKV("duration", Ticks<std::chrono::microseconds>(SteadyClock::now() - info.start));
226
5
        active_commands.push_back(std::move(entry));
227
5
    }
228
229
3
    UniValue result(UniValue::VOBJ);
230
3
    result.pushKV("active_commands", std::move(active_commands));
231
232
3
    const std::string path = LogInstance().m_file_path.utf8string();
233
3
    UniValue log_path(UniValue::VSTR, path);
234
3
    result.pushKV("logpath", std::move(log_path));
235
236
3
    return result;
237
3
}
238
2.73k
    };
239
2.73k
}
240
241
namespace {
242
UniValue OpenRPCArgSchema(const RPCArg& arg, bool include_hidden);
243
UniValue OpenRPCResultSchema(const RPCResult& result);
244
245
UniValue MakeObject(std::initializer_list<std::pair<std::string, UniValue>> entries)
246
3.57k
{
247
3.57k
    UniValue obj{UniValue::VOBJ};
248
4.61k
    for (const auto& [key, value] : entries) {
249
4.61k
        obj.pushKV(key, value);
250
4.61k
    }
251
3.57k
    return obj;
252
3.57k
}
253
254
void PushUniqueSchema(UniValue& schemas, std::unordered_set<std::string>& seen, UniValue schema)
255
48
{
256
48
    const std::string serialized{schema.write()};
257
48
    if (seen.insert(serialized).second) schemas.push_back(std::move(schema));
258
48
}
259
260
// NOLINTNEXTLINE(misc-no-recursion)
261
UniValue DedupArrayItemsSchema(std::span<const RPCArg> inner, bool include_hidden)
262
70
{
263
70
    if (inner.empty()) return UniValue{UniValue::VOBJ};
264
70
    if (inner.size() == 1) return OpenRPCArgSchema(inner.front(), include_hidden);
265
266
21
    UniValue one_of{UniValue::VARR};
267
21
    std::unordered_set<std::string> seen;
268
42
    for (const auto& item : inner) {
269
42
        PushUniqueSchema(one_of, seen, OpenRPCArgSchema(item, include_hidden));
270
42
    }
271
272
21
    if (one_of.size() == 1) return one_of[0];
273
274
15
    UniValue items{UniValue::VOBJ};
275
15
    items.pushKV("oneOf", std::move(one_of));
276
15
    return items;
277
21
}
278
279
// NOLINTNEXTLINE(misc-no-recursion)
280
UniValue DedupArrayItemsSchema(std::span<const RPCResult> inner)
281
363
{
282
363
    if (inner.empty()) return UniValue{UniValue::VOBJ};
283
363
    if (inner.size() == 1) return OpenRPCResultSchema(inner.front());
284
285
3
    UniValue one_of{UniValue::VARR};
286
3
    std::unordered_set<std::string> seen;
287
6
    for (const auto& item : inner) {
288
6
        PushUniqueSchema(one_of, seen, OpenRPCResultSchema(item));
289
6
    }
290
291
3
    if (one_of.size() == 1) return one_of[0];
292
293
3
    UniValue items{UniValue::VOBJ};
294
3
    items.pushKV("oneOf", std::move(one_of));
295
3
    return items;
296
3
}
297
298
void ApplyTypeStrOverride(UniValue& schema, const RPCArg& arg)
299
704
{
300
704
    if (arg.m_opts.type_str.size() != 2) return;
301
9
    const std::string& type_label{arg.m_opts.type_str[1]};
302
9
    if (type_label.empty()) return;
303
304
9
    static const std::unordered_set<std::string> number_or_string{
305
9
        "integer / string",
306
9
        "string or numeric",
307
9
    };
308
9
    if (number_or_string.contains(type_label)) {
309
9
        UniValue one_of{UniValue::VARR};
310
9
        one_of.push_back(MakeObject({{"type", "number"}}));
311
9
        one_of.push_back(MakeObject({{"type", "string"}}));
312
9
        schema = UniValue{UniValue::VOBJ};
313
9
        schema.pushKV("oneOf", std::move(one_of));
314
9
    } else {
315
0
        schema.pushKV("x-bitcoin-type-override", type_label);
316
0
    }
317
9
}
318
319
void ApplyArgFallback(UniValue& schema, const RPCArg& arg)
320
704
{
321
704
    if (const auto* def = std::get_if<RPCArg::Default>(&arg.m_fallback)) {
322
178
        schema.pushKV("default", *def);
323
526
    } else if (const auto* hint = std::get_if<RPCArg::DefaultHint>(&arg.m_fallback)) {
324
61
        schema.pushKV("x-bitcoin-default-hint", *hint);
325
61
    }
326
704
}
327
328
// NOLINTNEXTLINE(misc-no-recursion)
329
UniValue OpenRPCArgSchema(const RPCArg& arg, bool include_hidden)
330
704
{
331
704
    UniValue schema{UniValue::VOBJ};
332
704
    if (arg.m_opts.skip_type_check) {
333
42
        ApplyTypeStrOverride(schema, arg);
334
42
        if (schema.empty() && arg.m_type == RPCArg::Type::ARR) {
335
6
            UniValue one_of{UniValue::VARR};
336
6
            one_of.push_back(MakeObject({{"type", "array"}}));
337
6
            one_of.push_back(MakeObject({{"type", "object"}}));
338
6
            schema.pushKV("oneOf", std::move(one_of));
339
6
        }
340
42
        ApplyArgFallback(schema, arg);
341
42
        return schema;
342
42
    }
343
344
662
    switch (arg.m_type) {
345
192
    case RPCArg::Type::STR:
346
192
        schema = MakeObject({{"type", "string"}});
347
192
        break;
348
124
    case RPCArg::Type::STR_HEX:
349
124
        schema = MakeObject({{"type", "string"}, {"pattern", "^[0-9a-fA-F]+$"}});
350
124
        break;
351
113
    case RPCArg::Type::NUM:
352
113
        schema = MakeObject({{"type", "number"}});
353
113
        break;
354
85
    case RPCArg::Type::BOOL:
355
85
        schema = MakeObject({{"type", "boolean"}});
356
85
        break;
357
18
    case RPCArg::Type::AMOUNT: {
358
18
        UniValue one_of{UniValue::VARR};
359
18
        one_of.push_back(MakeObject({{"type", "number"}}));
360
18
        one_of.push_back(MakeObject({{"type", "string"}}));
361
18
        schema.pushKV("oneOf", std::move(one_of));
362
18
        break;
363
0
    }
364
18
    case RPCArg::Type::RANGE: {
365
18
        UniValue items{UniValue::VARR};
366
18
        items.push_back(MakeObject({{"type", "number"}}));
367
18
        items.push_back(MakeObject({{"type", "number"}}));
368
18
        UniValue range_schema{UniValue::VOBJ};
369
18
        range_schema.pushKV("type", "array");
370
18
        range_schema.pushKV("items", std::move(items));
371
18
        range_schema.pushKV("additionalItems", false);
372
18
        range_schema.pushKV("minItems", 2);
373
18
        range_schema.pushKV("maxItems", 2);
374
18
        UniValue one_of{UniValue::VARR};
375
18
        one_of.push_back(MakeObject({{"type", "number"}}));
376
18
        one_of.push_back(std::move(range_schema));
377
18
        schema.pushKV("oneOf", std::move(one_of));
378
18
        break;
379
0
    }
380
70
    case RPCArg::Type::ARR: {
381
70
        UniValue items{DedupArrayItemsSchema(arg.m_inner, include_hidden)};
382
70
        schema.pushKV("type", "array");
383
70
        schema.pushKV("items", std::move(items));
384
70
        break;
385
0
    }
386
30
    case RPCArg::Type::OBJ:
387
42
    case RPCArg::Type::OBJ_NAMED_PARAMS: {
388
42
        UniValue properties{UniValue::VOBJ};
389
42
        UniValue required{UniValue::VARR};
390
111
        for (const auto& inner : arg.m_inner) {
391
111
            if (!include_hidden && inner.m_opts.hidden) continue;
392
111
            UniValue prop{OpenRPCArgSchema(inner, include_hidden)};
393
111
            if (!inner.m_description.empty()) prop.pushKV("description", inner.m_description);
394
111
            if (inner.m_opts.placeholder) prop.pushKV("x-bitcoin-placeholder", true);
395
111
            if (inner.m_opts.also_positional) prop.pushKV("x-bitcoin-also-positional", true);
396
111
            properties.pushKV(inner.GetFirstName(), std::move(prop));
397
111
            if (!inner.IsOptional()) required.push_back(inner.GetFirstName());
398
111
        }
399
42
        schema.pushKV("type", "object");
400
42
        schema.pushKV("properties", std::move(properties));
401
42
        schema.pushKV("additionalProperties", false);
402
42
        if (!required.empty()) schema.pushKV("required", std::move(required));
403
42
        break;
404
30
    }
405
0
    case RPCArg::Type::OBJ_USER_KEYS: {
406
0
        schema.pushKV("type", "object");
407
0
        if (!arg.m_inner.empty()) {
408
0
            schema.pushKV("additionalProperties", OpenRPCArgSchema(arg.m_inner[0], include_hidden));
409
0
        } else {
410
0
            schema.pushKV("additionalProperties", true);
411
0
        }
412
0
        break;
413
30
    }
414
662
    } // no default case, so the compiler can warn about missing cases
415
662
    ApplyTypeStrOverride(schema, arg);
416
662
    ApplyArgFallback(schema, arg);
417
662
    return schema;
418
662
}
419
420
// NOLINTNEXTLINE(misc-no-recursion)
421
UniValue OpenRPCResultSchema(const RPCResult& result)
422
4.09k
{
423
4.09k
    if (result.m_opts.skip_type_check) {
424
11
        RPCResultOptions opts{result.m_opts};
425
11
        opts.skip_type_check = false;
426
11
        if (result.m_type == RPCResult::Type::OBJ) {
427
8
            UniValue obj_schema{OpenRPCResultSchema(RPCResult{result, std::move(opts)})};
428
8
            if (result.m_key_name.empty()) return obj_schema;
429
430
0
            UniValue one_of{UniValue::VARR};
431
0
            one_of.push_back(std::move(obj_schema));
432
0
            one_of.push_back(MakeObject({{"const", false}}));
433
0
            UniValue schema{UniValue::VOBJ};
434
0
            schema.pushKV("oneOf", std::move(one_of));
435
0
            return schema;
436
8
        }
437
3
        if (result.m_type == RPCResult::Type::ARR) return OpenRPCResultSchema(RPCResult{result, std::move(opts)});
438
0
        return UniValue{UniValue::VOBJ};
439
3
    }
440
441
4.08k
    switch (result.m_type) {
442
688
    case RPCResult::Type::STR:
443
688
        return MakeObject({{"type", "string"}});
444
165
    case RPCResult::Type::STR_AMOUNT:
445
165
        return MakeObject({{"type", "number"}, {"x-bitcoin-unit", "amount"}});
446
748
    case RPCResult::Type::STR_HEX:
447
748
        return MakeObject({{"type", "string"}, {"pattern", "^[0-9a-fA-F]+$"}});
448
1.10k
    case RPCResult::Type::NUM:
449
1.10k
        return MakeObject({{"type", "number"}});
450
130
    case RPCResult::Type::NUM_TIME: {
451
130
        UniValue schema{UniValue::VOBJ};
452
130
        schema.pushKV("type", "number");
453
130
        schema.pushKV("x-bitcoin-unit", "unix-time");
454
130
        return schema;
455
0
    }
456
199
    case RPCResult::Type::BOOL:
457
199
        return MakeObject({{"type", "boolean"}});
458
41
    case RPCResult::Type::NONE:
459
41
        return MakeObject({{"type", "null"}});
460
363
    case RPCResult::Type::ARR: {
461
363
        UniValue items{DedupArrayItemsSchema(result.m_inner)};
462
363
        UniValue schema{UniValue::VOBJ};
463
363
        schema.pushKV("type", "array");
464
363
        schema.pushKV("items", std::move(items));
465
363
        return schema;
466
0
    }
467
3
    case RPCResult::Type::ARR_FIXED: {
468
3
        UniValue items{UniValue::VARR};
469
15
        for (const auto& inner : result.m_inner) {
470
15
            items.push_back(OpenRPCResultSchema(inner));
471
15
        }
472
3
        UniValue schema{UniValue::VOBJ};
473
3
        schema.pushKV("type", "array");
474
3
        schema.pushKV("items", std::move(items));
475
3
        schema.pushKV("additionalItems", false);
476
3
        schema.pushKV("minItems", uint64_t(result.m_inner.size()));
477
3
        schema.pushKV("maxItems", uint64_t(result.m_inner.size()));
478
3
        return schema;
479
0
    }
480
561
    case RPCResult::Type::OBJ: {
481
561
        UniValue properties{UniValue::VOBJ};
482
561
        UniValue required{UniValue::VARR};
483
3.25k
        for (const auto& inner : result.m_inner) {
484
3.25k
            if (inner.m_key_name.empty()) continue;
485
3.25k
            UniValue prop{OpenRPCResultSchema(inner)};
486
3.25k
            if (!inner.m_description.empty()) prop.pushKV("description", inner.m_description);
487
3.25k
            properties.pushKV(inner.m_key_name, std::move(prop));
488
3.25k
            if (!inner.m_optional) required.push_back(inner.m_key_name);
489
3.25k
        }
490
561
        UniValue schema{UniValue::VOBJ};
491
561
        schema.pushKV("type", "object");
492
561
        schema.pushKV("properties", std::move(properties));
493
561
        schema.pushKV("additionalProperties", false);
494
561
        if (!required.empty()) schema.pushKV("required", std::move(required));
495
561
        return schema;
496
0
    }
497
65
    case RPCResult::Type::OBJ_DYN: {
498
65
        UniValue schema{UniValue::VOBJ};
499
65
        schema.pushKV("type", "object");
500
65
        if (!result.m_inner.empty()) {
501
65
            schema.pushKV("additionalProperties", OpenRPCResultSchema(result.m_inner[0]));
502
65
        } else {
503
0
            schema.pushKV("additionalProperties", UniValue{UniValue::VOBJ});
504
0
        }
505
65
        return schema;
506
0
    }
507
16
    case RPCResult::Type::ANY:
508
16
        return UniValue{UniValue::VOBJ};
509
4.08k
    } // no default case, so the compiler can warn about missing cases
510
4.08k
    NONFATAL_UNREACHABLE();
511
4.08k
}
512
} // namespace
513
514
static RPCResult OpenRPCDocResult()
515
5.47k
{
516
5.47k
    return RPCResult{
517
5.47k
        RPCResult::Type::OBJ, "", "",
518
5.47k
        {
519
5.47k
            {RPCResult::Type::STR, "openrpc", "OpenRPC specification version."},
520
5.47k
            {RPCResult::Type::OBJ, "info", "Metadata about this JSON-RPC interface.",
521
5.47k
                {
522
5.47k
                    {RPCResult::Type::STR, "title", "API title."},
523
5.47k
                    {RPCResult::Type::STR, "version", "Bitcoin Core version string."},
524
5.47k
                    {RPCResult::Type::STR, "description", "API description."},
525
5.47k
                }},
526
5.47k
            {RPCResult::Type::ARR, "methods", "Documented RPC methods.",
527
5.47k
                {{RPCResult::Type::OBJ, "", "An RPC method description object.",
528
5.47k
                    {
529
5.47k
                        {RPCResult::Type::STR, "name", "Method name."},
530
5.47k
                        {RPCResult::Type::STR, "description", "Method description."},
531
5.47k
                        {RPCResult::Type::ARR, "params", "Method parameters.",
532
5.47k
                            {{RPCResult::Type::OBJ, "", "A parameter.",
533
5.47k
                                {
534
5.47k
                                    {RPCResult::Type::STR, "name", "Parameter name."},
535
5.47k
                                    {RPCResult::Type::BOOL, "required", "Whether the parameter is required."},
536
5.47k
                                    {RPCResult::Type::ANY, "schema", "JSON Schema for the parameter."},
537
5.47k
                                    {RPCResult::Type::STR, "description", /*optional=*/true, "Parameter description."},
538
5.47k
                                    {RPCResult::Type::ARR, "x-bitcoin-aliases", /*optional=*/true, "Alternative parameter names.",
539
5.47k
                                        {{RPCResult::Type::STR, "", "An alias."}}},
540
5.47k
                                    {RPCResult::Type::BOOL, "x-bitcoin-placeholder", /*optional=*/true, "Whether the parameter is retained only for compatibility."},
541
5.47k
                                    {RPCResult::Type::BOOL, "x-bitcoin-also-positional", /*optional=*/true, "Whether the parameter can also be passed positionally."},
542
5.47k
                                }}}},
543
5.47k
                        {RPCResult::Type::OBJ, "result", "Method result.",
544
5.47k
                            {
545
5.47k
                                {RPCResult::Type::STR, "name", "Result name."},
546
5.47k
                                {RPCResult::Type::ANY, "schema", "JSON Schema for the result."},
547
5.47k
                            }},
548
5.47k
                        {RPCResult::Type::STR, "x-bitcoin-category", "RPC category."},
549
5.47k
                    }}}},
550
5.47k
        },
551
5.47k
        {.skip_type_check = true}};
552
5.47k
}
553
554
static RPCMethod getopenrpcinfo()
555
2.73k
{
556
2.73k
    return RPCMethod{
557
2.73k
        "getopenrpcinfo",
558
2.73k
        "Returns an OpenRPC document for currently available RPC commands.\n",
559
2.73k
        {
560
2.73k
            {"show_hidden", RPCArg::Type::BOOL, RPCArg::Default{false}, "Also include hidden RPC commands and arguments."},
561
2.73k
        },
562
2.73k
        OpenRPCDocResult(),
563
2.73k
        RPCExamples{
564
2.73k
            HelpExampleCli("getopenrpcinfo", "")
565
2.73k
            + HelpExampleRpc("getopenrpcinfo", "")
566
2.73k
        },
567
2.73k
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
568
2.73k
{
569
2
    const bool include_hidden{!request.params[0].isNull() && request.params[0].get_bool()};
570
2
    return tableRPC.buildOpenRPCDoc(include_hidden);
571
2
},
572
2.73k
    };
573
2.73k
}
574
575
static RPCMethod rpc_discover()
576
2.73k
{
577
2.73k
    return RPCMethod{
578
2.73k
        "rpc.discover",
579
2.73k
        "Returns an OpenRPC schema as a description of this service.\n",
580
2.73k
        {},
581
2.73k
        OpenRPCDocResult(),
582
2.73k
        RPCExamples{
583
2.73k
            HelpExampleCli("rpc.discover", "")
584
2.73k
            + HelpExampleRpc("rpc.discover", "")
585
2.73k
        },
586
2.73k
        [](const RPCMethod&, const JSONRPCRequest&) -> UniValue
587
2.73k
{
588
1
    return tableRPC.buildOpenRPCDoc(/*include_hidden=*/false);
589
1
},
590
2.73k
    };
591
2.73k
}
592
593
static const CRPCCommand vRPCCommands[]{
594
    /* Overall control/query calls */
595
    {"control", &getopenrpcinfo},
596
    {"control", &rpc_discover},
597
    {"control", &getrpcinfo},
598
    {"control", &help},
599
    {"control", &stop},
600
    {"control", &uptime},
601
};
602
603
CRPCTable::CRPCTable()
604
1.37k
{
605
8.23k
    for (const auto& c : vRPCCommands) {
606
8.23k
        appendCommand(c.name, &c);
607
8.23k
    }
608
1.37k
}
609
610
void CRPCTable::appendCommand(const std::string& name, const CRPCCommand* pcmd)
611
175k
{
612
175k
    CHECK_NONFATAL(!IsRPCRunning()); // Only add commands before rpc is running
613
614
175k
    mapCommands[name].push_back(pcmd);
615
175k
}
616
617
bool CRPCTable::removeCommand(const std::string& name, const CRPCCommand* pcmd)
618
24.3k
{
619
24.3k
    auto it = mapCommands.find(name);
620
24.3k
    if (it != mapCommands.end()) {
621
24.3k
        auto new_end = std::remove(it->second.begin(), it->second.end(), pcmd);
622
24.3k
        if (it->second.end() != new_end) {
623
24.3k
            it->second.erase(new_end, it->second.end());
624
24.3k
            if (it->second.empty()) {
625
24.3k
                mapCommands.erase(it);
626
24.3k
            }
627
24.3k
            return true;
628
24.3k
        }
629
24.3k
    }
630
0
    return false;
631
24.3k
}
632
633
void StartRPC()
634
1.12k
{
635
1.12k
    LogDebug(BCLog::RPC, "Starting RPC\n");
636
1.12k
    g_rpc_running = true;
637
1.12k
}
638
639
void InterruptRPC()
640
1.16k
{
641
1.16k
    static std::once_flag g_rpc_interrupt_flag;
642
    // This function could be called twice if the GUI has been started with -server=1.
643
1.16k
    std::call_once(g_rpc_interrupt_flag, []() {
644
1.16k
        LogDebug(BCLog::RPC, "Interrupting RPC\n");
645
        // Interrupt e.g. running longpolls
646
1.16k
        g_rpc_running = false;
647
1.16k
    });
648
1.16k
}
649
650
void StopRPC()
651
1.16k
{
652
1.16k
    static std::once_flag g_rpc_stop_flag;
653
    // This function could be called twice if the GUI has been started with -server=1.
654
1.16k
    assert(!g_rpc_running);
655
1.16k
    std::call_once(g_rpc_stop_flag, [&]() {
656
1.16k
        LogDebug(BCLog::RPC, "Stopping RPC\n");
657
1.16k
        DeleteAuthCookie();
658
1.16k
        LogDebug(BCLog::RPC, "RPC stopped.\n");
659
1.16k
    });
660
1.16k
}
661
662
bool IsRPCRunning()
663
184k
{
664
184k
    return g_rpc_running;
665
184k
}
666
667
void RpcInterruptionPoint()
668
8.53k
{
669
8.53k
    if (!IsRPCRunning()) throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "Shutting down");
670
8.53k
}
671
672
void SetRPCWarmupStatus(const std::string& newStatus)
673
7.66k
{
674
7.66k
    LOCK(g_rpc_warmup_mutex);
675
7.66k
    rpcWarmupStatus = newStatus;
676
7.66k
}
677
678
void SetRPCWarmupStarting()
679
695
{
680
695
    LOCK(g_rpc_warmup_mutex);
681
695
    fRPCInWarmup = true;
682
695
}
683
684
void SetRPCWarmupFinished()
685
1.00k
{
686
1.00k
    LOCK(g_rpc_warmup_mutex);
687
1.00k
    assert(fRPCInWarmup);
688
1.00k
    fRPCInWarmup = false;
689
1.00k
}
690
691
bool RPCIsInWarmup(std::string *outStatus)
692
794
{
693
794
    LOCK(g_rpc_warmup_mutex);
694
794
    if (outStatus)
695
727
        *outStatus = rpcWarmupStatus;
696
794
    return fRPCInWarmup;
697
794
}
698
699
bool IsDeprecatedRPCEnabled(const std::string& method)
700
83.9k
{
701
83.9k
    const std::vector<std::string> enabled_methods = gArgs.GetArgs("-deprecatedrpc");
702
703
83.9k
    return find(enabled_methods.begin(), enabled_methods.end(), method) != enabled_methods.end();
704
83.9k
}
705
706
UniValue JSONRPCExec(const JSONRPCRequest& jreq, bool catch_errors)
707
195k
{
708
195k
    UniValue result;
709
195k
    if (catch_errors) {
710
195k
        try {
711
195k
            result = tableRPC.execute(jreq);
712
195k
        } catch (UniValue& e) {
713
6.14k
            return JSONRPCReplyObj(NullUniValue, std::move(e), jreq.id, jreq.m_json_version);
714
6.14k
        } catch (const std::exception& e) {
715
0
            return JSONRPCReplyObj(NullUniValue, JSONRPCError(RPC_MISC_ERROR, e.what()), jreq.id, jreq.m_json_version);
716
0
        }
717
195k
    } else {
718
34
        result = tableRPC.execute(jreq);
719
34
    }
720
721
189k
    return JSONRPCReplyObj(std::move(result), NullUniValue, jreq.id, jreq.m_json_version);
722
195k
}
723
724
/**
725
 * Process named arguments into a vector of positional arguments, based on the
726
 * passed-in specification for the RPC call's arguments.
727
 */
728
static inline JSONRPCRequest transformNamedArguments(const JSONRPCRequest& in, const std::vector<std::pair<std::string, bool>>& argNames)
729
95.8k
{
730
95.8k
    JSONRPCRequest out = in;
731
95.8k
    out.params = UniValue(UniValue::VARR);
732
    // Build a map of parameters, and remove ones that have been processed, so that we can throw a focused error if
733
    // there is an unknown one.
734
95.8k
    const std::vector<std::string>& keys = in.params.getKeys();
735
95.8k
    const std::vector<UniValue>& values = in.params.getValues();
736
95.8k
    std::unordered_map<std::string, const UniValue*> argsIn;
737
159k
    for (size_t i=0; i<keys.size(); ++i) {
738
63.1k
        auto [_, inserted] = argsIn.emplace(keys[i], &values[i]);
739
63.1k
        if (!inserted) {
740
2
            throw JSONRPCError(RPC_INVALID_PARAMETER, "Parameter " + keys[i] + " specified multiple times");
741
2
        }
742
63.1k
    }
743
    // Process expected parameters. If any parameters were left unspecified in
744
    // the request before a parameter that was specified, null values need to be
745
    // inserted at the unspecified parameter positions, and the "hole" variable
746
    // below tracks the number of null values that need to be inserted.
747
    // The "initial_hole_size" variable stores the size of the initial hole,
748
    // i.e. how many initial positional arguments were left unspecified. This is
749
    // used after the for-loop to add initial positional arguments from the
750
    // "args" parameter, if present.
751
95.8k
    int hole = 0;
752
95.8k
    int initial_hole_size = 0;
753
95.8k
    const std::string* initial_param = nullptr;
754
95.8k
    UniValue options{UniValue::VOBJ};
755
139k
    for (const auto& [argNamePattern, named_only]: argNames) {
756
139k
        std::vector<std::string> vargNames = SplitString(argNamePattern, '|');
757
139k
        auto fr = argsIn.end();
758
143k
        for (const std::string & argName : vargNames) {
759
143k
            fr = argsIn.find(argName);
760
143k
            if (fr != argsIn.end()) {
761
62.2k
                break;
762
62.2k
            }
763
143k
        }
764
765
        // Handle named-only parameters by pushing them into a temporary options
766
        // object, and then pushing the accumulated options as the next
767
        // positional argument.
768
139k
        if (named_only) {
769
11.3k
            if (fr != argsIn.end()) {
770
496
                if (options.exists(fr->first)) {
771
0
                    throw JSONRPCError(RPC_INVALID_PARAMETER, "Parameter " + fr->first + " specified multiple times");
772
0
                }
773
496
                options.pushKVEnd(fr->first, *fr->second);
774
496
                argsIn.erase(fr);
775
496
            }
776
11.3k
            continue;
777
11.3k
        }
778
779
128k
        if (!options.empty() || fr != argsIn.end()) {
780
74.3k
            for (int i = 0; i < hole; ++i) {
781
                // Fill hole between specified parameters with JSON nulls,
782
                // but not at the end (for backwards compatibility with calls
783
                // that act based on number of specified parameters).
784
12.2k
                out.params.push_back(UniValue());
785
12.2k
            }
786
62.0k
            hole = 0;
787
62.0k
            if (!initial_param) initial_param = &argNamePattern;
788
66.5k
        } else {
789
66.5k
            hole += 1;
790
66.5k
            if (out.params.empty()) initial_hole_size = hole;
791
66.5k
        }
792
793
        // If named input parameter "fr" is present, push it onto out.params. If
794
        // options are present, push them onto out.params. If both are present,
795
        // throw an error.
796
128k
        if (fr != argsIn.end()) {
797
61.7k
            if (!options.empty()) {
798
1
                throw JSONRPCError(RPC_INVALID_PARAMETER, "Parameter " + fr->first + " conflicts with parameter " + options.getKeys().front());
799
1
            }
800
61.7k
            out.params.push_back(*fr->second);
801
61.7k
            argsIn.erase(fr);
802
61.7k
        }
803
128k
        if (!options.empty()) {
804
332
            out.params.push_back(std::move(options));
805
332
            options = UniValue{UniValue::VOBJ};
806
332
        }
807
128k
    }
808
    // If leftover "args" param was found, use it as a source of positional
809
    // arguments and add named arguments after. This is a convenience for
810
    // clients that want to pass a combination of named and positional
811
    // arguments as described in doc/JSON-RPC-interface.md#parameter-passing
812
95.8k
    auto positional_args{argsIn.extract("args")};
813
95.8k
    if (positional_args && positional_args.mapped()->isArray()) {
814
865
        if (initial_hole_size < (int)positional_args.mapped()->size() && initial_param) {
815
6
            throw JSONRPCError(RPC_INVALID_PARAMETER, "Parameter " + *initial_param + " specified twice both as positional and named argument");
816
6
        }
817
        // Assign positional_args to out.params and append named_args after.
818
859
        UniValue named_args{std::move(out.params)};
819
859
        out.params = *positional_args.mapped();
820
2.48k
        for (size_t i{out.params.size()}; i < named_args.size(); ++i) {
821
1.62k
            out.params.push_back(named_args[i]);
822
1.62k
        }
823
859
    }
824
    // If there are still arguments in the argsIn map, this is an error.
825
95.8k
    if (!argsIn.empty()) {
826
5
        throw JSONRPCError(RPC_INVALID_PARAMETER, "Unknown named parameter " + argsIn.begin()->first);
827
5
    }
828
    // Return request with named arguments transformed to positional arguments
829
95.8k
    return out;
830
95.8k
}
831
832
static bool ExecuteCommands(const std::vector<const CRPCCommand*>& commands, const JSONRPCRequest& request, UniValue& result)
833
196k
{
834
196k
    for (const auto& command : commands) {
835
196k
        if (ExecuteCommand(*command, request, result, &command == &commands.back())) {
836
190k
            return true;
837
190k
        }
838
196k
    }
839
5.90k
    return false;
840
196k
}
841
842
UniValue CRPCTable::execute(const JSONRPCRequest &request) const
843
195k
{
844
    // Return immediately if in warmup
845
195k
    {
846
195k
        LOCK(g_rpc_warmup_mutex);
847
195k
        if (fRPCInWarmup)
848
256
            throw JSONRPCError(RPC_IN_WARMUP, rpcWarmupStatus);
849
195k
    }
850
851
    // Find method
852
195k
    auto it = mapCommands.find(request.strMethod);
853
195k
    if (it != mapCommands.end()) {
854
195k
        UniValue result;
855
195k
        if (ExecuteCommands(it->second, request, result)) {
856
189k
            return result;
857
189k
        }
858
195k
    }
859
5.91k
    throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Method not found");
860
195k
}
861
862
static bool ExecuteCommand(const CRPCCommand& command, const JSONRPCRequest& request, UniValue& result, bool last_handler)
863
196k
{
864
196k
    try {
865
196k
        RPCCommandExecution execution(request.strMethod);
866
        // Execute, convert arguments to array if necessary
867
196k
        if (request.params.isObject()) {
868
95.8k
            return command.actor(transformNamedArguments(request, command.argNames), result, last_handler);
869
100k
        } else {
870
100k
            return command.actor(request, result, last_handler);
871
100k
        }
872
196k
    } catch (const UniValue::type_error& e) {
873
18
        throw JSONRPCError(RPC_TYPE_ERROR, e.what());
874
54
    } catch (const std::exception& e) {
875
54
        throw JSONRPCError(RPC_MISC_ERROR, e.what());
876
54
    }
877
196k
}
878
879
std::vector<std::string> CRPCTable::listCommands() const
880
1
{
881
1
    std::vector<std::string> commandList;
882
1
    commandList.reserve(mapCommands.size());
883
6
    for (const auto& i : mapCommands) commandList.emplace_back(i.first);
884
1
    return commandList;
885
1
}
886
887
UniValue CRPCTable::buildOpenRPCDoc(bool include_hidden) const
888
4
{
889
4
    std::vector<std::string> method_names;
890
354
    for (const auto& [name, cmds] : mapCommands) {
891
354
        if (cmds.empty()) continue;
892
354
        const CRPCCommand* cmd{cmds.front()};
893
354
        if ((!include_hidden && cmd->category == "hidden") || !cmd->metadata_fn) continue;
894
316
        method_names.push_back(name);
895
316
    }
896
4
    std::sort(method_names.begin(), method_names.end());
897
898
4
    UniValue methods{UniValue::VARR};
899
316
    for (const auto& method_name : method_names) {
900
316
        const CRPCCommand* cmd{mapCommands.at(method_name).front()};
901
316
        RPCMethod helpman{cmd->metadata_fn()};
902
903
316
        UniValue params{UniValue::VARR};
904
505
        for (const auto& arg : helpman.GetArgs()) {
905
505
            if (!include_hidden && arg.m_opts.hidden) continue;
906
502
            UniValue param{UniValue::VOBJ};
907
502
            param.pushKV("name", arg.GetFirstName());
908
502
            param.pushKV("required", !arg.IsOptional());
909
502
            param.pushKV("schema", OpenRPCArgSchema(arg, include_hidden));
910
911
502
            std::vector<std::string> names{SplitString(arg.m_names, '|')};
912
502
            if (names.size() > 1) {
913
6
                UniValue aliases{UniValue::VARR};
914
12
                for (size_t i{1}; i < names.size(); ++i) aliases.push_back(names[i]);
915
6
                param.pushKV("x-bitcoin-aliases", std::move(aliases));
916
6
            }
917
502
            if (arg.m_opts.placeholder) param.pushKV("x-bitcoin-placeholder", true);
918
502
            if (arg.m_opts.also_positional) param.pushKV("x-bitcoin-also-positional", true);
919
502
            if (!arg.m_description.empty()) param.pushKV("description", arg.m_description);
920
502
            params.push_back(std::move(param));
921
502
        }
922
923
316
        UniValue result_schema{UniValue::VOBJ};
924
316
        const auto& results{helpman.GetResults().m_results};
925
316
        if (results.size() == 1 && results[0].m_type != RPCResult::Type::ANY) {
926
269
            result_schema = OpenRPCResultSchema(results[0]);
927
269
        } else if (results.size() > 1) {
928
44
            UniValue one_of{UniValue::VARR};
929
116
            for (const auto& r : results) {
930
116
                if (r.m_type == RPCResult::Type::ANY) continue;
931
112
                UniValue schema{OpenRPCResultSchema(r)};
932
112
                if (!r.m_cond.empty()) schema.pushKV("description", r.m_cond);
933
112
                one_of.push_back(std::move(schema));
934
112
            }
935
44
            if (one_of.size() == 1) {
936
4
                result_schema = one_of[0];
937
40
            } else if (one_of.size() > 1) {
938
40
                result_schema.pushKV("oneOf", std::move(one_of));
939
40
            }
940
44
        }
941
942
316
        UniValue method{UniValue::VOBJ};
943
316
        method.pushKV("name", method_name);
944
316
        method.pushKV("description", util::TrimString(helpman.GetDescription()));
945
316
        method.pushKV("params", std::move(params));
946
316
        UniValue result{UniValue::VOBJ};
947
316
        result.pushKV("name", "result");
948
316
        result.pushKV("schema", std::move(result_schema));
949
316
        method.pushKV("result", std::move(result));
950
316
        method.pushKV("x-bitcoin-category", cmd->category);
951
316
        methods.push_back(std::move(method));
952
316
    }
953
954
4
    std::string version{"v" CLIENT_VERSION_STRING};
955
4
    if (!CLIENT_VERSION_IS_RELEASE) version += "-dev";
956
957
4
    UniValue info{UniValue::VOBJ};
958
4
    info.pushKV("title", "Bitcoin Core JSON-RPC");
959
4
    info.pushKV("version", version);
960
4
    info.pushKV("description", "Autogenerated from Bitcoin Core RPC metadata.");
961
962
4
    UniValue doc{UniValue::VOBJ};
963
4
    doc.pushKV("openrpc", "1.4.1");
964
4
    doc.pushKV("info", std::move(info));
965
4
    doc.pushKV("methods", std::move(methods));
966
4
    return doc;
967
4
}
968
969
UniValue CRPCTable::dumpArgMap(const JSONRPCRequest& args_request) const
970
2
{
971
2
    JSONRPCRequest request = args_request;
972
2
    request.mode = JSONRPCRequest::GET_ARGS;
973
974
2
    UniValue ret{UniValue::VARR};
975
348
    for (const auto& cmd : mapCommands) {
976
348
        UniValue result;
977
348
        if (ExecuteCommands(cmd.second, request, result)) {
978
896
            for (const auto& values : result.getValues()) {
979
896
                ret.push_back(values);
980
896
            }
981
348
        }
982
348
    }
983
2
    return ret;
984
2
}
985
986
CRPCTable tableRPC;