首页
/ Bitcoin Core:bitcoin-cli -addrinfo 行为变更与 getaddrmaninfo 依赖链详解(PR 26988)

Bitcoin Core:bitcoin-cli -addrinfo 行为变更与 getaddrmaninfo 依赖链详解(PR 26988)

2026-09-06 12:06:50作者:卓炯娓

本文以 Bitcoin Core 的发布说明 release-notes-26988.md 为主体,深入讲解 bitcoin-cli -addrinfo 命令在地址计数行为上的关键变更:为什么从"经过质量与新鲜度过滤的地址集合"改为"返回全部已知地址"、该命令为何从此依赖 getaddrmaninfo RPC(要求 bitcoind v26.0+),并逐层拆解 src/bitcoin-cli.cpp 中的请求处理链、src/rpc/net.cpp 中的 RPC 实现,直至 src/addrman.h 的地址管理器(AddrMan)底层计数逻辑。读完本文,你能准确判断 -addrinfo 输出与节点实际 peer 选择逻辑的一致性,并能自行排查版本不匹配时的报错。

一、变更核心:-addrinfo 不再过滤地址

PR #26988 对 CLI 的 -addrinfo 选项做出的修改可以概括为一句话(来自 release-notes-26988.md 原文):

CLI -addrinfo now returns the full set of known addresses. In previous versions (v22.0 - v30.0) the set of returned addresses was filtered for quality and recency. This was changed since it does not match the logic for selecting peers to connect to, which does not filter.

拆开来看包含三层信息:

  1. 变更前(v22.0 – v30.0)bitcoin-cli -addrinfo 返回的地址集合经过了质量(quality)和新鲜度(recency)过滤,即统计的是"经过筛选后认为仍然有效"的地址。
  2. 变更后-addrinfo 返回节点地址管理器中全量已知地址的计数,与网络无关的过滤条件一律不施加。
  3. 变更动机:节点在挑选连接对等点(peer)时的选择逻辑本身不做这类过滤。旧行为给出的数字与节点实际"可以从哪些地址里挑 peer"的口径不一致,容易造成运维误判——例如看到 -addrinfo 数字偏小,误以为节点缺乏连接能力,而实际上 peer 选择逻辑能看到全部地址。

这一变更的价值在于让诊断命令的统计口径与真实选路逻辑对齐:-addrinfo 报出的数字,现在就是节点选 peer 时"池子里"的地址规模。

二、版本约束:为什么 -addrinfo 要求 bitcoind v26.0+

同一份发布说明中给出了一个明确的兼容性约束:

Note: CLI -addrinfo now requires bitcoind v26.0 or later, as it uses the getaddrmaninfo RPC internally. Users querying older, unmaintained node versions would need to use an older bitcoin-cli version.

这里的关键依赖是 getaddrmaninfo RPC。该 RPC 是 Bitcoin Core v26.0 新增的,官方 26.0 发布说明 release-notes-26.0.md 中记载:

A new RPC getaddrmaninfo has been added to view the distribution of addresses in the new and tried table of the node's address manager across different networks(ipv4, ipv6, onion, i2p, cjdns). (#27511)

也就是说,-addrinfo 的"全量计数"实现方式是在 CLI 内部把请求转写成 getaddrmaninfo RPC 调用,而该 RPC 只存在于 v26.0 及之后的 bitcoind。因此:

  • 用新版 bitcoin-cli(含本变更)去连 v26.0 之前的旧节点:RPC 不存在,命令会失败;
  • 官方给出的解决方案是:查询旧节点请使用与节点同版本的旧 bitcoin-cli,或升级节点本身。

这一约束在源码中有直接对应。src/bitcoin-cli.cpp-addrinfo 专用的请求处理器 AddrinfoRequestHandler 在处理 RPC 响应时,会专门检查"方法不存在"这一种错误:

/** Process addrinfo requests */
struct AddrinfoRequestHandler : BaseRequestHandler {
    UniValue PrepareRequest(const std::string& method, const std::vector<std::string>& args) override
    {
        if (!args.empty()) {
            throw std::runtime_error("-addrinfo takes no arguments");
        }
        return JSONRPCRequestObj("getaddrmaninfo", NullUniValue, 1);
    }

