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