Logo
新用户订阅 RPC,首月享 6.5 折优惠查看优惠
OnFinality Learn
网络与协议指南阅读约 12 分钟

Base OP-Stack 最终性:通过 RPC 理解 Safe、Finalized 和 Latest 区块标签

了解 Base OP-Stack 的最终性阶段,以及如何在 JSON-RPC 调用中使用 safe、finalized 和 latest 区块标签。

TL;DR

在 Base(OP-Stack L2)上,JSON-RPC 区块标签 'latest'、'safe' 和 'finalized' 代表不同的最终性阶段。'latest' 是排序器的头部,可能发生重组;'safe' 已提交到不太可能重组的 L1 区块;'finalized' 是不可逆的。对于关键读取(如提款或索引器水位线),请使用 'finalized';对于大多数需要稳定性的应用逻辑,请使用 'safe'。

直接回答:在 Base 上,Safe、Finalized 和 Latest 分别代表什么?

当您查询 Base JSON-RPC 端点时,区块标签 latestsafefinalized 并不总是指向同一个区块。latest 是排序器所见的链头,可能被重组。safe 是其批次已提交到足够老的 L1 区块(被认为不太可能重组)的区块。finalized 是已通过 L1 最终性和 OP-Stack 派生过程完全确认的区块,实际上是不可逆的。本文解释了这些阶段背后的机制,以及如何在您的应用程序中正确使用它们。

有关权威细节,请参阅 Base 文档中的交易生命周期Optimism 文档中的交易最终性。这些是描述确切语义和当前行为的主要来源。

OP-Stack 架构如何产生最终性阶段

Base 是一个 OP-Stack(Optimism)L2。排序器提出区块,并定期将交易批次提交到 L1 合约。L1 合约以及争议游戏和输出根系统决定了 L2 区块何时被视为安全或最终。这个过程是异步的:排序器快速产生区块,但 L1 上的最终性需要时间。

Base 节点暴露的三个头部值为:

  • latest:排序器产生的最新区块。此区块尚未锚定到 L1,如果排序器重组或发生争议,则可能被重组。

  • safe:其批次已提交到足够老的 L1 区块(不太可能重组)的区块。这是大多数应用程序“可安全构建”的实际标记。

  • finalized:已通过 L1 最终性和规范 OP-Stack 派生完全确认的区块。这实际上是不可逆的。

这些阶段的确切深度和时间是文档化的行为,不是固定常量。它们取决于 L1 最终性和 OP-Stack 配置。作为开发人员,您不应假设固定的区块数或秒数;相反,应查询节点以获取当前的 safe 和 finalized 头部。

使用 JSON-RPC 查询 Safe、Finalized 和 Latest

您可以使用标准的 eth_getBlockByNumber 方法并传入区块标签参数来查询每个头部。区块标签可以是字符串,如 'latest''safe''finalized',也可以是 EIP-1898 区块号对象。例如,要获取最新区块:eth_getBlockByNumber('latest', false)。要获取安全区块:eth_getBlockByNumber('safe', false)。要获取最终区块:eth_getBlockByNumber('finalized', false)

返回的 safefinalized 区块号通常会落后于 latest。差异不是固定常量;它根据 L1 条件和排序器的批量提交计划而变化。您可以使用下面的脚本在您的环境中测量差异。

latest 用于不可逆读取是危险的。例如,如果您正在构建一个将最新区块记录为水位线的索引器,重组可能导致您处理一个后来被丢弃的区块。同样,如果您正在构造提款证明,您必须使用在 L1 上已最终确定的区块,而不仅仅是最新的 L2 区块。

可复现的 Node.js 脚本,用于测量最终性前沿

以下脚本连接到 Base 端点,获取最新、安全和最终的区块号,并打印差异。它还会随时间重新采样安全头部,以观察最终性前沿如何推进。将 YOUR_BASE_ENDPOINT 替换为您的实际端点 URL。

使用 Node.js(v18 或更高版本)运行脚本。它使用内置的 fetch API,因此不需要外部依赖。

const endpoint = 'YOUR_BASE_ENDPOINT';

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

async function getHeads() {
  const latest = parseInt(await rpc('eth_blockNumber', []), 16);
  const safe = parseInt((await rpc('eth_getBlockByNumber', ['safe', false])).number, 16);
  const finalized = parseInt((await rpc('eth_getBlockByNumber', ['finalized', false])).number, 16);
  return { latest, safe, finalized };
}

