Line data Source code
1 : // Copyright (c) 2009-2010 Satoshi Nakamoto
2 : // Copyright (c) 2009-2021 The Bitcoin Core developers
3 : // Copyright (c) 2014-2025 The Dash Core developers
4 : // Distributed under the MIT software license, see the accompanying
5 : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
6 :
7 : #if defined(HAVE_CONFIG_H)
8 : #include <config/bitcoin-config.h>
9 : #endif
10 :
11 : #include <chainparamsbase.h>
12 : #include <clientversion.h>
13 : #include <common/url.h>
14 : #include <compat/compat.h>
15 : #include <compat/stdin.h>
16 : #include <policy/feerate.h>
17 : #include <rpc/client.h>
18 : #include <rpc/mining.h>
19 : #include <rpc/protocol.h>
20 : #include <rpc/request.h>
21 : #include <stacktraces.h>
22 : #include <tinyformat.h>
23 : #include <univalue.h>
24 : #include <util/strencodings.h>
25 : #include <util/system.h>
26 : #include <util/translation.h>
27 :
28 : #include <algorithm>
29 : #include <chrono>
30 : #include <cmath>
31 : #include <cstdio>
32 : #include <functional>
33 : #include <memory>
34 : #include <optional>
35 : #include <string>
36 : #include <tuple>
37 :
38 : #ifndef WIN32
39 : #include <unistd.h>
40 : #endif
41 :
42 : #include <event2/buffer.h>
43 : #include <event2/keyvalq_struct.h>
44 : #include <support/events.h>
45 :
46 : // The server returns time values from a mockable system clock, but it is not
47 : // trivial to get the mocked time from the server, nor is it needed for now, so
48 : // just use a plain system_clock.
49 : using CliClock = std::chrono::system_clock;
50 :
51 1193 : const std::function<std::string(const char*)> G_TRANSLATION_FUN = nullptr;
52 : UrlDecodeFn* const URL_DECODE = urlDecode;
53 :
54 : static const char DEFAULT_RPCCONNECT[] = "127.0.0.1";
55 : static const int DEFAULT_HTTP_CLIENT_TIMEOUT=900;
56 : static constexpr int DEFAULT_WAIT_CLIENT_TIMEOUT = 0;
57 : static const bool DEFAULT_NAMED=false;
58 : static const int CONTINUE_EXECUTION=-1;
59 : static constexpr int8_t UNKNOWN_NETWORK{-1};
60 : // See GetNetworkName() in netbase.cpp
61 : static constexpr std::array NETWORKS{"not_publicly_routable", "ipv4", "ipv6", "onion", "i2p", "cjdns", "internal"};
62 : static constexpr std::array NETWORK_SHORT_NAMES{"npr", "ipv4", "ipv6", "onion", "i2p", "cjdns", "int"};
63 : static constexpr std::array UNREACHABLE_NETWORK_IDS{/*not_publicly_routable*/0, /*internal*/6};
64 :
65 : /** Default number of blocks to generate for RPC generatetoaddress. */
66 : static const std::string DEFAULT_NBLOCKS = "1";
67 :
68 : /** Default -color setting. */
69 : static const std::string DEFAULT_COLOR_SETTING{"auto"};
70 :
71 1193 : static void SetupCliArgs(ArgsManager& argsman)
72 : {
73 1193 : SetupHelpOptions(argsman);
74 :
75 1193 : const auto defaultBaseParams = CreateBaseChainParams(CBaseChainParams::MAIN);
76 1193 : const auto testnetBaseParams = CreateBaseChainParams(CBaseChainParams::TESTNET);
77 1193 : const auto devnetBaseParams = CreateBaseChainParams(CBaseChainParams::DEVNET);
78 1193 : const auto regtestBaseParams = CreateBaseChainParams(CBaseChainParams::REGTEST);
79 :
80 1193 : argsman.AddArg("-version", "Print version and exit", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
81 1193 : 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);
82 1193 : argsman.AddArg("-datadir=<dir>", "Specify data directory", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
83 2386 : argsman.AddArg("-generate",
84 1193 : strprintf("Generate blocks, equivalent to RPC getnewaddress followed by RPC generatetoaddress. Optional positional integer "
85 : "arguments are number of blocks to generate (default: %s) and maximum iterations to try (default: %s), equivalent to "
86 : "RPC generatetoaddress nblocks and maxtries arguments. Example: dash-cli -generate 4 1000",
87 : DEFAULT_NBLOCKS, DEFAULT_MAX_TRIES),
88 1193 : ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
89 1193 : argsman.AddArg("-addrinfo", "Get the number of addresses known to the node, per network and total, after filtering for quality and recency. The total number of addresses known to the node may be higher.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
90 1193 : argsman.AddArg("-getinfo", "Get general information from the remote server. Note that unlike server-side RPC calls, the output of -getinfo is the result of multiple non-atomic requests. Some entries in the output 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);
91 1193 : 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). Pass \"help\" for detailed help documentation.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
92 :
93 1193 : argsman.AddArg("-color=<when>", strprintf("Color setting for CLI output (default: %s). Valid values: always, auto (add color codes when standard output is connected to a terminal and OS is not WIN32), never.", DEFAULT_COLOR_SETTING), ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_NEGATION, OptionsCategory::OPTIONS);
94 1193 : argsman.AddArg("-named", strprintf("Pass named instead of positional arguments (default: %s)", DEFAULT_NAMED), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
95 1193 : 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);
96 1193 : argsman.AddArg("-rpcconnect=<ip>", strprintf("Send commands to node running on <ip> (default: %s)", DEFAULT_RPCCONNECT), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
97 1193 : 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);
98 1193 : argsman.AddArg("-rpcpassword=<pw>", "Password for JSON-RPC connections", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
99 1193 : argsman.AddArg("-rpcport=<port>", strprintf("Connect to JSON-RPC on <port> (default: %u, testnet: %u, devnet: %u, regtest: %u)", defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort(), devnetBaseParams->RPCPort(), regtestBaseParams->RPCPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::OPTIONS);
100 1193 : argsman.AddArg("-rpcuser=<user>", "Username for JSON-RPC connections", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
101 1193 : argsman.AddArg("-rpcwait", "Wait for RPC server to start", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
102 1193 : argsman.AddArg("-rpcwallet=<walletname>", "Send RPC for non-default wallet on RPC server (needs to exactly match corresponding -wallet option passed to dashd). This changes the RPC endpoint used, e.g. http://127.0.0.1:9998/wallet/<walletname>", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
103 1193 : argsman.AddArg("-rpcwaittimeout=<n>", strprintf("Timeout in seconds to wait for the RPC server to start, or 0 for no timeout. (default: %d)", DEFAULT_WAIT_CLIENT_TIMEOUT), ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_NEGATION, OptionsCategory::OPTIONS);
104 1193 : 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);
105 1193 : 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);
106 1193 : 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);
107 :
108 1193 : SetupChainParamsBaseOptions(argsman);
109 1193 : }
110 :
111 : /** libevent event log callback */
112 0 : static void libevent_log_cb(int severity, const char *msg)
113 : {
114 : // Ignore everything other than errors
115 0 : if (severity >= EVENT_LOG_ERR) {
116 0 : throw std::runtime_error(strprintf("libevent error: %s", msg));
117 : }
118 0 : }
119 :
120 : //
121 : // Exception thrown on connection error. This error is used to determine
122 : // when to wait if -rpcwait is given.
123 : //
124 : class CConnectionFailed : public std::runtime_error
125 : {
126 : public:
127 :
128 80 : explicit inline CConnectionFailed(const std::string& msg) :
129 40 : std::runtime_error(msg)
130 80 : {}
131 :
132 : };
133 :
134 : //
135 : // This function returns either one of EXIT_ codes when it's expected to stop the process or
136 : // CONTINUE_EXECUTION when it's expected to continue further.
137 : //
138 1193 : static int AppInitRPC(int argc, char* argv[])
139 : {
140 1193 : SetupCliArgs(gArgs);
141 1193 : std::string error;
142 1193 : if (!gArgs.ParseParameters(argc, argv, error)) {
143 0 : tfm::format(std::cerr, "Error parsing command line arguments: %s\n", error);
144 0 : return EXIT_FAILURE;
145 : }
146 :
147 1193 : if (gArgs.IsArgSet("-printcrashinfo")) {
148 0 : std::cout << GetCrashInfoStrFromSerializedStr(gArgs.GetArg("-printcrashinfo", "")) << std::endl;
149 0 : return true;
150 : }
151 :
152 1193 : if (argc < 2 || HelpRequested(gArgs) || gArgs.IsArgSet("-version")) {
153 4 : std::string strUsage = PACKAGE_NAME " RPC client version " + FormatFullVersion() + "\n";
154 :
155 4 : if (gArgs.IsArgSet("-version")) {
156 4 : strUsage += FormatParagraph(LicenseInfo());
157 4 : } else {
158 0 : strUsage += "\n"
159 : "Usage: dash-cli [options] <command> [params] Send command to " PACKAGE_NAME "\n"
160 : "or: dash-cli [options] -named <command> [name=value]... Send command to " PACKAGE_NAME " (with named arguments)\n"
161 : "or: dash-cli [options] help List commands\n"
162 : "or: dash-cli [options] help <command> Get help for a command\n";
163 0 : strUsage += "\n" + gArgs.GetHelpMessage();
164 : }
165 :
166 4 : tfm::format(std::cout, "%s", strUsage);
167 4 : if (argc < 2) {
168 0 : tfm::format(std::cerr, "Error: too few parameters\n");
169 0 : return EXIT_FAILURE;
170 : }
171 4 : return EXIT_SUCCESS;
172 4 : }
173 1189 : if (!CheckDataDirOption()) {
174 0 : tfm::format(std::cerr, "Error: Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", ""));
175 0 : return EXIT_FAILURE;
176 : }
177 1189 : if (!gArgs.ReadConfigFiles(error, true)) {
178 0 : tfm::format(std::cerr, "Error reading configuration file: %s\n", error);
179 0 : return EXIT_FAILURE;
180 : }
181 : // Check for chain settings (BaseParams() calls are only valid after this clause)
182 : try {
183 1189 : SelectBaseParams(gArgs.GetChainName());
184 1189 : } catch (const std::exception& e) {
185 0 : tfm::format(std::cerr, "Error: %s\n", e.what());
186 0 : return EXIT_FAILURE;
187 0 : }
188 1189 : return CONTINUE_EXECUTION;
189 1193 : }
190 :
191 :
192 : /** Reply structure for request_done to fill in */
193 : struct HTTPReply
194 : {
195 3903 : HTTPReply() = default;
196 :
197 1301 : int status{0};
198 1301 : int error{-1};
199 : std::string body;
200 : };
201 :
202 0 : static std::string http_errorstring(int code)
203 : {
204 0 : switch(code) {
205 : case EVREQ_HTTP_TIMEOUT:
206 0 : return "timeout reached";
207 : case EVREQ_HTTP_EOF:
208 0 : return "EOF reached";
209 : case EVREQ_HTTP_INVALID_HEADER:
210 0 : return "error while reading header, or invalid header";
211 : case EVREQ_HTTP_BUFFER_ERROR:
212 0 : return "error encountered while reading or writing";
213 : case EVREQ_HTTP_REQUEST_CANCEL:
214 0 : return "request was canceled";
215 : case EVREQ_HTTP_DATA_TOO_LONG:
216 0 : return "response body is larger than allowed";
217 : default:
218 0 : return "unknown";
219 : }
220 0 : }
221 :
222 1289 : static void http_request_done(struct evhttp_request *req, void *ctx)
223 : {
224 1289 : HTTPReply *reply = static_cast<HTTPReply*>(ctx);
225 :
226 1289 : if (req == nullptr) {
227 : /* If req is nullptr, it means an error occurred while connecting: the
228 : * error code will have been passed to http_error_cb.
229 : */
230 0 : reply->status = 0;
231 0 : return;
232 : }
233 :
234 1289 : reply->status = evhttp_request_get_response_code(req);
235 :
236 1289 : struct evbuffer *buf = evhttp_request_get_input_buffer(req);
237 1289 : if (buf)
238 : {
239 1289 : size_t size = evbuffer_get_length(buf);
240 1289 : const char *data = (const char*)evbuffer_pullup(buf, size);
241 1289 : if (data)
242 1249 : reply->body = std::string(data, size);
243 1289 : evbuffer_drain(buf, size);
244 1289 : }
245 1289 : }
246 :
247 0 : static void http_error_cb(enum evhttp_request_error err, void *ctx)
248 : {
249 0 : HTTPReply *reply = static_cast<HTTPReply*>(ctx);
250 0 : reply->error = err;
251 0 : }
252 :
253 : /** Class that handles the conversion from a command-line to a JSON-RPC request,
254 : * as well as converting back to a JSON object that can be shown as result.
255 : */
256 : class BaseRequestHandler
257 : {
258 : public:
259 1141 : virtual ~BaseRequestHandler() = default;
260 : virtual UniValue PrepareRequest(const std::string& method, const std::vector<std::string>& args) = 0;
261 : virtual UniValue ProcessReply(const UniValue &batch_in) = 0;
262 : };
263 :
264 : /** Process addrinfo requests */
265 : class AddrinfoRequestHandler : public BaseRequestHandler
266 : {
267 : private:
268 0 : int8_t NetworkStringToId(const std::string& str) const
269 : {
270 0 : for (size_t i = 0; i < NETWORKS.size(); ++i) {
271 0 : if (str == NETWORKS[i]) return i;
272 0 : }
273 0 : return UNKNOWN_NETWORK;
274 0 : }
275 :
276 : public:
277 0 : UniValue PrepareRequest(const std::string& method, const std::vector<std::string>& args) override
278 : {
279 0 : if (!args.empty()) {
280 0 : throw std::runtime_error("-addrinfo takes no arguments");
281 : }
282 0 : UniValue params{RPCConvertValues("getnodeaddresses", std::vector<std::string>{{"0"}})};
283 0 : return JSONRPCRequestObj("getnodeaddresses", params, 1);
284 0 : }
285 :
286 0 : UniValue ProcessReply(const UniValue& reply) override
287 : {
288 0 : if (!reply["error"].isNull()) return reply;
289 0 : const std::vector<UniValue>& nodes{reply["result"].getValues()};
290 0 : if (!nodes.empty() && nodes.at(0)["network"].isNull()) {
291 0 : throw std::runtime_error("-addrinfo requires dashd server to be running v21.0 and up");
292 : }
293 : // Count the number of peers known to our node, by network.
294 0 : std::array<uint64_t, NETWORKS.size()> counts{{}};
295 0 : for (const UniValue& node : nodes) {
296 0 : std::string network_name{node["network"].get_str()};
297 0 : const int8_t network_id{NetworkStringToId(network_name)};
298 0 : if (network_id == UNKNOWN_NETWORK) continue;
299 0 : ++counts.at(network_id);
300 0 : }
301 : // Prepare result to return to user.
302 0 : UniValue result{UniValue::VOBJ}, addresses{UniValue::VOBJ};
303 0 : uint64_t total{0}; // Total address count
304 0 : for (size_t i = 1; i < NETWORKS.size() - 1; ++i) {
305 0 : addresses.pushKV(NETWORKS[i], counts.at(i));
306 0 : total += counts.at(i);
307 0 : }
308 0 : addresses.pushKV("total", total);
309 0 : result.pushKV("addresses_known", addresses);
310 0 : return JSONRPCReplyObj(result, NullUniValue, 1);
311 0 : }
312 : };
313 :
314 : /** Process getinfo requests */
315 60 : class GetinfoRequestHandler: public BaseRequestHandler
316 : {
317 : public:
318 60 : const int ID_NETWORKINFO = 0;
319 60 : const int ID_BLOCKCHAININFO = 1;
320 60 : const int ID_WALLETINFO = 2;
321 60 : const int ID_BALANCES = 3;
322 :
323 : /** Create a simulated `getinfo` request. */
324 60 : UniValue PrepareRequest(const std::string& method, const std::vector<std::string>& args) override
325 : {
326 60 : if (!args.empty()) {
327 4 : throw std::runtime_error("-getinfo takes no arguments");
328 : }
329 56 : UniValue result(UniValue::VARR);
330 56 : result.push_back(JSONRPCRequestObj("getnetworkinfo", NullUniValue, ID_NETWORKINFO));
331 56 : result.push_back(JSONRPCRequestObj("getblockchaininfo", NullUniValue, ID_BLOCKCHAININFO));
332 56 : result.push_back(JSONRPCRequestObj("getwalletinfo", NullUniValue, ID_WALLETINFO));
333 56 : result.push_back(JSONRPCRequestObj("getbalances", NullUniValue, ID_BALANCES));
334 56 : return result;
335 56 : }
336 :
337 : /** Collect values from the batch and form a simulated `getinfo` reply. */
338 56 : UniValue ProcessReply(const UniValue &batch_in) override
339 : {
340 56 : UniValue result(UniValue::VOBJ);
341 56 : const std::vector<UniValue> batch = JSONRPCProcessBatchReply(batch_in);
342 : // Errors in getnetworkinfo() and getblockchaininfo() are fatal, pass them on;
343 : // getwalletinfo() and getbalances() are allowed to fail if there is no wallet.
344 56 : if (!batch[ID_NETWORKINFO]["error"].isNull()) {
345 0 : return batch[ID_NETWORKINFO];
346 : }
347 56 : if (!batch[ID_BLOCKCHAININFO]["error"].isNull()) {
348 0 : return batch[ID_BLOCKCHAININFO];
349 : }
350 56 : result.pushKV("version", batch[ID_NETWORKINFO]["result"]["version"]);
351 56 : result.pushKV("blocks", batch[ID_BLOCKCHAININFO]["result"]["blocks"]);
352 56 : result.pushKV("headers", batch[ID_BLOCKCHAININFO]["result"]["headers"]);
353 56 : result.pushKV("verificationprogress", batch[ID_BLOCKCHAININFO]["result"]["verificationprogress"]);
354 56 : result.pushKV("timeoffset", batch[ID_NETWORKINFO]["result"]["timeoffset"]);
355 :
356 56 : UniValue connections(UniValue::VOBJ);
357 56 : connections.pushKV("in", batch[ID_NETWORKINFO]["result"]["connections_in"]);
358 56 : connections.pushKV("out", batch[ID_NETWORKINFO]["result"]["connections_out"]);
359 56 : connections.pushKV("total", batch[ID_NETWORKINFO]["result"]["connections"]);
360 56 : connections.pushKV("mn_in", batch[ID_NETWORKINFO]["result"]["connections_mn_in"]);
361 56 : connections.pushKV("mn_out", batch[ID_NETWORKINFO]["result"]["connections_mn_out"]);
362 56 : connections.pushKV("mn_total", batch[ID_NETWORKINFO]["result"]["connections_mn"]);
363 56 : result.pushKV("connections", connections);
364 :
365 56 : result.pushKV("networks", batch[ID_NETWORKINFO]["result"]["networks"]);
366 56 : result.pushKV("difficulty", batch[ID_BLOCKCHAININFO]["result"]["difficulty"]);
367 56 : result.pushKV("chain", UniValue(batch[ID_BLOCKCHAININFO]["result"]["chain"]));
368 56 : if (!batch[ID_WALLETINFO]["result"].isNull()) {
369 28 : result.pushKV("coinjoin_balance", batch[ID_WALLETINFO]["result"]["coinjoin_balance"]);
370 28 : result.pushKV("has_wallet", true);
371 28 : result.pushKV("keypoolsize", batch[ID_WALLETINFO]["result"]["keypoolsize"]);
372 28 : result.pushKV("walletname", batch[ID_WALLETINFO]["result"]["walletname"]);
373 28 : if (!batch[ID_WALLETINFO]["result"]["unlocked_until"].isNull()) {
374 24 : result.pushKV("unlocked_until", batch[ID_WALLETINFO]["result"]["unlocked_until"]);
375 24 : }
376 28 : result.pushKV("paytxfee", batch[ID_WALLETINFO]["result"]["paytxfee"]);
377 28 : }
378 56 : if (!batch[ID_BALANCES]["result"].isNull()) {
379 28 : result.pushKV("balance", batch[ID_BALANCES]["result"]["mine"]["trusted"]);
380 28 : }
381 56 : result.pushKV("relayfee", batch[ID_NETWORKINFO]["result"]["relayfee"]);
382 56 : result.pushKV("warnings", batch[ID_NETWORKINFO]["result"]["warnings"]);
383 56 : return JSONRPCReplyObj(result, NullUniValue, 1);
384 56 : }
385 : };
386 :
387 : /** Process netinfo requests */
388 0 : class NetinfoRequestHandler : public BaseRequestHandler
389 : {
390 : private:
391 : static constexpr uint8_t MAX_DETAIL_LEVEL{4};
392 0 : std::array<std::array<uint16_t, NETWORKS.size() + 1>, 3> m_counts{{{}}}; //!< Peer counts by (in/out/total, networks/total)
393 0 : uint8_t m_block_relay_peers_count{0};
394 0 : uint8_t m_manual_peers_count{0};
395 0 : int8_t NetworkStringToId(const std::string& str) const
396 : {
397 0 : for (size_t i = 0; i < NETWORKS.size(); ++i) {
398 0 : if (str == NETWORKS[i]) return i;
399 0 : }
400 0 : return UNKNOWN_NETWORK;
401 0 : }
402 0 : uint8_t m_details_level{0}; //!< Optional user-supplied arg to set dashboard details level
403 0 : bool DetailsRequested() const { return m_details_level > 0 && m_details_level < 5; }
404 0 : bool IsAddressSelected() const { return m_details_level == 2 || m_details_level == 4; }
405 0 : bool IsVersionSelected() const { return m_details_level == 3 || m_details_level == 4; }
406 0 : bool m_is_asmap_on{false};
407 0 : size_t m_max_addr_length{0};
408 0 : size_t m_max_addr_processed_length{5};
409 0 : size_t m_max_addr_rate_limited_length{6};
410 0 : size_t m_max_age_length{5};
411 0 : size_t m_max_id_length{2};
412 : struct Peer {
413 : std::string addr;
414 : std::string sub_version;
415 : std::string conn_type;
416 : std::string network;
417 : std::string age;
418 : std::string transport_protocol_type;
419 : double min_ping;
420 : double ping;
421 : int64_t addr_processed;
422 : int64_t addr_rate_limited;
423 : int64_t last_blck;
424 : int64_t last_recv;
425 : int64_t last_send;
426 : int64_t last_trxn;
427 : int id;
428 : int mapped_as;
429 : int version;
430 : bool is_addr_relay_enabled;
431 : bool is_bip152_hb_from;
432 : bool is_bip152_hb_to;
433 : bool is_outbound;
434 : bool is_tx_relay;
435 0 : bool operator<(const Peer& rhs) const { return std::tie(is_outbound, min_ping) < std::tie(rhs.is_outbound, rhs.min_ping); }
436 : };
437 : std::vector<Peer> m_peers;
438 0 : std::string ChainToString() const
439 : {
440 0 : if (gArgs.GetChainName() == CBaseChainParams::TESTNET) return " testnet";
441 0 : if (gArgs.GetChainName() == CBaseChainParams::DEVNET) return " devnet";
442 0 : if (gArgs.GetChainName() == CBaseChainParams::REGTEST) return " regtest";
443 0 : return "";
444 0 : }
445 0 : std::string PingTimeToString(double seconds) const
446 : {
447 0 : if (seconds < 0) return "";
448 0 : const double milliseconds{round(1000 * seconds)};
449 0 : return milliseconds > 999999 ? "-" : ToString(milliseconds);
450 0 : }
451 0 : std::string ConnectionTypeForNetinfo(const std::string& conn_type) const
452 : {
453 0 : if (conn_type == "outbound-full-relay") return "full";
454 0 : if (conn_type == "block-relay-only") return "block";
455 0 : if (conn_type == "manual" || conn_type == "feeler") return conn_type;
456 0 : if (conn_type == "addr-fetch") return "addr";
457 0 : return "";
458 0 : }
459 :
460 : public:
461 : static constexpr int ID_PEERINFO = 0;
462 : static constexpr int ID_NETWORKINFO = 1;
463 :
464 0 : UniValue PrepareRequest(const std::string& method, const std::vector<std::string>& args) override
465 : {
466 0 : if (!args.empty()) {
467 0 : uint8_t n{0};
468 0 : if (ParseUInt8(args.at(0), &n)) {
469 0 : m_details_level = std::min(n, MAX_DETAIL_LEVEL);
470 0 : } else {
471 0 : throw std::runtime_error(strprintf("invalid -netinfo argument: %s\nFor more information, run: dash-cli -netinfo help", args.at(0)));
472 : }
473 0 : }
474 0 : UniValue result(UniValue::VARR);
475 0 : result.push_back(JSONRPCRequestObj("getpeerinfo", NullUniValue, ID_PEERINFO));
476 0 : result.push_back(JSONRPCRequestObj("getnetworkinfo", NullUniValue, ID_NETWORKINFO));
477 0 : return result;
478 0 : }
479 :
480 0 : UniValue ProcessReply(const UniValue& batch_in) override
481 : {
482 0 : const std::vector<UniValue> batch{JSONRPCProcessBatchReply(batch_in)};
483 0 : if (!batch[ID_PEERINFO]["error"].isNull()) return batch[ID_PEERINFO];
484 0 : if (!batch[ID_NETWORKINFO]["error"].isNull()) return batch[ID_NETWORKINFO];
485 :
486 0 : const UniValue& networkinfo{batch[ID_NETWORKINFO]["result"]};
487 0 : if (networkinfo["version"].getInt<int>() < 200000) {
488 0 : throw std::runtime_error("-netinfo requires dashd server to be running v20.0 and up");
489 : }
490 0 : const int64_t time_now{TicksSinceEpoch<std::chrono::seconds>(CliClock::now())};
491 :
492 : // Count peer connection totals, and if DetailsRequested(), store peer data in a vector of structs.
493 0 : for (const UniValue& peer : batch[ID_PEERINFO]["result"].getValues()) {
494 0 : const std::string network{peer["network"].get_str()};
495 0 : const int8_t network_id{NetworkStringToId(network)};
496 0 : if (network_id == UNKNOWN_NETWORK) continue;
497 0 : const bool is_outbound{!peer["inbound"].get_bool()};
498 0 : const bool is_tx_relay{peer["relaytxes"].isNull() ? true : peer["relaytxes"].get_bool()};
499 0 : const std::string conn_type{peer["connection_type"].get_str()};
500 0 : ++m_counts.at(is_outbound).at(network_id); // in/out by network
501 0 : ++m_counts.at(is_outbound).at(NETWORKS.size()); // in/out overall
502 0 : ++m_counts.at(2).at(network_id); // total by network
503 0 : ++m_counts.at(2).at(NETWORKS.size()); // total overall
504 0 : if (conn_type == "block-relay-only") ++m_block_relay_peers_count;
505 0 : if (conn_type == "manual") ++m_manual_peers_count;
506 0 : if (DetailsRequested()) {
507 : // Push data for this peer to the peers vector.
508 0 : const int peer_id{peer["id"].getInt<int>()};
509 0 : const int mapped_as{peer["mapped_as"].isNull() ? 0 : peer["mapped_as"].getInt<int>()};
510 0 : const int version{peer["version"].getInt<int>()};
511 0 : const int64_t addr_processed{peer["addr_processed"].isNull() ? 0 : peer["addr_processed"].getInt<int64_t>()};
512 0 : const int64_t addr_rate_limited{peer["addr_rate_limited"].isNull() ? 0 : peer["addr_rate_limited"].getInt<int64_t>()};
513 0 : const int64_t conn_time{peer["conntime"].getInt<int64_t>()};
514 0 : const int64_t last_blck{peer["last_block"].getInt<int64_t>()};
515 0 : const int64_t last_recv{peer["lastrecv"].getInt<int64_t>()};
516 0 : const int64_t last_send{peer["lastsend"].getInt<int64_t>()};
517 0 : const int64_t last_trxn{peer["last_transaction"].getInt<int64_t>()};
518 0 : const double min_ping{peer["minping"].isNull() ? -1 : peer["minping"].get_real()};
519 0 : const double ping{peer["pingtime"].isNull() ? -1 : peer["pingtime"].get_real()};
520 0 : const std::string addr{peer["addr"].get_str()};
521 0 : const std::string age{conn_time == 0 ? "" : ToString((time_now - conn_time) / 60)};
522 0 : const std::string sub_version{peer["subver"].get_str()};
523 0 : const std::string transport{peer["transport_protocol_type"].isNull() ? "v1" : peer["transport_protocol_type"].get_str()};
524 0 : const bool is_addr_relay_enabled{peer["addr_relay_enabled"].isNull() ? false : peer["addr_relay_enabled"].get_bool()};
525 0 : const bool is_bip152_hb_from{peer["bip152_hb_from"].get_bool()};
526 0 : const bool is_bip152_hb_to{peer["bip152_hb_to"].get_bool()};
527 0 : m_peers.push_back({addr, sub_version, conn_type, NETWORK_SHORT_NAMES[network_id], age, transport, min_ping, ping, addr_processed, addr_rate_limited, last_blck, last_recv, last_send, last_trxn, peer_id, mapped_as, version, is_addr_relay_enabled, is_bip152_hb_from, is_bip152_hb_to, is_outbound, is_tx_relay});
528 0 : m_max_addr_length = std::max(addr.length() + 1, m_max_addr_length);
529 0 : m_max_addr_processed_length = std::max(ToString(addr_processed).length(), m_max_addr_processed_length);
530 0 : m_max_addr_rate_limited_length = std::max(ToString(addr_rate_limited).length(), m_max_addr_rate_limited_length);
531 0 : m_max_age_length = std::max(age.length(), m_max_age_length);
532 0 : m_max_id_length = std::max(ToString(peer_id).length(), m_max_id_length);
533 0 : m_is_asmap_on |= (mapped_as != 0);
534 0 : }
535 0 : }
536 :
537 : // Generate report header.
538 0 : std::string result{strprintf("%s client %s%s - server %i%s\n\n", PACKAGE_NAME, FormatFullVersion(), ChainToString(), networkinfo["protocolversion"].getInt<int>(), networkinfo["subversion"].get_str())};
539 :
540 : // Report detailed peer connections list sorted by direction and minimum ping time.
541 0 : if (DetailsRequested() && !m_peers.empty()) {
542 0 : std::sort(m_peers.begin(), m_peers.end());
543 0 : result += strprintf("<-> type net v mping ping send recv txn blk hb %*s%*s%*s ",
544 0 : m_max_addr_processed_length, "addrp",
545 0 : m_max_addr_rate_limited_length, "addrl",
546 0 : m_max_age_length, "age");
547 0 : if (m_is_asmap_on) result += " asmap ";
548 0 : result += strprintf("%*s %-*s%s\n", m_max_id_length, "id", IsAddressSelected() ? m_max_addr_length : 0, IsAddressSelected() ? "address" : "", IsVersionSelected() ? "version" : "");
549 0 : for (const Peer& peer : m_peers) {
550 0 : std::string version{ToString(peer.version) + peer.sub_version};
551 0 : result += strprintf(
552 : "%3s %6s %5s %2s%7s%7s%5s%5s%5s%5s %2s %*s%*s%*s%*i %*s %-*s%s\n",
553 0 : peer.is_outbound ? "out" : "in",
554 0 : ConnectionTypeForNetinfo(peer.conn_type),
555 0 : peer.network,
556 0 : peer.transport_protocol_type.starts_with('v') == 0 ? peer.transport_protocol_type[1] : ' ',
557 0 : PingTimeToString(peer.min_ping),
558 0 : PingTimeToString(peer.ping),
559 0 : peer.last_send ? ToString(time_now - peer.last_send) : "",
560 0 : peer.last_recv ? ToString(time_now - peer.last_recv) : "",
561 0 : peer.last_trxn ? ToString((time_now - peer.last_trxn) / 60) : peer.is_tx_relay ? "" : "*",
562 0 : peer.last_blck ? ToString((time_now - peer.last_blck) / 60) : "",
563 0 : strprintf("%s%s", peer.is_bip152_hb_to ? "." : " ", peer.is_bip152_hb_from ? "*" : " "),
564 0 : m_max_addr_processed_length, // variable spacing
565 0 : peer.addr_processed ? ToString(peer.addr_processed) : peer.is_addr_relay_enabled ? "" : ".",
566 0 : m_max_addr_rate_limited_length, // variable spacing
567 0 : peer.addr_rate_limited ? ToString(peer.addr_rate_limited) : "",
568 0 : m_max_age_length, // variable spacing
569 0 : peer.age,
570 0 : m_is_asmap_on ? 7 : 0, // variable spacing
571 0 : m_is_asmap_on && peer.mapped_as ? ToString(peer.mapped_as) : "",
572 0 : m_max_id_length, // variable spacing
573 0 : peer.id,
574 0 : IsAddressSelected() ? m_max_addr_length : 0, // variable spacing
575 0 : IsAddressSelected() ? peer.addr : "",
576 0 : IsVersionSelected() && version != "0" ? version : "");
577 0 : }
578 0 : result += strprintf(" ms ms sec sec min min %*s\n\n", m_max_age_length, "min");
579 0 : }
580 :
581 : // Report peer connection totals by type.
582 0 : result += " ";
583 0 : std::vector<int8_t> reachable_networks;
584 0 : for (const UniValue& network : networkinfo["networks"].getValues()) {
585 0 : if (network["reachable"].get_bool()) {
586 0 : const std::string& network_name{network["name"].get_str()};
587 0 : const int8_t network_id{NetworkStringToId(network_name)};
588 0 : if (network_id == UNKNOWN_NETWORK) continue;
589 0 : result += strprintf("%8s", network_name); // column header
590 0 : reachable_networks.push_back(network_id);
591 0 : }
592 : };
593 :
594 0 : for (const size_t network_id : UNREACHABLE_NETWORK_IDS) {
595 0 : if (m_counts.at(2).at(network_id) == 0) continue;
596 0 : result += strprintf("%8s", NETWORK_SHORT_NAMES.at(network_id)); // column header
597 0 : reachable_networks.push_back(network_id);
598 : }
599 :
600 0 : result += " total block";
601 0 : if (m_manual_peers_count) result += " manual";
602 :
603 0 : const std::array rows{"in", "out", "total"};
604 0 : for (size_t i = 0; i < rows.size(); ++i) {
605 0 : result += strprintf("\n%-5s", rows[i]); // row header
606 0 : for (int8_t n : reachable_networks) {
607 0 : result += strprintf("%8i", m_counts.at(i).at(n)); // network peers count
608 : }
609 0 : result += strprintf(" %5i", m_counts.at(i).at(NETWORKS.size())); // total peers count
610 0 : if (i == 1) { // the outbound row has two extra columns for block relay and manual peer counts
611 0 : result += strprintf(" %5i", m_block_relay_peers_count);
612 0 : if (m_manual_peers_count) result += strprintf(" %5i", m_manual_peers_count);
613 0 : }
614 0 : }
615 :
616 : // Report local addresses, ports, and scores.
617 0 : result += "\n\nLocal addresses";
618 0 : const std::vector<UniValue>& local_addrs{networkinfo["localaddresses"].getValues()};
619 0 : if (local_addrs.empty()) {
620 0 : result += ": n/a\n";
621 0 : } else {
622 0 : size_t max_addr_size{0};
623 0 : for (const UniValue& addr : local_addrs) {
624 0 : max_addr_size = std::max(addr["address"].get_str().length() + 1, max_addr_size);
625 : }
626 0 : for (const UniValue& addr : local_addrs) {
627 0 : result += strprintf("\n%-*s port %6i score %6i", max_addr_size, addr["address"].get_str(), addr["port"].getInt<int>(), addr["score"].getInt<int>());
628 : }
629 : }
630 :
631 0 : return JSONRPCReplyObj(UniValue{result}, NullUniValue, 1);
632 0 : }
633 :
634 0 : const std::string m_help_doc{
635 : "-netinfo level|\"help\" \n\n"
636 : "Returns a network peer connections dashboard with information from the remote server.\n"
637 : "This human-readable interface will change regularly and is not intended to be a stable API.\n"
638 : "Under the hood, -netinfo fetches the data by calling getpeerinfo and getnetworkinfo.\n"
639 0 : + strprintf("An optional integer argument from 0 to %d can be passed for different peers listings; %d to 255 are parsed as %d.\n", MAX_DETAIL_LEVEL, MAX_DETAIL_LEVEL, MAX_DETAIL_LEVEL) +
640 : "Pass \"help\" to see this detailed help documentation.\n"
641 : "If more than one argument is passed, only the first one is read and parsed.\n"
642 : "Suggestion: use with the Linux watch(1) command for a live dashboard; see example below.\n\n"
643 : "Arguments:\n"
644 0 : + strprintf("1. level (integer 0-%d, optional) Specify the info level of the peers dashboard (default 0):\n", MAX_DETAIL_LEVEL) +
645 : " 0 - Peer counts for each reachable network as well as for block relay peers\n"
646 : " and manual peers, and the list of local addresses and ports\n"
647 : " 1 - Like 0 but preceded by a peers listing (without address and version columns)\n"
648 : " 2 - Like 1 but with an address column\n"
649 : " 3 - Like 1 but with a version column\n"
650 : " 4 - Like 1 but with both address and version columns\n"
651 : "2. help (string \"help\", optional) Print this help documentation instead of the dashboard.\n\n"
652 : "Result:\n\n"
653 0 : + strprintf("* The peers listing in levels 1-%d displays all of the peers sorted by direction and minimum ping time:\n\n", MAX_DETAIL_LEVEL) +
654 : " Column Description\n"
655 : " ------ -----------\n"
656 : " <-> Direction\n"
657 : " \"in\" - inbound connections are those initiated by the peer\n"
658 : " \"out\" - outbound connections are those initiated by us\n"
659 : " type Type of peer connection\n"
660 : " \"full\" - full relay, the default\n"
661 : " \"block\" - block relay; like full relay but does not relay transactions or addresses\n"
662 : " \"manual\" - peer we manually added using RPC addnode or the -addnode/-connect config options\n"
663 : " \"feeler\" - short-lived connection for testing addresses\n"
664 : " \"addr\" - address fetch; short-lived connection for requesting addresses\n"
665 : " net Network the peer connected through (\"ipv4\", \"ipv6\", \"onion\", \"i2p\", \"cjdns\", or \"npr\" (not publicly routable))\n"
666 : " v Version of transport protocol used for the connection\n"
667 : " mping Minimum observed ping time, in milliseconds (ms)\n"
668 : " ping Last observed ping time, in milliseconds (ms)\n"
669 : " send Time since last message sent to the peer, in seconds\n"
670 : " recv Time since last message received from the peer, in seconds\n"
671 : " txn Time since last novel transaction received from the peer and accepted into our mempool, in minutes\n"
672 : " \"*\" - we do not relay transactions to this peer (relaytxes is false)\n"
673 : " blk Time since last novel block passing initial validity checks received from the peer, in minutes\n"
674 : " hb High-bandwidth BIP152 compact block relay\n"
675 : " \".\" (to) - we selected the peer as a high-bandwidth peer\n"
676 : " \"*\" (from) - the peer selected us as a high-bandwidth peer\n"
677 : " addrp Total number of addresses processed, excluding those dropped due to rate limiting\n"
678 : " \".\" - we do not relay addresses to this peer (addr_relay_enabled is false)\n"
679 : " addrl Total number of addresses dropped due to rate limiting\n"
680 : " age Duration of connection to the peer, in minutes\n"
681 : " asmap Mapped AS (Autonomous System) number in the BGP route to the peer, used for diversifying\n"
682 : " peer selection (only displayed if the -asmap config option is set)\n"
683 : " id Peer index, in increasing order of peer connections since node startup\n"
684 : " address IP address and port of the peer\n"
685 : " version Peer version and subversion concatenated, e.g. \"70016/Satoshi:21.0.0/\"\n\n"
686 : "* The peer counts table displays the number of peers for each reachable network as well as\n"
687 : " the number of block relay peers and manual peers.\n\n"
688 : "* The local addresses table lists each local address broadcast by the node, the port, and the score.\n\n"
689 : "Examples:\n\n"
690 : "Peer counts table of reachable networks and list of local addresses\n"
691 : "> dash-cli -netinfo\n\n"
692 : "The same, preceded by a peers listing without address and version columns\n"
693 : "> dash-cli -netinfo 1\n\n"
694 : "Full dashboard\n"
695 0 : + strprintf("> dash-cli -netinfo %d\n\n", MAX_DETAIL_LEVEL) +
696 : "Full live dashboard, adjust --interval or --no-title as needed (Linux)\n"
697 0 : + strprintf("> watch --interval 1 --no-title dash-cli -netinfo %d\n\n", MAX_DETAIL_LEVEL) +
698 : "See this help\n"
699 : "> dash-cli -netinfo help\n"};
700 : };
701 :
702 : /** Process RPC generatetoaddress request. */
703 : class GenerateToAddressRequestHandler : public BaseRequestHandler
704 : {
705 : public:
706 36 : UniValue PrepareRequest(const std::string& method, const std::vector<std::string>& args) override
707 : {
708 36 : address_str = args.at(1);
709 36 : UniValue params{RPCConvertValues("generatetoaddress", args)};
710 28 : return JSONRPCRequestObj("generatetoaddress", params, 1);
711 36 : }
712 :
713 28 : UniValue ProcessReply(const UniValue &reply) override
714 : {
715 28 : UniValue result(UniValue::VOBJ);
716 28 : result.pushKV("address", address_str);
717 28 : result.pushKV("blocks", reply.get_obj()["result"]);
718 28 : return JSONRPCReplyObj(result, NullUniValue, 1);
719 28 : }
720 : protected:
721 : std::string address_str;
722 : };
723 :
724 : /** Process default single requests */
725 : class DefaultRequestHandler: public BaseRequestHandler {
726 : public:
727 1205 : UniValue PrepareRequest(const std::string& method, const std::vector<std::string>& args) override
728 : {
729 1205 : UniValue params;
730 1205 : if(gArgs.GetBoolArg("-named", DEFAULT_NAMED)) {
731 96 : params = RPCConvertNamedValues(method, args);
732 96 : } else {
733 1109 : params = RPCConvertValues(method, args);
734 : }
735 1205 : return JSONRPCRequestObj(method, params, 1);
736 1205 : }
737 :
738 1162 : UniValue ProcessReply(const UniValue &reply) override
739 : {
740 1162 : return reply.get_obj();
741 : }
742 : };
743 :
744 1301 : static UniValue CallRPC(BaseRequestHandler* rh, const std::string& strMethod, const std::vector<std::string>& args, const std::optional<std::string>& rpcwallet = {})
745 : {
746 1301 : std::string host;
747 : // In preference order, we choose the following for the port:
748 : // 1. -rpcport
749 : // 2. port in -rpcconnect (ie following : in ipv4 or ]: in ipv6)
750 : // 3. default port for chain
751 1301 : uint16_t port{BaseParams().RPCPort()};
752 1301 : SplitHostPort(gArgs.GetArg("-rpcconnect", DEFAULT_RPCCONNECT), port, host);
753 1301 : port = static_cast<uint16_t>(gArgs.GetIntArg("-rpcport", port));
754 :
755 : // Obtain event base
756 1301 : raii_event_base base = obtain_event_base();
757 :
758 : // Synchronously look up hostname
759 1301 : raii_evhttp_connection evcon = obtain_evhttp_connection_base(base.get(), host, port);
760 :
761 : // Set connection timeout
762 : {
763 1301 : const int timeout = gArgs.GetIntArg("-rpcclienttimeout", DEFAULT_HTTP_CLIENT_TIMEOUT);
764 1301 : if (timeout > 0) {
765 1301 : evhttp_connection_set_timeout(evcon.get(), timeout);
766 1301 : } else {
767 : // Indefinite request timeouts are not possible in libevent-http, so we
768 : // set the timeout to a very long time period instead.
769 :
770 0 : constexpr int YEAR_IN_SECONDS = 31556952; // Average length of year in Gregorian calendar
771 0 : evhttp_connection_set_timeout(evcon.get(), 5 * YEAR_IN_SECONDS);
772 : }
773 : }
774 :
775 1301 : HTTPReply response;
776 1301 : raii_evhttp_request req = obtain_evhttp_request(http_request_done, (void*)&response);
777 1301 : if (req == nullptr) {
778 0 : throw std::runtime_error("create http request failed");
779 : }
780 :
781 1301 : evhttp_request_set_error_cb(req.get(), http_error_cb);
782 :
783 : // Get credentials
784 1301 : std::string strRPCUserColonPass;
785 1301 : bool failedToGetAuthCookie = false;
786 1301 : if (gArgs.GetArg("-rpcpassword", "") == "") {
787 : // Try fall back to cookie-based authentication if no password is provided
788 1285 : if (!GetAuthCookie(&strRPCUserColonPass)) {
789 28 : failedToGetAuthCookie = true;
790 28 : }
791 1285 : } else {
792 16 : strRPCUserColonPass = gArgs.GetArg("-rpcuser", "") + ":" + gArgs.GetArg("-rpcpassword", "");
793 : }
794 :
795 1301 : struct evkeyvalq* output_headers = evhttp_request_get_output_headers(req.get());
796 1301 : assert(output_headers);
797 1301 : evhttp_add_header(output_headers, "Host", host.c_str());
798 1301 : evhttp_add_header(output_headers, "Connection", "close");
799 1301 : evhttp_add_header(output_headers, "Content-Type", "application/json");
800 1301 : evhttp_add_header(output_headers, "Authorization", (std::string("Basic ") + EncodeBase64(strRPCUserColonPass)).c_str());
801 :
802 : // Attach request data
803 1301 : std::string strRequest = rh->PrepareRequest(strMethod, args).write() + "\n";
804 1289 : struct evbuffer* output_buffer = evhttp_request_get_output_buffer(req.get());
805 1289 : assert(output_buffer);
806 1289 : evbuffer_add(output_buffer, strRequest.data(), strRequest.size());
807 :
808 : // check if we should use a special wallet endpoint
809 1289 : std::string endpoint = "/";
810 1289 : if (rpcwallet) {
811 436 : char* encodedURI = evhttp_uriencode(rpcwallet->data(), rpcwallet->size(), false);
812 436 : if (encodedURI) {
813 436 : endpoint = "/wallet/" + std::string(encodedURI);
814 436 : free(encodedURI);
815 436 : } else {
816 0 : throw CConnectionFailed("uri-encode failed");
817 : }
818 436 : }
819 1289 : int r = evhttp_make_request(evcon.get(), req.get(), EVHTTP_REQ_POST, endpoint.c_str());
820 1289 : req.release(); // ownership moved to evcon in above call
821 1289 : if (r != 0) {
822 0 : throw CConnectionFailed("send http request failed");
823 : }
824 :
825 1289 : event_base_dispatch(base.get());
826 :
827 1289 : if (response.status == 0) {
828 28 : std::string responseErrorMessage;
829 28 : if (response.error != -1) {
830 0 : responseErrorMessage = strprintf(" (error code %d - \"%s\")", response.error, http_errorstring(response.error));
831 0 : }
832 28 : throw CConnectionFailed(strprintf("Could not connect to the server %s:%d%s\n\n"
833 : "Make sure the dashd server is running and that you are connecting to the correct RPC port.\n"
834 : "Use \"dash-cli -help\" for more info.",
835 : host, port, responseErrorMessage));
836 1289 : } else if (response.status == HTTP_UNAUTHORIZED) {
837 12 : if (failedToGetAuthCookie) {
838 8 : throw std::runtime_error(strprintf(
839 : "Could not locate RPC credentials. No authentication cookie could be found, and RPC password is not set. See -rpcpassword and -stdinrpcpass. Configuration file: (%s)",
840 4 : fs::PathToString(GetConfigFile(gArgs.GetPathArg("-conf", BITCOIN_CONF_FILENAME)))));
841 : } else {
842 8 : throw std::runtime_error("Authorization failed: Incorrect rpcuser or rpcpassword");
843 : }
844 1249 : } else if (response.status == HTTP_SERVICE_UNAVAILABLE) {
845 3 : throw std::runtime_error(strprintf("Server response: %s", response.body));
846 1246 : } else if (response.status >= 400 && response.status != HTTP_BAD_REQUEST && response.status != HTTP_NOT_FOUND && response.status != HTTP_INTERNAL_SERVER_ERROR)
847 0 : throw std::runtime_error(strprintf("server returned HTTP error %d", response.status));
848 1246 : else if (response.body.empty())
849 0 : throw std::runtime_error("no response from server");
850 :
851 : // Parse reply
852 1246 : UniValue valReply(UniValue::VSTR);
853 1246 : if (!valReply.read(response.body))
854 0 : throw std::runtime_error("couldn't parse reply from server");
855 1246 : UniValue reply = rh->ProcessReply(valReply);
856 1246 : if (reply.empty())
857 0 : throw std::runtime_error("expected reply to have result, error and id properties");
858 :
859 1246 : return reply;
860 1336 : }
861 :
862 : /**
863 : * ConnectAndCallRPC wraps CallRPC with -rpcwait and an exception handler.
864 : *
865 : * @param[in] rh Pointer to RequestHandler.
866 : * @param[in] strMethod Reference to const string method to forward to CallRPC.
867 : * @param[in] rpcwallet Reference to const optional string wallet name to forward to CallRPC.
868 : * @returns the RPC response as a UniValue object.
869 : * @throws a CConnectionFailed std::runtime_error if connection failed or RPC server still in warmup.
870 : */
871 1277 : static UniValue ConnectAndCallRPC(BaseRequestHandler* rh, const std::string& strMethod, const std::vector<std::string>& args, const std::optional<std::string>& rpcwallet = {})
872 : {
873 1312 : UniValue response(UniValue::VOBJ);
874 : // Execute and handle connection failures with -rpcwait.
875 1277 : const bool fWait = gArgs.GetBoolArg("-rpcwait", false);
876 1277 : const int timeout = gArgs.GetIntArg("-rpcwaittimeout", DEFAULT_WAIT_CLIENT_TIMEOUT);
877 1277 : const auto deadline{std::chrono::steady_clock::now() + 1s * timeout};
878 :
879 1277 : do {
880 : try {
881 1301 : response = CallRPC(rh, strMethod, args, rpcwallet);
882 1246 : if (fWait) {
883 8 : const UniValue& error = response.find_value("error");
884 8 : if (!error.isNull() && error["code"].getInt<int>() == RPC_IN_WARMUP) {
885 4 : throw CConnectionFailed("server in warmup");
886 : }
887 4 : }
888 1242 : break; // Connection succeeded, no need to retry.
889 59 : } catch (const CConnectionFailed& e) {
890 60 : if (fWait && (timeout <= 0 || std::chrono::steady_clock::now() < deadline)) {
891 24 : UninterruptibleSleep(1s);
892 24 : } else {
893 8 : throw CConnectionFailed(strprintf("timeout on transient error: %s", e.what()));
894 : }
895 32 : }
896 24 : } while (fWait);
897 1242 : return response;
898 1344 : }
899 :
900 : /** Parse UniValue result to update the message to print to std::cout. */
901 970 : static void ParseResult(const UniValue& result, std::string& strPrint)
902 : {
903 970 : if (result.isNull()) return;
904 900 : strPrint = result.isStr() ? result.get_str() : result.write(2);
905 970 : }
906 :
907 : /** Parse UniValue error to update the message to print to std::cerr and the code to return. */
908 164 : static void ParseError(const UniValue& error, std::string& strPrint, int& nRet)
909 : {
910 164 : if (error.isObject()) {
911 164 : const UniValue& err_code = error.find_value("code");
912 164 : const UniValue& err_msg = error.find_value("message");
913 164 : if (!err_code.isNull()) {
914 164 : strPrint = "error code: " + err_code.getValStr() + "\n";
915 164 : }
916 164 : if (err_msg.isStr()) {
917 164 : strPrint += ("error message:\n" + err_msg.get_str());
918 164 : }
919 164 : if (err_code.isNum() && err_code.getInt<int>() == RPC_WALLET_NOT_SPECIFIED) {
920 20 : strPrint += "\nTry adding \"-rpcwallet=<filename>\" option to dash-cli command line.";
921 20 : }
922 164 : } else {
923 0 : strPrint = "error: " + error.write();
924 : }
925 164 : nRet = abs(error["code"].getInt<int>());
926 164 : }
927 :
928 : /**
929 : * GetWalletBalances calls listwallets; if more than one wallet is loaded, it then
930 : * fetches mine.trusted balances for each loaded wallet and pushes them to `result`.
931 : *
932 : * @param result Reference to UniValue object the wallet names and balances are pushed to.
933 : */
934 32 : static void GetWalletBalances(UniValue& result)
935 : {
936 32 : DefaultRequestHandler rh;
937 32 : const UniValue listwallets = ConnectAndCallRPC(&rh, "listwallets", /* args=*/{});
938 32 : if (!listwallets.find_value("error").isNull()) return;
939 32 : const UniValue& wallets = listwallets.find_value("result");
940 32 : if (wallets.size() <= 1) return;
941 :
942 8 : UniValue balances(UniValue::VOBJ);
943 28 : for (const UniValue& wallet : wallets.getValues()) {
944 20 : const std::string& wallet_name = wallet.get_str();
945 20 : const UniValue getbalances = ConnectAndCallRPC(&rh, "getbalances", /* args=*/{}, wallet_name);
946 20 : const UniValue& balance = getbalances.find_value("result")["mine"]["trusted"];
947 20 : balances.pushKV(wallet_name, balance);
948 20 : }
949 8 : result.pushKV("balances", balances);
950 32 : }
951 :
952 : /**
953 : * GetProgressBar constructs a progress bar with 5% intervals.
954 : *
955 : * @param[in] progress The proportion of the progress bar to be filled between 0 and 1.
956 : * @param[out] progress_bar String representation of the progress bar.
957 : */
958 0 : static void GetProgressBar(double progress, std::string& progress_bar)
959 : {
960 0 : if (progress < 0 || progress > 1) return;
961 :
962 : static constexpr double INCREMENT{0.05};
963 : static const std::string COMPLETE_BAR{"\u2592"};
964 : static const std::string INCOMPLETE_BAR{"\u2591"};
965 :
966 0 : for (int i = 0; i < progress / INCREMENT; ++i) {
967 0 : progress_bar += COMPLETE_BAR;
968 0 : }
969 :
970 0 : for (int i = 0; i < (1 - progress) / INCREMENT; ++i) {
971 0 : progress_bar += INCOMPLETE_BAR;
972 0 : }
973 0 : }
974 :
975 : /**
976 : * ParseGetInfoResult takes in -getinfo result in UniValue object and parses it
977 : * into a user friendly UniValue string to be printed on the console.
978 : * @param[out] result Reference to UniValue result containing the -getinfo output.
979 : */
980 56 : static void ParseGetInfoResult(UniValue& result)
981 : {
982 56 : if (!result.find_value("error").isNull()) return;
983 :
984 56 : std::string RESET, GREEN, BLUE, YELLOW, MAGENTA, CYAN;
985 56 : bool should_colorize = false;
986 :
987 : #ifndef WIN32
988 56 : if (isatty(fileno(stdout))) {
989 : // By default, only print colored text if OS is not WIN32 and stdout is connected to a terminal.
990 0 : should_colorize = true;
991 0 : }
992 : #endif
993 :
994 56 : if (gArgs.IsArgSet("-color")) {
995 12 : const std::string color{gArgs.GetArg("-color", DEFAULT_COLOR_SETTING)};
996 12 : if (color == "always") {
997 4 : should_colorize = true;
998 12 : } else if (color == "never") {
999 4 : should_colorize = false;
1000 8 : } else if (color != "auto") {
1001 4 : throw std::runtime_error("Invalid value for -color option. Valid values: always, auto, never.");
1002 : }
1003 12 : }
1004 :
1005 52 : if (should_colorize) {
1006 4 : RESET = "\x1B[0m";
1007 4 : GREEN = "\x1B[32m";
1008 4 : BLUE = "\x1B[34m";
1009 4 : YELLOW = "\x1B[33m";
1010 4 : MAGENTA = "\x1B[35m";
1011 4 : CYAN = "\x1B[36m";
1012 4 : }
1013 :
1014 52 : std::string result_string = strprintf("%sChain: %s%s\n", BLUE, result["chain"].getValStr(), RESET);
1015 52 : result_string += strprintf("Blocks: %s\n", result["blocks"].getValStr());
1016 52 : result_string += strprintf("Headers: %s\n", result["headers"].getValStr());
1017 :
1018 52 : const double ibd_progress{result["verificationprogress"].get_real()};
1019 52 : std::string ibd_progress_bar;
1020 : // Display the progress bar only if IBD progress is less than 99%
1021 52 : if (ibd_progress < 0.99) {
1022 0 : GetProgressBar(ibd_progress, ibd_progress_bar);
1023 : // Add padding between progress bar and IBD progress
1024 0 : ibd_progress_bar += " ";
1025 0 : }
1026 :
1027 52 : result_string += strprintf("Verification progress: %s%.4f%%\n", ibd_progress_bar, ibd_progress * 100);
1028 52 : result_string += strprintf("Difficulty: %s\n\n", result["difficulty"].getValStr());
1029 :
1030 52 : result_string += strprintf(
1031 : "%sNetwork: in %s, out %s, total %s, mn_in %s, mn_out %s, mn_total %s%s\n",
1032 : GREEN,
1033 52 : result["connections"]["in"].getValStr(),
1034 52 : result["connections"]["out"].getValStr(),
1035 52 : result["connections"]["total"].getValStr(),
1036 52 : result["connections"]["mn_in"].getValStr(),
1037 52 : result["connections"]["mn_out"].getValStr(),
1038 52 : result["connections"]["mn_total"].getValStr(),
1039 : RESET);
1040 52 : result_string += strprintf("Version: %s\n", result["version"].getValStr());
1041 52 : result_string += strprintf("Time offset (s): %s\n", result["timeoffset"].getValStr());
1042 :
1043 : // proxies
1044 52 : std::map<std::string, std::vector<std::string>> proxy_networks;
1045 52 : std::vector<std::string> ordered_proxies;
1046 :
1047 312 : for (const UniValue& network : result["networks"].getValues()) {
1048 260 : const std::string proxy = network["proxy"].getValStr();
1049 260 : if (proxy.empty()) continue;
1050 : // Add proxy to ordered_proxy if has not been processed
1051 20 : if (proxy_networks.find(proxy) == proxy_networks.end()) ordered_proxies.push_back(proxy);
1052 :
1053 20 : proxy_networks[proxy].push_back(network["name"].getValStr());
1054 260 : }
1055 :
1056 52 : std::vector<std::string> formatted_proxies;
1057 60 : for (const std::string& proxy : ordered_proxies) {
1058 8 : formatted_proxies.emplace_back(strprintf("%s (%s)", proxy, Join(proxy_networks.find(proxy)->second, ", ")));
1059 : }
1060 52 : result_string += strprintf("Proxies: %s\n", formatted_proxies.empty() ? "n/a" : Join(formatted_proxies, ", "));
1061 :
1062 52 : result_string += strprintf("Min tx relay fee rate (%s/kB): %s\n\n", CURRENCY_UNIT, result["relayfee"].getValStr());
1063 :
1064 52 : if (!result["has_wallet"].isNull()) {
1065 28 : const std::string walletname = result["walletname"].getValStr();
1066 28 : result_string += strprintf("%sWallet: %s%s\n", MAGENTA, walletname.empty() ? "\"\"" : walletname, RESET);
1067 :
1068 28 : result_string += strprintf("%sCoinJoin balance:%s %s\n", CYAN, RESET, result["coinjoin_balance"].getValStr());
1069 :
1070 28 : result_string += strprintf("Keypool size: %s\n", result["keypoolsize"].getValStr());
1071 28 : if (!result["unlocked_until"].isNull()) {
1072 24 : result_string += strprintf("Unlocked until: %s\n", result["unlocked_until"].getValStr());
1073 24 : }
1074 28 : result_string += strprintf("Transaction fee rate (-paytxfee) (%s/kB): %s\n\n", CURRENCY_UNIT, result["paytxfee"].getValStr());
1075 28 : }
1076 52 : if (!result["balance"].isNull()) {
1077 28 : result_string += strprintf("%sBalance:%s %s\n\n", CYAN, RESET, result["balance"].getValStr());
1078 28 : }
1079 :
1080 52 : if (!result["balances"].isNull()) {
1081 8 : result_string += strprintf("%sBalances%s\n", CYAN, RESET);
1082 :
1083 8 : size_t max_balance_length{10};
1084 :
1085 28 : for (const std::string& wallet : result["balances"].getKeys()) {
1086 20 : max_balance_length = std::max(result["balances"][wallet].getValStr().length(), max_balance_length);
1087 : }
1088 :
1089 28 : for (const std::string& wallet : result["balances"].getKeys()) {
1090 20 : result_string += strprintf("%*s %s\n",
1091 : max_balance_length,
1092 20 : result["balances"][wallet].getValStr(),
1093 20 : wallet.empty() ? "\"\"" : wallet);
1094 : }
1095 8 : result_string += "\n";
1096 8 : }
1097 :
1098 52 : const std::string warnings{result["warnings"].getValStr()};
1099 52 : result_string += strprintf("%sWarnings:%s %s", YELLOW, RESET, warnings.empty() ? "(none)" : warnings);
1100 :
1101 52 : result.setStr(result_string);
1102 60 : }
1103 :
1104 : /**
1105 : * Call RPC getnewaddress.
1106 : * @returns getnewaddress response as a UniValue object.
1107 : */
1108 84 : static UniValue GetNewAddress()
1109 : {
1110 84 : std::optional<std::string> wallet_name{};
1111 84 : if (gArgs.IsArgSet("-rpcwallet")) wallet_name = gArgs.GetArg("-rpcwallet", "");
1112 84 : DefaultRequestHandler rh;
1113 84 : return ConnectAndCallRPC(&rh, "getnewaddress", /* args=*/{}, wallet_name);
1114 84 : }
1115 :
1116 : /**
1117 : * Check bounds and set up args for RPC generatetoaddress params: nblocks, address, maxtries.
1118 : * @param[in] address Reference to const string address to insert into the args.
1119 : * @param args Reference to vector of string args to modify.
1120 : */
1121 52 : static void SetGenerateToAddressArgs(const std::string& address, std::vector<std::string>& args)
1122 : {
1123 60 : if (args.size() > 2) throw std::runtime_error("too many arguments (maximum 2 for nblocks and maxtries)");
1124 44 : if (args.size() == 0) {
1125 12 : args.emplace_back(DEFAULT_NBLOCKS);
1126 44 : } else if (args.at(0) == "0") {
1127 8 : throw std::runtime_error("the first argument (number of blocks to generate, default: " + DEFAULT_NBLOCKS + ") must be an integer value greater than zero");
1128 : }
1129 36 : args.emplace(args.begin() + 1, address);
1130 44 : }
1131 :
1132 1189 : static int CommandLineRPC(int argc, char *argv[])
1133 : {
1134 1189 : std::string strPrint;
1135 1189 : int nRet = 0;
1136 : try {
1137 : // Skip switches
1138 4283 : while (argc > 1 && IsSwitchChar(argv[1][0])) {
1139 1905 : argc--;
1140 1905 : argv++;
1141 : }
1142 1189 : std::string rpcPass;
1143 1189 : if (gArgs.GetBoolArg("-stdinrpcpass", false)) {
1144 16 : NO_STDIN_ECHO();
1145 16 : if (!StdinReady()) {
1146 0 : fputs("RPC password> ", stderr);
1147 0 : fflush(stderr);
1148 0 : }
1149 16 : if (!std::getline(std::cin, rpcPass)) {
1150 0 : throw std::runtime_error("-stdinrpcpass specified but failed to read from standard input");
1151 : }
1152 16 : if (StdinTerminal()) {
1153 0 : fputc('\n', stdout);
1154 0 : }
1155 16 : gArgs.ForceSetArg("-rpcpassword", rpcPass);
1156 16 : }
1157 1189 : std::vector<std::string> args = std::vector<std::string>(&argv[1], &argv[argc]);
1158 1189 : if (gArgs.GetBoolArg("-stdinwalletpassphrase", false)) {
1159 0 : NO_STDIN_ECHO();
1160 0 : std::string walletPass;
1161 0 : if (args.size() < 1 || args[0].substr(0, 16) != "walletpassphrase") {
1162 0 : throw std::runtime_error("-stdinwalletpassphrase is only applicable for walletpassphrase(change)");
1163 : }
1164 0 : if (!StdinReady()) {
1165 0 : fputs("Wallet passphrase> ", stderr);
1166 0 : fflush(stderr);
1167 0 : }
1168 0 : if (!std::getline(std::cin, walletPass)) {
1169 0 : throw std::runtime_error("-stdinwalletpassphrase specified but failed to read from standard input");
1170 : }
1171 0 : if (StdinTerminal()) {
1172 0 : fputc('\n', stdout);
1173 0 : }
1174 0 : args.insert(args.begin() + 1, walletPass);
1175 0 : }
1176 1189 : if (gArgs.GetBoolArg("-stdin", false)) {
1177 : // Read one arg per line from stdin and append
1178 8 : std::string line;
1179 16 : while (std::getline(std::cin, line)) {
1180 8 : args.push_back(line);
1181 : }
1182 8 : if (StdinTerminal()) {
1183 0 : fputc('\n', stdout);
1184 0 : }
1185 8 : }
1186 1189 : std::unique_ptr<BaseRequestHandler> rh;
1187 1189 : std::string method;
1188 1189 : if (gArgs.IsArgSet("-getinfo")) {
1189 60 : rh.reset(new GetinfoRequestHandler());
1190 1189 : } else if (gArgs.GetBoolArg("-netinfo", false)) {
1191 0 : if (!args.empty() && args.at(0) == "help") {
1192 0 : tfm::format(std::cout, "%s\n", NetinfoRequestHandler().m_help_doc);
1193 0 : return 0;
1194 : }
1195 0 : rh.reset(new NetinfoRequestHandler());
1196 1129 : } else if (gArgs.GetBoolArg("-generate", false)) {
1197 84 : const UniValue getnewaddress{GetNewAddress()};
1198 84 : const UniValue& error{getnewaddress.find_value("error")};
1199 84 : if (error.isNull()) {
1200 52 : SetGenerateToAddressArgs(getnewaddress.find_value("result").get_str(), args);
1201 36 : rh.reset(new GenerateToAddressRequestHandler());
1202 36 : } else {
1203 32 : ParseError(error, strPrint, nRet);
1204 : }
1205 1129 : } else if (gArgs.GetBoolArg("-addrinfo", false)) {
1206 0 : rh.reset(new AddrinfoRequestHandler());
1207 0 : } else {
1208 1045 : rh.reset(new DefaultRequestHandler());
1209 1045 : if (args.size() < 1) {
1210 0 : throw std::runtime_error("too few parameters (need at least command)");
1211 : }
1212 1045 : method = args[0];
1213 1045 : args.erase(args.begin()); // Remove trailing method name from arguments vector
1214 : }
1215 1173 : if (nRet == 0) {
1216 : // Perform RPC call
1217 1141 : std::optional<std::string> wallet_name{};
1218 1141 : if (gArgs.IsArgSet("-rpcwallet")) wallet_name = gArgs.GetArg("-rpcwallet", "");
1219 1141 : const UniValue reply = ConnectAndCallRPC(rh.get(), method, args, wallet_name);
1220 :
1221 : // Parse reply
1222 1106 : UniValue result = reply.find_value("result");
1223 1106 : const UniValue& error = reply.find_value("error");
1224 1106 : if (error.isNull()) {
1225 974 : if (gArgs.GetBoolArg("-getinfo", false)) {
1226 56 : if (!gArgs.IsArgSet("-rpcwallet")) {
1227 32 : GetWalletBalances(result); // fetch multiwallet balances and append to result
1228 32 : }
1229 56 : ParseGetInfoResult(result);
1230 52 : }
1231 :
1232 970 : ParseResult(result, strPrint);
1233 970 : } else {
1234 132 : ParseError(error, strPrint, nRet);
1235 : }
1236 1141 : }
1237 1189 : } catch (const std::exception& e) {
1238 55 : strPrint = std::string("error: ") + e.what();
1239 55 : nRet = EXIT_FAILURE;
1240 55 : } catch (...) {
1241 0 : PrintExceptionContinue(std::current_exception(), "CommandLineRPC()");
1242 0 : throw;
1243 55 : }
1244 :
1245 1189 : if (strPrint != "") {
1246 1119 : tfm::format(nRet == 0 ? std::cout : std::cerr, "%s\n", strPrint);
1247 1119 : }
1248 1189 : return nRet;
1249 1244 : }
1250 :
1251 1193 : MAIN_FUNCTION
1252 : {
1253 1193 : RegisterPrettyTerminateHander();
1254 1193 : RegisterPrettySignalHandlers();
1255 :
1256 : #ifdef WIN32
1257 : util::WinCmdLineArgs winArgs;
1258 : std::tie(argc, argv) = winArgs.get();
1259 : #endif
1260 1193 : SetupEnvironment();
1261 1193 : if (!SetupNetworking()) {
1262 0 : tfm::format(std::cerr, "Error: Initializing networking failed\n");
1263 0 : return EXIT_FAILURE;
1264 : }
1265 1193 : event_set_log_callback(&libevent_log_cb);
1266 :
1267 : try {
1268 1193 : int ret = AppInitRPC(argc, argv);
1269 1193 : if (ret != CONTINUE_EXECUTION)
1270 4 : return ret;
1271 1189 : } catch (...) {
1272 0 : PrintExceptionContinue(std::current_exception(), "AppInitRPC()");
1273 0 : return EXIT_FAILURE;
1274 0 : }
1275 :
1276 1189 : int ret = EXIT_FAILURE;
1277 : try {
1278 1189 : ret = CommandLineRPC(argc, argv);
1279 1189 : } catch (...) {
1280 0 : PrintExceptionContinue(std::current_exception(), "CommandLineRPC()");
1281 0 : }
1282 1189 : return ret;
1283 1193 : }
|