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