Coverage Report

Created: 2026-09-14 20:36

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