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