一条 inv 最多装五万条:超限宣告会被记过,发送方自己会拆批 图 1
一条 inv 最多装五万条:超限宣告会被记过,发送方自己会拆批 · 图 1

比特币网络里最繁忙的消息不是区块也不是交易,而是清单:inv 用来宣告「我有」,getdata 用来提货「我要」。这两种消息的载荷都是同一类条目数组——存货清单向量。既然允许一个对端一口气宣告上万笔待播交易,数组长度有没有边界?有。Bitcoin Core 31.0 源码里那个常量叫 MAX_INV_SZ,取值 50000。这条线的两端各有一套规矩:收的人怎么罚超限,发的人怎么自拆。

接收侧:超限不是截断,是记过

处理 inv 的代码在解析数组后第一件事就是量长度:超过 MAX_INV_SZ,直接调用 Misbehaving 并附上实际条数,然后整条消息作废。注意措辞——这不是「帮你修剪到五万条」,而是判定对方发了协议不该出现的东西。getdata 同样有一字不差的检查,超限记过理由写的是 getdata message size。

Misbehaving 在比特币节点里有固定含义:给这个对端的过失积分记分,攒够分值就断连并可能拉黑一段时间(过失分的完整账目可参考 什么样的行为会被别的节点断开?比特币节点 misbehaving 处理机制,清单结构本身的拆解见 Inv 清单的两个字段)。换句话说,一条塞了五万零一条目的 inv,代价不是少传播一条交易,而是发它的节点自己掉信用。这里还有一层容易被忽略的细节:超限判定发生在消息解析阶段,先于任何业务处理——五万零一条目不会先被接受五万条再丢弃剩余,而是整条一起按违规论处,不给「刚好超一点」的对端留灰色地带。

一条 inv 最多装五万条:超限宣告会被记过,发送方自己会拆批 图 2
一条 inv 最多装五万条:超限宣告会被记过,发送方自己会拆批 · 图 2

发送侧:到五万条就交作业

有接收侧的罚则,就有发送侧的自觉。Bitcoin Core 在组织地址宣告、区块宣告等批量消息时,代码里出现的是同一种模式:往待发送数组里逐条追加,一旦 vInv.size() 恰好等于 MAX_INV_SZ,就把装满的这一条消息先发出去、清空数组继续填下一批。三条主要宣告路径各自都有这个分支。也就是说,一个拥有巨量待发条目的节点,在网络上呈现的是背靠背的多个「满包」inv,而不是一条超长消息。

为什么要拆?直接约束是消息体积。比特币协议对单条网络消息有总闸:MAX_PROTOCOL_MESSAGE_LENGTH 定义为四百万字节,超限的帧在更早的消息读取层就被丢弃。每条 inv 条目本身占三十六字节(四字节类型标志加三十二字节哈希),五万条约一百八十万字节,恰好被安排在总闸之下还有余量。五万这个数字不是拍脑袋的整数,而是「清单语义上限」与「帧尺寸上限」之间对齐的结果(二十字节帧头与各字段分工见 比特币 P2P 消息帧的二十字节前奏)。

这笔账在真实网络里的样子

日常场景中五万条很难触顶:一笔新交易的 inv 只有一两条,区块头宣告也短。真正逼近上限的是两类时刻——一是新节点与对端做内存池同步时,对端积压的未确认交易可能以满包 inv 连续涌来;二是网络拥堵时段大量交易集中宣告。此时抓包会看到同一条连接上连续出现条目数恰好五万的 inv,这不是异常,而是发送侧分批纪律的正常体现。理解这一点,就不会把协议层的拆包误读成节点故障或攻击。

给运维与研究的两个提示

做监控时,以「单条 inv 条目数是否触顶」作为观察项意义有限,更值得盯的是连接是否出现 inv-too-large 类记过日志——那意味着某个对端在违反协议,值得核对版本与网络来源。做隐私研究时则要注意另一面:满包 inv 的边界与拆包节奏本身是一种可观察信号,同一批宣告在不同对端间的到达顺序与时间差,曾被用于推断交易的源节点簇。清单纪律约束了体积,也塑造了流量在链路层可见的形状。

一句话总结:五万条是一条双向的纪律——发的人自觉拆批,收的人严正记账;它在三十六字节一条的清单与四百万字节的消息总闸之间,给比特币的传播层留出了一块算得清的余地。