节点之间建立连接后交换的第一条消息是 version:协议号、服务位图、时间戳、双方地址、随机 nonce、用户代理字符串、起始高度,一长串字段按固定顺序排布。2012 年的 BIP-37 在这条消息尾部加了一个新字段 relay——一个布尔值,告诉对端”要不要把新交易的 inv 转发给我”,协议号同时升到 70001。BIP-37 本身成功了,今天它布隆过滤的那一半已被更省资源的方案取代,但 relay 留了下来。BIP-60《固定长度 version 消息》是 Amir Taaki 在 2013 年 6 月对这次修改开的”处方”:问题不在 relay 该不该有,而在它作为可选字段存在。这份提案状态 Closed,没有独立部署,但它争取的目标——每个协议版本的字段布局固定——其实以另一种方式实现了。
为什么可选字段值得专门写一份提案?提案给了两层理由。表层是工程纪律:比特币消息格式的一贯性质是字段数量固定,解析器可以像流水线一样按顺序读,不需要知道流里还剩多少字节。一旦某个字段”看情况出现”,它后面的字段位置就变得不确定——长度校验做不了,流式解析器也传不下去了,反序列化代码必须多带一个”剩余字节数”的状态。深层是安全纪律:消息总长度是个免费的完整性信号,一个多塞或漏截字节的畸形 version 包可以被长度检查当场拦下;字段可缺省时,这种检查失去锚点,畸形包的藏身空间变大。
BIP-60 的方案没有发明的成分,就是把规则写死:凡是双方协议号达到 70001 的连接,relay 就是 version 消息的一个必备字段,紧跟在 start_height 之后,占 1 字节布尔,缺了就是格式错误。提案给出的完整载荷表里,relay 之前的字段依次是 version(int32)、services(uint64)、timestamp(int64)、addr_recv(net_addr),协议号到 106 之后再接 addr_from、nonce、user_agent(var_str)、start_height(int32)。verack 在 version 被接受后发出,握手完成。整个提案的全部新意,就是把”可以没有”改成”必须有”。
后来的走向值得交代,因为它解释了为什么这份提案挂着 Closed 却”赢了一半”。主流实现从一开始就没有把 relay 做成真可选:当双方版本都达到 70001,字段实际总是按固定位置出现;版本不到,大家又早已不再互连。于是”在版本达到阈值的连接上字段必现”成为事实标准,恰是 BIP-60 想要的语义,只是不需要一次单独的规范确认。再往后,BIP-151、BIP-324 等提案给 P2P 通道加加密层时,也延续了消息布局精确到字节的风格——version 消息的字段纪律是这些设计的前提。
这条线的价值在排查故障时最能显形。节点实现者处理 version 解析报错时,能把它当协议违规直接记账,因为合法消息在该版本下只有一种长度;客户端开发者查”为什么对方把我断了”,可以对照字段表逐段核对字节。反过来,如果当年 relay 真是可选的,这类故障会变得难以归因——长度对不上说不清是恶意、老实现还是随机噪声。
常见误区有三。其一,以为 BIP-60 发明了 relay 字段:那是 BIP-37 的手笔,BIP-60 只负责把它焊死。其二,以为”固定字段数”意味着所有版本的消息长得一样:每个协议版本各有自己的布局表,跨版本差异靠版本号区分,同一版本内部才讲固定。其三,把这条规矩推广到全部消息类型:比特币消息家族里本来就存在 var_int 前缀的可变长字段(比如 inv 条目数),固定字段数说的是”字段序列的结构固定”,不是每条消息字节数恒定。
快速问答。问:今天的 Core 代码里还能看到这条规则的痕迹吗?答:能,version 的解析在各版本下都是按序定字段,不符合布局即按对端违规处理。问:提案为什么标 Closed?答:它的诉求被实现路径吸收,作为独立提案没有再推进的必要——Closed 不等于诉求被否决。问:普通用户会感知到吗?答:不会,字段布局之争发生在实现层,对外表现为”连得上/连不上”。
一条直觉线:把 version 消息想成船运提单——每个栏目都印死在表格里,缺栏即废单。BIP-37 当年加的是”如有则填”的备注栏,BIP-60 坚持把它改成必填栏。两种表格传输的内容几乎一样,但校验员的工作方式完全不同:前者每次都要现场判断备注栏到哪儿结束,后者只需逐格核对。
风险提示:本文是协议机制科普,不构成投资建议。

发表评论
还没有评论,来说两句吧。
评论区为展示样式,提交不会被处理。