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