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