Coverage Report

Created: 2026-09-02 14:16

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