Logo
新用户订阅 RPC,首月享 6.5 折优惠查看优惠
OnFinality Learn
可靠性与一致性阅读约 13 分钟

Polygon Bor 与 Erigon 客户端通过 RPC 处理重组

通过区块哈希作为键、沿 parentHash 回溯,并交叉核对 Heimdall 检查点,在 RPC 上检测并界定 Polygon PoS 重组。

TL;DR

Polygon PoS 重组并非简单的以太坊浅层重组:Bor 按 sprint 轮换区块生产者,最终性由锚定到以太坊 L1 的 Heimdall 检查点保障,因此一个区块可能在 Bor 上已是规范链,但尚未被检查点锚定。通过 RPC 检测重组的可靠方法是:以区块哈希而非高度为键,每次轮询重新读取链头,并沿 parentHash 向后回溯,直到重新连接到已存储的规范哈希,同时报告深度和分叉点。客户端选择很重要,因为 Bor(在 Erigon 迁移到 Bor 之后,Polygon 当前推荐的执行客户端)与旧版 Erigon 或 cdk-erigon 暴露的方法面有重叠但不完全相同,因此依赖特定客户端方法的索引器在端点由不同客户端提供服务时可能会失效。安全的设计以标准 eth_* 方法面加上检查点层为键,并将疑似重组与 Heimdall 检查点状态交叉核对,以区分真实重组与两个端点之间的不一致。

Polygon PoS 重组特征与以太坊 L1 的对比

在以太坊 L1 上,每个 slot 只选出一个提议者,因此由单个验证者产生的分叉具有可预测的形态:回滚集合的大小受限于该提议者在下一个诚实提议者扩展竞争链之前控制了多少个 slot。Polygon PoS 改变了这种形态,因为 Bor 按 sprint 而非按 slot 轮换区块生产者。一个 span 选出一个验证者子集,在该 span 内生产者每个 sprint 轮换一次,因此单个生产者可回滚的区块集合边界与以太坊 L1 模型不同。Polygon 关于 Bor 共识 的文档将 sprint、span 以及这种生产者轮换描述为 Bor 区块生产的核心。

第二个区别是最终性锚定。Heimdall 定期将 Bor 状态作为检查点提交到以太坊 L1,因此一个区块可能在 Bor 上已是规范链,但尚未被检查点锚定。这个间隙就是重组仍可能发生的窗口。实际上,这意味着 Polygon 索引器必须同时记住两层:Bor 执行层(区块、收据、状态)和 Heimdall/检查点层(最终性锚点)。将“区块在 Bor 上”与“区块已最终确定”混为一谈,是 Polygon 上大多数索引器 bug 的根源。关于生产者轮换机制的更多细节,请参阅 Polygon Bor sprint、span 与验证者集合。

  • Bor 在一个 span 内按 sprint 轮换生产者,因此单个生产者的分叉深度特征与以太坊 L1 基于 slot 的分叉不同。
  • Heimdall 检查点将 Bor 状态锚定到以太坊 L1;在区块被检查点锚定之前,它仍可能被重组。
  • 将“在 Bor 上规范”与“已最终确定”视为两种独立状态,而非同义词。

两层模型:Bor 执行层与 Heimdall 检查点

Bor 执行层是标准以太坊 JSON-RPC 端点所暴露的内容:eth_getBlockByNumber、eth_getBlockByHash、eth_getBlockReceipts、eth_getLogs,以及客户端支持时的 trace 模块。Heimdall/检查点层是一个独立的方法面,报告哪些 Bor 区块已提交到以太坊 L1。这两层通过不同的端点和不同的方法命名空间查询,并以不同的节奏更新。只读取 Bor 层的索引器可能观察到一个区块、将其索引,之后才发现它在任何检查点锚定之前已被重组掉。

