Logo
新用户订阅 RPC,首月享 6.5 折优惠查看优惠
OnFinality Learn
基础设施与运维阅读约 12 分钟

OP Stack 节点客户端:op-node、op-geth、op-reth

理解 OP Stack 节点的双进程架构,以及一个 op-node 二进制文件如何服务于所有 Superchain 链。

TL;DR

OP Stack 节点并非单一二进制文件,而是由两个进程组成:rollup 共识/派生客户端(op-node)和以太坊执行客户端(op-geth 或 op-reth),op-node 通过 Engine API 驱动后者。op-node 从 L1 读取排序器批次数据,派生出规范的 L2 链,并通过 newPayload 和 forkchoiceUpdated 将区块负载推送给执行客户端。执行客户端对外提供标准的 eth_* JSON-RPC 接口,因此无论使用 op-geth 还是 op-reth,dapp 看到的 RPC 都是一致的。同一个 op-node 二进制文件通过加载链特定的 rollup 配置,即可服务于任意 Superchain 链——Optimism、Base、Unichain——而无需不同的二进制文件。本文阐述 OP Stack 节点客户端对的架构、配置、验证与故障排查。

OP Stack 节点的双进程模型

与 Geth 或 Nethermind 等单体以太坊客户端不同,OP Stack 节点由两个通过 Engine API 通信的独立进程组成。第一个是 rollup 共识与派生客户端,通常是 op-node,它读取 L1 链(以太坊)上的排序器批次数据和派生输入。第二个是以太坊执行客户端——op-geth(Geth 的分叉)或 op-reth(基于 Reth 的客户端)——负责维护 L2 状态、执行交易并对外提供标准的 eth_* JSON-RPC 接口。这种分离在 Optimism 节点架构 中有详细说明,是 OP Stack 链运行的基础。

由于两个进程相互独立,你必须同时运行它们并正确连接。op-node 不执行交易也不维护状态;它只从 L1 数据中派生规范的 L2 链,并指示执行客户端导入哪些区块。执行客户端不直接读取 L1;它完全依赖 op-node 提供新的区块负载和 forkchoice 更新。这种分工意味着,即使执行客户端本身完全健康,不健康的 L1 RPC 端点也会导致 op-node 停滞,进而拖累执行客户端。

op-node 与执行客户端之间的 Engine API 连接通过 JWT 密钥进行认证。这与以太坊共识层使用的 Engine API 相同,但在这里 op-node 扮演共识客户端的角色。执行客户端必须在启用 Engine API 并使用与 op-node 相同的 JWT 密钥的情况下启动。没有这种认证,op-node 无法推送区块负载,节点将无法同步。

  • op-node:rollup 共识/派生客户端;读取 L1、派生 L2、通过 Engine API 驱动执行客户端。
  • op-geth 或 op-reth:执行客户端;维护 L2 状态,对外提供 eth_* JSON-RPC。
  • Engine API:用于 newPayload 和 forkchoiceUpdated 调用的认证通道(JWT)。
  • 两个进程都必须运行;对于完整的 OP Stack 节点而言,二者缺一不可。

op-node 的角色:派生与 Engine API 交互

op-node 是 OP Stack 节点的大脑。它持续读取 L1 链上的两类数据:排序器批次交易(包含 L2 交易数据)和派生输入(如 L1 区块属性)。利用这些数据,它按照 rollup 协议规则派生出规范的 L2 链。派生过程在 Optimism 规范 中有详细说明,涉及一个跟踪 L1 来源和 L2 安全头的状态机。

一旦 op-node 派生出新的 L2 区块,它就会通过 Engine API 的 newPayload 方法将区块负载推送给执行客户端。随后调用 forkchoiceUpdated 设置新的头部,并在适用时设置安全区块和最终确定区块的哈希。这与以太坊共识层使用的 Engine API 流程相同,但带有 OP Stack 特有的负载属性。执行客户端验证并导入区块,更新其状态,并通过 eth_getBlockByNumber 使新区块可用。

op-node 还暴露自己的 JSON-RPC 命名空间 optimism_*,其中包括 optimism_syncStatus 和 optimism_outputAtBlock 等方法。这些方法对于监控派生进度和验证 L2 头部是否跟踪 L1 派生头部非常有用。如需深入了解 Engine API 调用,请参阅 Engine API newPayload 与 forkchoice。

  • 从 L1 读取批次和派生输入。
  • 按 rollup 规则派生规范的 L2 链。
  • 通过 Engine API 的 newPayload 和 forkchoiceUpdated 推送区块负载。
  • 暴露 optimism_* RPC 用于同步状态和输出根。

执行客户端:op-geth 与 op-reth

