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_* 端点。
- 监控区块高度、对等节点数量和派生滞后。
- 考虑使用托管端点以实现低维护设置。
- 保持客户端更新,并参考官方文档。