实际后果是:你的重组检测器应在 Bor 层运行,但最终性确认应查询检查点层。当出现疑似重组时,检查点层会告诉你受影响的高度是否已被锚定。如果已被锚定,则两个端点不一致的可能性大于真实重组;如果尚未锚定,则真实重组是可能的。这种交叉核对正是区分真实重组与仅仅滞后或提供陈旧链头的端点之间的关键。通过 RPC 检测以太坊区块重组 一文介绍了这种 Polygon 特定设计所扩展的 L1 检测器模式。

  • Bor 层:用于区块、收据、日志和 trace 的 eth_* 方法。
  • Heimdall/检查点层:报告哪些 Bor 高度已锚定到以太坊 L1。
  • 用 Bor 层检测,用检查点层确认。

基于哈希键的重组检测与 parentHash 回溯

以太坊 JSON-RPC 规范定义 eth_getBlockByNumber 和 eth_getBlockByHash 返回一个区块,其 parentHash 将其链接到前驱区块,并且对未知区块都返回 null。这种 parentHash 链接正是重组检测器应使用的原语。以区块哈希而非高度为键,可以避免经典 bug:重组后某个高度被不同区块复用,从而静默损坏以高度为主键的索引。此行为的规范参考是 以太坊 JSON-RPC eth_getBlockByHash 文档。

一个有界检测器的工作方式如下:每次轮询时重新读取链头,将其哈希与最后存储的规范哈希比较,如果不同,则从新链头沿 parentHash 向后回溯,直到重新连接到已存储的规范哈希。重连点与旧链头之间的高度差就是重组深度,重连区块就是分叉点。用最大深度限制回溯,这样病态或配置错误的端点就无法让检测器无限循环。下面的代码是一个使用标准 eth_* 方法面的最小 Node.js 检测器。

const MAX_DEPTH = 128;

async function rpc(url, method, params) {
  const res = await fetch(url, {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params })
  });
  const json = await res.json();
  if (json.error) throw new Error(json.error.message);
  return json.result;
}

async function detectReorg(url, storedCanonical) {
  // storedCanonical: Map<height, hash> of blocks you consider canonical
  const head = await rpc(url, 'eth_getBlockByNumber', ['latest', false]);
  if (!head) return { status: 'no-head' };

  let cursor = head;
  let depth = 0;
  while (cursor && depth <= MAX_DEPTH) {
    const height = parseInt(cursor.number, 16);
    const known = storedCanonical.get(height);
    if (known && known === cursor.hash) {
      return {
        status: depth === 0 ? 'no-reorg' : 'reorg',
        depth,
        forkPointHeight: height,
        forkPointHash: cursor.hash,
        newHead: head.hash
      };
    }
    if (!cursor.parentHash || /^0x0+$/.test(cursor.parentHash)) break;
    cursor = await rpc(url, 'eth_getBlockByHash', [cursor.parentHash, false]);
    depth += 1;
  }
  return { status: 'unresolved', depth };
}

module.exports = { detectReorg };

客户端格局:Bor、旧版 Erigon 与 cdk-erigon

Polygon 一直在从 Erigon 迁移到 Bor 作为推荐的执行客户端,而 cdk-erigon 则用于 CDK 链。搜索 Polygon 重组处理时,第一页结果大多被客户端迁移和发布说明类内容占据,这说明读者的下一个问题是“用哪个客户端,这对我的数据意味着什么”。Bor 与旧版 Erigon 或 cdk-erigon 暴露的方法面和 trace 模块有重叠但不完全相同。依赖特定客户端方法的索引器在端点由不同客户端提供服务时会失效,即使链数据完全相同。

安全的设计以标准 eth_* 方法面加上检查点层为键,并将客户端特定扩展视为“有文档说明 / 因客户端而异”。如果你必须使用 trace 或 debug 方法,请在启动时检测客户端,并在缺失时大声失败,而不是静默降级。当你比较端点时,多端点 RPC 一致性与链头滞后 模式适用:两个端点可能在链头上不一致,但两者都没有错,这种不一致不是重组。关于 Polygon 端点的提供商层面背景,请参阅 Polygon RPC 提供商指南。

  • Bor 是当前 Polygon 推荐的执行客户端;旧版 Erigon 和 cdk-erigon 仍在生态系统中。
  • 方法面有重叠但不完全相同;trace 和 debug 模块因客户端而异。
  • 在启动时检测客户端,并在缺失方法时大声失败,而不是静默降级。