执行客户端负责维护 L2 状态、执行交易并提供标准的以太坊 JSON-RPC API。目前有两种主要实现:op-geth,即 Go Ethereum 的分叉;以及 op-reth,基于 Reth 客户端架构构建。两者实现相同的执行和状态转换逻辑,并暴露相同的 eth_* JSON-RPC 接口。这意味着从 dapp 的角度来看,选择哪种执行客户端不会改变 RPC 接口——eth_chainId、eth_blockNumber、eth_getBlockByNumber 以及所有其他标准方法的行为完全一致。

在 op-geth 和 op-reth 之间做选择是一种运维上的权衡。op-geth 受益于 Geth 代码库的成熟度和熟悉度,拥有丰富的工具和文档。op-reth 较新,由于其 Rust 实现和模块化架构,在某些工作负载下可能具有性能优势。不过,两者都已达到生产就绪状态,并被节点运营者使用。决策应基于团队的专业知识、监控栈和性能需求,而非 RPC 兼容性。

两种客户端都支持 Engine API,并可由 op-node 驱动。它们也支持相同的数据库格式和快照机制,不过 op-reth 的存储需求可能有所不同。运行节点时,必须确保执行客户端版本与 op-node 版本兼容,因为 Engine API 和 OP Stack 协议都在不断演进。

  • op-geth:Geth 分叉,成熟,工具熟悉。
  • op-reth:基于 Reth,Rust 实现,可能具有性能优势。
  • 两者暴露完全相同的 eth_* JSON-RPC 接口。
  • 对 dapp 而言,选择是运维层面的,而非功能层面的。

一个 op-node 二进制文件服务任意 Superchain 链

OP Stack 的一个关键设计特性是,单个 op-node 二进制文件通过加载链特定的配置,即可服务于 Superchain 中的任意链——Optimism、Base、Unichain 等。这种配置通常称为 rollup 配置或链配置,包含 L2 链 ID、系统配置合约地址、所派生的 L1 链以及排序器 feed URL 等参数。每条链没有单独的二进制文件;同一套代码在所有地方运行。

这就是“一个 Superchain”叙事背后的机制。启动 op-node 时,你为其提供目标链的 rollup 配置。例如,要运行 Base 节点,就使用 Base 的 rollup 配置;要运行 Optimism 节点,就使用 Optimism 的 rollup 配置。op-node 随后读取相应的 L1 合约并派生出正确的 L2 链。这种统一性简化了工具链,并允许基础设施提供商以相同的操作流程支持多条链。

rollup 配置通常以 JSON 文件形式分发,或从链注册表获取。它包含 L1 链 ID、L2 链 ID、批次收件箱地址、排序器地址以及其他参数。有关 OP Stack 链及其配置的列表,请参阅 Optimism 文档。

  • 所有 Superchain 链共用同一个 op-node 二进制文件。
  • 链特定参数通过 rollup 配置加载。
  • 配置包括 L2 链 ID、L1 链 ID、合约地址、排序器 feed。
  • 实现跨链统一的工具链和运维。

搭建 OP Stack 节点:构建与配置

要运行 OP Stack 节点,你需要构建或拉取 op-node 和执行客户端的二进制文件,然后配置它们协同工作。首先从官方仓库获取最新版本:op-node 来自 Optimism 单体仓库,op-geth 或 op-reth 来自各自的仓库。你还需要一个健康的 L1 以太坊 RPC 端点,因为 op-node 要从 L1 读取批次数据。这个 L1 端点是硬依赖;如果不可用,op-node 就无法派生新的 L2 区块。

接下来,为 Engine API 认证生成 JWT 密钥。op-node 和执行客户端必须使用相同的密钥。然后,在启用 Engine API 的情况下启动执行客户端,指向 JWT 密钥和数据目录。对于 op-geth,这通常涉及 --http、--http.api、--authrpc.jwtsecret 和 --rollup 等标志。对于 op-reth,存在类似的标志。执行客户端运行后,使用 L1 RPC URL、rollup 配置和执行客户端的 Engine API 端点启动 op-node。

最后,暴露执行客户端的 eth_* 端点供 dapp 读取。这是应用程序用来查询 L2 链的端点。你也可以暴露 op-node 的 optimism_* 端点用于监控。确保防火墙规则允许访问所需端口,并且有足够的磁盘空间容纳 L2 数据库,该数据库可能根据链的活动量而变得很大。

#!/bin/bash
# Example: Start op-geth and op-node for an OP Stack chain

# Generate JWT secret
openssl rand -hex 32 > jwt.hex

