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