Skip to content

Commit 05aebe3

Browse files
committed
Merge bitcoin/bitcoin#30930: netinfo: add peer services column and outbound-only option
87532fe netinfo: allow setting an outbound-only peer list (Jon Atack) 681ebcc netinfo: rename and hoist max level constant to use in top-level help (Jon Atack) e7d307c netinfo: clarify relaytxes and addr_relay_enabled help docs (Jon Atack) eef2a9d netinfo: add peer services column (Jon Atack) Pull request description: Been using this since May 2023. - add a peer services column (considered displaying the p2p_v2 flag as "p" or "2"; proposing "2" here for continuity with the "v" column, but "p" is fine for me as well) - clarify in the help that "relaytxes" and "addr_relay_enabled" are from getpeerinfo - hoist (and rename) the max level constant to use in top-level help, to avoid overlooking to update the top-level help if the value of the constant changes (as caught by Larry Ruane in review below) - add an optional "outonly" (or "o") argument for an outbound-only peer list, as suggested by Vasil Dimov in his review below. Several people have requested this, to keep the output within screen limits when running netinfo as a live dashboard (i.e. with `watch`) on a node with many peers. While doing this, also permit passing "h" for the help in addition to "help". ACKs for top commit: achow101: ACK 87532fe rkrux: tACK 87532fe tdb3: cr re ACK 87532fe brunoerg: crACK 87532fe Tree-SHA512: 35b1b0de28dfecaad58bf5af194757a5e0f563553cf69ea4d76f2e1963f8d662717254df2549114c7bba4a041bf5282d5cb3fba8d436b2807f2a00560787d64c
2 parents 6463117 + 87532fe commit 05aebe3

File tree

1 file changed

+61
-21
lines changed

1 file changed

+61
-21
lines changed

src/bitcoin-cli.cpp

Lines changed: 61 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ static const int DEFAULT_HTTP_CLIENT_TIMEOUT=900;
5757
static constexpr int DEFAULT_WAIT_CLIENT_TIMEOUT = 0;
5858
static const bool DEFAULT_NAMED=false;
5959
static const int CONTINUE_EXECUTION=-1;
60+
static constexpr uint8_t NETINFO_MAX_LEVEL{4};
6061
static constexpr int8_t UNKNOWN_NETWORK{-1};
6162
// See GetNetworkName() in netbase.cpp
6263
static constexpr std::array NETWORKS{"not_publicly_routable", "ipv4", "ipv6", "onion", "i2p", "cjdns", "internal"};
@@ -90,7 +91,7 @@ static void SetupCliArgs(ArgsManager& argsman)
9091
ArgsManager::ALLOW_ANY, OptionsCategory::CLI_COMMANDS);
9192
argsman.AddArg("-addrinfo", "Get the number of addresses known to the node, per network and total, after filtering for quality and recency. The total number of addresses known to the node may be higher.", ArgsManager::ALLOW_ANY, OptionsCategory::CLI_COMMANDS);
9293
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);
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::CLI_COMMANDS);
94+
argsman.AddArg("-netinfo", strprintf("Get network peer connection information from the remote server. An optional argument from 0 to %d can be passed for different peers listings (default: 0). If a non-zero value is passed, an additional \"outonly\" (or \"o\") argument can be passed to see outbound peers only. Pass \"help\" (or \"h\") for detailed help documentation.", NETINFO_MAX_LEVEL), ArgsManager::ALLOW_ANY, OptionsCategory::CLI_COMMANDS);
9495