async function main() {
  console.log('Sampling Base finality heads...');
  const samples = [];
  for (let i = 0; i < 5; i++) {
    const heads = await getHeads();
    samples.push(heads);
    console.log(`Sample ${i+1}: latest=${heads.latest}, safe=${heads.safe}, finalized=${heads.finalized}, behind_safe=${heads.latest - heads.safe}, behind_finalized=${heads.latest - heads.finalized}`);
    await new Promise(resolve => setTimeout(resolve, 5000)); // wait 5 seconds
  }
  console.log('\nFill in the table below with your observed values:');
  console.log('| Sample | latest | safe | finalized | behind_safe | behind_finalized |');
  console.log('|--------|--------|------|-----------|-------------|------------------|');
  samples.forEach((s, i) => {
    console.log(`| ${i+1} | ${s.latest} | ${s.safe} | ${s.finalized} | ${s.latest - s.safe} | ${s.latest - s.finalized} |`);
  });
}

main().catch(err => { console.error(err); process.exit(1); });

预期输出和填写结果表格

当您运行脚本时,您将看到类似于以下的输出(实际数字因网络条件和端点而异):

在下面的表格中记录您自己的观察结果。这些值特定于环境,并会随时间变化。此方法允许您验证所选端点上的最终性行为。

  • | Sample | latest | safe | finalized | behind_safe | behind_finalized |
  • |--------|--------|------|-----------|-------------|------------------|
  • | 1 | | | | | |
  • | 2 | | | | | |
  • | 3 | | | | | |
  • | 4 | | | | | |
  • | 5 | | | | | |
Sample 1: latest=12345678, safe=12345670, finalized=12345660, behind_safe=8, behind_finalized=18

何时使用哪个头部?

选择正确的区块标签取决于您的用例。以下是一个决策指南:

  • 使用 finalized 用于:不可逆操作,例如构造提款证明、完成跨域转账或记录永久索引器水位线。这确保您永远不会构建在可能被重组的区块上。

  • 使用 safe 用于:大多数需要稳定链视图的应用逻辑,例如读取账户余额、执行依赖状态的智能合约调用,或构建不应被重组的交易。safe 是需要一致性的读取的推荐默认值。

  • 使用 latest 用于:实时监控、可容忍重组的面向用户的交易状态,或当您需要最新区块并了解重组风险时。

对于关键金融操作,始终优先使用 finalizedsafe 而不是 latest。例如,如果您正在构建一个在 Base 上锁定资产的桥,您应该等待 finalized 再考虑存款不可逆。

常见陷阱及如何避免

开发人员在处理 L2 最终性时经常犯错。以下是最常见的陷阱和修复方法:

  • latest 视为最终:这是最常见的错误。latest 可能重组。对于任何必须永久的内容,始终使用 safefinalized
  • 假设固定的重组深度latestsafe 之间的差异不是常量。它取决于 L1 条件和排序器批量提交。在您的环境中测量它,而不是硬编码一个值。
  • 使用 L2 收据进行跨域最终性:Base 上的交易收据并不意味着交易在以太坊上是最终的。跨域提款需要 Optimism 证明窗口,该窗口比 L2 最终性更长。有关详细信息,请参阅 Optimism 文档中的跨域通信
  • 忽略交错推进safefinalized 不会同步推进。safe 通常比 finalized 推进得更快。您的应用程序应独立处理两者。
  • 不使用 EIP-1898 查询特定区块:如果您需要查询特定区块号,请使用 EIP-1898 对象格式,例如 { blockNumber: '0x...' },以避免歧义。

局限性和权衡

虽然 safefinalized 提供了更强的保证,但它们也有权衡。safefinalized 区块落后于 latest,因此使用它们意味着您的应用程序会看到稍微延迟的链视图。对于大多数用例,这种延迟是可以接受的,但对于价格馈送或实时仪表板等实时应用程序,您可能需要使用 latest 并优雅地处理重组。

此外,safefinalized 的确切语义可能会随着 OP-Stack 的变化而演变。始终参考官方文档以获取最新行为。Base 文档Optimism 文档 是权威来源。

最后,请注意,此处描述的行为特定于像 Base 这样的 OP-Stack L2。其他 L2(例如 Arbitrum、zkSync)具有不同的最终性模型。不要假设相同的区块标签在不同网络上意味着相同的事情。

后续步骤和进一步阅读

既然您了解了 Base 最终性,您可以将此知识应用于构建更健壮的应用程序。有关 Base RPC 使用的更多详细信息,请探索以下资源:

  • API 服务 - 了解 OnFinality 的 API 产品。

永远不用担心基础设施

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

开始