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