核验清单
- 同一笔 eth_call 读取排序器状态喂价的 answer、startedAt,以及价格喂价的 answer、updatedAt。
- answer 为 0 表示排序器在运行,为 1 表示宕机。不得把 0 抄成宕机。
- 以消费者合约里的宽限期 G 为准。若 answer 不为 0,或恢复后的秒数 ≤ G,禁止把价格写成可用,即使 updatedAt 很新。
- 宽限期通过之后,再单独做心跳检查。两口时钟不能互相代替。
1. updatedAt 是价格时钟,排序器状态是另一口
研究笔记最常见的一句是「updatedAt 距现在 12 秒,喂价是新鲜的」。在以太坊主网上,这通常接着做心跳比较就够了。在有中心化排序器的 L2 上,价格聚合器能更新,并不表示读取它的合约按规范应该接受它。Chainlink 的 L2 排序器状态喂价说明把检查拆成两步:先看排序器是不是在运行,恢复之后再等一段宽限期,然后才去读价格。
这和本站「预言机延迟」不是同一件事。那篇问的是价格轮次本身有多旧,操纵者能利用几个区块的窗口。本篇假定 updatedAt 已经很新,问的是排序器刚恢复时,这口新鲜时钟是否还被宽限期挡住。也和断路器不同:断路器看的是价格偏离幅度会不会暂停喂价。这里价格可以完全没偏离,仍然不该被使用。也和「钱包已确认不等于主网最终性」不同:那篇比较的是交易落在哪一口确认时钟,本篇比较的是喂价消费者该不该采纳这一轮价格。
2. 教学案例:价格 12 秒前更新,宽限期还剩 45 分钟
假设消费者合约把宽限期 G 写成 3600 秒,与 Chainlink 示例相同。同一时刻:排序器状态 answer = 0,startedAt 距现在 900 秒,也就是恢复了 15 分钟。价格喂价 updatedAt 距现在 12 秒,心跳 3600 秒,年龄只占心跳的 12/3600。只看价格轮次,会写成「新鲜」。恢复秒数 900 ≤ 3600,按示例应拒绝。研究者若把这笔价格写进清算或净值,规则判定失败。下表全部为教学假设。
| 字段 | 教学值 | 判定 |
|---|---|---|
| 排序器 answer | 0 | 在运行,不是宕机 |
| 恢复后秒数 | 900 | startedAt 距现在 |
| 宽限期 G | 3600 秒 | 来自消费者合约,不是面板 |
| 900 与 G | 900 ≤ 3600 | 宽限期未结束,价格不可用 |
| 价格 updatedAt | 12 秒前 | 看起来很新 |
| 相对心跳 | 12 / 3600 | 心跳通过,不能覆盖宽限期 |
12 秒不是喂价坏了。排序器恢复后,聚合器可以马上写出新轮次,管道里仍可能留着宕机期间的状态。宽限期把「轮次新」和「允许使用」拆开。宕机方向更直接:answer = 1 时,哪怕 updatedAt 是 30 秒前,也不得写成可用。0 与 1 的含义和日常布尔相反,抄反一次,结论整列变号。
3. 宽限期在消费者合约里,不在价格面板上
状态喂价返回的是最新轮次,它不会替借贷或永续合约执行宽限期。示例里的判断是:answer 不为 0 就拒绝;answer 为 0 时,用 block.timestamp - startedAt,结果小于等于 G 也拒绝。G 写在调用方。有的协议用 3600,有的改成 1800 或 7200。笔记必须读该合约的常量,不能把文档示例当成所有部署的事实。
OP Stack 的停机说明和 Arbitrum 的排序器说明都承认排序器是交易进入 L2 的入口。排序器停下时,用户交易进不去,但链上已有的存储仍可被读到。所以「RPC 还能返回一个价格」和「这个价格按规范可用」可以同时成立。Arbitrum 上 startedAt 为 0 还有一层含义:状态合约尚未初始化。Chainlink 写明这种 0 只出现在未初始化阶段,其他 L2 在构造时就会把 startedAt 设为区块时间。看到 0,先判定未初始化,不要把它当成「刚刚恢复、秒数为 0、因此宽限期刚好开始」以外的另一种故事,更不能把它当成已经运行了很久。
消费者若根本没有读取排序器状态,发现本身就是结论:价格轮次可以新鲜,规范要求的第二口时钟缺失。这时不得写成「已按 Chainlink 示例核验通过」,只能写成「该合约未实现排序器检查」。实现了但 G 与文档示例不同,要记下实际 G,用实际 G 做小于等于比较。
4. 筛选线:宽限期未结束,新鲜 updatedAt 不得写成可用
教学筛选按这个顺序,不能跳步。第一,answer 不为 0:不可用。第二,answer 为 0 且恢复秒数 ≤ 消费者合约的 G:不可用。第三,前两步都过了,才比较价格年龄与心跳;年龄大于心跳才是另一类过期。前两步失败时,心跳再漂亮也不改判定。这条线对齐的是示例里的小于等于,不是一个拍出来的 0.5。协议把 G 写成别的数,比较对象跟着改,不等式方向不改。
该筛选不回答交易有没有在主网上最终确定,也不回答价格偏离会不会触发断路器。最终性是交易的时钟,断路器是偏离的开关,心跳是价格轮次的年龄。本篇只回答:在这一刻,规范是否允许采用这个价格。通过一次也不等于下一小时都可用。排序器可以再次停下,answer 从 0 变成 1,之前的「已过宽限期」立即作废。外推时至少给出这段时间里 answer 与恢复秒数的路径,不能用一次新鲜的 updatedAt 代表整段。
面板还常把「排序器在线」和「宽限期已过」画成同一个绿点。在线只满足第一行。恢复后的前 3600 秒里,绿点可以是真的,价格仍然不该被清算合约使用。笔记要分开写两行。
5. 一份别人能复现的记录
最低字段包括:L2 链 ID、排序器状态喂价地址、价格喂价地址、消费者合约地址及其 G 的来源(源码常量或读取到的存储)、同一笔调用的 answer、startedAt、价格 answer、updatedAt、区块时间、恢复秒数、是否 ≤ G、心跳秒数、价格年龄、三步判定各自的结果。两次 RPC 相差数秒就要分开记账。用浏览器里的「最后更新」文案代替 roundData,等于没核验。
对账顺序固定为:消费者是否读了状态喂价 → answer 含义 → 恢复秒数与 G → 最后才是 updatedAt 与心跳。未读状态喂价的协议,结论停在「检查缺失」。Arbitrum 上 startedAt 为 0 时停在「未初始化」,不计算宽限期比值。需要把 gas 资产换到该 L2 才能自行调用时,把兑换路径记在费用附注里,不要写进价格是否可用的判定。
6. 结论:可用要过两口时钟,不是 updatedAt 一口
加密研究的错误句式是「updatedAt 很新,所以这笔 L2 喂价可以用」。更可靠的句子是「answer;恢复秒数;G;是否 ≤ G;然后才是价格年龄与心跳」。answer 为 0 只说明排序器在运行。运行之后的宽限期,才决定这一轮新鲜价格能不能被采用。两口时钟缺一口,可用就是未完成核验的面板。
本文是分析框架和教学算例,未对任何 L2、预言机或消费者合约做质量背书,不构成投资建议。