Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
501} else {502throw 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}506UniValue result(UniValue::VARR);507result.push_back(JSONRPCRequestObj("getpeerinfo", NullUniValue, ID_PEERINFO));508result.push_back(JSONRPCRequestObj("getnetworkinfo", NullUniValue, ID_NETWORKINFO));509return result;510}512UniValue ProcessReply(const UniValue& batch_in) override513{514const std::vector<UniValue> batch{JSONRPCProcessBatchReply(batch_in)};515if (!batch[ID_PEERINFO]["error"].isNull()) return batch[ID_PEERINFO];516if (!batch[ID_NETWORKINFO]["error"].isNull()) return batch[ID_NETWORKINFO];518const UniValue& networkinfo{batch[ID_NETWORKINFO]["result"]};519if (networkinfo["version"].getInt<int>() < 209900) {520throw std::runtime_error("-netinfo requires bitcoind server to be running v0.21.0 and up");521}522const int64_t time_now{TicksSinceEpoch<std::chrono::seconds>(CliClock::now())};524// Count peer connection totals, and if DetailsRequested(), store peer data in a vector of structs.525for (const UniValue& peer : batch[ID_PEERINFO]["result"].getValues()) {526const std::string network{peer["network"].get_str()};527const int8_t network_id{NetworkStringToId(network)};528if (network_id == UNKNOWN_NETWORK) continue;529const bool is_outbound{!peer["inbound"].get_bool()};530const bool is_tx_relay{peer["relaytxes"].isNull() ? true : peer["relaytxes"].get_bool()};531const std::string conn_type{peer["connection_type"].get_str()};532++m_counts.at(is_outbound).at(network_id); // in/out by network533++m_counts.at(is_outbound).at(NETWORKS.size()); // in/out overall534++m_counts.at(2).at(network_id); // total by network535++m_counts.at(2).at(NETWORKS.size()); // total overall536if (conn_type == "block-relay-only") ++m_block_relay_peers_count;537if (conn_type == "manual") ++m_manual_peers_count;538if (m_outbound_only_selected && !is_outbound) continue;539if (DetailsRequested()) {540// Push data for this peer to the peers vector.541const int peer_id{peer["id"].getInt<int>()};542const int mapped_as{peer["mapped_as"].isNull() ? 0 : peer["mapped_as"].getInt<int>()};543const int version{peer["version"].getInt<int>()};544const int64_t addr_processed{peer["addr_processed"].isNull() ? 0 : peer["addr_processed"].getInt<int64_t>()};545const int64_t addr_rate_limited{peer["addr_rate_limited"].isNull() ? 0 : peer["addr_rate_limited"].getInt<int64_t>()};546const int64_t conn_time{peer["conntime"].getInt<int64_t>()};547const int64_t last_blck{peer["last_block"].getInt<int64_t>()};548const int64_t last_recv{peer["lastrecv"].getInt<int64_t>()};549const int64_t last_send{peer["lastsend"].getInt<int64_t>()};550const int64_t last_trxn{peer["last_transaction"].getInt<int64_t>()};551const double min_ping{peer["minping"].isNull() ? -1 : peer["minping"].get_real()};552const double ping{peer["pingtime"].isNull() ? -1 : peer["pingtime"].get_real()};553const std::string addr{peer["addr"].get_str()};554const std::string age{conn_time == 0 ? "" : ToString((time_now - conn_time) / 60)};555const std::string services{FormatServices(peer["servicesnames"])};556const std::string sub_version{peer["subver"].get_str()};557const std::string transport{peer["transport_protocol_type"].isNull() ? "v1" : peer["transport_protocol_type"].get_str()};558const bool is_addr_relay_enabled{peer["addr_relay_enabled"].isNull() ? false : peer["addr_relay_enabled"].get_bool()};559const bool is_bip152_hb_from{peer["bip152_hb_from"].get_bool()};560const bool is_bip152_hb_to{peer["bip152_hb_to"].get_bool()};561m_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});562m_max_addr_length = std::max(addr.length() + 1, m_max_addr_length);563m_max_addr_processed_length = std::max(ToString(addr_processed).length(), m_max_addr_processed_length);564m_max_addr_rate_limited_length = std::max(ToString(addr_rate_limited).length(), m_max_addr_rate_limited_length);565m_max_age_length = std::max(age.length(), m_max_age_length);566m_max_id_length = std::max(ToString(peer_id).length(), m_max_id_length);567m_max_services_length = std::max(services.length(), m_max_services_length);568m_is_asmap_on |= (mapped_as != 0);569}570}572// Generate report header.573const std::string services{DetailsRequested() ? strprintf(" - services %s", FormatServices(networkinfo["localservicesnames"])) : ""};574std::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)};576// Report detailed peer connections list sorted by direction and minimum ping time.577if (DetailsRequested() && !m_peers.empty()) {578std::sort(m_peers.begin(), m_peers.end());579result += strprintf("<-> type net %*s v mping ping send recv txn blk hb %*s%*s%*s ",580m_max_services_length, "serv",581m_max_addr_processed_length, "addrp",582m_max_addr_rate_limited_length, "addrl",583m_max_age_length, "age");584if (m_is_asmap_on) result += " asmap ";585result += strprintf("%*s %-*s%s\n", m_max_id_length, "id", IsAddressSelected() ? m_max_addr_length : 0, IsAddressSelected() ? "address" : "", IsVersionSelected() ? "version" : "");586for (const Peer& peer : m_peers) {587std::string version{ToString(peer.version) + peer.sub_version};588result += strprintf(589"%3s %6s %5s %*s %2s%7s%7s%5s%5s%5s%5s %2s %*s%*s%*s%*i %*s %-*s%s\n",590peer.is_outbound ? "out" : "in",591ConnectionTypeForNetinfo(peer.conn_type),592peer.network,593m_max_services_length, // variable spacing594peer.services,595(peer.transport_protocol_type.size() == 2 && peer.transport_protocol_type[0] == 'v') ? peer.transport_protocol_type[1] : ' ',596PingTimeToString(peer.min_ping),597PingTimeToString(peer.ping),598peer.last_send ? ToString(time_now - peer.last_send) : "",599peer.last_recv ? ToString(time_now - peer.last_recv) : "",600peer.last_trxn ? ToString((time_now - peer.last_trxn) / 60) : peer.is_tx_relay ? "" : "*",601peer.last_blck ? ToString((time_now - peer.last_blck) / 60) : "",602strprintf("%s%s", peer.is_bip152_hb_to ? "." : " ", peer.is_bip152_hb_from ? "*" : " "),603m_max_addr_processed_length, // variable spacing604peer.addr_processed ? ToString(peer.addr_processed) : peer.is_addr_relay_enabled ? "" : ".",605m_max_addr_rate_limited_length, // variable spacing606peer.addr_rate_limited ? ToString(peer.addr_rate_limited) : "",607m_max_age_length, // variable spacing608peer.age,609m_is_asmap_on ? 7 : 0, // variable spacing610m_is_asmap_on && peer.mapped_as ? ToString(peer.mapped_as) : "",611m_max_id_length, // variable spacing612peer.id,613IsAddressSelected() ? m_max_addr_length : 0, // variable spacing614IsAddressSelected() ? peer.addr : "",615IsVersionSelected() && version != "0" ? version : "");616}617result += strprintf(" %*s ms ms sec sec min min %*s\n\n", m_max_services_length, "", m_max_age_length, "min");618}620// Report peer connection totals by type.621result += " ";622std::vector<int8_t> reachable_networks;623for (const UniValue& network : networkinfo["networks"].getValues()) {624if (network["reachable"].get_bool()) {625const std::string& network_name{network["name"].get_str()};626const int8_t network_id{NetworkStringToId(network_name)};627if (network_id == UNKNOWN_NETWORK) continue;628result += strprintf("%8s", network_name); // column header629reachable_networks.push_back(network_id);630}631};633for (const size_t network_id : UNREACHABLE_NETWORK_IDS) {634if (m_counts.at(2).at(network_id) == 0) continue;635result += strprintf("%8s", NETWORK_SHORT_NAMES.at(network_id)); // column header636reachable_networks.push_back(network_id);637}639result += " total block";640if (m_manual_peers_count) result += " manual";642const std::array rows{"in", "out", "total"};643for (size_t i = 0; i < rows.size(); ++i) {644result += strprintf("\n%-5s", rows[i]); // row header645for (int8_t n : reachable_networks) {646result += strprintf("%8i", m_counts.at(i).at(n)); // network peers count647}648result += strprintf(" %5i", m_counts.at(i).at(NETWORKS.size())); // total peers count649if (i == 1) { // the outbound row has two extra columns for block relay and manual peer counts650result += strprintf(" %5i", m_block_relay_peers_count);651if (m_manual_peers_count) result += strprintf(" %5i", m_manual_peers_count);652}653}655// Report local services, addresses, ports, and scores.656if (!DetailsRequested()) {657result += strprintf("\n\nLocal services: %s", ServicesList(networkinfo["localservicesnames"]));658}659result += "\n\nLocal addresses";660const std::vector<UniValue>& local_addrs{networkinfo["localaddresses"].getValues()};661if (local_addrs.empty()) {662result += ": n/a\n";663} else {664size_t max_addr_size{0};665for (const UniValue& addr : local_addrs) {666max_addr_size = std::max(addr["address"].get_str().length() + 1, max_addr_size);667}668for (const UniValue& addr : local_addrs) {669result += strprintf("\n%-*s port %6i score %6i", max_addr_size, addr["address"].get_str(), addr["port"].getInt<int>(), addr["score"].getInt<int>());670}671}673return JSONRPCReplyObj(UniValue{result}, NullUniValue, /*id=*/1, JSONRPCVersion::V2);674}676const 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"