共识层为什么要把提案权交给应用
在经典 ABCI 模型里,Tendermint/CometBFT 共识引擎与应用(App)的分工很干脆:共识引擎从交易池取交易、拼成区块、组织投票,应用只在区块被决定之后通过 FinalizeBlock 逐笔执行。应用对“区块里有什么、按什么顺序执行”没有发言权。ABCI 2.0(也常写作 ABCI++)改变了这一点:它允许应用在共识执行的三个关键位置介入——新提案即将生成时、提案即将被验证时、预投票发出或接收时。对应的接口就是 PrepareProposal、ProcessProposal 与 ExtendVote/VerifyVoteExtension。理解这条分界线,是读懂 Cosmos 系链上很多高级特性(优先费市场、批量压缩、应用级准入过滤)的前提:它们不是共识引擎自带的功能,而是应用利用这三个钩子自己搭出来的。
PrepareProposal:出块者的自由裁量窗口
当某个验证者进入自己是提案者的轮次,CometBFT 会先从自己的交易池按优先级收集交易,生成区块头,拼出一个“原始提案”,再调用 PrepareProposal。应用在这里可以做四类操作:原样返回、重排序、删除交易(不从交易池删除,只是本轮不带上,相当于延迟),或者加入交易池之外的新交易。一个常被引用的场景是批量优化:把多笔独立交易重排或聚合,让同一种状态变更集中发生。需要强调的是,这段逻辑允许是非确定性的——同一高度内出块者可能多次被调用,每次拿到的原始交易集合都可能不同,结果不同也可以接受,因为它只影响“这个出块者提出什么”,不影响别人如何验证;但若本轮已有验证通过的候选值,共识引擎会直接沿用而不再调用。
代价是它踩在共识的关键路径上:CometBFT 在接口返回之前无法推进,如果应用耗时过长,其他节点的提案超时先到期,就会对空值预投票,导致这一轮作废重来。官方规范要求返回的交易总字节数不超过请求里的 max_tx_bytes,超了必须从尾部删到限内,并建议运维按应用实际情况调大提案超时参数。超时虽会随轮次自动增长兜底,但官方明确警告:依赖这种自适应会拖累性能。
ProcessProposal:验证者的接受或拒绝
同一时刻,收到提案的节点会调用 ProcessProposal。这里的规则与前者几乎相反:逻辑必须确定性,同样的提案在任何正确节点上都必须得到同样的接受或拒绝结论。而且官方给出的默认姿态是“应当接受”——即便提案里有无效部分,也更建议先接受区块、把无效部分留到执行阶段忽略。原因是拒绝的代价太高:一旦应用返回拒绝,共识会在这一轮预投票空值,对活跃度有强影响,官方文档因此提醒实现者除非非常清楚后果否则不要返回拒绝。还有一条不太直观的规则:提案者自己广播的提案也会投递给自己,所以出块者同样会经历一次验证调用。
这带来一个工程后果:立即执行(在验证阶段就把块跑完)是可行的,但产生的候选状态必须单独存放,在 FinalizeBlock 确认哪个块被决定之前,绝不能覆盖正常执行状态。官方一致性要求还规定:正确进程按规则提出的块,必须能被另一个正确进程接受——若两家实现在校验细节上出现分歧,就可能陷入没有任何提案能过审的僵局。
投票扩展:把附加数据塞进下一轮
ExtendVote 与 VerifyVoteExtension 让应用要求验证者在预投票里附带一小段自定义数据,并由其他节点验证这段数据。它由共识参数 VoteExtensionsEnableHeight 启用:从设定高度 H 起,预投票必须携带扩展(即使是空数据也必须签名);到 H+1,出块接口才第一次能看到上一轮的扩展数据。切换高度本身很讲究:H 这一轮的提案还不能带扩展数据,但该高度的预投票必须已带扩展。这条通道是很多链上“应用级检查”的基础,例如让验证者附带对某份数据的签名承诺,供下一轮出块者组织区块时引用。
对使用者意味着什么
对普通用户,这层机制不改变“交易要等区块被决定”的体验,但解释了为什么有的 Cosmos 系链能实现毫秒级的数据可用性检查、批量交易压缩或应用自定义的提案准入。对开发者和节点运营者,三条红线要记牢:出块钩子可以做非确定逻辑但要控时;验证钩子必须确定且默认接受;扩展数据有固定启用高度,不能想加就加。交易被应用从提案里剔除也不等于被拒——它还在交易池里,只是等下一轮。具体字段与调用序列以 CometBFT 当前版本的官方 ABCI 规范为准;概率最终性和确定性最终性有什么区别?两种改不了的规则 从最终性角度解释了为什么“决定前的一切都可重来”。本文为机制说明,不构成任何投资建议。
发表评论
还没有评论,来说两句吧。
评论区为展示样式,提交不会被处理。