/bitcoin/src/rpc/server.cpp
Line | Count | Source |
1 | | // Copyright (c) 2010 Satoshi Nakamoto |
2 | | // Copyright (c) 2009-2022 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 <cassert> |
25 | | #include <chrono> |
26 | | #include <memory> |
27 | | #include <mutex> |
28 | | #include <unordered_map> |
29 | | |
30 | | using util::SplitString; |
31 | | |
32 | | static GlobalMutex g_rpc_warmup_mutex; |
33 | | static std::atomic<bool> g_rpc_running{false}; |
34 | | static bool fRPCInWarmup GUARDED_BY(g_rpc_warmup_mutex) = true; |
35 | | static std::string rpcWarmupStatus GUARDED_BY(g_rpc_warmup_mutex) = "RPC server started"; |
36 | | /* Timer-creating functions */ |
37 | | static RPCTimerInterface* timerInterface = nullptr; |
38 | | /* Map of name to timer. */ |
39 | | static GlobalMutex g_deadline_timers_mutex; |
40 | | static std::map<std::string, std::unique_ptr<RPCTimerBase> > deadlineTimers GUARDED_BY(g_deadline_timers_mutex); |
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 | 2.35M | { |
62 | 2.35M | LOCK(g_rpc_server_info.mutex); |
63 | 2.35M | it = g_rpc_server_info.active_commands.insert(g_rpc_server_info.active_commands.end(), {method, SteadyClock::now()}); |
64 | 2.35M | } |
65 | | ~RPCCommandExecution() |
66 | 2.35M | { |
67 | 2.35M | LOCK(g_rpc_server_info.mutex); |
68 | 2.35M | g_rpc_server_info.active_commands.erase(it); |
69 | 2.35M | } |
70 | | }; |
71 | | |
72 | | std::string CRPCTable::help(const std::string& strCommand, const JSONRPCRequest& helpreq) const |
73 | 0 | { |
74 | 0 | std::string strRet; |
75 | 0 | std::string category; |
76 | 0 | std::set<intptr_t> setDone; |
77 | 0 | std::vector<std::pair<std::string, const CRPCCommand*> > vCommands; |
78 | 0 | vCommands.reserve(mapCommands.size()); |
79 | |
|
80 | 0 | for (const auto& entry : mapCommands) Branch (80:28): [True: 0, False: 0]
|
81 | 0 | vCommands.emplace_back(entry.second.front()->category + entry.first, entry.second.front()); |
82 | 0 | sort(vCommands.begin(), vCommands.end()); |
83 | |
|
84 | 0 | JSONRPCRequest jreq = helpreq; |
85 | 0 | jreq.mode = JSONRPCRequest::GET_HELP; |
86 | 0 | jreq.params = UniValue(); |
87 | |
|
88 | 0 | for (const std::pair<std::string, const CRPCCommand*>& command : vCommands) Branch (88:68): [True: 0, False: 0]
|
89 | 0 | { |
90 | 0 | const CRPCCommand *pcmd = command.second; |
91 | 0 | std::string strMethod = pcmd->name; |
92 | 0 | if ((strCommand != "" || pcmd->category == "hidden") && strMethod != strCommand) Branch (92:14): [True: 0, False: 0]
Branch (92:34): [True: 0, False: 0]
Branch (92:65): [True: 0, False: 0]
|
93 | 0 | continue; |
94 | 0 | jreq.strMethod = strMethod; |
95 | 0 | try |
96 | 0 | { |
97 | 0 | UniValue unused_result; |
98 | 0 | if (setDone.insert(pcmd->unique_id).second) Branch (98:17): [True: 0, False: 0]
|
99 | 0 | pcmd->actor(jreq, unused_result, /*last_handler=*/true); |
100 | 0 | } |
101 | 0 | catch (const std::exception& e) |
102 | 0 | { |
103 | | // Help text is returned in an exception |
104 | 0 | std::string strHelp = std::string(e.what()); |
105 | 0 | if (strCommand == "") Branch (105:17): [True: 0, False: 0]
|
106 | 0 | { |
107 | 0 | if (strHelp.find('\n') != std::string::npos) Branch (107:21): [True: 0, False: 0]
|
108 | 0 | strHelp = strHelp.substr(0, strHelp.find('\n')); |
109 | |
|
110 | 0 | if (category != pcmd->category) Branch (110:21): [True: 0, False: 0]
|
111 | 0 | { |
112 | 0 | if (!category.empty()) Branch (112:25): [True: 0, False: 0]
|
113 | 0 | strRet += "\n"; |
114 | 0 | category = pcmd->category; |
115 | 0 | strRet += "== " + Capitalize(category) + " ==\n"; |
116 | 0 | } |
117 | 0 | } |
118 | 0 | strRet += strHelp + "\n"; |
119 | 0 | } |
120 | 0 | } |
121 | 0 | if (strRet == "") Branch (121:9): [True: 0, False: 0]
|
122 | 0 | strRet = strprintf("help: unknown command: %s\n", strCommand); |
123 | 0 | strRet = strRet.substr(0,strRet.size()-1); |
124 | 0 | return strRet; |
125 | 0 | } |
126 | | |
127 | | static RPCHelpMan help() |
128 | 22.1k | { |
129 | 22.1k | return RPCHelpMan{ |
130 | 22.1k | "help", |
131 | 22.1k | "List all commands, or get help for a specified command.\n", |
132 | 22.1k | { |
133 | 22.1k | {"command", RPCArg::Type::STR, RPCArg::DefaultHint{"all commands"}, "The command to get help on"}, |
134 | 22.1k | }, |
135 | 22.1k | { |
136 | 22.1k | RPCResult{RPCResult::Type::STR, "", "The help text"}, |
137 | 22.1k | RPCResult{RPCResult::Type::ANY, "", ""}, |
138 | 22.1k | }, |
139 | 22.1k | RPCExamples{""}, |
140 | 22.1k | [&](const RPCHelpMan& self, const JSONRPCRequest& jsonRequest) -> UniValue |
141 | 22.1k | { |
142 | 0 | std::string strCommand; |
143 | 0 | if (jsonRequest.params.size() > 0) { Branch (143:9): [True: 0, False: 0]
|
144 | 0 | strCommand = jsonRequest.params[0].get_str(); |
145 | 0 | } |
146 | 0 | if (strCommand == "dump_all_command_conversions") { Branch (146:9): [True: 0, False: 0]
|
147 | | // Used for testing only, undocumented |
148 | 0 | return tableRPC.dumpArgMap(jsonRequest); |
149 | 0 | } |
150 | | |
151 | 0 | return tableRPC.help(strCommand, jsonRequest); |
152 | 0 | }, |
153 | 22.1k | }; |
154 | 22.1k | } |
155 | | |
156 | | static RPCHelpMan stop() |
157 | 33.2k | { |
158 | 33.2k | static const std::string RESULT{CLIENT_NAME " stopping"}; |
159 | 33.2k | return RPCHelpMan{ |
160 | 33.2k | "stop", |
161 | | // Also accept the hidden 'wait' integer argument (milliseconds) |
162 | | // For instance, 'stop 1000' makes the call wait 1 second before returning |
163 | | // to the client (intended for testing) |
164 | 33.2k | "Request a graceful shutdown of " CLIENT_NAME ".", |
165 | 33.2k | { |
166 | 33.2k | {"wait", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "how long to wait in ms", RPCArgOptions{.hidden=true}}, |
167 | 33.2k | }, |
168 | 33.2k | RPCResult{RPCResult::Type::STR, "", "A string with the content '" + RESULT + "'"}, |
169 | 33.2k | RPCExamples{""}, |
170 | 33.2k | [&](const RPCHelpMan& self, const JSONRPCRequest& jsonRequest) -> UniValue |
171 | 33.2k | { |
172 | | // Event loop will exit after current HTTP requests have been handled, so |
173 | | // this reply will get back to the client. |
174 | 11.0k | CHECK_NONFATAL((CHECK_NONFATAL(EnsureAnyNodeContext(jsonRequest.context).shutdown_request))()); |
175 | 11.0k | if (jsonRequest.params[0].isNum()) { Branch (175:9): [True: 0, False: 11.0k]
|
176 | 0 | UninterruptibleSleep(std::chrono::milliseconds{jsonRequest.params[0].getInt<int>()}); |
177 | 0 | } |
178 | 11.0k | return RESULT; |
179 | 11.0k | }, |
180 | 33.2k | }; |
181 | 33.2k | } |
182 | | |
183 | | static RPCHelpMan uptime() |
184 | 22.1k | { |
185 | 22.1k | return RPCHelpMan{ |
186 | 22.1k | "uptime", |
187 | 22.1k | "Returns the total uptime of the server.\n", |
188 | 22.1k | {}, |
189 | 22.1k | RPCResult{ |
190 | 22.1k | RPCResult::Type::NUM, "", "The number of seconds that the server has been running" |
191 | 22.1k | }, |
192 | 22.1k | RPCExamples{ |
193 | 22.1k | HelpExampleCli("uptime", "") |
194 | 22.1k | + HelpExampleRpc("uptime", "") |
195 | 22.1k | }, |
196 | 22.1k | [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue |
197 | 22.1k | { |
198 | 0 | return GetTime() - GetStartupTime(); |
199 | 0 | } |
200 | 22.1k | }; |
201 | 22.1k | } |
202 | | |
203 | | static RPCHelpMan getrpcinfo() |
204 | 22.1k | { |
205 | 22.1k | return RPCHelpMan{ |
206 | 22.1k | "getrpcinfo", |
207 | 22.1k | "Returns details of the RPC server.\n", |
208 | 22.1k | {}, |
209 | 22.1k | RPCResult{ |
210 | 22.1k | RPCResult::Type::OBJ, "", "", |
211 | 22.1k | { |
212 | 22.1k | {RPCResult::Type::ARR, "active_commands", "All active commands", |
213 | 22.1k | { |
214 | 22.1k | {RPCResult::Type::OBJ, "", "Information about an active command", |
215 | 22.1k | { |
216 | 22.1k | {RPCResult::Type::STR, "method", "The name of the RPC command"}, |
217 | 22.1k | {RPCResult::Type::NUM, "duration", "The running time in microseconds"}, |
218 | 22.1k | }}, |
219 | 22.1k | }}, |
220 | 22.1k | {RPCResult::Type::STR, "logpath", "The complete file path to the debug log"}, |
221 | 22.1k | } |
222 | 22.1k | }, |
223 | 22.1k | RPCExamples{ |
224 | 22.1k | HelpExampleCli("getrpcinfo", "") |
225 | 22.1k | + HelpExampleRpc("getrpcinfo", "")}, |
226 | 22.1k | [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue |
227 | 22.1k | { |
228 | 0 | LOCK(g_rpc_server_info.mutex); |
229 | 0 | UniValue active_commands(UniValue::VARR); |
230 | 0 | for (const RPCCommandExecutionInfo& info : g_rpc_server_info.active_commands) { Branch (230:46): [True: 0, False: 0]
|
231 | 0 | UniValue entry(UniValue::VOBJ); |
232 | 0 | entry.pushKV("method", info.method); |
233 | 0 | entry.pushKV("duration", int64_t{Ticks<std::chrono::microseconds>(SteadyClock::now() - info.start)}); |
234 | 0 | active_commands.push_back(std::move(entry)); |
235 | 0 | } |
236 | |
|
237 | 0 | UniValue result(UniValue::VOBJ); |
238 | 0 | result.pushKV("active_commands", std::move(active_commands)); |
239 | |
|
240 | 0 | const std::string path = LogInstance().m_file_path.utf8string(); |
241 | 0 | UniValue log_path(UniValue::VSTR, path); |
242 | 0 | result.pushKV("logpath", std::move(log_path)); |
243 | |
|
244 | 0 | return result; |
245 | 0 | } |
246 | 22.1k | }; |
247 | 22.1k | } |
248 | | |
249 | | static const CRPCCommand vRPCCommands[]{ |
250 | | /* Overall control/query calls */ |
251 | | {"control", &getrpcinfo}, |
252 | | {"control", &help}, |
253 | | {"control", &stop}, |
254 | | {"control", &uptime}, |
255 | | }; |
256 | | |
257 | | CRPCTable::CRPCTable() |
258 | 11.0k | { |
259 | 44.3k | for (const auto& c : vRPCCommands) { Branch (259:24): [True: 44.3k, False: 11.0k]
|
260 | 44.3k | appendCommand(c.name, &c); |
261 | 44.3k | } |
262 | 11.0k | } |
263 | | |
264 | | void CRPCTable::appendCommand(const std::string& name, const CRPCCommand* pcmd) |
265 | 1.86M | { |
266 | 1.86M | CHECK_NONFATAL(!IsRPCRunning()); // Only add commands before rpc is running |
267 | | |
268 | 1.86M | mapCommands[name].push_back(pcmd); |
269 | 1.86M | } |
270 | | |
271 | | bool CRPCTable::removeCommand(const std::string& name, const CRPCCommand* pcmd) |
272 | 654k | { |
273 | 654k | auto it = mapCommands.find(name); |
274 | 654k | if (it != mapCommands.end()) { Branch (274:9): [True: 654k, False: 0]
|
275 | 654k | auto new_end = std::remove(it->second.begin(), it->second.end(), pcmd); |
276 | 654k | if (it->second.end() != new_end) { Branch (276:13): [True: 654k, False: 0]
|
277 | 654k | it->second.erase(new_end, it->second.end()); |
278 | 654k | return true; |
279 | 654k | } |
280 | 654k | } |
281 | 0 | return false; |
282 | 654k | } |
283 | | |
284 | | void StartRPC() |
285 | 11.0k | { |
286 | 11.0k | LogDebug(BCLog::RPC, "Starting RPC\n"); |
287 | 11.0k | g_rpc_running = true; |
288 | 11.0k | } |
289 | | |
290 | | void InterruptRPC() |
291 | 11.0k | { |
292 | 11.0k | static std::once_flag g_rpc_interrupt_flag; |
293 | | // This function could be called twice if the GUI has been started with -server=1. |
294 | 11.0k | std::call_once(g_rpc_interrupt_flag, []() { |
295 | 11.0k | LogDebug(BCLog::RPC, "Interrupting RPC\n"); |
296 | | // Interrupt e.g. running longpolls |
297 | 11.0k | g_rpc_running = false; |
298 | 11.0k | }); |
299 | 11.0k | } |
300 | | |
301 | | void StopRPC() |
302 | 11.0k | { |
303 | 11.0k | static std::once_flag g_rpc_stop_flag; |
304 | | // This function could be called twice if the GUI has been started with -server=1. |
305 | 11.0k | assert(!g_rpc_running); Branch (305:5): [True: 11.0k, False: 0]
|
306 | 11.0k | std::call_once(g_rpc_stop_flag, [&]() { |
307 | 11.0k | LogDebug(BCLog::RPC, "Stopping RPC\n"); |
308 | 11.0k | WITH_LOCK(g_deadline_timers_mutex, deadlineTimers.clear()); |
309 | 11.0k | DeleteAuthCookie(); |
310 | 11.0k | LogDebug(BCLog::RPC, "RPC stopped.\n"); |
311 | 11.0k | }); |
312 | 11.0k | } |
313 | | |
314 | | bool IsRPCRunning() |
315 | 1.86M | { |
316 | 1.86M | return g_rpc_running; |
317 | 1.86M | } |
318 | | |
319 | | void RpcInterruptionPoint() |
320 | 0 | { |
321 | 0 | if (!IsRPCRunning()) throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "Shutting down"); Branch (321:9): [True: 0, False: 0]
|
322 | 0 | } |
323 | | |
324 | | void SetRPCWarmupStatus(const std::string& newStatus) |
325 | 88.7k | { |
326 | 88.7k | LOCK(g_rpc_warmup_mutex); |
327 | 88.7k | rpcWarmupStatus = newStatus; |
328 | 88.7k | } |
329 | | |
330 | | void SetRPCWarmupFinished() |
331 | 11.0k | { |
332 | 11.0k | LOCK(g_rpc_warmup_mutex); |
333 | 11.0k | assert(fRPCInWarmup); Branch (333:5): [True: 11.0k, False: 0]
|
334 | 11.0k | fRPCInWarmup = false; |
335 | 11.0k | } |
336 | | |
337 | | bool RPCIsInWarmup(std::string *outStatus) |
338 | 0 | { |
339 | 0 | LOCK(g_rpc_warmup_mutex); |
340 | 0 | if (outStatus) Branch (340:9): [True: 0, False: 0]
|
341 | 0 | *outStatus = rpcWarmupStatus; |
342 | 0 | return fRPCInWarmup; |
343 | 0 | } |
344 | | |
345 | | bool IsDeprecatedRPCEnabled(const std::string& method) |
346 | 88.7k | { |
347 | 88.7k | const std::vector<std::string> enabled_methods = gArgs.GetArgs("-deprecatedrpc"); |
348 | | |
349 | 88.7k | return find(enabled_methods.begin(), enabled_methods.end(), method) != enabled_methods.end(); |
350 | 88.7k | } |
351 | | |
352 | | UniValue JSONRPCExec(const JSONRPCRequest& jreq, bool catch_errors) |
353 | 2.35M | { |
354 | 2.35M | UniValue result; |
355 | 2.35M | if (catch_errors) { Branch (355:9): [True: 2.35M, False: 0]
|
356 | 2.35M | try { |
357 | 2.35M | result = tableRPC.execute(jreq); |
358 | 2.35M | } catch (UniValue& e) { |
359 | 4.78k | return JSONRPCReplyObj(NullUniValue, std::move(e), jreq.id, jreq.m_json_version); |
360 | 4.78k | } catch (const std::exception& e) { |
361 | 0 | return JSONRPCReplyObj(NullUniValue, JSONRPCError(RPC_MISC_ERROR, e.what()), jreq.id, jreq.m_json_version); |
362 | 0 | } |
363 | 2.35M | } else { |
364 | 0 | result = tableRPC.execute(jreq); |
365 | 0 | } |
366 | | |
367 | 2.35M | return JSONRPCReplyObj(std::move(result), NullUniValue, jreq.id, jreq.m_json_version); |
368 | 2.35M | } |
369 | | |
370 | | /** |
371 | | * Process named arguments into a vector of positional arguments, based on the |
372 | | * passed-in specification for the RPC call's arguments. |
373 | | */ |
374 | | static inline JSONRPCRequest transformNamedArguments(const JSONRPCRequest& in, const std::vector<std::pair<std::string, bool>>& argNames) |
375 | 0 | { |
376 | 0 | JSONRPCRequest out = in; |
377 | 0 | out.params = UniValue(UniValue::VARR); |
378 | | // Build a map of parameters, and remove ones that have been processed, so that we can throw a focused error if |
379 | | // there is an unknown one. |
380 | 0 | const std::vector<std::string>& keys = in.params.getKeys(); |
381 | 0 | const std::vector<UniValue>& values = in.params.getValues(); |
382 | 0 | std::unordered_map<std::string, const UniValue*> argsIn; |
383 | 0 | for (size_t i=0; i<keys.size(); ++i) { Branch (383:22): [True: 0, False: 0]
|
384 | 0 | auto [_, inserted] = argsIn.emplace(keys[i], &values[i]); |
385 | 0 | if (!inserted) { Branch (385:13): [True: 0, False: 0]
|
386 | 0 | throw JSONRPCError(RPC_INVALID_PARAMETER, "Parameter " + keys[i] + " specified multiple times"); |
387 | 0 | } |
388 | 0 | } |
389 | | // Process expected parameters. If any parameters were left unspecified in |
390 | | // the request before a parameter that was specified, null values need to be |
391 | | // inserted at the unspecified parameter positions, and the "hole" variable |
392 | | // below tracks the number of null values that need to be inserted. |
393 | | // The "initial_hole_size" variable stores the size of the initial hole, |
394 | | // i.e. how many initial positional arguments were left unspecified. This is |
395 | | // used after the for-loop to add initial positional arguments from the |
396 | | // "args" parameter, if present. |
397 | 0 | int hole = 0; |
398 | 0 | int initial_hole_size = 0; |
399 | 0 | const std::string* initial_param = nullptr; |
400 | 0 | UniValue options{UniValue::VOBJ}; |
401 | 0 | for (const auto& [argNamePattern, named_only]: argNames) { Branch (401:50): [True: 0, False: 0]
|
402 | 0 | std::vector<std::string> vargNames = SplitString(argNamePattern, '|'); |
403 | 0 | auto fr = argsIn.end(); |
404 | 0 | for (const std::string & argName : vargNames) { Branch (404:42): [True: 0, False: 0]
|
405 | 0 | fr = argsIn.find(argName); |
406 | 0 | if (fr != argsIn.end()) { Branch (406:17): [True: 0, False: 0]
|
407 | 0 | break; |
408 | 0 | } |
409 | 0 | } |
410 | | |
411 | | // Handle named-only parameters by pushing them into a temporary options |
412 | | // object, and then pushing the accumulated options as the next |
413 | | // positional argument. |
414 | 0 | if (named_only) { Branch (414:13): [True: 0, False: 0]
|
415 | 0 | if (fr != argsIn.end()) { Branch (415:17): [True: 0, False: 0]
|
416 | 0 | if (options.exists(fr->first)) { Branch (416:21): [True: 0, False: 0]
|
417 | 0 | throw JSONRPCError(RPC_INVALID_PARAMETER, "Parameter " + fr->first + " specified multiple times"); |
418 | 0 | } |
419 | 0 | options.pushKVEnd(fr->first, *fr->second); |
420 | 0 | argsIn.erase(fr); |
421 | 0 | } |
422 | 0 | continue; |
423 | 0 | } |
424 | | |
425 | 0 | if (!options.empty() || fr != argsIn.end()) { Branch (425:13): [True: 0, False: 0]
Branch (425:13): [True: 0, False: 0]
Branch (425:33): [True: 0, False: 0]
|
426 | 0 | for (int i = 0; i < hole; ++i) { Branch (426:29): [True: 0, False: 0]
|
427 | | // Fill hole between specified parameters with JSON nulls, |
428 | | // but not at the end (for backwards compatibility with calls |
429 | | // that act based on number of specified parameters). |
430 | 0 | out.params.push_back(UniValue()); |
431 | 0 | } |
432 | 0 | hole = 0; |
433 | 0 | if (!initial_param) initial_param = &argNamePattern; Branch (433:17): [True: 0, False: 0]
|
434 | 0 | } else { |
435 | 0 | hole += 1; |
436 | 0 | if (out.params.empty()) initial_hole_size = hole; Branch (436:17): [True: 0, False: 0]
|
437 | 0 | } |
438 | | |
439 | | // If named input parameter "fr" is present, push it onto out.params. If |
440 | | // options are present, push them onto out.params. If both are present, |
441 | | // throw an error. |
442 | 0 | if (fr != argsIn.end()) { Branch (442:13): [True: 0, False: 0]
|
443 | 0 | if (!options.empty()) { Branch (443:17): [True: 0, False: 0]
|
444 | 0 | throw JSONRPCError(RPC_INVALID_PARAMETER, "Parameter " + fr->first + " conflicts with parameter " + options.getKeys().front()); |
445 | 0 | } |
446 | 0 | out.params.push_back(*fr->second); |
447 | 0 | argsIn.erase(fr); |
448 | 0 | } |
449 | 0 | if (!options.empty()) { Branch (449:13): [True: 0, False: 0]
|
450 | 0 | out.params.push_back(std::move(options)); |
451 | 0 | options = UniValue{UniValue::VOBJ}; |
452 | 0 | } |
453 | 0 | } |
454 | | // If leftover "args" param was found, use it as a source of positional |
455 | | // arguments and add named arguments after. This is a convenience for |
456 | | // clients that want to pass a combination of named and positional |
457 | | // arguments as described in doc/JSON-RPC-interface.md#parameter-passing |
458 | 0 | auto positional_args{argsIn.extract("args")}; |
459 | 0 | if (positional_args && positional_args.mapped()->isArray()) { Branch (459:9): [True: 0, False: 0]
Branch (459:28): [True: 0, False: 0]
|
460 | 0 | if (initial_hole_size < (int)positional_args.mapped()->size() && initial_param) { Branch (460:13): [True: 0, False: 0]
Branch (460:74): [True: 0, False: 0]
|
461 | 0 | throw JSONRPCError(RPC_INVALID_PARAMETER, "Parameter " + *initial_param + " specified twice both as positional and named argument"); |
462 | 0 | } |
463 | | // Assign positional_args to out.params and append named_args after. |
464 | 0 | UniValue named_args{std::move(out.params)}; |
465 | 0 | out.params = *positional_args.mapped(); |
466 | 0 | for (size_t i{out.params.size()}; i < named_args.size(); ++i) { Branch (466:43): [True: 0, False: 0]
|
467 | 0 | out.params.push_back(named_args[i]); |
468 | 0 | } |
469 | 0 | } |
470 | | // If there are still arguments in the argsIn map, this is an error. |
471 | 0 | if (!argsIn.empty()) { Branch (471:9): [True: 0, False: 0]
|
472 | 0 | throw JSONRPCError(RPC_INVALID_PARAMETER, "Unknown named parameter " + argsIn.begin()->first); |
473 | 0 | } |
474 | | // Return request with named arguments transformed to positional arguments |
475 | 0 | return out; |
476 | 0 | } |
477 | | |
478 | | static bool ExecuteCommands(const std::vector<const CRPCCommand*>& commands, const JSONRPCRequest& request, UniValue& result) |
479 | 2.35M | { |
480 | 2.35M | for (const auto& command : commands) { Branch (480:30): [True: 2.35M, False: 4.78k]
|
481 | 2.35M | if (ExecuteCommand(*command, request, result, &command == &commands.back())) { Branch (481:13): [True: 2.35M, False: 4.78k]
|
482 | 2.35M | return true; |
483 | 2.35M | } |
484 | 2.35M | } |
485 | 4.78k | return false; |
486 | 2.35M | } |
487 | | |
488 | | UniValue CRPCTable::execute(const JSONRPCRequest &request) const |
489 | 2.35M | { |
490 | | // Return immediately if in warmup |
491 | 2.35M | { |
492 | 2.35M | LOCK(g_rpc_warmup_mutex); |
493 | 2.35M | if (fRPCInWarmup) Branch (493:13): [True: 0, False: 2.35M]
|
494 | 0 | throw JSONRPCError(RPC_IN_WARMUP, rpcWarmupStatus); |
495 | 2.35M | } |
496 | | |
497 | | // Find method |
498 | 2.35M | auto it = mapCommands.find(request.strMethod); |
499 | 2.35M | if (it != mapCommands.end()) { Branch (499:9): [True: 2.35M, False: 0]
|
500 | 2.35M | UniValue result; |
501 | 2.35M | if (ExecuteCommands(it->second, request, result)) { Branch (501:13): [True: 2.35M, False: 4.78k]
|
502 | 2.35M | return result; |
503 | 2.35M | } |
504 | 2.35M | } |
505 | 4.78k | throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Method not found"); |
506 | 2.35M | } |
507 | | |
508 | | static bool ExecuteCommand(const CRPCCommand& command, const JSONRPCRequest& request, UniValue& result, bool last_handler) |
509 | 2.35M | { |
510 | 2.35M | try { |
511 | 2.35M | RPCCommandExecution execution(request.strMethod); |
512 | | // Execute, convert arguments to array if necessary |
513 | 2.35M | if (request.params.isObject()) { Branch (513:13): [True: 0, False: 2.35M]
|
514 | 0 | return command.actor(transformNamedArguments(request, command.argNames), result, last_handler); |
515 | 2.35M | } else { |
516 | 2.35M | return command.actor(request, result, last_handler); |
517 | 2.35M | } |
518 | 2.35M | } catch (const UniValue::type_error& e) { |
519 | 0 | throw JSONRPCError(RPC_TYPE_ERROR, e.what()); |
520 | 0 | } catch (const std::exception& e) { |
521 | 0 | throw JSONRPCError(RPC_MISC_ERROR, e.what()); |
522 | 0 | } |
523 | 2.35M | } |
524 | | |
525 | | std::vector<std::string> CRPCTable::listCommands() const |
526 | 0 | { |
527 | 0 | std::vector<std::string> commandList; |
528 | 0 | commandList.reserve(mapCommands.size()); |
529 | 0 | for (const auto& i : mapCommands) commandList.emplace_back(i.first); Branch (529:24): [True: 0, False: 0]
|
530 | 0 | return commandList; |
531 | 0 | } |
532 | | |
533 | | UniValue CRPCTable::dumpArgMap(const JSONRPCRequest& args_request) const |
534 | 0 | { |
535 | 0 | JSONRPCRequest request = args_request; |
536 | 0 | request.mode = JSONRPCRequest::GET_ARGS; |
537 | |
|
538 | 0 | UniValue ret{UniValue::VARR}; |
539 | 0 | for (const auto& cmd : mapCommands) { Branch (539:26): [True: 0, False: 0]
|
540 | 0 | UniValue result; |
541 | 0 | if (ExecuteCommands(cmd.second, request, result)) { Branch (541:13): [True: 0, False: 0]
|
542 | 0 | for (const auto& values : result.getValues()) { Branch (542:37): [True: 0, False: 0]
|
543 | 0 | ret.push_back(values); |
544 | 0 | } |
545 | 0 | } |
546 | 0 | } |
547 | 0 | return ret; |
548 | 0 | } |
549 | | |
550 | | void RPCSetTimerInterfaceIfUnset(RPCTimerInterface *iface) |
551 | 0 | { |
552 | 0 | if (!timerInterface) Branch (552:9): [True: 0, False: 0]
|
553 | 0 | timerInterface = iface; |
554 | 0 | } |
555 | | |
556 | | void RPCSetTimerInterface(RPCTimerInterface *iface) |
557 | 11.0k | { |
558 | 11.0k | timerInterface = iface; |
559 | 11.0k | } |
560 | | |
561 | | void RPCUnsetTimerInterface(RPCTimerInterface *iface) |
562 | 11.0k | { |
563 | 11.0k | if (timerInterface == iface) Branch (563:9): [True: 11.0k, False: 0]
|
564 | 11.0k | timerInterface = nullptr; |
565 | 11.0k | } |
566 | | |
567 | | void RPCRunLater(const std::string& name, std::function<void()> func, int64_t nSeconds) |
568 | 0 | { |
569 | 0 | if (!timerInterface) Branch (569:9): [True: 0, False: 0]
|
570 | 0 | throw JSONRPCError(RPC_INTERNAL_ERROR, "No timer handler registered for RPC"); |
571 | 0 | LOCK(g_deadline_timers_mutex); |
572 | 0 | deadlineTimers.erase(name); |
573 | 0 | LogDebug(BCLog::RPC, "queue run of timer %s in %i seconds (using %s)\n", name, nSeconds, timerInterface->Name()); |
574 | 0 | deadlineTimers.emplace(name, std::unique_ptr<RPCTimerBase>(timerInterface->NewTimer(func, nSeconds*1000))); |
575 | 0 | } |
576 | | |
577 | | CRPCTable tableRPC; |