Coverage Report

Created: 2026-08-25 19:22

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