limenka-cli.cpp raw

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