bitcoin-cli.cpp raw

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