检查点交叉核对:真实重组与端点不一致

两个端点不一致并不自动意味着重组。一个端点可能滞后、提供陈旧链头或暂时分区。检查点层为你提供决胜依据:如果受影响的高度已在 Heimdall 上被检查点锚定,那么该高度发生真实重组就不太可能,更可能的解释是端点不一致。如果该高度尚未锚定,则真实重组仍有可能,此时你应信任基于哈希键的回溯,而非任何单个端点的链头。

交叉核对应成为检测器输出的一部分,而不是手动步骤。当检测器报告重组时,记录分叉点高度在检测时是否已被检查点锚定。随着时间推移,该字段会告诉你观察到的重组是否集中在未锚定窗口,而这正是两层模型预测它们应出现的位置。当你比较多个端点时,这个字段也是区分真实重组与提供商侧链头滞后伪影的关键。

  • 已锚定高度 + 端点不一致:怀疑端点不一致,而非重组。
  • 未锚定高度 + 哈希不匹配:真实重组是可能的。
  • 在每次检测到的重组上记录已锚定/未锚定标志。

可复现的测量:针对你的端点填写结果表

由于重组行为取决于你的端点、轮询节奏以及当时的网络状况,唯一诚实的描述方式就是自己测量。以固定间隔对你自己的端点运行上述检测器,记录每个重组事件的深度、分叉点和锚定标志,并在你选择的窗口内聚合。不要依赖任何已发布的延迟、吞吐量或速率数字,包括供应商材料中可能出现的任何数字;要针对你实际使用的端点进行测量。

下表是一个模板。从你自己的日志中填写“观测值”列。“文档化行为”列说明协议或客户端文档所支持的内容,这样你就能区分测量与假设。保持窗口和轮询间隔在各行之间固定,以便比较有意义。

为了让测量更具体,以下 curl 命令从你的端点获取最新区块。以与检测器相同的节奏运行它,并记录返回的哈希和编号;在相同高度比较连续哈希,是在原始 RPC 输出中发现重组的最简单方法。预期输出是一个 JSON 对象,包含 number(十六进制高度)、hash 和 parentHash 等字段;如果调用返回 null,则端点没有可提供的链头,你应将其视为端点问题而非重组。

  • 指标:观测到的重组深度(区块)—— 文档化行为:受每个 sprint 的生产者轮换和检查点锚定限制;观测值:从你的日志填写。
  • 指标:分叉点高度相对于链头 —— 文档化行为:parentHash 回溯在分叉点重新连接;观测值:从你的日志填写。
  • 指标:检测时的锚定标志 —— 文档化行为:未锚定高度可能被重组;观测值:从你的日志填写。
  • 指标:端点不一致计数 —— 文档化行为:不等同于重组;观测值:从你的日志填写。
  • 指标:检测器未解决率 —— 文档化行为:受 MAX_DEPTH 限制;观测值:从你的日志填写。
curl -s -X POST https://your-polygon-rpc-endpoint \
  -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"eth_getBlockByNumber","params":["latest",false]}'

轮询节奏、链头重读与有界回溯

检测器必须在每次轮询时重新读取链头,而不是缓存它,因为链头是重组可能发生的唯一可靠信号。缓存链头并仅比较高度会漏掉高度被复用的重组。parentHash 回溯应由你根据风险承受能力和观察窗口选择的最大深度来限制;如果回溯超过界限,报告“未解决”而不是猜测。未解决的结果是检查端点的信号,而不是重组。

