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