跑一条 Cosmos 系公链会听到一个说法:共识层和应用层是两台机器。CometBFT 负责让一堆互不信任的节点对「区块顺序」达成一致,应用负责执行交易、维护状态。ABCI++ 是两者之间的接口规范,其中最直观的变化是把交易执行收进了一个叫 FinalizeBlock 的单一调用。本文解释这个调用在什么时刻发生、要交出什么、为什么这样设计。
没有 FinalizeBlock 之前:执行散在三处
规范文档(CometBFT 仓库 spec/abci 目录的 ABCI++ 系列)描述的旧接口 ABCI 里,应用要在多个时点分别参与共识:新交易进池时调 CheckTx 预检;每轮共识开始时调 BeginBlock 做块级开场检查;共识定下这个块之后,对块里每笔交易逐笔调 DeliverTx 执行并记账;块尾再用 EndBlock 收尾(比如结算投票、清理到期事项)。
这意味着「交易的最终效果」是好几步调用的混合结果,而且不同客户端实现要在多处小心维持一致性:CheckTx 与 DeliverTx 的验证逻辑必须等价,否则会出现交易能进池却在执行时失败的边界情况;BeginBlock/EndBlock 的副作用混在块首尾,让状态变更的来源变多。对做形式化验证和并行执行优化的人来说,分散的入口就是分散的假设。
FinalizeBlock:一个块一次调用,输入输出都是清单
ABCI++ 把上述三个执行类调用合并为一个:共识对某个区块达成一致之后、节点应用这个块之前,对应用调用一次 FinalizeBlock。请求里带着一份完整的待执行清单——按最终顺序排好的交易列表、本块的最后提交时间(把时间戳的确认也交给共识)、证据(要惩罚的双签行为),以及上一块之后的已投票信息。应用在响应里逐项交回:每笔交易各自的执行结果码和事件、对验证者集合的变更(增删谁、公钥和权重怎么改)、以及新状态的承诺根。
规范划了一条硬线:调用 FinalizeBlock 时,应用必须已经能确定这个块的全部内容与顺序——它不能借这次调用再改「哪些交易在块里、以什么顺序跑」。需要挑交易或重排交易的场景(比如按优先费重排、插入前一块未完成的交易)被移到提案阶段的 PrepareProposal 与 ProcessProposal 两个调用里,那是另一对接口的事。这样一来,「排序在哪解决」和「执行在哪结算」被切成两段,各自有明确的输入契约。

好处落在验证、重放与并行上
对验证者或桥这样的第三方来说,一个块的「故事」被收拢成一次函数调用:给定上块状态加这份请求,必然得到响应里那个状态根。任何节点或审计工具都可以拿请求重放执行、复现状态根,不必还原 CheckTx 的中间态或推断块首块尾的隐式副作用。这也是 Cosmos 生态谈无状态验证、并行执行和确定性重放时反复引用这次合并的原因:接口形状本身消灭了一整类「散落在回调里的状态来源」。
对应用开发者也有直接后果。第一,交易入池检查(CheckTx)依然存在,但它被规范明确定位为「软门槛」——只为流量过滤服务,任何安全相关的验证必须在 FinalizeBlock 里重做一遍,因为进池规则不是共识保证。第二,过去写在 BeginBlock/EndBlock 里的块级逻辑现在要在一次调用里按固定次序完成,迁移旧链时要重新排这段执行顺序。
边界与核验提示
第一,ABCI++ 不改变共识算法本身:两阶段投票、三分之二多数这些还是原来的样子,改的是共识层向应用提交结果的形状。第二,FinalizeBlock 合并的是执行时刻,不承诺并行执行——应用内部要不要并发跑交易,仍取决于实现。第三,接口细节仍在随 CometBFT 版本演进,规范文档里的方法字段以你实际部署版本的 spec/abci 文档为准;本文描述的是 ABCI++ 规范的机制骨架,不绑定某一具体发布日期。
想核对原文,可查 CometBFT 官方仓库 spec/abci 下的 ABCI++ basic concepts 与 methods 文档,以及 docs 下的 PrepareProposal/ProcessProposal 相关 ADR。
风险提示:本文为技术机制科普,不构成投资建议;部署相关变更请核对所用版本的官方规范。
发表评论
还没有评论,来说两句吧。
评论区为展示样式,提交不会被处理。