# Start op-geth (execution client)
./op-geth \
  --datadir ./datadir \
  --http \
  --http.addr 0.0.0.0 \
  --http.port 8545 \
  --http.api eth,net,web3,debug \
  --http.corsdomain '*' \
  --authrpc.addr 0.0.0.0 \
  --authrpc.port 8551 \
  --authrpc.jwtsecret ./jwt.hex \
  --rollup \
  --rollup.sequencerhttp https://mainnet-sequencer.optimism.io \
  --syncmode full

# Start op-node (rollup consensus client)
./op-node \
  --l1 https://ethereum-rpc.example.com \
  --l2 http://localhost:8551 \
  --rollup.config ./rollup.json \
  --rpc.addr 0.0.0.0 \
  --rpc.port 9545 \
  --p2p.listen.ip 0.0.0.0 \
  --p2p.listen.tcp 9003 \
  --p2p.listen.udp 9003 \
  --sequencer.enabled=false \
  --jwt-secret ./jwt.hex

验证节点健康与派生进度

启动节点后,必须验证它是否正确同步,以及派生过程是否诚实。首先调用执行客户端端点上的 eth_chainId,确认其与预期的 L2 链 ID 匹配。然后,间隔短暂时间调用两次 eth_blockNumber,确认头部在推进。如果区块号没有增加,说明节点没有同步。

接下来,使用 eth_getBlockByNumber 查询 'safe' 和 'finalized' 区块标签。随着派生赶上进度,这些标签应该向前推进。安全头代表从 L1 批次派生出的区块,而最终确定头代表在 L1 上也已最终确定的区块。如果安全头卡在不安全头(排序器头)之后,说明 op-node 没有派生出新的区块,可能是由于 L1 RPC 不健康或配置问题。

最后,查询 op-node 的 optimism_syncStatus,确认 L2 头部跟踪 L1 派生头部。该方法返回当前 L1 和 L2 头部的信息,以及安全 L2 头和最终确定 L2 头。如果 L2 头远远落后于 L1 头,派生可能滞后。有关使用 Engine API 获取同步状态的详细指南,请参阅 通过 Engine API 查看 OP Stack 节点同步状态。

// Node.js script to verify OP Stack node health
const { ethers } = require('ethers');

async function checkHealth() {
  const provider = new ethers.JsonRpcProvider('http://localhost:8545');
  const opNodeProvider = new ethers.JsonRpcProvider('http://localhost:9545');

  // 1. Check chain ID
  const chainId = await provider.getNetwork();
  console.log('Chain ID:', chainId.chainId);

  // 2. Check block number advancement
  const block1 = await provider.getBlockNumber();
  await new Promise(r => setTimeout(r, 5000));
  const block2 = await provider.getBlockNumber();
  console.log('Block number:', block1, '->', block2);
  if (block2 <= block1) console.warn('Head not advancing!');

  // 3. Check safe and finalized blocks
  const safe = await provider.getBlock('safe');
  const finalized = await provider.getBlock('finalized');
  console.log('Safe block:', safe?.number, 'Finalized block:', finalized?.number);

  // 4. Check op-node sync status
  const syncStatus = await opNodeProvider.send('optimism_syncStatus', []);
  console.log('Sync status:', JSON.stringify(syncStatus, null, 2));
}

checkHealth().catch(console.error);

不安全、安全与最终确定头:确认策略

OP Stack 定义了三种不同的区块标签:不安全(unsafe)、安全(safe)和最终确定(finalized)。不安全头是排序器产生的最新区块;它尚未从 L1 派生,如果排序器行为不当,可能会被重组。安全头是从 L1 批次派生出来的,被认为不受排序器重组的影响,但如果 L1 本身发生重组,它仍可能被重组。最终确定头是从以太坊上已最终确定的 L1 区块派生出来的,因此最安全,但也最滞后。

构建应用程序时,你必须有意选择确认策略。对于高价值交易,等待最终确定头能提供最强的保证。对于价值较低或时间敏感的操作,安全头可能就足够了。不安全头只应用于实时 UI 更新,不应用于结算。这是在延迟和安全性之间的权衡;最终确定头比不安全头滞后 L1 最终确定时间(以太坊上通常为 12-15 分钟)。

你可以使用 eth_getBlockByNumber 并传入 'safe' 和 'finalized' 标签来查询这些头,或者使用 op-node 上的 optimism_syncStatus 方法。有关 L1 派生时间戳的更多信息,请参阅 OP Stack L1 派生时间戳。

  • 不安全:排序器头,未派生,可能受排序器重组影响。
  • 安全:从 L1 批次派生,不受排序器重组影响,但尚未 L1 最终确定。
  • 最终确定:从 L1 最终确定的区块派生,保证最强,延迟最高。
  • 根据交易价值和延迟容忍度选择确认标签。

自托管与托管端点:权衡与选择

