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