Line data Source code
1 : // Copyright (c) 2009-2010 Satoshi Nakamoto
2 : // Copyright (c) 2009-2020 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 : #if defined(HAVE_CONFIG_H)
7 : #include <config/bitcoin-config.h>
8 : #endif
9 :
10 : #include <chainparamsbase.h>
11 : #include <clientversion.h>
12 : #include <optional.h>
13 : #include <rpc/client.h>
14 : #include <rpc/mining.h>
15 : #include <rpc/protocol.h>
16 : #include <rpc/request.h>
17 : #include <util/strencodings.h>
18 : #include <util/system.h>
19 : #include <util/translation.h>
20 : #include <util/url.h>
21 :
22 : #include <functional>
23 : #include <memory>
24 : #include <stdio.h>
25 : #include <string>
26 : #include <tuple>
27 :
28 : #include <event2/buffer.h>
29 : #include <event2/keyvalq_struct.h>
30 : #include <support/events.h>
31 :
32 : #include <univalue.h>
33 : #include <compat/stdin.h>
34 :
35 419 : const std::function<std::string(const char*)> G_TRANSLATION_FUN = nullptr;
36 : UrlDecodeFn* const URL_DECODE = urlDecode;
37 :
38 : static const char DEFAULT_RPCCONNECT[] = "127.0.0.1";
39 : static const int DEFAULT_HTTP_CLIENT_TIMEOUT=900;
40 : static const bool DEFAULT_NAMED=false;
41 : static const int CONTINUE_EXECUTION=-1;
42 419 : static const std::string ONION{".onion"};
43 419 : static const size_t ONION_LEN{ONION.size()};
44 :
45 : /** Default number of blocks to generate for RPC generatetoaddress. */
46 419 : static const std::string DEFAULT_NBLOCKS = "1";
47 :
48 419 : static void SetupCliArgs(ArgsManager& argsman)
49 : {
50 419 : SetupHelpOptions(argsman);
51 :
52 419 : const auto defaultBaseParams = CreateBaseChainParams(CBaseChainParams::MAIN);
53 419 : const auto testnetBaseParams = CreateBaseChainParams(CBaseChainParams::TESTNET);
54 419 : const auto regtestBaseParams = CreateBaseChainParams(CBaseChainParams::REGTEST);
55 :
56 419 : argsman.AddArg("-version", "Print version and exit", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
57 419 : argsman.AddArg("-conf=<file>", strprintf("Specify configuration file. Relative paths will be prefixed by datadir location. (default: %s)", BITCOIN_CONF_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
58 419 : argsman.AddArg("-datadir=<dir>", "Specify data directory", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
59 419 : argsman.AddArg("-generate", strprintf("Generate blocks immediately, equivalent to RPC generatenewaddress followed by RPC generatetoaddress. Optional positional integer arguments are number of blocks to generate (default: %s) and maximum iterations to try (default: %s), equivalent to RPC generatetoaddress nblocks and maxtries arguments. Example: bitcoin-cli -generate 4 1000", DEFAULT_NBLOCKS, DEFAULT_MAX_TRIES), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
60 419 : argsman.AddArg("-getinfo", "Get general information from the remote server. Note that unlike server-side RPC calls, the results of -getinfo is the result of multiple non-atomic requests. Some entries in the result may represent results from different states (e.g. wallet balance may be as of a different block from the chain state reported)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
61 419 : argsman.AddArg("-netinfo", "Get network peer connection information from the remote server. An optional integer argument from 0 to 4 can be passed for different peers listings (default: 0).", ArgsManager::ALLOW_INT, OptionsCategory::OPTIONS);
62 :
63 419 : SetupChainParamsBaseOptions(argsman);
64 419 : argsman.AddArg("-named", strprintf("Pass named instead of positional arguments (default: %s)", DEFAULT_NAMED), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
65 419 : argsman.AddArg("-rpcclienttimeout=<n>", strprintf("Timeout in seconds during HTTP requests, or 0 for no timeout. (default: %d)", DEFAULT_HTTP_CLIENT_TIMEOUT), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
66 419 : argsman.AddArg("-rpcconnect=<ip>", strprintf("Send commands to node running on <ip> (default: %s)", DEFAULT_RPCCONNECT), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
67 419 : argsman.AddArg("-rpccookiefile=<loc>", "Location of the auth cookie. Relative paths will be prefixed by a net-specific datadir location. (default: data dir)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
68 419 : argsman.AddArg("-rpcpassword=<pw>", "Password for JSON-RPC connections", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
69 419 : argsman.AddArg("-rpcport=<port>", strprintf("Connect to JSON-RPC on <port> (default: %u, testnet: %u, regtest: %u)", defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort(), regtestBaseParams->RPCPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::OPTIONS);
70 419 : argsman.AddArg("-rpcuser=<user>", "Username for JSON-RPC connections", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
71 419 : argsman.AddArg("-rpcwait", "Wait for RPC server to start", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
72 419 : argsman.AddArg("-rpcwallet=<walletname>", "Send RPC for non-default wallet on RPC server (needs to exactly match corresponding -wallet option passed to bitcoind). This changes the RPC endpoint used, e.g. http://127.0.0.1:8332/wallet/<walletname>", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
73 419 : argsman.AddArg("-stdin", "Read extra arguments from standard input, one per line until EOF/Ctrl-D (recommended for sensitive information such as passphrases). When combined with -stdinrpcpass, the first line from standard input is used for the RPC password.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
74 419 : argsman.AddArg("-stdinrpcpass", "Read RPC password from standard input as a single line. When combined with -stdin, the first line from standard input is used for the RPC password. When combined with -stdinwalletpassphrase, -stdinrpcpass consumes the first line, and -stdinwalletpassphrase consumes the second.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
75 419 : argsman.AddArg("-stdinwalletpassphrase", "Read wallet passphrase from standard input as a single line. When combined with -stdin, the first line from standard input is used for the wallet passphrase.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
76 419 : }
77 :
78 : /** libevent event log callback */
79 0 : static void libevent_log_cb(int severity, const char *msg)
80 : {
81 : #ifndef EVENT_LOG_ERR // EVENT_LOG_ERR was added in 2.0.19; but before then _EVENT_LOG_ERR existed.
82 : # define EVENT_LOG_ERR _EVENT_LOG_ERR
83 : #endif
84 : // Ignore everything other than errors
85 0 : if (severity >= EVENT_LOG_ERR) {
86 0 : throw std::runtime_error(strprintf("libevent error: %s", msg));
87 : }
88 0 : }
89 :
90 : //////////////////////////////////////////////////////////////////////////////
91 : //
92 : // Start
93 : //
94 :
95 : //
96 : // Exception thrown on connection error. This error is used to determine
97 : // when to wait if -rpcwait is given.
98 : //
99 6 : class CConnectionFailed : public std::runtime_error
100 : {
101 : public:
102 :
103 6 : explicit inline CConnectionFailed(const std::string& msg) :
104 3 : std::runtime_error(msg)
105 6 : {}
106 :
107 : };
108 :
109 : //
110 : // This function returns either one of EXIT_ codes when it's expected to stop the process or
111 : // CONTINUE_EXECUTION when it's expected to continue further.
112 : //
113 419 : static int AppInitRPC(int argc, char* argv[])
114 : {
115 : //
116 : // Parameters
117 : //
118 419 : SetupCliArgs(gArgs);
119 419 : std::string error;
120 419 : if (!gArgs.ParseParameters(argc, argv, error)) {
121 0 : tfm::format(std::cerr, "Error parsing command line arguments: %s\n", error);
122 0 : return EXIT_FAILURE;
123 : }
124 419 : if (argc < 2 || HelpRequested(gArgs) || gArgs.IsArgSet("-version")) {
125 1 : std::string strUsage = PACKAGE_NAME " RPC client version " + FormatFullVersion() + "\n";
126 1 : if (!gArgs.IsArgSet("-version")) {
127 0 : strUsage += "\n"
128 : "Usage: bitcoin-cli [options] <command> [params] Send command to " PACKAGE_NAME "\n"
129 : "or: bitcoin-cli [options] -named <command> [name=value]... Send command to " PACKAGE_NAME " (with named arguments)\n"
130 : "or: bitcoin-cli [options] help List commands\n"
131 : "or: bitcoin-cli [options] help <command> Get help for a command\n";
132 0 : strUsage += "\n" + gArgs.GetHelpMessage();
133 0 : }
134 :
135 1 : tfm::format(std::cout, "%s", strUsage);
136 1 : if (argc < 2) {
137 0 : tfm::format(std::cerr, "Error: too few parameters\n");
138 0 : return EXIT_FAILURE;
139 : }
140 1 : return EXIT_SUCCESS;
141 1 : }
142 418 : if (!CheckDataDirOption()) {
143 0 : tfm::format(std::cerr, "Error: Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", ""));
144 0 : return EXIT_FAILURE;
145 : }
146 418 : if (!gArgs.ReadConfigFiles(error, true)) {
147 0 : tfm::format(std::cerr, "Error reading configuration file: %s\n", error);
148 0 : return EXIT_FAILURE;
149 : }
150 : // Check for -chain, -testnet or -regtest parameter (BaseParams() calls are only valid after this clause)
151 : try {
152 418 : SelectBaseParams(gArgs.GetChainName());
153 0 : } catch (const std::exception& e) {
154 0 : tfm::format(std::cerr, "Error: %s\n", e.what());
155 : return EXIT_FAILURE;
156 0 : }
157 418 : return CONTINUE_EXECUTION;
158 419 : }
159 :
160 :
161 : /** Reply structure for request_done to fill in */
162 876 : struct HTTPReply
163 : {
164 876 : HTTPReply(): status(0), error(-1) {}
165 :
166 : int status;
167 : int error;
168 : std::string body;
169 : };
170 :
171 0 : static std::string http_errorstring(int code)
172 : {
173 0 : switch(code) {
174 : #if LIBEVENT_VERSION_NUMBER >= 0x02010300
175 : case EVREQ_HTTP_TIMEOUT:
176 0 : return "timeout reached";
177 : case EVREQ_HTTP_EOF:
178 0 : return "EOF reached";
179 : case EVREQ_HTTP_INVALID_HEADER:
180 0 : return "error while reading header, or invalid header";
181 : case EVREQ_HTTP_BUFFER_ERROR:
182 0 : return "error encountered while reading or writing";
183 : case EVREQ_HTTP_REQUEST_CANCEL:
184 0 : return "request was canceled";
185 : case EVREQ_HTTP_DATA_TOO_LONG:
186 0 : return "response body is larger than allowed";
187 : #endif
188 : default:
189 0 : return "unknown";
190 : }
191 0 : }
192 :
193 435 : static void http_request_done(struct evhttp_request *req, void *ctx)
194 : {
195 435 : HTTPReply *reply = static_cast<HTTPReply*>(ctx);
196 :
197 435 : if (req == nullptr) {
198 : /* If req is nullptr, it means an error occurred while connecting: the
199 : * error code will have been passed to http_error_cb.
200 : */
201 0 : reply->status = 0;
202 0 : return;
203 : }
204 :
205 435 : reply->status = evhttp_request_get_response_code(req);
206 :
207 435 : struct evbuffer *buf = evhttp_request_get_input_buffer(req);
208 435 : if (buf)
209 : {
210 435 : size_t size = evbuffer_get_length(buf);
211 435 : const char *data = (const char*)evbuffer_pullup(buf, size);
212 435 : if (data)
213 431 : reply->body = std::string(data, size);
214 435 : evbuffer_drain(buf, size);
215 435 : }
216 435 : }
217 :
218 : #if LIBEVENT_VERSION_NUMBER >= 0x02010300
219 0 : static void http_error_cb(enum evhttp_request_error err, void *ctx)
220 : {
221 0 : HTTPReply *reply = static_cast<HTTPReply*>(ctx);
222 0 : reply->error = err;
223 0 : }
224 : #endif
225 :
226 : /** Class that handles the conversion from a command-line to a JSON-RPC request,
227 : * as well as converting back to a JSON object that can be shown as result.
228 : */
229 431 : class BaseRequestHandler
230 : {
231 : public:
232 431 : virtual ~BaseRequestHandler() {}
233 : virtual UniValue PrepareRequest(const std::string& method, const std::vector<std::string>& args) = 0;
234 : virtual UniValue ProcessReply(const UniValue &batch_in) = 0;
235 : };
236 :
237 : /** Process getinfo requests */
238 66 : class GetinfoRequestHandler: public BaseRequestHandler
239 : {
240 : public:
241 11 : const int ID_NETWORKINFO = 0;
242 11 : const int ID_BLOCKCHAININFO = 1;
243 11 : const int ID_WALLETINFO = 2;
244 11 : const int ID_BALANCES = 3;
245 :
246 : /** Create a simulated `getinfo` request. */
247 11 : UniValue PrepareRequest(const std::string& method, const std::vector<std::string>& args) override
248 : {
249 11 : if (!args.empty()) {
250 1 : throw std::runtime_error("-getinfo takes no arguments");
251 : }
252 10 : UniValue result(UniValue::VARR);
253 10 : result.push_back(JSONRPCRequestObj("getnetworkinfo", NullUniValue, ID_NETWORKINFO));
254 10 : result.push_back(JSONRPCRequestObj("getblockchaininfo", NullUniValue, ID_BLOCKCHAININFO));
255 10 : result.push_back(JSONRPCRequestObj("getwalletinfo", NullUniValue, ID_WALLETINFO));
256 10 : result.push_back(JSONRPCRequestObj("getbalances", NullUniValue, ID_BALANCES));
257 : return result;
258 10 : }
259 :
260 : /** Collect values from the batch and form a simulated `getinfo` reply. */
261 10 : UniValue ProcessReply(const UniValue &batch_in) override
262 : {
263 10 : UniValue result(UniValue::VOBJ);
264 10 : const std::vector<UniValue> batch = JSONRPCProcessBatchReply(batch_in);
265 : // Errors in getnetworkinfo() and getblockchaininfo() are fatal, pass them on;
266 : // getwalletinfo() and getbalances() are allowed to fail if there is no wallet.
267 10 : if (!batch[ID_NETWORKINFO]["error"].isNull()) {
268 0 : return batch[ID_NETWORKINFO];
269 : }
270 10 : if (!batch[ID_BLOCKCHAININFO]["error"].isNull()) {
271 0 : return batch[ID_BLOCKCHAININFO];
272 : }
273 10 : result.pushKV("version", batch[ID_NETWORKINFO]["result"]["version"]);
274 10 : result.pushKV("blocks", batch[ID_BLOCKCHAININFO]["result"]["blocks"]);
275 10 : result.pushKV("headers", batch[ID_BLOCKCHAININFO]["result"]["headers"]);
276 10 : result.pushKV("verificationprogress", batch[ID_BLOCKCHAININFO]["result"]["verificationprogress"]);
277 10 : result.pushKV("timeoffset", batch[ID_NETWORKINFO]["result"]["timeoffset"]);
278 :
279 10 : UniValue connections(UniValue::VOBJ);
280 10 : connections.pushKV("in", batch[ID_NETWORKINFO]["result"]["connections_in"]);
281 10 : connections.pushKV("out", batch[ID_NETWORKINFO]["result"]["connections_out"]);
282 10 : connections.pushKV("total", batch[ID_NETWORKINFO]["result"]["connections"]);
283 10 : result.pushKV("connections", connections);
284 :
285 10 : result.pushKV("proxy", batch[ID_NETWORKINFO]["result"]["networks"][0]["proxy"]);
286 10 : result.pushKV("difficulty", batch[ID_BLOCKCHAININFO]["result"]["difficulty"]);
287 10 : result.pushKV("chain", UniValue(batch[ID_BLOCKCHAININFO]["result"]["chain"]));
288 10 : if (!batch[ID_WALLETINFO]["result"].isNull()) {
289 6 : result.pushKV("keypoolsize", batch[ID_WALLETINFO]["result"]["keypoolsize"]);
290 6 : if (!batch[ID_WALLETINFO]["result"]["unlocked_until"].isNull()) {
291 5 : result.pushKV("unlocked_until", batch[ID_WALLETINFO]["result"]["unlocked_until"]);
292 5 : }
293 6 : result.pushKV("paytxfee", batch[ID_WALLETINFO]["result"]["paytxfee"]);
294 6 : }
295 10 : if (!batch[ID_BALANCES]["result"].isNull()) {
296 6 : result.pushKV("balance", batch[ID_BALANCES]["result"]["mine"]["trusted"]);
297 6 : }
298 10 : result.pushKV("relayfee", batch[ID_NETWORKINFO]["result"]["relayfee"]);
299 10 : result.pushKV("warnings", batch[ID_NETWORKINFO]["result"]["warnings"]);
300 10 : return JSONRPCReplyObj(result, NullUniValue, 1);
301 10 : }
302 : };
303 :
304 : /** Process netinfo requests */
305 0 : class NetinfoRequestHandler : public BaseRequestHandler
306 : {
307 : private:
308 0 : bool IsAddrIPv6(const std::string& addr) const
309 : {
310 0 : return !addr.empty() && addr.front() == '[';
311 : }
312 0 : bool IsInboundOnion(const std::string& addr_local, int mapped_as) const
313 : {
314 0 : return mapped_as == 0 && addr_local.find(ONION) != std::string::npos;
315 : }
316 0 : bool IsOutboundOnion(const std::string& addr, int mapped_as) const
317 : {
318 0 : const size_t addr_len{addr.size()};
319 0 : const size_t onion_pos{addr.rfind(ONION)};
320 0 : return mapped_as == 0 && onion_pos != std::string::npos && addr_len > ONION_LEN &&
321 0 : (onion_pos == addr_len - ONION_LEN || onion_pos == addr.find_last_of(":") - ONION_LEN);
322 : }
323 0 : uint8_t m_details_level{0}; //!< Optional user-supplied arg to set dashboard details level
324 0 : bool DetailsRequested() const { return m_details_level > 0 && m_details_level < 5; }
325 0 : bool IsAddressSelected() const { return m_details_level == 2 || m_details_level == 4; }
326 0 : bool IsVersionSelected() const { return m_details_level == 3 || m_details_level == 4; }
327 : enum struct NetType {
328 : ipv4,
329 : ipv6,
330 : onion,
331 : };
332 0 : struct Peer {
333 : int id;
334 : int mapped_as;
335 : int version;
336 : int64_t conn_time;
337 : int64_t last_blck;
338 : int64_t last_recv;
339 : int64_t last_send;
340 : int64_t last_trxn;
341 : double min_ping;
342 : double ping;
343 : std::string addr;
344 : std::string sub_version;
345 : NetType net_type;
346 : bool is_block_relay;
347 : bool is_outbound;
348 0 : bool operator<(const Peer& rhs) const { return std::tie(is_outbound, min_ping) < std::tie(rhs.is_outbound, rhs.min_ping); }
349 : };
350 0 : std::string NetTypeEnumToString(NetType t)
351 : {
352 0 : switch (t) {
353 0 : case NetType::ipv4: return "ipv4";
354 0 : case NetType::ipv6: return "ipv6";
355 0 : case NetType::onion: return "onion";
356 : } // no default case, so the compiler can warn about missing cases
357 0 : assert(false);
358 0 : }
359 0 : std::string ChainToString() const
360 : {
361 0 : if (gArgs.GetChainName() == CBaseChainParams::TESTNET) return " testnet";
362 0 : if (gArgs.GetChainName() == CBaseChainParams::REGTEST) return " regtest";
363 0 : return "";
364 0 : }
365 : public:
366 0 : const int ID_PEERINFO = 0;
367 0 : const int ID_NETWORKINFO = 1;
368 :
369 0 : UniValue PrepareRequest(const std::string& method, const std::vector<std::string>& args) override
370 : {
371 0 : if (!args.empty()) {
372 0 : uint8_t n{0};
373 0 : if (ParseUInt8(args.at(0), &n)) {
374 0 : m_details_level = n;
375 0 : }
376 0 : }
377 0 : UniValue result(UniValue::VARR);
378 0 : result.push_back(JSONRPCRequestObj("getpeerinfo", NullUniValue, ID_PEERINFO));
379 0 : result.push_back(JSONRPCRequestObj("getnetworkinfo", NullUniValue, ID_NETWORKINFO));
380 : return result;
381 0 : }
382 :
383 0 : UniValue ProcessReply(const UniValue& batch_in) override
384 : {
385 0 : const std::vector<UniValue> batch{JSONRPCProcessBatchReply(batch_in)};
386 0 : if (!batch[ID_PEERINFO]["error"].isNull()) return batch[ID_PEERINFO];
387 0 : if (!batch[ID_NETWORKINFO]["error"].isNull()) return batch[ID_NETWORKINFO];
388 :
389 0 : const UniValue& networkinfo{batch[ID_NETWORKINFO]["result"]};
390 0 : if (networkinfo["version"].get_int() < 209900) {
391 0 : throw std::runtime_error("-netinfo requires bitcoind server to be running v0.21.0 and up");
392 : }
393 :
394 : // Count peer connection totals, and if DetailsRequested(), store peer data in a vector of structs.
395 0 : const int64_t time_now{GetSystemTimeInSeconds()};
396 0 : int ipv4_i{0}, ipv6_i{0}, onion_i{0}, block_relay_i{0}, total_i{0}; // inbound conn counters
397 0 : int ipv4_o{0}, ipv6_o{0}, onion_o{0}, block_relay_o{0}, total_o{0}; // outbound conn counters
398 0 : size_t max_peer_id_length{2}, max_addr_length{0};
399 : bool is_asmap_on{false};
400 0 : std::vector<Peer> peers;
401 0 : const UniValue& getpeerinfo{batch[ID_PEERINFO]["result"]};
402 :
403 0 : for (const UniValue& peer : getpeerinfo.getValues()) {
404 0 : const std::string addr{peer["addr"].get_str()};
405 0 : const std::string addr_local{peer["addrlocal"].isNull() ? "" : peer["addrlocal"].get_str()};
406 0 : const int mapped_as{peer["mapped_as"].isNull() ? 0 : peer["mapped_as"].get_int()};
407 0 : const bool is_block_relay{!peer["relaytxes"].get_bool()};
408 0 : const bool is_inbound{peer["inbound"].get_bool()};
409 : NetType net_type{NetType::ipv4};
410 0 : if (is_inbound) {
411 0 : if (IsAddrIPv6(addr)) {
412 : net_type = NetType::ipv6;
413 0 : ++ipv6_i;
414 0 : } else if (IsInboundOnion(addr_local, mapped_as)) {
415 : net_type = NetType::onion;
416 0 : ++onion_i;
417 0 : } else {
418 0 : ++ipv4_i;
419 : }
420 0 : if (is_block_relay) ++block_relay_i;
421 : } else {
422 0 : if (IsAddrIPv6(addr)) {
423 : net_type = NetType::ipv6;
424 0 : ++ipv6_o;
425 0 : } else if (IsOutboundOnion(addr, mapped_as)) {
426 : net_type = NetType::onion;
427 0 : ++onion_o;
428 0 : } else {
429 0 : ++ipv4_o;
430 : }
431 0 : if (is_block_relay) ++block_relay_o;
432 : }
433 0 : if (DetailsRequested()) {
434 : // Push data for this peer to the peers vector.
435 0 : const int peer_id{peer["id"].get_int()};
436 0 : const int version{peer["version"].get_int()};
437 0 : const std::string sub_version{peer["subver"].get_str()};
438 0 : const int64_t conn_time{peer["conntime"].get_int64()};
439 0 : const int64_t last_blck{peer["last_block"].get_int64()};
440 0 : const int64_t last_recv{peer["lastrecv"].get_int64()};
441 0 : const int64_t last_send{peer["lastsend"].get_int64()};
442 0 : const int64_t last_trxn{peer["last_transaction"].get_int64()};
443 0 : const double min_ping{peer["minping"].isNull() ? -1 : peer["minping"].get_real()};
444 0 : const double ping{peer["pingtime"].isNull() ? -1 : peer["pingtime"].get_real()};
445 0 : peers.push_back({peer_id, mapped_as, version, conn_time, last_blck, last_recv, last_send, last_trxn, min_ping, ping, addr, sub_version, net_type, is_block_relay, !is_inbound});
446 0 : max_peer_id_length = std::max(ToString(peer_id).length(), max_peer_id_length);
447 0 : max_addr_length = std::max(addr.length() + 1, max_addr_length);
448 0 : is_asmap_on |= (mapped_as != 0);
449 0 : }
450 0 : }
451 :
452 : // Generate report header.
453 0 : std::string result{strprintf("%s %s%s - %i%s\n\n", PACKAGE_NAME, FormatFullVersion(), ChainToString(), networkinfo["protocolversion"].get_int(), networkinfo["subversion"].get_str())};
454 :
455 : // Report detailed peer connections list sorted by direction and minimum ping time.
456 0 : if (DetailsRequested() && !peers.empty()) {
457 0 : std::sort(peers.begin(), peers.end());
458 0 : result += "Peer connections sorted by direction and min ping\n<-> relay net mping ping send recv txn blk uptime ";
459 0 : if (is_asmap_on) result += " asmap ";
460 0 : result += strprintf("%*s %-*s%s\n", max_peer_id_length, "id", IsAddressSelected() ? max_addr_length : 0, IsAddressSelected() ? "address" : "", IsVersionSelected() ? "version" : "");
461 0 : for (const Peer& peer : peers) {
462 0 : std::string version{ToString(peer.version) + peer.sub_version};
463 0 : result += strprintf(
464 : "%3s %5s %5s%6s%7s%5s%5s%5s%5s%7s%*i %*s %-*s%s\n",
465 0 : peer.is_outbound ? "out" : "in",
466 0 : peer.is_block_relay ? "block" : "full",
467 0 : NetTypeEnumToString(peer.net_type),
468 0 : peer.min_ping == -1 ? "" : ToString(round(1000 * peer.min_ping)),
469 0 : peer.ping == -1 ? "" : ToString(round(1000 * peer.ping)),
470 0 : peer.last_send == 0 ? "" : ToString(time_now - peer.last_send),
471 0 : peer.last_recv == 0 ? "" : ToString(time_now - peer.last_recv),
472 0 : peer.last_trxn == 0 ? "" : ToString((time_now - peer.last_trxn) / 60),
473 0 : peer.last_blck == 0 ? "" : ToString((time_now - peer.last_blck) / 60),
474 0 : peer.conn_time == 0 ? "" : ToString((time_now - peer.conn_time) / 60),
475 0 : is_asmap_on ? 7 : 0, // variable spacing
476 0 : is_asmap_on && peer.mapped_as != 0 ? ToString(peer.mapped_as) : "",
477 : max_peer_id_length, // variable spacing
478 0 : peer.id,
479 0 : IsAddressSelected() ? max_addr_length : 0, // variable spacing
480 0 : IsAddressSelected() ? peer.addr : "",
481 0 : IsVersionSelected() && version != "0" ? version : "");
482 0 : }
483 0 : result += " ms ms sec sec min min min\n\n";
484 : }
485 :
486 : // Report peer connection totals by type.
487 0 : total_i = ipv4_i + ipv6_i + onion_i;
488 0 : total_o = ipv4_o + ipv6_o + onion_o;
489 0 : result += " ipv4 ipv6 onion total block-relay\n";
490 0 : result += strprintf("in %5i %5i %5i %5i %5i\n", ipv4_i, ipv6_i, onion_i, total_i, block_relay_i);
491 0 : result += strprintf("out %5i %5i %5i %5i %5i\n", ipv4_o, ipv6_o, onion_o, total_o, block_relay_o);
492 0 : result += strprintf("total %5i %5i %5i %5i %5i\n", ipv4_i + ipv4_o, ipv6_i + ipv6_o, onion_i + onion_o, total_i + total_o, block_relay_i + block_relay_o);
493 :
494 : // Report local addresses, ports, and scores.
495 0 : result += "\nLocal addresses";
496 0 : const UniValue& local_addrs{networkinfo["localaddresses"]};
497 0 : if (local_addrs.empty()) {
498 0 : result += ": n/a\n";
499 : } else {
500 0 : for (const UniValue& addr : local_addrs.getValues()) {
501 0 : result += strprintf("\n%-40i port %5i score %6i", addr["address"].get_str(), addr["port"].get_int(), addr["score"].get_int());
502 : }
503 : }
504 :
505 0 : return JSONRPCReplyObj(UniValue{result}, NullUniValue, 1);
506 0 : }
507 : };
508 :
509 : /** Process RPC generatetoaddress request. */
510 45 : class GenerateToAddressRequestHandler : public BaseRequestHandler
511 : {
512 : public:
513 9 : UniValue PrepareRequest(const std::string& method, const std::vector<std::string>& args) override
514 : {
515 9 : address_str = args.at(1);
516 9 : UniValue params{RPCConvertValues("generatetoaddress", args)};
517 7 : return JSONRPCRequestObj("generatetoaddress", params, 1);
518 9 : }
519 :
520 7 : UniValue ProcessReply(const UniValue &reply) override
521 : {
522 7 : UniValue result(UniValue::VOBJ);
523 7 : result.pushKV("address", address_str);
524 7 : result.pushKV("blocks", reply.get_obj()["result"]);
525 7 : return JSONRPCReplyObj(result, NullUniValue, 1);
526 7 : }
527 : protected:
528 : std::string address_str;
529 : };
530 :
531 : /** Process default single requests */
532 2030 : class DefaultRequestHandler: public BaseRequestHandler {
533 : public:
534 418 : UniValue PrepareRequest(const std::string& method, const std::vector<std::string>& args) override
535 : {
536 418 : UniValue params;
537 418 : if(gArgs.GetBoolArg("-named", DEFAULT_NAMED)) {
538 35 : params = RPCConvertNamedValues(method, args);
539 35 : } else {
540 383 : params = RPCConvertValues(method, args);
541 : }
542 418 : return JSONRPCRequestObj(method, params, 1);
543 418 : }
544 :
545 414 : UniValue ProcessReply(const UniValue &reply) override
546 : {
547 414 : return reply.get_obj();
548 : }
549 : };
550 :
551 438 : static UniValue CallRPC(BaseRequestHandler* rh, const std::string& strMethod, const std::vector<std::string>& args, const Optional<std::string>& rpcwallet = {})
552 : {
553 438 : std::string host;
554 : // In preference order, we choose the following for the port:
555 : // 1. -rpcport
556 : // 2. port in -rpcconnect (ie following : in ipv4 or ]: in ipv6)
557 : // 3. default port for chain
558 438 : int port = BaseParams().RPCPort();
559 438 : SplitHostPort(gArgs.GetArg("-rpcconnect", DEFAULT_RPCCONNECT), port, host);
560 438 : port = gArgs.GetArg("-rpcport", port);
561 :
562 : // Obtain event base
563 438 : raii_event_base base = obtain_event_base();
564 :
565 : // Synchronously look up hostname
566 438 : raii_evhttp_connection evcon = obtain_evhttp_connection_base(base.get(), host, port);
567 :
568 : // Set connection timeout
569 : {
570 438 : const int timeout = gArgs.GetArg("-rpcclienttimeout", DEFAULT_HTTP_CLIENT_TIMEOUT);
571 438 : if (timeout > 0) {
572 438 : evhttp_connection_set_timeout(evcon.get(), timeout);
573 : } else {
574 : // Indefinite request timeouts are not possible in libevent-http, so we
575 : // set the timeout to a very long time period instead.
576 :
577 : constexpr int YEAR_IN_SECONDS = 31556952; // Average length of year in Gregorian calendar
578 0 : evhttp_connection_set_timeout(evcon.get(), 5 * YEAR_IN_SECONDS);
579 0 : }
580 0 : }
581 :
582 438 : HTTPReply response;
583 438 : raii_evhttp_request req = obtain_evhttp_request(http_request_done, (void*)&response);
584 438 : if (req == nullptr)
585 0 : throw std::runtime_error("create http request failed");
586 : #if LIBEVENT_VERSION_NUMBER >= 0x02010300
587 438 : evhttp_request_set_error_cb(req.get(), http_error_cb);
588 : #endif
589 :
590 : // Get credentials
591 438 : std::string strRPCUserColonPass;
592 : bool failedToGetAuthCookie = false;
593 438 : if (gArgs.GetArg("-rpcpassword", "") == "") {
594 : // Try fall back to cookie-based authentication if no password is provided
595 434 : if (!GetAuthCookie(&strRPCUserColonPass)) {
596 : failedToGetAuthCookie = true;
597 1 : }
598 : } else {
599 4 : strRPCUserColonPass = gArgs.GetArg("-rpcuser", "") + ":" + gArgs.GetArg("-rpcpassword", "");
600 : }
601 :
602 438 : struct evkeyvalq* output_headers = evhttp_request_get_output_headers(req.get());
603 438 : assert(output_headers);
604 438 : evhttp_add_header(output_headers, "Host", host.c_str());
605 438 : evhttp_add_header(output_headers, "Connection", "close");
606 438 : evhttp_add_header(output_headers, "Authorization", (std::string("Basic ") + EncodeBase64(strRPCUserColonPass)).c_str());
607 :
608 : // Attach request data
609 441 : std::string strRequest = rh->PrepareRequest(strMethod, args).write() + "\n";
610 435 : struct evbuffer* output_buffer = evhttp_request_get_output_buffer(req.get());
611 435 : assert(output_buffer);
612 435 : evbuffer_add(output_buffer, strRequest.data(), strRequest.size());
613 :
614 : // check if we should use a special wallet endpoint
615 435 : std::string endpoint = "/";
616 435 : if (rpcwallet) {
617 192 : char* encodedURI = evhttp_uriencode(rpcwallet->data(), rpcwallet->size(), false);
618 192 : if (encodedURI) {
619 192 : endpoint = "/wallet/" + std::string(encodedURI);
620 192 : free(encodedURI);
621 : } else {
622 0 : throw CConnectionFailed("uri-encode failed");
623 : }
624 192 : }
625 435 : int r = evhttp_make_request(evcon.get(), req.get(), EVHTTP_REQ_POST, endpoint.c_str());
626 435 : req.release(); // ownership moved to evcon in above call
627 435 : if (r != 0) {
628 0 : throw CConnectionFailed("send http request failed");
629 : }
630 :
631 435 : event_base_dispatch(base.get());
632 :
633 435 : if (response.status == 0) {
634 1 : std::string responseErrorMessage;
635 1 : if (response.error != -1) {
636 0 : responseErrorMessage = strprintf(" (error code %d - \"%s\")", response.error, http_errorstring(response.error));
637 0 : }
638 1 : throw CConnectionFailed(strprintf("Could not connect to the server %s:%d%s\n\nMake sure the bitcoind server is running and that you are connecting to the correct RPC port.", host, port, responseErrorMessage));
639 435 : } else if (response.status == HTTP_UNAUTHORIZED) {
640 3 : if (failedToGetAuthCookie) {
641 2 : throw std::runtime_error(strprintf(
642 : "Could not locate RPC credentials. No authentication cookie could be found, and RPC password is not set. See -rpcpassword and -stdinrpcpass. Configuration file: (%s)",
643 1 : GetConfigFile(gArgs.GetArg("-conf", BITCOIN_CONF_FILENAME)).string()));
644 : } else {
645 2 : throw std::runtime_error("Authorization failed: Incorrect rpcuser or rpcpassword");
646 : }
647 431 : } else if (response.status >= 400 && response.status != HTTP_BAD_REQUEST && response.status != HTTP_NOT_FOUND && response.status != HTTP_INTERNAL_SERVER_ERROR)
648 0 : throw std::runtime_error(strprintf("server returned HTTP error %d", response.status));
649 431 : else if (response.body.empty())
650 0 : throw std::runtime_error("no response from server");
651 :
652 : // Parse reply
653 431 : UniValue valReply(UniValue::VSTR);
654 431 : if (!valReply.read(response.body))
655 0 : throw std::runtime_error("couldn't parse reply from server");
656 431 : const UniValue reply = rh->ProcessReply(valReply);
657 431 : if (reply.empty())
658 0 : throw std::runtime_error("expected reply to have result, error and id properties");
659 :
660 : return reply;
661 439 : }
662 :
663 : /**
664 : * ConnectAndCallRPC wraps CallRPC with -rpcwait and an exception handler.
665 : *
666 : * @param[in] rh Pointer to RequestHandler.
667 : * @param[in] strMethod Reference to const string method to forward to CallRPC.
668 : * @param[in] rpcwallet Reference to const optional string wallet name to forward to CallRPC.
669 : * @returns the RPC response as a UniValue object.
670 : * @throws a CConnectionFailed std::runtime_error if connection failed or RPC server still in warmup.
671 : */
672 436 : static UniValue ConnectAndCallRPC(BaseRequestHandler* rh, const std::string& strMethod, const std::vector<std::string>& args, const Optional<std::string>& rpcwallet = {})
673 : {
674 443 : UniValue response(UniValue::VOBJ);
675 : // Execute and handle connection failures with -rpcwait.
676 436 : const bool fWait = gArgs.GetBoolArg("-rpcwait", false);
677 436 : do {
678 : try {
679 438 : response = CallRPC(rh, strMethod, args, rpcwallet);
680 431 : if (fWait) {
681 3 : const UniValue& error = find_value(response, "error");
682 3 : if (!error.isNull() && error["code"].get_int() == RPC_IN_WARMUP) {
683 2 : throw CConnectionFailed("server in warmup");
684 : }
685 3 : }
686 : break; // Connection succeeded, no need to retry.
687 9 : } catch (const CConnectionFailed&) {
688 3 : if (fWait) {
689 2 : UninterruptibleSleep(std::chrono::milliseconds{1000});
690 : } else {
691 1 : throw;
692 : }
693 3 : }
694 2 : } while (fWait);
695 : return response;
696 446 : }
697 :
698 : /** Parse UniValue result to update the message to print to std::cout. */
699 343 : static void ParseResult(const UniValue& result, std::string& strPrint)
700 : {
701 343 : if (result.isNull()) return;
702 311 : strPrint = result.isStr() ? result.get_str() : result.write(2);
703 343 : }
704 :
705 : /** Parse UniValue error to update the message to print to std::cerr and the code to return. */
706 64 : static void ParseError(const UniValue& error, std::string& strPrint, int& nRet)
707 : {
708 64 : if (error.isObject()) {
709 64 : const UniValue& err_code = find_value(error, "code");
710 64 : const UniValue& err_msg = find_value(error, "message");
711 64 : if (!err_code.isNull()) {
712 64 : strPrint = "error code: " + err_code.getValStr() + "\n";
713 64 : }
714 64 : if (err_msg.isStr()) {
715 64 : strPrint += ("error message:\n" + err_msg.get_str());
716 64 : }
717 64 : if (err_code.isNum() && err_code.get_int() == RPC_WALLET_NOT_SPECIFIED) {
718 6 : strPrint += "\nTry adding \"-rpcwallet=<filename>\" option to bitcoin-cli command line.";
719 6 : }
720 64 : } else {
721 0 : strPrint = "error: " + error.write();
722 : }
723 64 : nRet = abs(error["code"].get_int());
724 64 : }
725 :
726 : /**
727 : * GetWalletBalances calls listwallets; if more than one wallet is loaded, it then
728 : * fetches mine.trusted balances for each loaded wallet and pushes them to `result`.
729 : *
730 : * @param result Reference to UniValue object the wallet names and balances are pushed to.
731 : */
732 4 : static void GetWalletBalances(UniValue& result)
733 : {
734 4 : DefaultRequestHandler rh;
735 4 : const UniValue listwallets = ConnectAndCallRPC(&rh, "listwallets", /* args=*/{});
736 4 : if (!find_value(listwallets, "error").isNull()) return;
737 4 : const UniValue& wallets = find_value(listwallets, "result");
738 4 : if (wallets.size() <= 1) return;
739 :
740 2 : UniValue balances(UniValue::VOBJ);
741 7 : for (const UniValue& wallet : wallets.getValues()) {
742 5 : const std::string wallet_name = wallet.get_str();
743 5 : const UniValue getbalances = ConnectAndCallRPC(&rh, "getbalances", /* args=*/{}, wallet_name);
744 5 : const UniValue& balance = find_value(getbalances, "result")["mine"]["trusted"];
745 5 : balances.pushKV(wallet_name, balance);
746 5 : }
747 2 : result.pushKV("balances", balances);
748 6 : }
749 :
750 : /**
751 : * Call RPC getnewaddress.
752 : * @returns getnewaddress response as a UniValue object.
753 : */
754 21 : static UniValue GetNewAddress()
755 : {
756 21 : Optional<std::string> wallet_name{};
757 21 : if (gArgs.IsArgSet("-rpcwallet")) wallet_name = gArgs.GetArg("-rpcwallet", "");
758 21 : DefaultRequestHandler rh;
759 21 : return ConnectAndCallRPC(&rh, "getnewaddress", /* args=*/{}, wallet_name);
760 21 : }
761 :
762 : /**
763 : * Check bounds and set up args for RPC generatetoaddress params: nblocks, address, maxtries.
764 : * @param[in] address Reference to const string address to insert into the args.
765 : * @param args Reference to vector of string args to modify.
766 : */
767 13 : static void SetGenerateToAddressArgs(const std::string& address, std::vector<std::string>& args)
768 : {
769 15 : if (args.size() > 2) throw std::runtime_error("too many arguments (maximum 2 for nblocks and maxtries)");
770 11 : if (args.size() == 0) {
771 3 : args.emplace_back(DEFAULT_NBLOCKS);
772 11 : } else if (args.at(0) == "0") {
773 2 : throw std::runtime_error("the first argument (number of blocks to generate, default: " + DEFAULT_NBLOCKS + ") must be an integer value greater than zero");
774 : }
775 9 : args.emplace(args.begin() + 1, address);
776 11 : }
777 :
778 418 : static int CommandLineRPC(int argc, char *argv[])
779 : {
780 418 : std::string strPrint;
781 418 : int nRet = 0;
782 : try {
783 : // Skip switches
784 1100 : while (argc > 1 && IsSwitchChar(argv[1][0])) {
785 682 : argc--;
786 682 : argv++;
787 : }
788 418 : std::string rpcPass;
789 418 : if (gArgs.GetBoolArg("-stdinrpcpass", false)) {
790 4 : NO_STDIN_ECHO();
791 4 : if (!StdinReady()) {
792 0 : fputs("RPC password> ", stderr);
793 0 : fflush(stderr);
794 : }
795 4 : if (!std::getline(std::cin, rpcPass)) {
796 0 : throw std::runtime_error("-stdinrpcpass specified but failed to read from standard input");
797 : }
798 4 : if (StdinTerminal()) {
799 0 : fputc('\n', stdout);
800 : }
801 4 : gArgs.ForceSetArg("-rpcpassword", rpcPass);
802 4 : }
803 418 : std::vector<std::string> args = std::vector<std::string>(&argv[1], &argv[argc]);
804 418 : if (gArgs.GetBoolArg("-stdinwalletpassphrase", false)) {
805 0 : NO_STDIN_ECHO();
806 0 : std::string walletPass;
807 0 : if (args.size() < 1 || args[0].substr(0, 16) != "walletpassphrase") {
808 0 : throw std::runtime_error("-stdinwalletpassphrase is only applicable for walletpassphrase(change)");
809 : }
810 0 : if (!StdinReady()) {
811 0 : fputs("Wallet passphrase> ", stderr);
812 0 : fflush(stderr);
813 : }
814 0 : if (!std::getline(std::cin, walletPass)) {
815 0 : throw std::runtime_error("-stdinwalletpassphrase specified but failed to read from standard input");
816 : }
817 0 : if (StdinTerminal()) {
818 0 : fputc('\n', stdout);
819 : }
820 0 : args.insert(args.begin() + 1, walletPass);
821 0 : }
822 418 : if (gArgs.GetBoolArg("-stdin", false)) {
823 : // Read one arg per line from stdin and append
824 2 : std::string line;
825 4 : while (std::getline(std::cin, line)) {
826 2 : args.push_back(line);
827 : }
828 2 : if (StdinTerminal()) {
829 0 : fputc('\n', stdout);
830 : }
831 2 : }
832 418 : std::unique_ptr<BaseRequestHandler> rh;
833 418 : std::string method;
834 418 : if (gArgs.IsArgSet("-getinfo")) {
835 11 : rh.reset(new GetinfoRequestHandler());
836 418 : } else if (gArgs.GetBoolArg("-netinfo", false)) {
837 0 : rh.reset(new NetinfoRequestHandler());
838 407 : } else if (gArgs.GetBoolArg("-generate", false)) {
839 21 : const UniValue getnewaddress{GetNewAddress()};
840 21 : const UniValue& error{find_value(getnewaddress, "error")};
841 21 : if (error.isNull()) {
842 13 : SetGenerateToAddressArgs(find_value(getnewaddress, "result").get_str(), args);
843 9 : rh.reset(new GenerateToAddressRequestHandler());
844 9 : } else {
845 8 : ParseError(error, strPrint, nRet);
846 : }
847 21 : } else {
848 386 : rh.reset(new DefaultRequestHandler());
849 386 : if (args.size() < 1) {
850 0 : throw std::runtime_error("too few parameters (need at least command)");
851 : }
852 386 : method = args[0];
853 386 : args.erase(args.begin()); // Remove trailing method name from arguments vector
854 : }
855 414 : if (nRet == 0) {
856 : // Perform RPC call
857 406 : Optional<std::string> wallet_name{};
858 406 : if (gArgs.IsArgSet("-rpcwallet")) wallet_name = gArgs.GetArg("-rpcwallet", "");
859 406 : const UniValue reply = ConnectAndCallRPC(rh.get(), method, args, wallet_name);
860 :
861 : // Parse reply
862 399 : UniValue result = find_value(reply, "result");
863 399 : const UniValue& error = find_value(reply, "error");
864 399 : if (error.isNull()) {
865 343 : if (gArgs.IsArgSet("-getinfo") && !gArgs.IsArgSet("-rpcwallet")) {
866 4 : GetWalletBalances(result); // fetch multiwallet balances and append to result
867 : }
868 343 : ParseResult(result, strPrint);
869 : } else {
870 56 : ParseError(error, strPrint, nRet);
871 : }
872 406 : }
873 418 : } catch (const std::exception& e) {
874 11 : strPrint = std::string("error: ") + e.what();
875 11 : nRet = EXIT_FAILURE;
876 11 : } catch (...) {
877 0 : PrintExceptionContinue(nullptr, "CommandLineRPC()");
878 0 : throw;
879 11 : }
880 :
881 418 : if (strPrint != "") {
882 386 : tfm::format(nRet == 0 ? std::cout : std::cerr, "%s\n", strPrint);
883 : }
884 418 : return nRet;
885 429 : }
886 :
887 : #ifdef WIN32
888 : // Export main() and ensure working ASLR on Windows.
889 : // Exporting a symbol will prevent the linker from stripping
890 : // the .reloc section from the binary, which is a requirement
891 : // for ASLR. This is a temporary workaround until a fixed
892 : // version of binutils is used for releases.
893 : __declspec(dllexport) int main(int argc, char* argv[])
894 : {
895 : util::WinCmdLineArgs winArgs;
896 : std::tie(argc, argv) = winArgs.get();
897 : #else
898 419 : int main(int argc, char* argv[])
899 : {
900 : #endif
901 419 : SetupEnvironment();
902 419 : if (!SetupNetworking()) {
903 0 : tfm::format(std::cerr, "Error: Initializing networking failed\n");
904 0 : return EXIT_FAILURE;
905 : }
906 419 : event_set_log_callback(&libevent_log_cb);
907 :
908 : try {
909 419 : int ret = AppInitRPC(argc, argv);
910 419 : if (ret != CONTINUE_EXECUTION)
911 1 : return ret;
912 418 : }
913 : catch (const std::exception& e) {
914 0 : PrintExceptionContinue(&e, "AppInitRPC()");
915 : return EXIT_FAILURE;
916 0 : } catch (...) {
917 0 : PrintExceptionContinue(nullptr, "AppInitRPC()");
918 : return EXIT_FAILURE;
919 0 : }
920 :
921 : int ret = EXIT_FAILURE;
922 : try {
923 418 : ret = CommandLineRPC(argc, argv);
924 418 : }
925 : catch (const std::exception& e) {
926 0 : PrintExceptionContinue(&e, "CommandLineRPC()");
927 0 : } catch (...) {
928 0 : PrintExceptionContinue(nullptr, "CommandLineRPC()");
929 0 : }
930 : return ret;
931 419 : }
|