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

以太坊信标链轻客户端最终性与通过 Beacon API 的乐观更新

一份实用指南,介绍如何通过 Beacon REST API 消费以太坊共识层轻客户端更新,并区分乐观头部更新与最终性检查点。

TL;DR

以太坊合并后的架构将执行层(EL)与共识层(CL)分离。轻客户端通过同步委员会签名验证 CL 区块头,然后信任其提交的 EL 区块哈希。Beacon REST API 暴露了轻客户端端点:bootstrap 用于初始化同步委员会,optimistic_update 通过签名推进头部但并非最终确定,finality_update 携带 justification/finalization 对,finalized_root 允许重新引导。本文解释每个端点、如何根据分叉摘要验证更新,并提供可运行的 Node.js 示例。还涵盖持久化、跨同步委员会周期轮换的重新引导、用于在自有端点上验证更新的结果表,以及轻客户端信任的诚实局限性。

合并后的双层分离

自合并以来,以太坊作为两个不同的层运行:处理交易并维护状态的执行层(EL),以及为区块排序并提供最终性的共识层(CL)。EL 在 eth_ 命名空间下暴露 JSON-RPC 方法,而 CL 暴露由 Ethereum Beacon API 规范标准化的 REST API。轻客户端必须验证 CL 区块头,然后信任其提交的 EL 区块哈希。

连接两层的机制是嵌入在每个信标区块中的执行负载头。当 CL 区块头被验证后,客户端读取 body.execution_payload_header.block_hash 字段,这是共识层对特定执行区块的承诺。由于该哈希被信标区块根上的同步委员会签名所覆盖,篡改它会使签名失效,因此 EL 哈希继承了 CL 区块头的最小信任保证。

这种分离意味着轻客户端无法在没有额外证明的情况下直接验证执行层状态。相反,它验证 CL 区块头,提取 EL 区块哈希,然后使用该哈希向 EL 节点查询区块数据或状态证明。要深入了解执行层证明,请参阅 EVM eth_getProof 状态与存储证明。

  • EL:JSON-RPC eth_ 命名空间,处理交易、状态和收据。
  • CL:Beacon REST API,处理共识、同步委员会和最终性。
  • 轻客户端:通过同步委员会签名验证 CL 区块头,然后信任 EL 区块哈希。

Beacon API 轻客户端端点及其作用

Beacon API 定义了四个轻客户端端点:bootstrap、optimistic_update、finality_update 和 finalized_root。每个端点在轻客户端同步协议中都有特定用途。bootstrap 端点(GET /eth/v1/beacon/light_client/bootstrap/{block_root})从已知的最终化根初始化同步委员会和当前区块头。optimistic_update 端点(GET /eth/v1/beacon/light_client/optimistic_update)使用同步委员会签名推进头部,但它指定的头部并非最终确定,可能被重组。

每个响应都携带一个 data 对象,其中包含 header(或 attested_header/finalized_header)以及包含聚合 BLS 签名和参与位域的 sync_aggregate。bootstrap 响应还包含 current_sync_committee 和 current_sync_committee_branch,后者是将委员会绑定到最终化区块头状态根的 Merkle 证明。客户端存储该分支,以便之后无需重新获取完整状态即可证明委员会成员资格。

finality_update 端点(GET /eth/v1/beacon/light_client/finality_update)携带由同步委员会签名的 justification/finalization 对,提升最终化检查点。finalized_root 端点(GET /eth/v1/beacon/light_client/finalized_root)提供一个可用于重新引导的最终化根。这些端点在 Ethereum Beacon API 规范中有文档说明。

  • Bootstrap:从最终化根初始化同步委员会和当前区块头。
  • Optimistic update:使用同步委员会签名推进头部,并非最终确定。
  • Finality update:携带 justification/finalization 对,提升最终化检查点。
  • Finalized root:提供用于重新引导的最终化根。

为什么乐观更新是最小信任但并非最终确定

乐观更新相对于同步委员会是最小信任的:它包含来自委员会绝对多数成员的签名,该委员会是随机选择并定期轮换的。然而,它指定的头部并非最终确定,可能被重组。立即依赖头部的应用程序可能容易受到短重组的影响。有关重组检测策略,请参阅 以太坊区块重组检测与 RPC 深度。

