共识轮次里最容易被忽略的一件事是:投票开始前,节点手里往往还没有那个区块的完整内容。CometBFT 系链的提议者发出的提案消息不带交易本体,只带一份”这个区块长什么样”的摘要;区块真正的字节,是被切成一串小块之后,通过邻居之间点对点补齐的。这套切块与补齐机制就是 PartSet。理解它,才能解释为什么有的链在区块体积变大后出块时间会抖动,也能解释节点日志里那些轮次升高的成因。
提案消息里只有指纹
一次共识轮开始时,被排到的提议者向全网广播 Proposal。规范写得很清楚:这条消息携带的是 BlockID,而 BlockID 里装的是两个默克尔根以及块数——区块头各字段累加出的根,加上另一颗树:把序列化后的整块切成等长小块,每块作为叶子建树得到的根,规范里叫 PartSetHeader。它还带一个 Total 字段,规定这块总共被切成几份,取值必须大于零。也就是说,收到提案的节点此刻只知道”应当拼出什么样的东西”,还不知道东西本身。
交易的来源因此和提案是两条路:节点各自去已经连着的 peers 那里要那些自己还缺的小块。规范把这一步描述为对当前轮提议者所提区块的 PartSet 分片做 gossip,并注明用的是 LibSwift 风格的快速广播思路。落到实现上,每个节点会为每个对端维护一张位图,记录对方已经拥有哪几份,于是发送方每次只挑对方缺的那一份递过去,而不是把整块对每个邻居重发一遍。
两份默克尔根各自防什么

PartSetHeader 的哈希是对”切块后的完整序列化区块”取的默克尔根,每份 Part 带三样:位置编号、内容字节、以及指向那棵树的证明。节点收到任意一份时不需要等其它份到齐,就能当场用证明把它核对进那根哈希——位置错了、内容被改了一个字节、证明对不上根,都会立刻被发现。
这层设计的价值在争议场景里更明显。BlockID 同时锁住了区块头根和切块根:前者是共识语义上的身份,后者是传输层面的完整性承诺。任何一方想事后拼出一份”根相同但内容不同”的块,都要撞上默克尔树的第二原像问题,成本上不可行。而 Total 字段的存在,堵住了另一类麻烦——有人少发一份让接收方永远拼不齐。位置索引必须大于等于零、总份数必须为正,这类边界在数据结构的字段表里逐项列着。
为什么要切开传
把区块切成小片的直接动机是时间。共识轮次是有时限的:提议者要在提案超时之前让足够多的节点拿到完整区块,否则这一轮作废,进入下一轮换人提议。一条 TCP 连接串行传几十兆字节的块,慢邻居会把所有人拖进超时。切块之后,一份块可以被同时从多个邻居那里并行拉取;已经到手的份可以继续参与投票准备,不必等最后一片。投票的计时和数据的搬运因此能重叠进行,这是规范愿意为传块单独写一章的原因。
代价也要写在账上。一是切块与建证明本身要算,块越大份数越多,树越深,每份的证明路径越长。二是网络请求数上升:一次完整传块在实现层面表现为”给每个对端反复挑份发送”的循环,日志里高频出现的分片发送记录属于正常形态,不是异常。三是份数由提议者决定,同一条链不同区块的份数可以不同,节点要按 BlockID 里声明的 Total 来验收,不能按上一块的经验猜。
节点与使用者的视角
对跑节点的人来说,PartSet 提供了几个可核对的抓手。如果出块明显变慢而 CPU 与磁盘都不忙,一个方向是查传块是否卡在缺片:看该高度的轮次(round)有没有升高,看与若干邻居的分片位图是否长期填不满。轮次升高意味着上一任提议者的块没能及时被大家拼出来,共识换人重试——这既可能是网络拓扑问题,也可能是某个提议者带宽不足。
对持币者和应用来说,PartSet 不改变任何一笔交易语义,它影响的只是一件间接的事:区块体积增长时,链的出块稳定性取决于传输与验证能不能在时限内跑完。评估一条 Cosmos 系链时,“最大区块多大""分片在实现里如何取块大小”这类参数以官方规范与所用 CometBFT 版本的字段表为准;本文描述的是机制骨架,具体数值随版本变化,不做固定承诺。本文是协议机制解释,不构成任何投资建议。
发表评论
还没有评论,来说两句吧。
评论区为展示样式,提交不会被处理。