Optimism op-node batch_decoder:从 L1 批次交易中还原 Channel 的离线调试工具
【免费下载链接】optimismOptimism is Ethereum, scaled.项目地址: https://gitcode.com/GitHub_Trending/op/optimism
batch_decoder是 Optimism monorepo 中op-node自带的一个离线调试工具,用于排查 batcher(批次提交器)与 op-node 之间的数据链路:它拉取链上发往 Batch Inbox 合约的交易,将其解码为 frame,再重组为 channel,最终还原出其中承载的批次。读完本文,你将掌握该工具的三阶段工作流(fetch / reassemble / force-close)、各命令行参数的含义与默认值、落盘 JSON 的数据结构,以及一套可直接复制的jq分析命令,用于定位批次提交异常。
设计哲学:基于磁盘 JSON 的两阶段处理
batch_decoder的设计目标是"简单且灵活",它把尽可能多的数据分析工作交给其他工具(尤其是jq),核心思路是围绕磁盘上的 JSON 文件做文章(见 README):
- 第一阶段(fetch,触网):拉取指定 L1 块区间内所有发往 batch inbox 地址的交易。这一步就把交易解码成 frame,并连同元信息一起记录。
- 第二阶段(reassemble,离线):把缓存目录里的 frame 重新组装成 channel,并写出带元数据的 channel 文件——这一步完全不触网,因此可以反复运行、自由调整。
这种拆分让网络请求只发生一次:即使后续分析需要多次迭代(改解码逻辑、换 jq 查询),fetch 的结果都可以复用。两个阶段的产物落盘后,文件命名即天然索引——交易文件以交易哈希命名,channel 文件以 Channel ID 命名。
数据模型:交易与 channel 的 JSON 结构
理解落盘 JSON 是熟练使用该工具的前提,两个结构分别在 fetch/fetch.go 与 reassemble/reassemble.go 中定义。
TransactionWithMetadata(fetch 产物)
每个被识别为批次提交的交易写成一个<tx_hash>.json,字段包括:
| 字段 | JSON key | 含义 |
|---|---|---|
TxIndex | tx_index | 交易在区块内的索引 |
InboxAddr | inbox_address | 批次 inbox 地址 |
BlockNumber/BlockHash/BlockTime | block_number等 | 包含区块信息 |
Sender/ValidSender | sender/valid_sender | 交易发送者及其是否为已知 batcher |
Frames/ValidFrames | frames/valid_data | 解码出的 frame 列表、每段数据是否解析成功 |
FrameErrs | frame_parse_error | frame 解析错误信息 |
Tx | tx | 原始交易完整序列化 |
ChannelWithMetadata(reassemble 产物)
每个重组出的 channel 写成一个<channel_id>.json,字段包括:
id:Channel ID;is_ready:channel 是否已收到闭合帧(是否 ready);invalid_frames/invalid_batches:frame 拼接或批次解码过程中是否出现异常;frames:每个 frame 附带transaction_hash、inclusion_block、timestamp、block_hash等元数据;batches/batch_types/compr_algos:从 channel 中还原出的批次对象、批次类型序列、压缩算法序列。
命令一:fetch —— 拉取并解码 L1 批次交易
batch_decoder fetch拉取给定 L1 块区间内发往 batch inbox 地址的全部交易,以交易哈希为文件名写成 JSON。入口实现在 main.go,核心逻辑在 Batches。
参数说明
| 参数 | 必填 | 默认值 | 说明 |
|---|---|---|---|
--start | 是 | - | 起始块号(含) |
--end | 是 | - | 结束块号(不含) |
--l1 | 是 | - | L1 RPC URL,支持环境变量L1_RPC |
--l1.beacon | 否 | - | L1 Beacon 节点 HTTP 端点,支持环境变量L1_BEACON |
--l2-chain-id | 二选一 | - | L2 链 ID,从 superchain-registry 加载 inbox 与 sender |
--inbox/--sender | 二选一 | - | 手动指定 Batch Inbox 地址与 Batcher 地址 |
--out | 否 | /tmp/batch_decoder/transactions_cache | 交易 JSON 缓存目录 |
--concurrent-requests | 否 | 10 | 抓取 L1 时的并发度 |
两种定位批次链路的方式:
- 提供
--l2-chain-id时,工具调用 LoadOPStackRollupConfig 从 superchain-registry 中取出BatchInboxAddress与Genesis.SystemConfig.BatcherAddr,省去手工查合约地址; - 否则必须同时提供
--inbox与--sender(两者缺一会直接报错 "either --l2-chain-id or both --inbox and --sender must be set")。
源码层面的关键细节
- 并发抓取:
Batches使用errgroup.SetLimit(concurrentRequests)限制并发,逐块调用fetchBatchesPerBlock,每块有独立的 10 秒超时。 - Blob 交易(Ecotone 之后):post-ecotone 的 channel frame 走 EIP-4844 blob 交易承载。fetch 时若遇到 blob 交易而未提供
--l1.beacon,会打印 "Unable to handle blob transaction" 并跳过;若提供了 Beacon 端点,则通过beacon.GetBlobsByHash取回 blob 并还原为数据段。因此分析 Ecotone 之后的主网数据时必须配置--l1.beacon。 - frame 解码:对每段数据调用 ParseFrames。L1 交易数据的序列化格式为
data = DerivationVersion0 ++ Frame(s),要求首字节为 version 0、至少解析出一个 frame 且无剩余字节。 - 有效性标记:发送者不在
BatchSenders白名单中,或任一数据段解析失败,都会把该交易标记为 invalid,但仍写入磁盘(ValidSender/ValidFrames字段记录原因),方便事后排查。
命令二:reassemble —— 离线重组 channel 并解码批次
batch_decoder reassemble遍历缓存目录中所有 frame,将其按 Channel ID 分组重组为 channel,写出以 Channel ID 命名的 JSON 文件;每个 channel 可以包含多个批次。核心实现在 Channels。
参数说明
| 参数 | 必填 | 默认值 | 说明 |
|---|---|---|---|
--in | 否 | /tmp/batch_decoder/transactions_cache | fetch 输出的交易缓存目录 |
--out | 否 | /tmp/batch_decoder/channel_cache | channel JSON 输出目录 |
--l2-chain-id | 二选一 | - | 从 superchain-registry 加载 rollup 配置 |
--rollup-config | 二选一 | rollup.json | 本地 rollup 配置 JSON 路径;不使用--l2-chain-id时必须设置 |
重组与批次解码流程
ProcessFrames 对每个 channel 执行以下逻辑:
- 排序:frame 先按(块号,区块内交易索引)排序以匹配链上派生顺序(见 LoadFrames),再按
FrameNumber排序。 - 回放加帧:逐个调用
ch.AddFrame;若 channel 已 ready 却还有多余帧、或加帧报错,标记invalid_frames。 - 批次解码:仅当 channel ready 时,用
BatchReader迭代读取批次,并依据批次类型分派:- Singular 批次:直接
derive.GetSingularBatch解出父哈希、epoch、交易列表等; - Span 批次:调用 DeriveSpanBatch 做本地派生——这正是 README 中提到的:对 span batch,
batch_decoder依据 rollup 配置中的L2BlockTime、L2GenesisTime与L2ChainID本地推导出完整批次(main.go 中L2ChainID、L2GenesisTime、L2BlockTime均取自 rollup 配置);对 singular 批次则不做派生、按原样存储。
- Singular 批次:直接
- 结果落盘:无论成功与否都写出
ChannelWithMetadata,失败情况体现在invalid_batches、invalid_frames布尔字段上,而不是报错退出——这对批量分析非常友好。
命令三:force-close —— 生成强制闭合交易数据
batch_decoder force-close生成一段可直接从 batcher 地址发往 batch inbox 的交易数据,用于强制闭合指定 channel,从而让后续 channel 无需等待超时即可被读取。实现见 main.go。
参数说明
| 参数 | 必填 | 默认值 | 说明 |
|---|---|---|---|
--id | 是 | - | 要闭合的 Channel ID |
--inbox | 否 | 0x0000...0000(零地址) | Batch Inbox 地址;零地址表示不过滤、加载缓存中全部 frame |
--in | 否 | /tmp/batch_decoder/transactions_cache | fetch 输出的交易缓存目录 |
它依赖fetch的结果,因为闭合交易的数据形态取决于 channel 的链上状态(见 ForceCloseTxData):
- channel 尚未闭合(帧序列中没有
IsLast帧):直接生成一个指向该 channel 的空闭合帧(frame 0 位置、IsLast=true)即可; - channel 已闭合但缺帧:需要为
[0, closeNumber]中缺失的每个 frame 编号各生成一个空帧(IsLast=false),补齐后再闭合——这就是 README 强调的"已闭合但缺帧时,帧的生成方式与简单闭合不同"的原因。
生成的交易数据以十六进制打印到 stdout,可复制后用 batcher 钱包签名发送。注意该命令只是"创建交易数据",实际广播、签名、gas 费用由操作者自行完成。
jq 分析手册(README 原版命令完整保留)
jq是与batch_decoder搭配的核心分析工具,以下是 README 提供的速查命令($TX_DIR为 fetch 输出目录、$CHANNEL_DIR/$CHANNEL_FILE为 reassemble 输出):
# Pretty print a JSON file jq . $JSON_FILE # Print the number of valid & invalid transactions jq .valid_data $TX_DIR/* | sort | uniq -c # Select all transactions that have invalid data & then print the transaction hash jq "select(.valid_data == false)|.tx.hash" $TX_DIR # Select all channels that are not ready and then get the id and inclusion block & tx hash of the first frame. jq "select(.is_ready == false)|[.id, .frames[0].inclusion_block, .frames[0].transaction_hash]" $CHANNEL_DIR # Show all of the frames in a channel without seeing the batches or frame data jq 'del(.batches)|del(.frames[]|.frame.data)' $CHANNEL_FILE # Show all batches (without timestamps) in a channel jq '.batches|del(.[]|.Transactions)' $CHANNEL_FILE典型排查路径:先用fetch统计 invalid 交易占比,确认是发送者异常还是数据损坏;再对未 ready 的 channel 定位其首帧的包含块与交易哈希,回到 L1 上核对 batcher 行为;最后用最后两条命令在不被 frame 原始数据淹没的前提下检查 channel 骨架。
已知局限与 Roadmap
README 末尾给出了两条规划中的增强(对应内部任务 CLI-3565、CLI-3560):
- 将批次从 channel 中进一步拆分存储进
ChannelWithMetadata,记录交易字节用量、未压缩总字节数与压缩后字节数(二者并不相同); - 反转
ChannelWithMetadata的索引方式,使块号/块哈希能映射到提交它们的 channel。
此外从源码结构看,当前版本还有几点适用边界值得注意:fetch 只处理 version 0 的 frame 序列化格式(ParseFrames 的注释说明当前仅支持该版本);--l2-chain-id依赖仓库内嵌的 superchain-registry 配置,因此目标链需已收录在 registry 中,未收录的链应改用--inbox/--sender与--rollup-config手动指定。整体而言,batch_decoder是一个以"JSON 落盘 + 离线二次加工"为骨架的实用排障工具:fetch 保证链上事实只抓取一次,reassemble 提供可重复的解码视角,force-close 则把"卡住的 channel"转化为一条可直接发送的修复交易。
【免费下载链接】optimismOptimism is Ethereum, scaled.项目地址: https://gitcode.com/GitHub_Trending/op/optimism
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考