头部并非最终确定的原因在于,以太坊的最终性需要两个连续的已证明 epoch,这至少需要两个 epoch(正常情况下大约 12.8 分钟)才能完成。乐观更新仅证明当前同步委员会的绝对多数成员证明了一个区块头;它并不证明相应的 epoch 已被证明或最终确定。因此,在最终性赶上之前,仍可能发生比乐观头部更深的重组。

相比之下,最终性更新提升最终化检查点,这是持久的且极不可能被回滚。这是应用程序应用于不可逆操作的锚点。区别至关重要:乐观更新提供低延迟的头部信息,而最终性更新提供安全保证。

  • 乐观更新:由同步委员会签名,但头部可能被重组。
  • 最终性更新:提升最终化检查点,持久锚点。
  • 使用乐观更新进行低延迟读取;使用最终性更新进行不可逆操作。

验证同步委员会签名与分叉摘要

同步委员会签名根据聚合公钥和分叉摘要进行验证。分叉摘要源自分叉版本和创世验证者根,确保来自错误分叉的更新被拒绝。以太坊共识规范定义了确切的验证过程。轻客户端必须计算签名根、聚合公钥并验证 BLS 签名。

具体来说,客户端通过获取 sync_committee_bits 位域并选择位被设置的委员会成员的公钥,然后聚合它们来重建 SyncAggregate。它将签名根计算为包含对象根和域的 SigningData 容器的哈希树根,其中域由分叉摘要和 DOMAIN_SYNC_COMMITTEE 类型构建。然后对签名根、聚合公钥和 sync_committee_signature 运行 BLS FastAggregateVerify。

分叉版本包含在区块头中,必须与客户端期望的分叉版本匹配。如果客户端收到分叉版本不匹配的更新,应拒绝它。这可以防止跨分叉重放攻击,并确保客户端遵循正确的链。

  • 根据聚合公钥验证 BLS 签名。
  • 检查分叉摘要是否与期望的分叉版本匹配。
  • 拒绝分叉版本不匹配的更新。

交接给执行层:EL 区块哈希与 eth_getBlockByHash

一旦 CL 区块头被验证,轻客户端就从区块头中提取 EL 区块哈希。该哈希是交接给执行层读取的切入点。然后客户端可以在 EL 节点上调用 eth_getBlockByHash 以检索完整区块,或调用 eth_getProof 以验证特定状态。有关 EL 查询的更多信息,请参阅 以太坊 RPC 节点指南(RPC Assistant)。

交接的强度仅取决于你查询的 EL 节点。如果 EL 节点是诚实的,它会返回哈希与已验证承诺匹配的区块;如果不是,它可以返回不同的区块或过时状态,而轻客户端仅凭 CL 区块头无法检测到这一点。这就是为什么 EL 哈希是一种承诺而非证明:它将 CL 区块头绑定到 EL 区块,但并不证明该区块的内容。

需要注意的是,轻客户端并不验证执行层状态本身;它信任 EL 节点为给定区块哈希返回正确的数据。对于无需信任的状态证明,需要像 eth_getProof 这样的额外机制,这些内容在 EVM eth_getProof 状态与存储证明中有所涵盖。

  • 从已验证的 CL 区块头中提取 EL 区块哈希。
  • 使用 eth_getBlockByHash 从 EL 节点获取完整区块。
  • 轻客户端信任 EL 节点获取状态;使用 eth_getProof 进行无需信任的证明。

跨同步委员会周期的持久化与重新引导

同步委员会大约每 256 个 epoch(约 27 小时)轮换一次,这一周期在共识规范中有记录,并可能随网络升级而变化。轻客户端必须持久化引导数据和当前周期。当周期变化时,客户端必须使用新周期的最终化根重新引导。finalized_root 端点提供这样的根。

轮换并非在单个 slot 处硬切换;周期 N 的委员会在一系列 slot 内有效,客户端应跟踪随引导和更新响应一起返回的 period 字段。当当前 slot 跨入新周期时,旧委员会的签名不再能根据新委员会的聚合公钥验证,因此客户端必须获取新的引导,其 current_sync_committee 属于新周期。持久化引导意味着存储区块头、委员会、委员会分支和周期索引,以便客户端在重启后无需从头重新获取即可恢复。

