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