LCOV - code coverage report
Current view: top level - src - bitcoin-cli.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 443 765 57.9 %
Date: 2026-06-25 07:23:43 Functions: 45 80 56.2 %

          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 : }

Generated by: LCOV version 1.16