先把目录读完再要正文:比特币节点的 getheaders 与两阶段同步 图 1
先把目录读完再要正文:比特币节点的 getheaders 与两阶段同步 · 图 1

同步链为什么先要目录

一台刚从零启动的比特币节点要追上几百万个区块。最笨的办法是挨个下载完整区块、边下边验,但这样一来,对手方塞什么块都得先花大带宽收下来才能判断好坏。协议选择了另一条路:先要目录,再要正文。节点发一条 getheaders,附上自己已知的区块哈希列表(叫区块定位器),对端沿这条链往后返回一段 headers 消息;节点把收到的每一条 80 字节区块头做工作量证明与难度调整校验,合格就接着发出下一轮索求,直到拿到链尖。

这个分批结构有明确上限:协议把 headers 消息的单轮条数封顶在两千条——源码里对应 MAX_HEADERS_RESULTS 这个常量。按每条头部八十字节粗算,一轮目录传输大约在十六万字节量级;累计推进五百轮上下,就能在不搬任何一个完整区块的前提下探明一百万个区块的骨架与每一步的工作量是否名副其实。

先把目录读完再要正文:比特币节点的 getheaders 与两阶段同步 图 2
先把目录读完再要正文:比特币节点的 getheaders 与两阶段同步 · 图 2

目录验完才开始取货

拿到headers之后,节点才知道该向谁要哪些块:它对感兴趣的哈希发 getdata,把完整区块从多个对等方并行拉回来,逐块执行全部共识规则。两条路径的分工值得记住:getheaders 要的是元数据,getdata 要的是本体;前者服务”看清地形”,后者服务”搬回货物”。如果哪个对等方只肯给坏块、或者在你索要目录时支支吾吾,两阶段的结构让你在没有付出大块带宽成本之前就能识别并断开它。

对家庭用户来说,这套机制解释了为什么新节点日志里长时间刷的是 headers 相关活动却”一个块都没装”:先收目录是设计行为,不是卡住。只有当目录接近链尖、或者客户端进入块下载高峰,才会看到区块写入的速率陡增。

一笔同步的算术

把目录阶段单独拎出来做笔账。每条区块头固定八十字节,其中三十二字节指向父块、四字节难度目标、四字节时间戳、四字节随机数、四字节版本——资金与脚本一个字节都不在里面。两千条一批意味着单轮约十六万字节;对一个已经存了几十万块的节点,索回”从某高度到链尖”的完整目录往往只是几轮往还,几秒内完成。目录里还藏着免费的体检表:逐条难度目标递推能复算出累计工作量,两条分支谁重谁轻在取块之前就已判出;分叉点对齐靠区块定位器——实现通常从最新已知块出发,头几条按单块间隔回退,之后按翻倍间隔取样,让一次请求就能覆盖长短各异的重组深度。

日志读法也值得提前记住。刚开始同步的节点长时间停在”高度在涨、区块数为零”,那是目录阶段正常工作;进入下载阶段后,getblockfrompeer 或下载队列的速率才是主战场;若卡在目录阶段迟迟不推进,优先检查的是网络可达性与对手方质量——能不能建立足够的出站连接、防火墙是否掐断了长连接,而不是硬盘速度。把这三个阶段分开看,同步问题九成能在日志里定位,不需要瞎猜。

快速问答

问:getheaders 和 getdata 都能要区块,差别在哪? 答:getheaders 只能换回 80 字节的头,响应格式固定;要完整区块必须用 getdata 指名哈希。前者是成批扫路的工具,后者是精确取货的工具。

问:两轮目录之间为什么要等? 答:每轮响应有上限,收到后需要验证并更新已知集合,才能构造下一轮的定位器;这也顺带形成了天然的限速,让任何单一节点都没法用目录洪水压垮对方。

问:同步慢怎么判断卡在哪个阶段? 答:看日志里是在交换 headers 还是在下载区块;前者慢多半是网络可达性或对手方质量,后者慢多是对等方给块速率或本地校验带宽。

风险提示:节点同步与网络配置涉及带宽与存储成本,请评估自身环境;本文不构成投资建议。