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