未能重新引导将导致签名无效,因为同步委员会已更改。客户端应监控周期并在必要时触发重新引导。这是长期运行的轻客户端的关键操作细节。

  • 同步委员会每约 256 个 epoch(约 27 小时)轮换一次。
  • 持久化引导数据和当前周期。
  • 当周期变化时使用 finalized_root 重新引导。

可运行的 Node.js 示例:Bootstrap、乐观更新、最终性更新

以下 Node.js 脚本使用 fetch 与 Beacon API 端点交互。它从已知的最终化根引导,获取乐观更新,获取最终性更新,并打印分叉版本。将 BEACON_API_URL 替换为你的提供者端点。请注意,某些共识节点可能禁用了轻客户端服务器标志,因此端点可能返回 404。

此示例假设你有一个可信的最终化根。在实践中,你会从可信来源或先前的最终性更新中获取它。脚本打印乐观更新中的 slot 和头部根、最终性更新中的最终化 slot 和根,以及区块头中的分叉版本。

const BEACON_API_URL = 'https://your-beacon-node.example.com';
const FINALIZED_ROOT = '0x...'; // Replace with a known finalized root

async function fetchLightClientData() {
  // 1. Bootstrap
  const bootstrapRes = await fetch(`${BEACON_API_URL}/eth/v1/beacon/light_client/bootstrap/${FINALIZED_ROOT}`);
  const bootstrap = await bootstrapRes.json();
  console.log('Bootstrap slot:', bootstrap.data.header.beacon.slot);
  console.log('Fork version:', bootstrap.data.header.beacon.fork_version);

  // 2. Optimistic update
  const optimisticRes = await fetch(`${BEACON_API_URL}/eth/v1/beacon/light_client/optimistic_update`);
  const optimistic = await optimisticRes.json();
  console.log('Optimistic slot:', optimistic.data.attested_header.beacon.slot);
  console.log('Optimistic head root:', optimistic.data.attested_header.beacon.body_root);

  // 3. Finality update
  const finalityRes = await fetch(`${BEACON_API_URL}/eth/v1/beacon/light_client/finality_update`);
  const finality = await finalityRes.json();
  console.log('Finalized slot:', finality.data.finalized_header.beacon.slot);
  console.log('Finalized root:', finality.data.finalized_header.beacon.body_root);

  // 4. Fork version from header
  console.log('Fork version from finality header:', finality.data.finalized_header.beacon.fork_version);
}

fetchLightClientData().catch(console.error);

持久化引导并在周期轮换时重新引导

长期运行的轻客户端不能仅将引导保存在内存中;它必须将引导区块头、当前同步委员会、委员会分支和周期索引写入持久存储,以便重启不会强制进行完整的重新同步。一种实用的布局是以引导时使用的最终化根为键的小型 JSON 或键值记录,并将周期索引一起存储,以便客户端可以将其与最新更新的 slot 所暗示的周期进行比较。

重新引导的触发条件是周期不匹配:当传入更新的 slot 落入与存储的周期不同的周期时,客户端从 finalized_root 端点获取新的引导,并替换存储的委员会和分支。由于最终化根本身就是最终化检查点,新引导锚定在与原始引导相同的安全假设上,一旦新委员会被验证,客户端就可以丢弃旧委员会。

在操作上,这意味着客户端应将引导视为具有显式失效规则的缓存,而不是一次性初始化。在每次重新引导时记录周期索引和最终化根,可以审计客户端在轮换期间是否保持在正确的委员会上,并揭示提供者返回过时最终化根的情况。

  • 持久化区块头、同步委员会、委员会分支和周期索引。
  • 当更新的 slot 跨入新周期时触发重新引导。
  • 从 finalized_root 端点获取新的引导并替换存储的委员会。
  • 在每次重新引导时记录周期索引和最终化根以供审计。
const BEACON_API_URL = 'https://your-beacon-node.example.com';