运行自己的 OP Stack 节点让你拥有完全的控制权、隐私和独立于第三方提供商的能力。然而,它需要大量资源:磁盘空间、带宽和持续维护。你必须监控节点、应用更新并处理 L1 RPC 依赖。对许多团队来说,托管端点更实用,尤其是在开发或低流量应用中。

托管 RPC 提供商(如 OnFinality)提供 OP Stack 链端点,抽象掉了节点基础设施。你无需运行任何软件即可获得可靠的 eth_* 端点。代价是你依赖提供商来保证正常运行时间和数据准确性。对于有高合规要求的生产系统,自托管可能是必要的。有关端点选项的比较,请参阅 Optimism RPC 端点(RPC Assistant)。

选择托管提供商时,要考虑速率限制、支持的方法和地理分布等因素。OnFinality 提供 API 服务,有多种方案;你可以查看 RPC 定价 以找到合适的层级。有关网络的更广泛概述,请访问 OnFinality Learn 中心。

  • 自托管:完全控制、隐私,但运维开销高。
  • 托管端点:维护成本低,但依赖提供商。
  • 考虑速率限制、方法支持和成本。
  • OnFinality 提供 OP Stack 链端点,方案灵活。

局限性与假设

本文假设你运行的是完整的 OP Stack 节点,而非排序器。排序器操作需要额外配置,本文不涉及。它还假设你拥有可靠的 L1 以太坊 RPC 端点;没有它,op-node 无法运行。性能和资源需求因链和执行客户端而异;你应该在预期负载下对设置进行基准测试。

OP Stack 正在积极开发中。客户端版本、配置标志和 RPC 方法可能会变化。请始终参考官方文档获取最新信息。本文描述的验证方法可复现,但确切结果将取决于你节点的同步状态和网络条件。我们不提供具体的延迟或吞吐量数字,因为这些取决于环境。

最后,本文侧重于客户端架构,不涵盖快照、修剪或自定义 rollup 配置等高级主题。有关这些内容,请查阅 Optimism 规范 和客户端特定文档。

  • 假设为完整节点,而非排序器。
  • 需要健康的 L1 RPC 端点。
  • 客户端版本和标志可能变化;请查阅官方文档。
  • 不提供具体性能数字;请自行测量。

故障排查手册

如果 op-node 无法启动或无法连接到执行客户端,请检查 Engine API 认证。确保两个进程使用相同的 JWT 密钥,并且执行客户端的 authrpc 端口可访问。常见错误包括 'invalid jwt' 或 'connection refused'。验证 JWT 文件路径和权限。

如果 op-node 正在运行但 L2 头没有推进,请检查 L1 RPC 端点。op-node 从 L1 读取批次数据;如果 L1 RPC 不健康或受到速率限制,派生将停滞。用简单的 eth_blockNumber 调用测试 L1 端点。同时,验证 rollup 配置与你打算运行的链匹配;链 ID 不匹配会导致 op-node 派生错误的链。

如果安全头卡在不安全头之后,说明 op-node 没有派生出新的安全区块。这可能是由于 L1 批次提交滞后或批次收件箱合约出现问题。检查 rollup 配置中的 L1 批次收件箱地址,确保它与链的当前合约匹配。有关重组处理的更多信息,请参阅 Bor 和 Erigon 客户端重组处理。

  • Engine API 认证失败:检查 JWT 密钥和端口。
  • op-node 不推进:验证 L1 RPC 健康状况和 rollup 配置。
  • 安全头卡住:检查批次收件箱合约和 L1 批次提交。
  • 链 ID 不匹配:确认 rollup 配置与目标链匹配。

后续步骤与更多资源

节点同步后,你可以将 dapp 连接到执行客户端的 eth_* 端点。对于生产环境,考虑设置对区块高度、对等节点数量和派生滞后的监控。你可以使用 optimism_syncStatus 方法跟踪派生进度。如果你更喜欢托管解决方案,可以探索 Optimism RPC 端点(RPC Assistant) 或 OnFinality 网络页面。

要加深理解,请查阅 Optimism 规范 了解派生细节,以及 Optimism 节点架构 获取运维指导。有关 OP Stack RPC 的相关主题,请参阅我们的文章 Engine API newPayload 与 forkchoice 和 通过 Engine API 查看 OP Stack 节点同步状态。

最后,及时了解客户端版本。op-geth 和 op-reth 都在积极开发中,新版本可能包含性能改进和错误修复。定期检查官方仓库并升级节点,以享受最新功能。

  • 将 dapp 连接到执行客户端的 eth_* 端点。
  • 监控区块高度、对等节点数量和派生滞后。
  • 考虑使用托管端点以实现低维护设置。
  • 保持客户端更新,并参考官方文档。

永远不用担心基础设施

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

开始