レッスン2 — op-rbuilder を読む(Reth ベースの OP Stack sequencer)
問い
今日 OP Stack chain を立ち上げると、ブロック生成バイナリはほぼ確実に flashbots/op-rbuilder — OP 派生 rollup 向けに Paradigm が書いた Rust block builder。「Reth 上の sequencer」の本番参照実装で、マーケティング図解では見えない実際の sequencer が何をしているかを理解したければここを読む。
原理(最小モデル)
- 3 入力ストリーム. Engine API(コンセンサス側から「何の上に構築すべきか」)+ Mempool(pending user tx)+ L1 Inbox(必ず含める deposit tx)。
- OP Stack の 5 順序付けルール. Deposit を先頭 / L1 epoch 帰属 / Sequencer 署名 / Gas limit / Force inclusion。op-rbuilder がすべて強制。
- Sequencer が「選べる」vs「選べない」. 選べる = ユーザ tx の選択 + 順序 + L1 epoch + timestamp(範囲内)。選べない = Deposit include 有無 + Force-included tx スキップ + Block validity ルール。
- build_payload の 4 段. Deposit tx 強制 include → Force-inclusion tx → Mempool から gas limit まで pull → Block seal(root + 署名)。
- MEV 3 立場. Vanilla FIFO / Priority-fee 順序付け(OP Stack デフォルト)/ MEV 認識 builder market(PBS)。
- Pre-confirmation は単一 sequencer のキラー機能. 即時署名 → 100ms confirmed。複数当事者では投票必要 → 不可。
- Pre-conf に reorg リスク. L2 reorg で tx が canonical から外れる、設計次第で sequencer slash。
具体例
op-rbuilder アーキテクチャ:
flowchart TB
EngineAPI["Engine API<br/>(rollup consensus から)"] -->|forkchoiceUpdated| Builder["Block Builder"]
Mempool["L2 Mempool"] -->|pending txs| Builder
L1Inbox["L1 Inbox<br/>(deposit + force-include)"] -->|deposit イベント| Builder
Builder -->|ブロック構築| EVM["revm<br/>(実行)"]
EVM -->|state 変更| State["State DB"]
Builder -->|署名ブロック| EngineAPI2["Engine API<br/>(getPayload レスポンス)"]
Builder -->|broadcast| P2P["P2P network<br/>(ノードに伝播)"]
OP Stack 順序付け 5 ルール:
- Deposit を先頭に: L1 inbox からの deposit tx はブロック先頭
- L1 epoch 帰属: ブロックは L1 ブロック(「L1 origin」)を参照必須
- Sequencer 署名: ブロックは現役 sequencer の鍵で署名必須
- Gas limit: OP 固有上下限の範囲内(mainnet と異なる)
- Force inclusion: L1 inbox 内 tx が期限超過なら必ず含める
Sequencer が選べる / 選べない:
| 選べる | 選べない |
|---|---|
| どのユーザ tx を含めるか | Deposit を含めるかどうか(必須) |
| ユーザ tx の順序 | Force-included tx をスキップするかどうか(必ず含める) |
| 帰属させる L1 epoch | Block validity ルール(gas limit / base fee 計算) |
| ブロック timestamp(範囲内) |
build_payload 関数(op-rbuilder source、おおよその形):
async fn build_payload(
attributes: PayloadAttributes,
parent: BlockHash,
state: StateProvider,
pool: TxPool,
) -> eyre::Result<ExecutionPayload> {
let mut block_env = BlockEnv::from(parent, attributes);
let mut executor = Executor::new(state, &block_env);
let mut included_txs = Vec::new();
// 1. Deposit tx を強制 include
for deposit_tx in attributes.l1_deposits {
executor.execute(&deposit_tx)?;
included_txs.push(deposit_tx);
}
// 2. Force-inclusion tx を include (期限超過 inbox)
for force_tx in attributes.force_included {
executor.execute(&force_tx)?;
included_txs.push(force_tx);
}
// 3. Gas limit までの mempool から pull
let pending = pool.best_pending();
for tx in pending {
if executor.gas_used() + tx.gas_limit() > block_env.gas_limit {
break;
}
match executor.execute(&tx) {
Ok(_) => included_txs.push(tx),
Err(_) => continue, // 失敗 tx スキップ
}
}
// 4. ブロック seal (root 計算、署名)
let payload = ExecutionPayload {
header: build_header(&block_env, &executor, &included_txs),
transactions: included_txs,
};
Ok(payload)
}
op-rbuilder の主要パス(reth バージョンで揺れる):
| パス | 役割 |
|---|---|
crates/builder/src/payload.rs | コアのブロック構築ループ |
crates/builder/src/ordering.rs | Tx の順序付け戦略 |
crates/builder/src/deposit.rs | Deposit tx 処理 |
crates/builder/src/seal.rs | ブロックの sealing + 署名 |
MEV 3 立場:
| 立場 | Sequencer がすること | 例 |
|---|---|---|
| Vanilla FIFO | 提出時刻順 | naive な実装 |
| Priority-fee 順序付け | ガスチップ順(Ethereum mainnet 系) | OP Stack デフォルト |
| MEV 認識 builder market | 外部入札を受け入れる | OP Stack + op-rbuilder + bundle market |
失敗例(誤解)
「Sequencer は何でもできる」— 半分間違い。「選べる」と「選べない」が明確に分かれる。Deposit / Force-include / validity ルールは 必ず守る → 違反すれば L1 検証失敗 → ブロック reorg。「何でもできる」は UX レベル(順序 + 選択)のみ。
「Pre-confirmation はタダ」— 間違い。sequencer は inclusion をコミット → L2 reorg があれば tx が canonical chain から外れる可能性 → 設計次第で sequencer slash。reorg リスクと L1 challenge リスクを負う。
「FIFO が MEV 中立」— 間違い。FIFO でも提出 latency 優位 + toxic flow 被害が残る。中立は 設計選択、FIFO は中立に見えるが実は MEV 抽出経路を作る。
🛑 予測。 Sequencer は ~2s ごとにブロック生成必要。ボトルネックは実行速度(revm)かブロック構築(選択 + 順序付け)?(答え: ブロック構築 > 実行速度。revm 自体は 数十 ms で 数千 tx 実行可能。ボトルネックは ① mempool から「最良」tx を選ぶ heuristic + ② gas 推定の精度 + ③ 非同期実行(構築中に新 tx 到着)+ ④ reorg 処理(parent が変わる場合)。op-rbuilder の最適化は 構築アルゴリズム にあり、実行エンジンにはない。)
ステップで組み立てる
Step 1: 3 入力ストリーム
Engine API(コンセンサス)+ Mempool(user tx)+ L1 Inbox(deposit)。
Step 2: OP Stack 5 順序付けルール
Deposit 先頭 / L1 epoch 帰属 / Sequencer 署名 / Gas limit / Force inclusion。
Step 3: 「選べる」vs「選べない」を区別
選べる = UX 自由度(tx 選択 + 順序 + epoch + timestamp)/ 選べない = consensus 強制(deposit + force-include + validity)。
Step 4: build_payload 4 段を辿る
Deposit → Force-inclusion → Mempool(gas limit まで)→ Seal。
Step 5: MEV 3 立場を判別
Vanilla FIFO / Priority-fee / Builder market。chain の MEV policy は source code に現れる。
Step 6: Pre-confirmation の仕組みとリスク
単一 sequencer の即時署名 → 100ms confirmed → reorg リスク(tx が canonical から外れる、設計次第で slash)。
答え合わせ
- 「選べない」を違反すると起きること: L1 検証失敗(OP Stack 仕様準拠の executor が「ブロック invalid」と判定)→ チェーン分岐 / reorg → 違反ブロックが canonical から削除。consensus rule は L1 側で強制、sequencer が回避できない。
- Pre-conf を発行する sequencer のリスク: ① L2 reorg(稀でも起こりうる、parent が変わる場合)→ tx が canonical から外れる → ユーザ「confirmed」を見たのに含まれない、② 設計次第で sequencer が slash(pre-conf 違反として on-chain 罰)+ ③ 評判ダメージ。100ms confirmed の代価。
- MEV 3 立場の source code 現れ方: ① Vanilla FIFO =
pool.iter().take_until(gas_limit)(順序付け hook なし)、② Priority-fee =pool.best_pending()(gas tip ソート)、③ Builder market =extend_builder_with_mev_sharefeature flag + external bundle 受け入れ API。コードを読む = MEV policy を読む。
合格基準
- 3 入力ストリームを即答できる。
- OP Stack 5 順序付けルールを暗唱できる。
- 「選べる」vs「選べない」を 4 項目ずつ言える。
- build_payload 4 段を辿れる。
- MEV 3 立場を source code パターンで判別できる。
まとめ(3行)
- op-rbuilder = OP Stack sequencer の本番参照実装、3 入力(Engine API / Mempool / L1 Inbox)+ 5 順序付けルール強制 + build_payload 4 段。
- Sequencer が「選べる」(UX 自由度)と「選べない」(consensus 強制)を区別 — 違反は L1 検証失敗で reorg。
- Pre-confirmation は単一 sequencer のキラー機能(100ms confirmed)だが reorg リスク、MEV 3 立場(Vanilla FIFO / Priority-fee / Builder market)は source code に現れる。