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