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