一台全新部署的比特币节点,网络层的开局相当窘迫:地址库(addrman)里一个对端都没有,熟人账本 peers.dat 不存在,DNS 种子解析出的地址还没经过任何验证。它要做的第一件事不是同步区块,而是先把「认识谁」这关过了。Bitcoin Core 31.0 为此安排了一条专门的冷启动补给线——addrfetch:由出站连接线程发起、直敲种子节点域名、带着明确的进货目的的连接通道。本文把这条通道的启动顺序按源码排一遍。
触发:地址库归零,且还有种子可敲
出站连接主循环 ThreadOpenConnections 在启动时做两个判定:地址库当前是否一条地址都没有;用户配置的种子节点清单(-seednode,未配置则用内置的固定种子列表,可用 -fixedseeds=0 关掉)是否还有剩余。两者都满足,就把「需要补货」标志置真,并从种子清单里弹出一个域名塞进待取队列。源码日志分两种口吻:地址库全空时说「addrman 为空,把种子节点加入 addrfetch」;另一种情况——清单还在但迟迟连不上任何对端——则说「若干秒内没能连上 addrman 里的对端,把下一个种子加入 addrfetch」。这个判定以 ADD_NEXT_SEEDNODE 为节拍,取十秒:每过十秒仍未开张,就换下一个种子再试。

队列一次只放一个客户
补货请求排成一条先进先出的队列。每轮循环里,线程先调用 ProcessAddrFetch:若队列非空,弹出队首的域名,此时才真正动手建立连接——前提还有一条闸门:出站连接的空闲槽位(信号量)必须拿得到。拿到槽位后,OpenNetworkConnection 被以「空地址、指定域名、addrfetch 连接类型」的形态调用:注意第一个实参是默认构造的地址对象——此刻节点连对方的 IP 与端口都不知道,全靠域名解析现查。同一轮主循环里,补货动作之后还有五百万分之一秒单位的常规节拍(主循环节律与 feeler、块中继等调度并列),保证补货插队而不垄断。
顺带一个时代细节:发起这类连接时,若本机宣告了 NODE_P2P_V2 服务能力,会先尝试 v2 加密传输握手,失败再回退 v1(P2P 加密传输见相关协议文档)。冷启动的第一批连接,从出生起就在试新协议。
与 DNS 种子、feeler 的分工
三种机制常被混为一谈,源码里其实是三道独立的门。DNS 种子节点的作用是把域名解析成一堆 IP、往新库里填原料,它解决的是「从无到有的一批陌生地址」;addrfetch 解决的是「有域名、需要一个真正能说话的对端」——它建立的是完整连接,走握手、要 getaddr 清单(见 全新节点第一批邻居从哪来:DNS 种子节点启动链路拆解 对整条启动链路的分镜);feeler 则是地址库略有积蓄后的短命探针,从 new 表抽人验一验能不能连(见 feeler 连接:比特币节点伸进地址库的短命探针)。三者的连接类型在源码里泾渭分明,日志与 getconnectioncount 类统计也各自归账。
为什么需要专用连接类型
把补货连接设为独立类型,是为了让调度器能区分意图:addrfetch 连接不承担区块与交易中继职责,只为交换地址;它不挤占正常出站对端名额的上限语义,也允许在地址库持续恶化(重启后种子失效、网络分区)时重新触发。理解这一层,对排障有直接价值:日志里反复出现 addrfetch 字样,说明节点在对地址库的可持续性不满意——种子不可达、DNS 被污染、或防火墙只放了单向,都会让补货队列一波接一波。
运维检查清单
第一,-seednode 与 -fixedseeds 的默认值适合绝大多数场景,除非运行隔离测试网,不要手动关闭固定种子——那等于在空库里关掉了补给线。第二,容器部署注意 DNS 可用性:种子以域名形式配置,容器内 resolver 异常会让 addrfetch 全军覆没,表现恰恰是「起不来、一直在换种子」。第三,日志关键字按序排:Empty addrman、Couldn’t connect to peers from addrman、以及连接类型统计,三步就能判断冷启动卡在哪一环。
冷启动的设计哲学在这条通道里很清晰:先承认新节点一无所知,再用最低成本的敲门(域名、十秒一拍、单线程排队)换取第一份可验证的社交圈。之后的一切——令牌桶限速、年龄重写、feeler 抽检——都从这几次敲门开始。
发表评论
还没有评论,来说两句吧。
评论区为展示样式,提交不会被处理。