轮询节奏与重组检测相互作用:非常慢的轮询可能完全跳过短重组,而非常快的轮询会增加负载并可能触及速率限制。没有普遍正确的节奏,任何具体数字都会是提供商特定或工作负载特定的选择。选择一个节奏,在测量窗口内保持固定,并将其与结果一起记录,这样数字才可解释。OnFinality Learn 中心 收集了相关的可靠性模式,如果你想跨链比较节奏。

  • 每次轮询都重新读取链头;绝不要只比较高度。
  • 限制 parentHash 回溯,并在超过界限时报告“未解决”。
  • 在测量窗口内固定轮询节奏,并将其与结果一起记录。

基于哈希键检测的局限与权衡

基于哈希键的检测配合 parentHash 回溯很稳健,但并非没有代价。它需要为每个高度存储一个规范哈希,这会随保留窗口增长,并且回溯的每一步都需要额外的 eth_getBlockByHash 调用。在深度重组时,回溯成本随深度增长,因此 MAX_DEPTH 界限也是成本界限。如果你的保留窗口很短,你可能很快重新连接,但会失去检测深度重组的能力;如果很长,你会付出更多存储和更多回溯步骤。

检查点交叉核对增加了对第二个方法面的依赖,这是第二个可能失败或滞后的东西。将检查点层故障视为“未知”,而不是“已锚定”或“未锚定”。最后,客户端特定方法是可移植性风险:任何依赖 trace 或 debug 扩展的检测器都绑定到提供该方法的客户端。标准 eth_* 方法面加上检查点层是可移植子集,也是本设计使用的子集。对于超出重组窗口的历史数据需求,请参阅 Polygon 归档节点与历史 RPC。

  • 存储随保留窗口增长;回溯成本随重组深度增长。
  • 检查点层故障应视为“未知”,而不是最终性信号。
  • 客户端特定方法降低可移植性;优先使用标准 eth_* 方法面。

排查常见的重组检测失败

最常见的失败是基于高度键的索引,重组复用一个高度,索引静默覆盖或重复数据。修复方法是以哈希为键,并将高度存储为属性,而不是主键。第二常见的失败是将两个端点不一致视为重组;修复方法是上述检查点交叉核对。第三是无界的 parentHash 回溯,在行为异常的端点上循环或超时;修复方法是 MAX_DEPTH 加上“未解决”状态。

第四个失败是静默的客户端漂移:原本提供 Bor 服务的端点开始提供不同客户端,客户端特定方法消失。修复方法是启动时检测客户端,并在缺失方法时大声失败。第五个是陈旧链头缓存,检测器与缓存的链头比较而漏掉重组;修复方法是每次轮询重新读取链头。如果你记录正确的字段,这些失败都可以在结果表中观察到。

  • 基于高度键的索引:改为基于哈希键的存储。
  • 两个端点不一致:添加检查点交叉核对。
  • 无界回溯:添加 MAX_DEPTH 和“未解决”状态。
  • 客户端漂移:在启动时检测客户端并大声失败。
  • 陈旧链头:每次轮询重新读取链头。

下一步:加固 Polygon 索引器

首先对你自己的端点运行检测器,并在固定窗口内填写结果表。然后添加检查点交叉核对和锚定标志到日志中,并重新运行窗口以便比较。一旦有了基线,根据观测到的深度而非假设来决定保留窗口和 MAX_DEPTH。如果你需要关于 Polygon 端点的提供商层面指导,Polygon RPC 提供商指南 和 Polygon 网络页面 是起点。

在生产环境中,将检测器与多端点一致性检查配对,这样单个滞后端点就无法伪装成重组,如果你正在规划端点容量,请查看 RPC 定价 和 API 服务。更广泛的可靠性模式位于 OnFinality Learn 中心,而本 Polygon 设计所扩展的 L1 检测器模式位于 通过 RPC 检测以太坊区块重组。

  • 运行检测器,填写结果表,然后添加检查点交叉核对。
  • 根据观测到的深度而非假设选择保留窗口和 MAX_DEPTH。
  • 在生产环境中将检测器与多端点一致性检查配对。

永远不用担心基础设施

OnFinality 消除了 DevOps 的繁重工作,让您能够更聪明、更快地构建。

开始