9596
SetupChainParamsBaseOptions(argsman);
9697
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);
@@ -379,7 +380,6 @@ class GetinfoRequestHandler: public BaseRequestHandler
379380
class NetinfoRequestHandler : public BaseRequestHandler
380381
{
381382
private:
382-
static constexpr uint8_t MAX_DETAIL_LEVEL{4};
383383
std::array<std::array<uint16_t, NETWORKS.size() + 1>, 3> m_counts{{{}}}; //!< Peer counts by (in/out/total, networks/total)
384384
uint8_t m_block_relay_peers_count{0};
385385
uint8_t m_manual_peers_count{0};
@@ -394,18 +394,21 @@ class NetinfoRequestHandler : public BaseRequestHandler
394394
bool DetailsRequested() const { return m_details_level > 0 && m_details_level < 5; }
395395
bool IsAddressSelected() const { return m_details_level == 2 || m_details_level == 4; }
396396
bool IsVersionSelected() const { return m_details_level == 3 || m_details_level == 4; }
397+
bool m_outbound_only_selected{false};
397398
bool m_is_asmap_on{false};
398399
size_t m_max_addr_length{0};
399400
size_t m_max_addr_processed_length{5};
400401
size_t m_max_addr_rate_limited_length{6};
401402
size_t m_max_age_length{5};
402403
size_t m_max_id_length{2};
404+
size_t m_max_services_length{6};
403405
struct Peer {
404406
std::string addr;
405407
std::string sub_version;
406408
std::string conn_type;
407409
std::string network;
408410
std::string age;
411+
std::string services;
409412
std::string transport_protocol_type;
410413
double min_ping;
411414
double ping;
@@ -456,6 +459,15 @@ class NetinfoRequestHandler : public BaseRequestHandler
456459
if (conn_type == "addr-fetch") return "addr";
457460
return "";
458461
}
462+
std::string FormatServices(const UniValue& services)
463+
{
464+
std::string str;
465+
for (size_t i = 0; i < services.size(); ++i) {
466+
const std::string s{services[i].get_str()};
467+
str += s == "NETWORK_LIMITED" ? 'l' : s == "P2P_V2" ? '2' : ToLower(s[0]);
468+
}
469+
return str;
470+
}
459471

460472
public:
461473
static constexpr int ID_PEERINFO = 0;
@@ -466,9 +478,18 @@ class NetinfoRequestHandler : public BaseRequestHandler
466478
if (!args.empty()) {
467479
uint8_t n{0};
468480
if (ParseUInt8(args.at(0), &n)) {
469-
m_details_level = std::min(n, MAX_DETAIL_LEVEL);
481+
m_details_level = std::min(n, NETINFO_MAX_LEVEL);
470482
} else {
471-
throw std::runtime_error(strprintf("invalid -netinfo argument: %s\nFor more information, run: bitcoin-cli -netinfo help", args.at(0)));
483+
throw std::runtime_error(strprintf("invalid -netinfo level argument: %s\nFor more information, run: bitcoin-cli -netinfo help", args.at(0)));
484+
}
485+
if (args.size() > 1) {
486+
if (std::string_view s{args.at(1)}; n && (s == "o" || s == "outonly")) {
487+
m_outbound_only_selected = true;
488+
} else if (n) {
489+
throw std::runtime_error(strprintf("invalid -netinfo outonly argument: %s\nFor more information, run: bitcoin-cli -netinfo help", s));
490+
} else {
491+
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));
492+
}
472493
}
473494
}
474495
UniValue result(UniValue::VARR);
@@ -503,6 +524,7 @@ class NetinfoRequestHandler : public BaseRequestHandler
503524
++m_counts.at(2).at(NETWORKS.size()); // total overall
504525
if (conn_type == "block-relay-only") ++m_block_relay_peers_count;
505526
if (conn_type == "manual") ++m_manual_peers_count;
527+
if (m_outbound_only_selected && !is_outbound) continue;
506528
if (DetailsRequested()) {
507529
// Push data for this peer to the peers vector.
508530
const int peer_id{peer["id"].getInt<int>()};
@@ -519,17 +541,19 @@ class NetinfoRequestHandler : public BaseRequestHandler
519541
const double ping{peer["pingtime"].isNull() ? -1 : peer["pingtime"].get_real()};
520542
const std::string addr{peer["addr"].get_str()};
521543
const std::string age{conn_time == 0 ? "" : ToString((time_now - conn_time) / 60)};
544+
const std::string services{FormatServices(peer["servicesnames"])};
522545
const std::string sub_version{peer["subver"].get_str()};
523546
const std::string transport{peer["transport_protocol_type"].isNull() ? "v1" : peer["transport_protocol_type"].get_str()};
524547
const bool is_addr_relay_enabled{peer["addr_relay_enabled"].isNull() ? false : peer["addr_relay_enabled"].get_bool()};
525548
const bool is_bip152_hb_from{peer["bip152_hb_from"].get_bool()};
526549
const bool is_bip152_hb_to{peer["bip152_hb_to"].get_bool()};
527-
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});
550+
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});
528551
m_max_addr_length = std::max(addr.length() + 1, m_max_addr_length);
529552
m_max_addr_processed_length = std::max(ToString(addr_processed).length(), m_max_addr_processed_length);
530553
m_max_addr_rate_limited_length = std::max(ToString(addr_rate_limited).length(), m_max_addr_rate_limited_length);
531554
m_max_age_length = std::max(age.length(), m_max_age_length);
532555
m_max_id_length = std::max(ToString(peer_id).length(), m_max_id_length);
556+
m_max_services_length = std::max(services.length(), m_max_services_length);
533557
m_is_asmap_on |= (mapped_as != 0);
534558
}
535559
}
@@ -540,7 +564,8 @@ class NetinfoRequestHandler : public BaseRequestHandler
540564
// Report detailed peer connections list sorted by direction and minimum ping time.
541565
if (DetailsRequested() && !m_peers.empty()) {
542566
std::sort(m_peers.begin(), m_peers.end());
543-
result += strprintf("<-> type net v mping ping send recv txn blk hb %*s%*s%*s ",
567+
result += strprintf("<-> type net %*s v mping ping send recv txn blk hb %*s%*s%*s ",
568+
m_max_services_length, "serv",
544569
m_max_addr_processed_length, "addrp",
545570
m_max_addr_rate_limited_length, "addrl",
546571
m_max_age_length, "age");
@@ -549,10 +574,12 @@ class NetinfoRequestHandler : public BaseRequestHandler
549574
for (const Peer& peer : m_peers) {
550575
std::string version{ToString(peer.version) + peer.sub_version};
551576
result += strprintf(
552-
"%3s %6s %5s %2s%7s%7s%5s%5s%5s%5s %2s %*s%*s%*s%*i %*s %-*s%s\n",
577+
"%3s %6s %5s %*s %2s%7s%7s%5s%5s%5s%5s %2s %*s%*s%*s%*i %*s %-*s%s\n",
553578
peer.is_outbound ? "out" : "in",
554579
ConnectionTypeForNetinfo(peer.conn_type),
555580
peer.network,
581+
m_max_services_length, // variable spacing
582+
peer.services,
556583
(peer.transport_protocol_type.size() == 2 && peer.transport_protocol_type[0] == 'v') ? peer.transport_protocol_type[1] : ' ',
557584
PingTimeToString(peer.min_ping),
558585
PingTimeToString(peer.ping),
@@ -575,7 +602,7 @@ class NetinfoRequestHandler : public BaseRequestHandler
575602
IsAddressSelected() ? peer.addr : "",
576603
IsVersionSelected() && version != "0" ? version : "");
577604
}
578-
result += strprintf(" ms ms sec sec min min %*s\n\n", m_max_age_length, "min");
605+
result += strprintf(" %*s ms ms sec sec min min %*s\n\n", m_max_services_length, "", m_max_age_length, "min");
579606
}
580607

581608
// Report peer connection totals by type.
@@ -632,25 +659,28 @@ class NetinfoRequestHandler : public BaseRequestHandler
632659
}
633660