// Minimal in-memory store; swap for a file or database in production.
let store = { period: null, finalizedRoot: null, committee: null };

async function bootstrapFrom(finalizedRoot) {
  const res = await fetch(`${BEACON_API_URL}/eth/v1/beacon/light_client/bootstrap/${finalizedRoot}`);
  const { data } = await res.json();
  store = {
    period: data.current_sync_committee_branch ? data.header.beacon.slot : null,
    finalizedRoot,
    committee: data.current_sync_committee,
  };
  return data;
}

async function maybeRebootstrap(updateSlot, currentPeriod) {
  if (store.period !== currentPeriod) {
    const rootRes = await fetch(`${BEACON_API_URL}/eth/v1/beacon/light_client/finalized_root`);
    const { data } = await rootRes.json();
    await bootstrapFrom(data.root);
    console.log('Re-bootstrapped for period', currentPeriod, 'at slot', updateSlot);
  }
}

// Example: call maybeRebootstrap(updateSlot, periodFromSlot(updateSlot)) on each update.

针对你的端点的可复现结果表

要衡量你自己端点的行为,请用运行中的值填写下表。这有助于比较提供者并检测异常。多次运行脚本以观察 slot 推进和最终性延迟。

表格应包含:引导 slot、乐观 slot 推进(乐观 slot 与引导 slot 之差)、最终性 slot、最终化根、分叉版本和同步委员会周期。随时间记录这些值以了解端点的性能。

  • 引导 slot:bootstrap 返回的区块头的 slot。
  • 乐观 slot 推进:乐观 slot 减去引导 slot。
  • 最终性 slot:最终化区块头的 slot。
  • 最终化根:最终化区块头的 body root。
  • 分叉版本:来自区块头(例如 0x03000000)。
  • 同步委员会周期:当前周期索引。

结果表:在自有端点上验证轻客户端更新

下表是用于记录针对你自己的 Beacon API 端点执行每个验证步骤结果的模板。由于轻客户端行为取决于提供者、网络和你查询的时刻,这些值预计在每次运行之间会有所不同;重点是持续捕获它们,以便比较端点并发现回归。每次运行填写一行,并将原始 JSON 响应与行一起保存以供后续检查。

当你验证更新时,重要的检查是:BLS 签名根据从存储的委员会派生的聚合公钥验证通过,分叉摘要与你的期望分叉版本匹配,最终性更新中的最终化根与你用于重新引导的根匹配,以及更新 slot 所暗示的周期与存储的周期匹配。将每项检查记录为通过或失败,而不仅仅是最终结论,可以在出现问题时清楚地看出哪一步失败。

将表格作为活文档使用:在提供者变更后、在改变分叉版本的网络升级后,以及在你自己的持久化逻辑发生任何变化后重新运行它。由于同步委员会会轮换,对一个周期准确的表格可能对下一个周期不准确,因此请在每一行中包含周期索引。

  • 运行:时间戳或运行标识符。
  • 使用的引导 slot 和最终化根。
  • 乐观 slot 及相对于引导的 slot 推进。
  • 返回的最终性 slot 和最终化根。
  • 在区块头中观察到的分叉版本。
  • 同步委员会周期索引。
  • 签名验证结果(通过/失败)。
  • 分叉摘要匹配结果(通过/失败)。
// Sketch of a verification harness that prints one table row per run.
async function verifyOnce() {
  const bootstrap = await (await fetch(`${BEACON_API_URL}/eth/v1/beacon/light_client/bootstrap/${FINALIZED_ROOT}`)).json();
  const optimistic = await (await fetch(`${BEACON_API_URL}/eth/v1/beacon/light_client/optimistic_update`)).json();
  const finality = await (await fetch(`${BEACON_API_URL}/eth/v1/beacon/light_client/finality_update`)).json();

  const row = {
    bootstrapSlot: bootstrap.data.header.beacon.slot,
    optimisticSlot: optimistic.data.attested_header.beacon.slot,
    finalitySlot: finality.data.finalized_header.beacon.slot,
    finalizedRoot: finality.data.finalized_header.beacon.body_root,
    forkVersion: finality.data.finalized_header.beacon.fork_version,
  };
  console.log(JSON.stringify(row));
}

