Base OP-Stack 节点通过两条不同的路径获知新区块:排序器 Feed,一种发布/订阅流,可立即传递不安全区块;以及 L1 派生,它从发布到以太坊的批次和状态根重建安全链。来自 Feed 的不安全头可能与从 L1 派生的安全头不同,两者都通过 op-node RPC 方法(如 optimism_syncStatus 或 Engine API 的 forkchoice 状态)暴露。若应用程序信任不安全头来执行涉及价值的操作,则面临重组风险,因为只有安全头和最终头才具备 L1 支持的保证。本文解释了数据流模型,展示了可运行的 Node.js 和 curl 示例,用于读取头和重连 Feed,并提供了一个可复现的结果表,用于在您自己的端点上测量不安全到安全的差距。
OP-Stack 数据流模型:排序器、Feed 与派生
在 docs.optimism.io/stack/rollup/overview 记录的 OP-Stack 模型中,排序器对交易进行排序并传播生成的区块。消费者 op-node 连接到排序器 Feed(一种通常通过 WebSocket 或 HTTP 承载的发布/订阅流),以立即将这些区块作为不安全区块接收。同时,同一节点从发布到 L1 的排序器批次和状态根派生安全链。
这种双路径设计意味着节点始终至少有两个链视图:快速、由排序器提供的不安全头,以及较慢、由 L1 派生的安全头。OP Stack Rollup 节点规范详细描述了排序器 Feed 和 op-node 行为,包括非排序器消费者如何连接到排序器端点。
对于运维人员,实际后果是 Feed 是一种延迟优化,而非信任锚点。Feed 告诉您排序器当前正在生成什么;派生告诉您 L1 已证明什么。两者都需要才能获得完整视图,而 Base OP Stack 最终性、安全区块与最终区块 一文介绍了这些头如何通过 RPC 标记。
- 排序器:对交易排序并将区块传播到 Feed。
- Feed:向消费者 op-node 传递不安全区块的发布/订阅流。
- 派生:从 L1 批次和状态根重建安全链。
- 不安全头:从 Feed 看到的最新区块,可能发生重组。
- 安全头:L1 派生的区块,等同于 L2 安全标签。
- 最终头:L1 最终确定的区块,最强的保证。
为什么不安全头与安全头会分叉
不安全头在排序器发布区块后立即推进,而安全头仅在相应批次被包含在 L1 区块中并经派生处理后推进。这种时间差是两者分叉的主要原因。在正常运行下,不安全头领先安全头一定数量的区块;在 L1 拥堵或排序器重启期间,差距可能扩大。
分叉不一定是错误。这是将快速排序与 L1 支持的结算分离的 rollup 的预期行为。当应用程序将不安全头视为安全头时,风险就出现了。由于不安全头可能发生重组,任何仅基于它的涉及价值的操作都可能被作废。Base OP-Stack 重组深度与索引器回滚 一文讨论了如何为索引器推理回滚深度。
要读取两个头,您可以查询 op-node 的 rollup 或 admin RPC。optimism_syncStatus 方法返回不安全、安全和最终 L2 区块号及其 L1 来源,为您提供节点视图的单一快照。
- 不安全头领先,因为 Feed 比 L1 包含更快。
- 安全头滞后于发布和派生批次所需的时间。
- 最终头进一步滞后,等待 L1 最终性。
- 差距扩大是检查 L1 健康状况和排序器状态的信号。
使用 optimism_syncStatus 读取不安全、安全和最终头
op-node 通过其 RPC 接口暴露 optimism_syncStatus。一次调用即可返回当前不安全、安全和最终 L2 区块号、它们的 L1 来源以及节点的同步状态。这是在运行节点上观察 Feed 与派生关系的最直接方式。
下面的示例使用 curl 针对本地 op-node RPC 端点。将 URL 替换为您自己的 op-node RPC 地址。响应结构在 OP Stack Rollup 节点规范中有记录,并且在包括 Base 在内的 OP-Stack 链上保持稳定。
如果您通过托管端点而非本地 op-node 连接,请检查暴露了哪些方法。Base RPC 端点(RPC Assistant) 页面描述了标准 Base RPC 接口,而像 optimism_syncStatus 这样的 op-node 特定方法通常仅在您自己的节点或明确暴露它们的提供商上可用。
curl -s -X POST http://localhost:9545 \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"optimism_syncStatus","params":[]}' \
| jq '{unsafe: .result.unsafe_l2.number, safe: .result.safe_l2.number, finalized: .result.finalized_l2.number, unsafe_l1: .result.unsafe_l2.l1origin.number, safe_l1: .result.safe_l2.l1origin.number}'排序器 Feed 区块负载的 JSON 结构
排序器 Feed 通过发布/订阅传输以 JSON 消息传递区块负载。确切信封因客户端和传输而异,但负载通常包含执行负载字段:父哈希、区块号、状态根、时间戳、交易以及 L1 来源元数据。理解此结构有助于您解析 Feed 消息并将其与派生区块进行比较。
下面的示例展示了代表性的负载结构。字段名遵循 op-node 使用的 Engine API 执行负载约定。将其视为结构参考;始终根据节点实际发出的消息进行验证,因为提供商特定的帧格式可能不同。
当您直接消费 Feed 时,您需要自行跟踪不安全头。节点自身的 optimism_syncStatus 仍然是安全头和最终头的权威来源,Feed 不携带这些信息。
{
"parentHash": "0xabc...",
"blockNumber": "0x112a880",
"stateRoot": "0xdef...",
"timestamp": "0x66f1a2c0",
"transactions": ["0x02f8..."],
"l1Origin": {
"blockNumber": "0x14a2b3c",
"blockHash": "0x123..."
}
}订阅 Feed 并使用退避重连
消费者 op-node 连接到排序器端点以接收 Feed。当 Feed 断开或节点重启时,节点回退到 L1 派生并必须追赶。仅监视 Feed 的应用程序在此窗口期间会看到缺口,因为 Feed 不会重放错过的区块。正确的恢复模式是使用指数退避重连,然后与 optimism_syncStatus 对账以检测任何缺口。
下面的 Node.js 示例实现了带退避的重连循环和对账步骤。它使用 WebSocket 连接到 Feed,并通过 HTTP 定期轮询 optimism_syncStatus。将端点替换为您自己的。该模式与传输无关;相同的逻辑适用于 HTTP 长轮询 Feed。
重连后,将您从 Feed 看到的最后一个区块号与 optimism_syncStatus 报告的不安全头进行比较。如果节点的不安全头领先,则您错过了区块,应从节点的 RPC 回填,而不是假设 Feed 会重新发送它们。
const WebSocket = require('ws');
const fetch = require('node-fetch');
const FEED_URL = 'ws://localhost:8546';
const RPC_URL = 'http://localhost:9545';
let lastSeen = 0;
let backoff = 1000;
async function syncStatus() {
const res = await fetch(RPC_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'optimism_syncStatus', params: [] })
});
const json = await res.json();
return json.result;
}
function connect() {
const ws = new WebSocket(FEED_URL);
ws.on('open', () => { backoff = 1000; console.log('feed connected'); });
ws.on('message', (data) => {
const block = JSON.parse(data);
lastSeen = parseInt(block.blockNumber, 16);
console.log('unsafe block', lastSeen);
});
ws.on('close', async () => {
const status = await syncStatus();
const nodeUnsafe = parseInt(status.unsafe_l2.number, 16);
if (nodeUnsafe > lastSeen) {
console.log('gap detected: missed', nodeUnsafe - lastSeen, 'blocks; backfill required');
}
setTimeout(connect, backoff);
backoff = Math.min(backoff * 2, 30000);
});
ws.on('error', (err) => console.error('feed error', err.message));
}
connect();为索引器和桥接选择正确的头
您信任的头应与风险价值相匹配。对于仅显示的数据(如区块浏览器的最新区块计数器),不安全头是可接受的,因为重组只会改变显示。对于为分析提供数据的索引,安全头通常是正确选择,因为它由 L1 派生,等同于 L2 安全区块标签。对于桥接和任何转移价值的操作,最终头是唯一携带 L1 最终性保证的头。
这种分层与 OP Stack 文档中描述的头模型一致。Base OP Stack 节点同步状态与 Engine API 一文解释了 Engine API 的 forkchoice 状态如何映射到这些头,当您直接驱动执行客户端时这很有用。
一个实用规则:永远不要基于不安全头最终确定提款、贷记存款或释放托管。等待最终头,并将安全头用作非价值状态的中间检查点。
- 不安全头:UI 计数器、内存池式监控、非价值遥测。
- 安全头:索引、分析、可容忍罕见回滚的非价值状态。
- 最终头:桥接、提款、存款、托管、任何价值转移。
- 始终将 L1 来源与 L2 区块一起记录,以便审计。
Flashblocks 与派生模型:不同的信号
Flashblocks 是位于 OP-Stack 模型之上的亚秒级预确认。它们是一种面向延迟的信号,旨在为应用程序提供待处理状态的早期视图,而不是安全或最终派生的替代品。将 Flashblocks 与派生混淆会导致对最终性的错误假设。
Base OP-Stack L1 派生与时间戳 一文介绍了如何通过 RPC 读取派生时间戳,这是推理区块何时变为安全的正确方式。Flashblocks 不会改变派生时间线;它们只是缩短了获得初步信号的时间。
将 Flashblocks 视为用户体验的优化层。将您的安全和最终逻辑锚定在派生和 L1 最终性上,仅在可接受容忍重组、低延迟信号的地方使用 Flashblocks。
- Flashblocks:亚秒级预确认,面向延迟,容忍重组。
- 派生:L1 支持的安全头,索引检查点的基础。
- 最终性:L1 最终确定的头,涉及价值操作的基础。
- 不要用 Flashblocks 替代安全或最终检查。
针对您自己端点的可复现结果表
由于 Feed 行为、派生延迟和重连时间取决于您的节点、网络条件和 L1 健康状况,唯一可靠的数字是您自己测量的数字。使用下表记录来自您自己端点的观察结果。同时运行 syncStatus 查询和 Feed 订阅者,并以固定间隔记录值。
用时间戳、不安全和安全 L2 区块号、它们的 L1 来源、计算出的差距以及任何重连事件填写每一行。至少在 L1 拥堵期和节点重启期间重复,以捕获有趣的情况。这为您提供了一个基线,可在配置更改后进行比较。
不要将任何单一测量视为基准。目标是可复现的方法,而不是发布的数字。如果您需要提供商级别的性能数据,请向您的提供商索取,而不是从单次运行中推断。
- 时间戳:样本的 ISO 8601 时间。
- 不安全 L2 区块:来自 optimism_syncStatus 或 Feed。
- 安全 L2 区块:来自 optimism_syncStatus。
- 最终 L2 区块:来自 optimism_syncStatus。
- 不安全 L1 来源:支持不安全头的 L1 区块。
- 安全 L1 来源:支持安全头的 L1 区块。
- 不安全到安全差距:不安全区块号减去安全区块号。
- 到安全的时间:从不安全观察到安全观察的挂钟时间。
- Feed 重连次数:间隔内的重连事件数。
基于 Feed 消费的局限性与权衡
排序器 Feed 是排序器提供的服务。其可用性、帧格式和保留策略由排序器运营商控制,而非协议。自托管 Feed 消费需要运行 op-node;没有 op-node 就无法有意义地消费 Feed,因为仅 Feed 不会给您安全或最终头。
信任不安全头进行涉及价值的操作是不安全的,因为它可能发生重组。Feed 不会重放错过的区块,因此断开连接会造成缺口,必须从节点的 RPC 回填。运行自己的 op-node 会增加运营成本,但消除了对第三方 Feed 端点的依赖,并让您直接访问 optimism_syncStatus。
对于不想运行基础设施的团队,托管端点可以暴露标准 Base RPC 接口。有关选项,请参阅 Base RPC 端点(RPC Assistant) 和 RPC 定价,以及 API 服务 了解托管访问模式。请注意,op-node 特定方法可能并非在所有托管端点上可用;在设计之前请先验证。
- Feed 可用性由排序器运营商控制。
- Feed 不会重放错过的区块;回填是您的责任。
- 自托管消费需要 op-node。
- 不安全头容易重组,不适合价值操作。
- 托管端点可能不暴露 op-node 特定的 RPC 方法。
Feed 与派生问题排查
大多数 Feed 问题分为几类:连接失败、静默缺口和头分叉。首先确认 op-node 正在运行,并且 optimism_syncStatus 返回一致的快照。如果不安全头在推进但安全头停滞,问题通常出在 L1 派生侧,而不是 Feed。
如果 Feed 反复断开,请检查网络稳定性和排序器端点的可达性。增加退避上限以避免重连风暴。如果重连后看到缺口,请从节点的 RPC 回填,而不是等待 Feed 重新发送。
如果安全头远远落后于不安全头,请检查 L1 gas 价格和批次发布。派生滞后通常是 L1 经济问题。Base OP Stack 节点同步状态与 Engine API 一文介绍了当 rollup RPC 不够用时如何通过 Engine API 检查同步状态。
- Feed 已连接但无消息:验证订阅主题和传输。
- 不安全推进,安全停滞:调查 L1 派生和批次发布。
- 反复断开:检查网络、端点可达性、退避上限。
- 重连后缺口:从节点 RPC 回填,不要等待重放。
- 节点间头不一致:比较 L1 来源和同步状态。
Base 节点运营者的后续步骤
首先运行 op-node 并查询 optimism_syncStatus,为不安全、安全和最终头建立基线。然后添加带重连和退避的 Feed 订阅者,并记录上述结果表。这为您提供了一个可复现的视图,了解节点在正常和降级条件下的行为。
有关网络层面的背景,请参阅 Base 网络页面 和 OnFinality Learn 中心 获取相关深度文章。如果您正在设计索引器,接下来请阅读 Base OP-Stack 重组深度与索引器回滚 一文,并结合 Base OP Stack 最终性、安全区块与最终区块 指南了解 RPC 层面的头处理。
最后,为每个操作决定您的信任层级:显示用不安全,索引用安全,价值用最终。将该决定记录在您的运行手册中,并在更改端点或提供商时重新审视。
- 运行 op-node 并建立 optimism_syncStatus 基线。
- 添加带重连和退避的 Feed 订阅者。
- 在正常和降级期间记录结果表。
- 为每个操作定义信任层级并记录。
- 在设计基于 op-node RPC 的方案之前,审查提供商方法覆盖范围。