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