在 Cosmos 系链上查一笔交易的执行结果,或者挂个订阅等新块,很多人默认节点”什么都存着”。实际上 CometBFT 的索引是一套有明确取舍的设计:它只为查询建键,区块本体压根不进索引,能查什么取决于你选了哪种索引器,甚至属性值是负数还是超大整数都会影响结果。本文按官方索引与订阅文档把这套边界摊开。
谁被索引、按什么键索引
文档说明 CometBFT 允许索引交易与区块,之后再查询或订阅它们的结果。索引的来源是执行响应:交易由 ResponseFinalizeBlock.tx_results.events 里的内容索引,区块则由 ResponseFinalizeBlock.events 索引。两类对象的键结构不同:交易除了事件键,还有一张以交易哈希为主键的表,映射并保存对应的交易执行结果;而区块的主键只包含区块高度,映射到的也只是高度本身——文档明确写道,也就是区块本体从不被存储。
事件本身由一个类型加一组键值属性构成。每个事件带一个复合键,拼法是事件类型与属性键用点号相连。文档的例子是名为 jack 的事件带属性 account.number,对应的复合键就是 jack.account.number。同在一个高度内的不同事件允许拥有相等的属性值。
默认情况下节点只保留几张最基础的索引:交易侧是 tx.height 与 tx.hash,区块侧是 block.height。想查更细的东西,得靠应用自己在 FinalizeBlock 响应里发出事件——把键值对以 UTF-8 字符串形式放进事件的属性里,属性上还有一个是否建索引的开关。文档同时提醒:CometBFT 并没有暴露”哪些事件要索引、哪些忽略”的配置能力,发不发由应用决定。
两种索引器,能力不重叠
算子通过配置文件的 [tx-index] 段选择索引器,indexer 字段可填 null(彻底关闭)、kv(默认)或 psql。文档写明只要取值里包含 null,无论其他值怎么填都不索引。

kv 是嵌在 CometBFT 主数据库里的键值存储,它把事件的每一个属性单独存成一条记录,键由事件类型、属性键、属性值、事件生成者(例如 FinalizeBlock)、高度与事件计数器拼成,值只是一个一。这么拼是为了能把同一事件内的多个属性关联起来。文档特别交代了这个事件编号的性质:它是索引器内部维护、每处理一个新事件就递增的 int64 变量,除了在同高度内关联同一事件的属性之外没有别的语义,而且它并非原子递增,因为索引是按确定性进行的——一旦这个前提改变,事件编号生成就坏了。好处是能直接对着 RPC 查区块和交易事件,代价是查询语法能力有限,文档还写它将来可能被废弃或整体移除。
psql 则把事件代理给外部 PostgreSQL 实例,落成关系表。运维可以直接用 SQL 做 kv 不支持的复杂查询。但有个反直觉的点被写得很清楚:正因为可以直接用 SQL,通过 CometBFT 的 RPC 做搜索对 psql 索引器是关闭的,任何这类查询都会失败。也就是说,换到 psql 之后原来那套事件查询接口不再好用,你必须去查库。文档还要求建表必须显式先做——模式定义在 state/indexer/sink/psql/schema.sql,要在节点启动并启用 psql 之前建好关系。
查询侧的三个坑
第一是历史索引的语义变化。文档记载,存储事件序号这件事是从 CometBFT 0.34.26 才引入的;在那之前直到 Tendermint Core 0.34.26,事件序号并不写进 kvstore,事件只按高度存储,于是查询会返回那些属性在同一高度内匹配的区块与交易,即使这些属性其实分属不同事件。这个行为在新版本被修好了,但如果数据是在更早的版本下建立索引且没有重建,查询结果就仍按”同高度内所有属性属于同一事件”的老口径返回。这是典型的”节点没坏、答案是旧的”。
第二是属性值类型。任何人都能拿任意字符串当事件值,但一旦是数字就要注意:负数在查询索引器时不能被正确取回;事件值会被转换成 math/big 提供的大浮点数,精度按该整数所需的位数设置,以避免大整数用默认精度(64 位)转换时丢精度。比较两个值时,只要有一方是浮点,另一方也会被按大浮点表示。和所有浮点比较一样,带小数的浮点比较可能得出不精确的结果。
第三是查询语法本身的限制。订阅文档直接写明查询不能包含负数;另外在 v0.38.x 之前,浮点是不被支持作为查询参数的。数值条件查不出来,不一定是链上没有对应事件,也可能只是这些规则在起作用。
订阅与按事件翻区块
事件可以通过 Websocket 订阅。从命令行可以用 wscat 之类工具连上节点的 /websocket 端点(RPC 端点开了 TLS 就改用 wss 而不是 ws),然后在 subscribe 方法里带一个合法查询,订阅新块的查询写法是 tm.event='NewBlock'。除了内置事件类型,也可以用在 FinalizeBlock 响应里发出的标签去查交易结果。验证者集合发生变化时还会发布验证者集合更新事件,其载荷是一组公钥与票数对,列表内容与 ABCI 应用收到的一致。
想按事件翻历史区块,文档给出的是 /block_search 端点,例如带上 block.height > 10 这样的查询串取回分页结果。配合前面几条限制看,就能理解这套接口的设计取向:它是给”按键找结果”用的轻量索引,不是给复杂分析用的数据仓库,真要复杂条件就得上 psql 自己写 SQL,或者交给外部索引服务。
风险与阅读建议
本文只描述 CometBFT 官方文档记载的索引与订阅机制,字段名、默认索引项与索引器行为以你所运行版本对应的官方文档为准,版本升级或索引器被废弃都会改变可用范围。索引缺失、历史索引未重建或属性值类型问题都可能让查询返回空结果,排查时应先确认索引器配置与节点版本,而不是直接推断链上没有发生过某件事。内容不涉及资产估值,也不构成投资建议。
发表评论
还没有评论,来说两句吧。
评论区为展示样式,提交不会被处理。