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