    UniValue ProcessReply(const UniValue& reply) override
    {
        if (!reply["error"].isNull()) {
            if (reply["error"]["code"].getInt<int>() == RPC_METHOD_NOT_FOUND) {
                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.");
            }
            return reply;
        }
        ...

可以看出两点实现事实:

  1. PrepareRequest 阶段把 -addrinfo 固定转写为 getaddrmaninfo 调用,且不接受任何参数(传入参数直接报错 -addrinfo takes no arguments);
  2. ProcessReply 阶段对 RPC_METHOD_NOT_FOUND(RPC 方法不存在)单独拦截,抛出与发布说明措辞一致的引导性错误信息,帮助运维直接定位到"节点版本太旧"这一根因,而不是抛出一个晦涩的 JSON-RPC 错误。

CLI 主流程在解析命令行参数后,检测到 -addrinfo 开关即切换到该处理器(见 src/bitcoin-cli.cpp):

} else if (gArgs.GetBoolArg("-addrinfo", false)) {
    rh.reset(new AddrinfoRequestHandler());
}

三、实现链路:从 -addrinfo 到 AddrMan 的三层调用

-addrinfo 的完整数据链路可以归纳为三层:CLI 处理器 → getaddrmaninfo RPC → AddrMan 计数接口。

3.1 CLI 层:把 RPC 结果压缩成 addresses_known

AddrinfoRequestHandler::ProcessReply 的后半段(src/bitcoin-cli.cpp)遍历 getaddrmaninfo 返回的每个网络键,只抽取每个网络的 total 字段,组装成精简输出:

// Process getaddrmaninfo reply
const std::vector<std::string>& network_types{reply["result"].getKeys()};
const std::vector<UniValue>& addrman_counts{reply["result"].getValues()};

// Prepare result to return to user.
UniValue result{UniValue::VOBJ}, addresses{UniValue::VOBJ};

for (size_t i = 0; i < network_types.size(); ++i) {
    int addr_count = addrman_counts[i]["total"].getInt<int>();
    if (network_types[i] == "all_networks") {
        addresses.pushKV("total", addr_count);
    } else {
        addresses.pushKV(network_types[i], addr_count);
    }
}
result.pushKV("addresses_known", std::move(addresses));

从这段源码可以确认:CLI 输出结构为 {"addresses_known": {<network>: total, ..., "total": <all_networks 的 total>}},其中 total 键来自 all_networks 条目。CLI 只做"取 total 再展平"的转换,任何计数逻辑都不在 CLI 本地发生——这也正是它能"无过滤"的原因:数据全部来自节点侧 RPC。

3.2 RPC 层:getaddrmaninfo 遍历各网络的新表/尝试表

getaddrmaninfo 的实现位于 src/rpc/net.cpp,核心逻辑是遍历所有 Network 枚举值(跳过 NET_UNROUTABLENET_INTERNAL),对每个网络调用 AddrMan::Size 三种重载组合,输出 new/tried/total 三个计数,最后再追加一个 all_networks 汇总项:

[](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue {
    AddrMan& addrman = EnsureAnyAddrman(request.context);

    UniValue ret(UniValue::VOBJ);
    for (int n = 0; n < NET_MAX; ++n) {
        enum Network network = static_cast<enum Network>(n);
        if (network == NET_UNROUTABLE || network == NET_INTERNAL) continue;
        UniValue obj(UniValue::VOBJ);
        obj.pushKV("new", addrman.Size(network, true));
        obj.pushKV("tried", addrman.Size(network, false));
        obj.pushKV("total", addrman.Size(network));
        ret.pushKV(GetNetworkName(network), std::move(obj));
    }
    UniValue obj(UniValue::VOBJ);
    obj.pushKV("new", addrman.Size(std::nullopt, true));
    obj.pushKV("tried", addrman.Size(std::nullopt, false));
    obj.pushKV("total", addrman.Size());
    ret.pushKV("all_networks", std::move(obj));
    return ret;
},

RPC 文档字符串对返回字段的官方解释也值得注意(同文件):

  • new:new 表中的地址数,"represent potential peers the node has discovered but hasn't yet successfully connected to"(节点发现但尚未成功连接过的候选 peer);
  • tried:tried 表中的地址数,"represent peers the node has successfully connected to in the past"(历史上成功连接过的 peer);
  • total:两张表之和。

该 RPC 归属 network 命令组(注册处见 src/rpc/net.cpp)。

3.3 AddrMan 层:Size 是纯计数,不含过滤

getaddrmaninfo 依赖的计数接口声明于 src/addrman.h

size_t Size(std::optional<Network> net = std::nullopt, std::optional<bool> in_new = std::nullopt) const;

两个可选参数分别限定"按哪个网络统计"与"按 new 表还是 tried 表统计";实现见 src/addrman.cpp,是一个直接委托给内部实现(m_impl->Size(net, in_new))的薄封装。从源码结构看,Size 就是对地址管理器内部表容量的直接读取,没有施加质量评分或新鲜度阈值——这与 PR #26988 发布说明中"peer 选择逻辑不过滤"的描述相互印证:-addrinfo 现在与节点选 peer 时看到的地址池同源。

四、实操:命令用法与输出解读

在节点版本满足要求(bitcoind ≥ v26.0)的前提下,用法与历史版本一致,无需任何额外参数:

bitcoin-cli -addrinfo
  • 该选项不接受任何位置参数,多余参数会触发 -addrinfo takes no arguments 错误(见 src/bitcoin-cli.cpp);
  • 选项在 CLI 参数表中的注册说明为 "Get the number of addresses known to the node, per network and total."(见 src/bitcoin-cli.cpp);
  • 输出按前述 3.1 节的组装逻辑,形如(数值为示意结构,实际数量取决于节点运行状态):
{
  "addresses_known": {
    "ipv4": 0,
    "ipv6": 0,
    "onion": 0,
    "i2p": 0,
    "cjdns": 0,
    "total": 0
  }
}

其中各网络键来自 getaddrmaninfo 返回的网络名(跳过 unroutable/internal),total 键对应 all_networks 的 total。

排查要点:若命令报出

-addrinfo requires bitcoind v26.0 or later which supports getaddrmaninfo RPC.
Please upgrade your node or use bitcoin-cli from the same version.

即可确定原因是节点侧不支持 getaddrmaninfo(即节点版本早于 v26.0),应按错误提示升级节点或改用与节点同版本的旧 bitcoin-cli

需要区分的是:-addrinfo(CLI 开关,精简计数输出)与 getaddrmaninfo(RPC,含 new/tried/total 明细)是两个观测入口,底层数据同源。如果还需要查看地址在 new/tried 两表中的分布细节,应直接使用 RPC:

bitcoin-cli getaddrmaninfo

其行为的自动化验证可参考功能测试 test/functional/rpc_net.py(其中包含对 getaddrmaninfo 的调用与断言),fuzz 侧则覆盖于 src/test/fuzz/rpc.cpp

五、小结

要点 变更前(v22.0 – v30.0) 变更后(PR #26988)
-addrinfo 统计口径 地址集合按质量与新鲜度过滤后再统计 返回全量已知地址计数,与 peer 选择逻辑口径一致
底层数据源 旧版过滤逻辑 内部转写 getaddrmaninfo RPC
节点版本要求 无此约束 要求 bitcoind ≥ v26.0(getaddrmaninfo 于 v26.0 引入)
版本不匹配时的表现 明确的 RPC_METHOD_NOT_FOUND 拦截与升级提示

PR #26988 的实质是一次"诊断命令口径对齐"的修正:让运维通过 -addrinfo 看到的世界与节点真正用于选 peer 的地址池保持一致,同时把兼容性代价(依赖 v26.0+ 的 getaddrmaninfo)通过清晰的错误提示显式化。阅读 src/bitcoin-cli.cppAddrinfoRequestHandlersrc/rpc/net.cppgetaddrmaninfo 的实现,可以完整复核本文所述的每一处行为。

登录后查看全文
热门项目推荐
相关项目推荐