634661
const std::string m_help_doc{
635-
"-netinfo level|\"help\" \n\n"
662+
"-netinfo (level [outonly]) | help\n\n"
636663
"Returns a network peer connections dashboard with information from the remote server.\n"
637664
"This human-readable interface will change regularly and is not intended to be a stable API.\n"
638665
"Under the hood, -netinfo fetches the data by calling getpeerinfo and getnetworkinfo.\n"
639-
+ 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) +
640-
"Pass \"help\" to see this detailed help documentation.\n"
641-
"If more than one argument is passed, only the first one is read and parsed.\n"
642-
"Suggestion: use with the Linux watch(1) command for a live dashboard; see example below.\n\n"
666+
+ 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) +
667+
"If that argument is passed, an optional additional \"outonly\" argument may be passed to obtain the listing with outbound peers only.\n"
668+
"Pass \"help\" or \"h\" to see this detailed help documentation.\n"
669+
"If more than two arguments are passed, only the first two are read and parsed.\n"
670+
"Suggestion: use -netinfo with the Linux watch(1) command for a live dashboard; see example below.\n\n"
643671
"Arguments:\n"
644-
+ strprintf("1. level (integer 0-%d, optional) Specify the info level of the peers dashboard (default 0):\n", MAX_DETAIL_LEVEL) +
672+
+ strprintf("1. level (integer 0-%d, optional) Specify the info level of the peers dashboard (default 0):\n", NETINFO_MAX_LEVEL) +
645673
" 0 - Peer counts for each reachable network as well as for block relay peers\n"
646674
" and manual peers, and the list of local addresses and ports\n"
647675
" 1 - Like 0 but preceded by a peers listing (without address and version columns)\n"
648676
" 2 - Like 1 but with an address column\n"
649677
" 3 - Like 1 but with a version column\n"
650678
" 4 - Like 1 but with both address and version columns\n"
651-
"2. help (string \"help\", optional) Print this help documentation instead of the dashboard.\n\n"
679+
"2. outonly (\"outonly\" or \"o\", optional) Return the peers listing with outbound peers only, i.e. to save screen space\n"
680+
" when a node has many inbound peers. Only valid if a level is passed.\n\n"
681+
"help (\"help\" or \"h\", optional) Print this help documentation instead of the dashboard.\n\n"
652682
"Result:\n\n"
653-
+ 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) +
683+
+ 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) +
654684
" Column Description\n"
655685
" ------ -----------\n"
656686
" <-> Direction\n"
@@ -663,19 +693,27 @@ class NetinfoRequestHandler : public BaseRequestHandler
663693
" \"feeler\" - short-lived connection for testing addresses\n"
664694
" \"addr\" - address fetch; short-lived connection for requesting addresses\n"
665695
" net Network the peer connected through (\"ipv4\", \"ipv6\", \"onion\", \"i2p\", \"cjdns\", or \"npr\" (not publicly routable))\n"
696+
" serv Services offered by the peer\n"
697+
" \"n\" - NETWORK: peer can serve the full block chain\n"
698+
" \"b\" - BLOOM: peer can handle bloom-filtered connections (see BIP 111)\n"
699+
" \"w\" - WITNESS: peer can be asked for blocks and transactions with witness data (SegWit)\n"
700+
" \"c\" - COMPACT_FILTERS: peer can handle basic block filter requests (see BIPs 157 and 158)\n"
701+
" \"l\" - NETWORK_LIMITED: peer limited to serving only the last 288 blocks (~2 days)\n"
702+
" \"2\" - P2P_V2: peer supports version 2 P2P transport protocol, as defined in BIP 324\n"
703+
" \"u\" - UNKNOWN: unrecognized bit flag\n"
666704
" v Version of transport protocol used for the connection\n"
667705
" mping Minimum observed ping time, in milliseconds (ms)\n"
668706
" ping Last observed ping time, in milliseconds (ms)\n"
669707
" send Time since last message sent to the peer, in seconds\n"
670708
" recv Time since last message received from the peer, in seconds\n"
671709
" txn Time since last novel transaction received from the peer and accepted into our mempool, in minutes\n"
672-
" \"*\" - we do not relay transactions to this peer (relaytxes is false)\n"
710+
" \"*\" - we do not relay transactions to this peer (getpeerinfo \"relaytxes\" is false)\n"
673711
" blk Time since last novel block passing initial validity checks received from the peer, in minutes\n"
674712
" hb High-bandwidth BIP152 compact block relay\n"
675713
" \".\" (to) - we selected the peer as a high-bandwidth peer\n"
676714
" \"*\" (from) - the peer selected us as a high-bandwidth peer\n"
677715
" addrp Total number of addresses processed, excluding those dropped due to rate limiting\n"
678-
" \".\" - we do not relay addresses to this peer (addr_relay_enabled is false)\n"
716+
" \".\" - we do not relay addresses to this peer (getpeerinfo \"addr_relay_enabled\" is false)\n"
679717
" addrl Total number of addresses dropped due to rate limiting\n"
680718
" age Duration of connection to the peer, in minutes\n"
681719
" asmap Mapped AS (Autonomous System) number at the end of the BGP route to the peer, used for diversifying\n"
@@ -692,9 +730,11 @@ class NetinfoRequestHandler : public BaseRequestHandler
692730
"The same, preceded by a peers listing without address and version columns\n"
693731
"> bitcoin-cli -netinfo 1\n\n"
694732
"Full dashboard\n"
695-
+ strprintf("> bitcoin-cli -netinfo %d\n\n", MAX_DETAIL_LEVEL) +
733+
+ strprintf("> bitcoin-cli -netinfo %d\n\n", NETINFO_MAX_LEVEL) +
734+
"Full dashboard, but with outbound peers only\n"
735+
+ strprintf("> bitcoin-cli -netinfo %d outonly\n\n", NETINFO_MAX_LEVEL) +
696736
"Full live dashboard, adjust --interval or --no-title as needed (Linux)\n"
697-
+ strprintf("> watch --interval 1 --no-title bitcoin-cli -netinfo %d\n\n", MAX_DETAIL_LEVEL) +
737+
+ strprintf("> watch --interval 1 --no-title bitcoin-cli -netinfo %d\n\n", NETINFO_MAX_LEVEL) +
698738
"See this help\n"
699739
"> bitcoin-cli -netinfo help\n"};
700740
};
@@ -1219,7 +1259,7 @@ static int CommandLineRPC(int argc, char *argv[])
12191259
if (gArgs.IsArgSet("-getinfo")) {
12201260
rh.reset(new GetinfoRequestHandler());
12211261
} else if (gArgs.GetBoolArg("-netinfo", false)) {
1222-
if (!args.empty() && args.at(0) == "help") {
1262+
if (!args.empty() && (args.at(0) == "h" || args.at(0) == "help")) {
12231263
tfm::format(std::cout, "%s\n", NetinfoRequestHandler().m_help_doc);
12241264
return 0;
12251265
}

0 commit comments

Comments
 (0)