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