Coverage Report

Created: 2026-08-14 20:23

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