Inside Bitcoin
Opening the selected file…
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.
251tfm::format(std::cerr, "Error: %s\n", e.what());252return EXIT_FAILURE;253}254return CONTINUE_EXECUTION;255}257struct HTTPResponse258{259int status{0};260std::string body;261};263static int8_t NetworkStringToId(const std::string& str)264{265for (size_t i = 0; i < NETWORKS.size(); ++i) {266if (str == NETWORKS[i]) return i;267}268return UNKNOWN_NETWORK;269}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*/274struct BaseRequestHandler {275virtual ~BaseRequestHandler() = default;276virtual UniValue PrepareRequest(const std::string& method, const std::vector<std::string>& args) = 0;277virtual UniValue ProcessReply(const UniValue &batch_in) = 0;278};280/** Process addrinfo requests */281struct AddrinfoRequestHandler : BaseRequestHandler {282UniValue PrepareRequest(const std::string& method, const std::vector<std::string>& args) override283{284if (!args.empty()) {285throw std::runtime_error("-addrinfo takes no arguments");286}287return JSONRPCRequestObj("getaddrmaninfo", NullUniValue, 1);288}290UniValue ProcessReply(const UniValue& reply) override291{292if (!reply["error"].isNull()) {293if (reply["error"]["code"].getInt<int>() == RPC_METHOD_NOT_FOUND) {294throw 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}296return reply;297}298// Process getaddrmaninfo reply299const std::vector<std::string>& network_types{reply["result"].getKeys()};300const std::vector<UniValue>& addrman_counts{reply["result"].getValues()};302// Prepare result to return to user.303UniValue result{UniValue::VOBJ}, addresses{UniValue::VOBJ};305for (size_t i = 0; i < network_types.size(); ++i) {306int addr_count = addrman_counts[i]["total"].getInt<int>();307if (network_types[i] == "all_networks") {308addresses.pushKV("total", addr_count);309} else {310addresses.pushKV(network_types[i], addr_count);311}312}313result.pushKV("addresses_known", std::move(addresses));314return JSONRPCReplyObj(std::move(result), NullUniValue, /*id=*/1, JSONRPCVersion::V2);315}316};318/** Process getinfo requests */319struct GetinfoRequestHandler : BaseRequestHandler {320const int ID_NETWORKINFO = 0;321const int ID_BLOCKCHAININFO = 1;322const int ID_WALLETINFO = 2;323const int ID_BALANCES = 3;325/** Create a simulated `getinfo` request. */326UniValue PrepareRequest(const std::string& method, const std::vector<std::string>& args) override327{328if (!args.empty()) {329throw std::runtime_error("-getinfo takes no arguments");330}331UniValue result(UniValue::VARR);332result.push_back(JSONRPCRequestObj("getnetworkinfo", NullUniValue, ID_NETWORKINFO));333result.push_back(JSONRPCRequestObj("getblockchaininfo", NullUniValue, ID_BLOCKCHAININFO));334result.push_back(JSONRPCRequestObj("getwalletinfo", NullUniValue, ID_WALLETINFO));335result.push_back(JSONRPCRequestObj("getbalances", NullUniValue, ID_BALANCES));336return result;337}339/** Collect values from the batch and form a simulated `getinfo` reply. */340UniValue ProcessReply(const UniValue &batch_in) override341{342UniValue result(UniValue::VOBJ);343const 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.346if (!batch[ID_NETWORKINFO]["error"].isNull()) {347return batch[ID_NETWORKINFO];348}349if (!batch[ID_BLOCKCHAININFO]["error"].isNull()) {350return batch[ID_BLOCKCHAININFO];351}352result.pushKV("version", batch[ID_NETWORKINFO]["result"]["version"]);353result.pushKV("blocks", batch[ID_BLOCKCHAININFO]["result"]["blocks"]);354result.pushKV("headers", batch[ID_BLOCKCHAININFO]["result"]["headers"]);355result.pushKV("verificationprogress", batch[ID_BLOCKCHAININFO]["result"]["verificationprogress"]);356result.pushKV("timeoffset", batch[ID_NETWORKINFO]["result"]["timeoffset"]);358UniValue connections(UniValue::VOBJ);359connections.pushKV("in", batch[ID_NETWORKINFO]["result"]["connections_in"]);360connections.pushKV("out", batch[ID_NETWORKINFO]["result"]["connections_out"]);361connections.pushKV("total", batch[ID_NETWORKINFO]["result"]["connections"]);362result.pushKV("connections", std::move(connections));364result.pushKV("networks", batch[ID_NETWORKINFO]["result"]["networks"]);365result.pushKV("difficulty", batch[ID_BLOCKCHAININFO]["result"]["difficulty"]);366result.pushKV("chain", UniValue(batch[ID_BLOCKCHAININFO]["result"]["chain"]));367if (!batch[ID_WALLETINFO]["result"].isNull()) {368result.pushKV("has_wallet", true);369result.pushKV("keypoolsize", batch[ID_WALLETINFO]["result"]["keypoolsize"]);370result.pushKV("walletname", batch[ID_WALLETINFO]["result"]["walletname"]);371if (!batch[ID_WALLETINFO]["result"]["unlocked_until"].isNull()) {372result.pushKV("unlocked_until", batch[ID_WALLETINFO]["result"]["unlocked_until"]);373}374}375if (!batch[ID_BALANCES]["result"].isNull()) {376result.pushKV("balance", batch[ID_BALANCES]["result"]["mine"]["trusted"]);377}378result.pushKV("relayfee", batch[ID_NETWORKINFO]["result"]["relayfee"]);379result.pushKV("warnings", batch[ID_NETWORKINFO]["result"]["warnings"]);380return JSONRPCReplyObj(std::move(result), NullUniValue, /*id=*/1, JSONRPCVersion::V2);381}382};384/** Process netinfo requests */385class NetinfoRequestHandler : public BaseRequestHandler386{387private:388std::array<std::array<uint16_t, NETWORKS.size() + 1>, 3> m_counts{{{}}}; //!< Peer counts by (in/out/total, networks/total)389uint8_t m_block_relay_peers_count{0};390uint8_t m_manual_peers_count{0};391uint8_t m_details_level{0}; //!< Optional user-supplied arg to set dashboard details level392bool DetailsRequested() const { return m_details_level; }393bool IsAddressSelected() const { return m_details_level == 2 || m_details_level == 4; }394bool IsVersionSelected() const { return m_details_level == 3 || m_details_level == 4; }395bool m_outbound_only_selected{false};396bool m_is_asmap_on{false};397size_t m_max_addr_length{0};398size_t m_max_addr_processed_length{5};399size_t m_max_addr_rate_limited_length{6};400size_t m_max_age_length{5};401size_t m_max_id_length{2};402size_t m_max_services_length{6};403struct Peer {404std::string addr;405std::string sub_version;406std::string conn_type;407std::string network;408std::string age;409std::string services;410std::string transport_protocol_type;411double min_ping;412double ping;413int64_t addr_processed;414int64_t addr_rate_limited;415int64_t last_blck;416int64_t last_recv;417int64_t last_send;418int64_t last_trxn;419int id;420int mapped_as;421int version;422bool is_addr_relay_enabled;423bool is_bip152_hb_from;424bool is_bip152_hb_to;425bool is_outbound;426bool is_tx_relay;427bool operator<(const Peer& rhs) const { return std::tie(is_outbound, min_ping) < std::tie(rhs.is_outbound, rhs.min_ping); }428};429std::vector<Peer> m_peers;430std::string ChainToString() const431{432switch (gArgs.GetChainType()) {433case ChainType::TESTNET4:434return " testnet4";435case ChainType::TESTNET:436return " testnet";437case ChainType::SIGNET:438return " signet";439case ChainType::REGTEST:440return " regtest";441case ChainType::MAIN:442return "";443}444assert(false);445}446std::string PingTimeToString(double seconds) const447{448if (seconds < 0) return "";449const double milliseconds{round(1000 * seconds)};450return milliseconds > 999999 ? "-" : ToString(milliseconds);451}452std::string ConnectionTypeForNetinfo(const std::string& conn_type) const453{454if (conn_type == "outbound-full-relay") return "full";455if (conn_type == "block-relay-only") return "block";456if (conn_type == "manual" || conn_type == "feeler") return conn_type;457if (conn_type == "addr-fetch") return "addr";458if (conn_type == "private-broadcast") return "priv";459return "";460}461std::string FormatServices(const UniValue& services)462{463std::string str;464for (size_t i = 0; i < services.size(); ++i) {465const std::string s{services[i].get_str()};466str += s == "NETWORK_LIMITED" ? 'l' : s == "P2P_V2" ? '2' : ToLower(s[0]);467}468return str;469}470static std::string ServicesList(const UniValue& services)471{472std::string str{services.size() ? services[0].get_str() : ""};473for (size_t i{1}; i < services.size(); ++i) {474str += ", " + services[i].get_str();475}476for (auto& c: str) {477c = (c == '_' ? ' ' : ToLower(c));478}479return str;480}482public:483static constexpr int ID_PEERINFO = 0;484static constexpr int ID_NETWORKINFO = 1;486UniValue PrepareRequest(const std::string& method, const std::vector<std::string>& args) override487{488if (!args.empty()) {489uint8_t n{0};490if (const auto res{ToIntegral<uint8_t>(args.at(0))}) {491n = *res;492m_details_level = std::min(n, NETINFO_MAX_LEVEL);493} else {494throw std::runtime_error(strprintf("invalid -netinfo level argument: %s\nFor more information, run: bitcoin-cli -netinfo help", args.at(0)));495}496if (args.size() > 1) {497if (std::string_view s{args.at(1)}; n && (s == "o" || s == "outonly")) {498m_outbound_only_selected = true;499} else if (n) {500throw std::runtime_error(strprintf("invalid -netinfo outonly argument: %s\nFor more information, run: bitcoin-cli -netinfo help", s));