你在钱包里看到的资产明细、一笔交易的来龙去脉、某个脚本地址下此刻的 UTxO 集合,都不是节点「直接」给你的——中间隔着一层查询服务。Cardano 生态里这层服务各家接口互不相同,同一地址在不同工具里数字对不上,很多时候是查询层口径问题,而不是链本身出了岔子。CIP-139「Universal Query Layer」想做的,是给这层插座定一个统一规格。截至撰稿时它是 Proposed 状态,由 Vladimir Kalnitsky 与 MLabs 的 Giovanni Garufi 起草,是对 CPS-0012 问题陈述的回应。
两条老路:自建跟随器与通用查询服务
提案先划清生态里现成的两类做法。一类是定制化链跟随器:程序贴着 cardano-node 处理每一笔新区块交易和回滚事件,维护 dApp 自己的状态,优点是运行时资源省,缺点是每个项目各修各的路。另一类是通用查询层(Blockfrost、Maestro 等),一套 API 服务所有项目,dApp 拿数据自己拼状态,复用性好,但各家 API 形状不一,换服务商近乎重写。CIP-139 明确只解决第二类:定义一个 JSON 格式、传输无关的查询 API,让适配层去对接不同服务商。

传输无关:一套端点,三种外壳
查询层的端点清单和返回类型对齐 CIP-116 的域类型 JSON 编码,再由同一份高层定义生成三种外壳:HTTP 用的 OpenAPI schema、JSON-RPC schema、以及注入页面的 TypeScript 接口对象。它还把自己整理成兼容 把 DNS 记录放进名字服务时要放弃什么:ERC-1185 的按记录集存取的扩展形态(CIP-0144 方向),意图是让「全数据钱包」和「全数据 dApp」在同一个接口词汇表里对话,换服务商时写转换适配器即可。
一个反直觉决定:故意不做分页
提案在分页上的取舍值得细读。CIP-30 里的分页本就不牢靠:两次调用之间 UTxO 集合可能已经变化,翻到第二页时数据已经不是同一时刻的快照。而查询层里大量端点的结果规模根本不受控——你无法预知一个脚本地址下究竟有多少 UTxO。与其让开发者在「效率」与「一致性」之间二选一,作者选择把分页从这版 API 里拿掉,宁可一次给全,也避免返回一个自相矛盾的拼接结果,并希望在后续提案里找到两全方案。
回滚:从事件推送退到拉取模式
节点看到的链尖可能与其他节点冲突,冲突解决时会发生回滚。自建跟随器原则上能把回滚事件实时推给 dApp;通用查询层则不推——它没有 dApp 的状态信息,无从判断某个回滚与哪个项目有关。所以使用查询层的 dApp 走的是拉取模式:按需重新请求,而不是订阅事件。提案因此把回滚定位为「不同查询结果之间潜在不一致的来源」,要求开发者自己把它算进核对流程。这也解释了为什么同一笔交易在不同工具里会短暂地出现又消失,相关现象可以参考 同一笔交易两个浏览器确认数不同?链尖快照、索引延迟与节点同步一文的思路。
版本与错误码
这版 API 带一个返回当前版本号的端点:草案期为 0.0.0,正式合并即 1.0.0,之后所有改动按语义化版本走。错误处理分域错误与传输错误两类,规范只定义前者;域错误码直接沿用 CIP-30 的四件套——InvalidRequest、InternalError、Refused、AccountChange,理由是这套查询 API 大概率与 CIP-30 并肩部署,统一错误语义能少一层翻译。
现状与普通读者的用法
CIP-139 的验收标准(至少两个协议适配器、至少两个链下库实现)都还没勾上,它描述的是还没有建成的插座。对普通用户,它的价值是一套排查视角:两份工具资产对不上时,先怀疑查询层口径、缓存时点与回滚处理,再怀疑钱包弄丢了钱。
换查询服务商之前,先学会三步勾稽
假设某工具升级后换了底层查询服务商,资产数突然对不上,可以按这套顺序核对,每一步都在上述规范语境里找得到出处。第一步,确认两边读的是同一个账户与同一组策略地址:AccountChange 这类错误码的存在本身就说明「换账户后沿用旧连接」是常见事故源,工具切号但 dApp 没重新握手,看到的自然是对家的余额。第二步,比结果的时间戳而不是比数字:查询层返回的是某高度快照上的世界,两边各查一次最近稳定区块的同一地址,再看是否收敛——这与在两个区块浏览器之间对账是同一个方法论。第三步,给回滚留窗口:涉及 UTxO 密集的地址,短暂差一两笔属于链尖冲突的正常代价,隔几分钟重拉比立刻下结论便宜得多。若三步走完仍对不上,再把问题归给具体服务商并留下截图与请求参数——那时你手里握着的,恰好就是这套统一查询层将来想标准化的东西。涉及资产的操作请以链上数据为准并逐项核对金额与地址,本文不构成投资建议。
发表评论
还没有评论,来说两句吧。
评论区为展示样式,提交不会被处理。