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