verifyOnce().catch(console.error);

排查常见的 Beacon API 轻客户端问题

如果 bootstrap 端点返回 404,你的共识节点可能禁用了轻客户端服务器标志。请检查提供者的文档。一些提供者默认不启用轻客户端端点。有关以太坊节点列表,请参阅 以太坊 RPC 节点指南(RPC Assistant)。

404 也可能意味着你传入的最终化根对该节点未知,例如节点仍在同步或该根属于不同的网络。区分这两种情况很简单:查询一个已知良好的端点,例如节点的版本或健康路由,并在假设轻客户端标志关闭之前确认网络。如果节点健康且位于正确的网络上,但 bootstrap 仍然 404,则该标志很可能是原因。

如果签名验证失败,请确保分叉版本匹配,并且你使用的是来自 bootstrap 的正确聚合公钥。如果周期已更改,请重新引导。如果遇到速率限制,请考虑使用像 OnFinality 的 API 服务这样的专用提供者,或查看 RPC 定价以获取更高的限制。

  • bootstrap 返回 404:轻客户端服务器标志可能已禁用。
  • 签名验证失败:检查分叉版本和聚合公钥。
  • 周期变化:使用 finalized_root 重新引导。
  • 速率限制:使用具有更高限制的提供者。

轻客户端信任的局限性与权衡

Beacon API 是 CL 客户端的 REST 接口,其路径由共识规范标准化,但可用性和速率限制因提供者而异。某些共识节点可能禁用了轻客户端服务器标志,导致 404。浏览器中的同步委员会验证仍然需要来自同一可信引导的聚合公钥,因此信任模型并非完全无需信任。

信任模型最好描述为一系列假设:你信任引导源为你提供正确的最终化根,你信任同步委员会是诚实的且足够去中心化,你信任 EL 节点提供执行层数据。每个环节都比从创世验证每个区块的完整共识客户端弱,但组合起来比运行完整节点便宜得多,并且对于许多面向读取的应用程序来说已经足够。

轻客户端提供最小信任的最终性,而不是任意执行层存储的完全无需信任状态证明。对于无需信任的状态证明,你需要像 eth_getProof 这样的额外机制。此外,轻客户端信任 EL 节点提供执行层数据,因此整体信任模型是 CL 验证和 EL 信任的组合。有关 EL 信任的更多信息,请参阅 以太坊归档节点与历史 RPC。

  • Beacon API 的可用性和速率限制因提供者而异。
  • 轻客户端服务器标志可能被禁用,导致 404。
  • 浏览器验证需要来自可信引导的聚合公钥。
  • 轻客户端提供最小信任的最终性,而非完全无需信任的状态证明。

下一步:将轻客户端更新集成到你的应用程序中

要集成轻客户端更新,首先选择一个支持 Beacon API 轻客户端端点的提供者。使用 bootstrap 端点进行初始化,然后轮询 optimistic_update 获取头部信息,轮询 finality_update 获取最终性。持久化引导和周期,并在周期变化时重新引导。对于生产环境,考虑使用像 OnFinality 的 API 服务这样的可靠提供者,并查看 RPC 定价以满足你的需求。

一种合理的集成模式是运行一个后台循环,以短间隔获取乐观更新以跟踪头部,以较长间隔获取最终性更新以获取持久锚点,并将两者写入你的持久化层。仅将不可逆操作限制在最终化检查点上,并将乐观头部视为参考。当周期索引变化时,暂停循环,重新引导,并在新委员会验证后恢复。

如需进一步阅读,请浏览 OnFinality Learn 中心获取更多关于以太坊可靠性与一致性的指南。此外,查看 以太坊网络页面了解网络特定细节。在构建过程中,使用 以太坊 eth_syncing 与节点同步状态监控节点的同步状态,以确保你的 EL 节点健康。

  • 选择支持轻客户端端点的提供者。
  • 引导,然后轮询乐观更新和最终性更新。
  • 持久化引导和周期;在周期变化时重新引导。
  • 监控 EL 节点同步状态以确保健康。

永远不用担心基础设施

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

开始