レッスン20 — Custom executor(execution layer を差し替える)
問い
Executor は「tx を実行して post-state を生成するもの」。Ethereum mainnet では vanilla revm、Optimism では revm + deposit-tx 処理 + L1 cost 計算 + 異なる precompile。Reth がこの実行レイヤーをどう差し替えさせるか?
原理(最小モデル)
- 3 trait の境界.
ConfigureEvm(block context → 適切な precompile / gas schedule で revm 構成)+BlockExecutionStrategy(block から tx 取り出し → revm 流し → receipt + state change 蓄積)+ExecutorBuilder(NodeBuilder スロット)。 - Optimism が override する 4 つ. Custom precompile リスト(L1 block hash アクセスなど)+ Deposit transaction 処理(署名検証スキップ)+ L1 cost 計算(calldata の L1 投稿コスト)+ Pre-execution hook(L1 block oracle slot 更新)。
- Precompile は
ConfigureEvm、L1 cost は実行戦略. Custom precompile = config、L1 cost = executor メインループ(precompile に収まらない consensus-critical ロジック)。 - 配線. ChainSpec → [どの fork アクティブ?] → EVM config → [アクティブ precompile set] → revm。
- Execution loop は mainnet + 2 行差. Deposit tx 判定 + L1 cost apply 以外は mainnet と同じ。
具体例
3 trait:
ConfigureEvm— block コンテキスト → revm インスタンス構成BlockExecutionStrategy(または類似)— block から tx 取り出し → revm 流し → receipt + state change 蓄積ExecutorBuilder— NodeBuilder スロット
Optimism の override:
| Override | 理由 |
|---|---|
| Custom precompile リスト | OP は L1 block hash アクセスなど追加 |
| Deposit transaction 処理 | Deposit tx は署名検証スキップ(L1 で認証済み) |
| L1 cost 計算 | OP tx は L2 gas に加え L1 data cost 支払い |
| Pre-execution hook | block 内最初の tx 実行前、L1 block oracle slot 更新 |
配線:
ChainSpec ──[どの fork がアクティブ?]──▶ EVM config ──[アクティブな precompile set]──▶ revm
Execution loop 擬似コード:
for tx in block.body:
if is_deposit_tx(tx) and current_fork.allows_deposits():
skip_signature_verify()
else:
verify_signature(tx)?
db = state_provider.load_relevant_accounts(tx)
cfg = configure_evm(chainspec, block, db) // precompile、gas schedule をセット
result = revm.transact(cfg, tx)
apply_l1_cost(tx, result, db) // L2 固有
state.commit(result.state_changes)
receipts.push(result.receipt)
return post_state_root(state), receipts
Mainnet では 3 行目(deposit 判定)と 9 行目(L1 cost)が消えるだけ。他はまったく同じ。
L1 cost 計算ステップ:
- 各 tx 実行前に既知の storage slot から L1 base fee + blob gas price を読む
l1_cost = calldata_gas × l1_base_fee + blob_overheadを計算- L2 gas 課金 に加えて 送信者残高から控除
- Fee vault に入金
Tempo(L1)で予測される差分:
- "deposit tx" 概念なし(親 chain なし)
- L1 cost 課金なし
- ただし: 決済 primitives 用 custom precompile(FX、settlement attestation)+ Pre-execution hook(FX rate oracle slot)+ 異なる fee 市場構造(stablecoin-native)
失敗例(誤解)
「L1 cost charging を precompile で実装すれば speed が出る」— 間違い。precompile は「tx 実行中の特定 CALL に応じる」もの → tx 実行 前 に送信者残高から控除できない。L1 cost は実行戦略レベル → executor メインループ。
「Custom precompile は executor に直書きすれば良い」— 間違い。ConfigureEvm impl が revm に precompile セットを手渡す → ChainSpec の hardfork schedule で gate → 各 fork で正しい precompile set。直書きすると hardfork transition で壊れる。
「Deposit tx は通常 tx と同じ実行で良い」— 間違い。Deposit tx は L1 で既に認証済み(L1 コントラクトが認証)→ L2 側で署名検証する署名がない / 検証する必要なし。OP の deposit tx は署名フィールドが空または特殊値、署名検証スキップが必須。
🛑 予測。 OP の L1 cost charging はなぜ precompile として実装できないのか? 「performance のため」だけなら掘り下げが足りない — precompile が tx 実行前に任意のアカウントから控除できない consensus 上の理由 は?(答え: precompile は tx 実行中の特定 CALL に応じる純粋関数 → 任意のアカウント残高を tx 実行 前 に控除する権限がない(既存の precompile アドレス
0x01-0x0aも全部入出力ベースで、ホスト state を勝手に書き換えない)。L1 cost は ① tx 実行前、② 送信者残高から、③ block 内全 tx に適用 → これは executor の責務、Yellow Paper の framework 外。consensus-critical ロジックは executor に住む — precompile では tx-scoped、executor は block-scoped。)
ステップで組み立てる
Step 1: 3 trait 境界を即答
ConfigureEvm / BlockExecutionStrategy / ExecutorBuilder。
Step 2: Optimism の 4 override
Custom precompile / Deposit tx / L1 cost / Pre-execution hook。
Step 3: Precompile vs Executor の判断軸
「tx-scoped 入出力か」= precompile / 「block-scoped、tx 実行前後、ホスト state 任意書き換え」= executor。
Step 4: Execution loop 擬似コード暗唱
mainnet との差分 2 行(deposit 判定 + L1 cost apply)= extension model の最小差分。
Step 5: Tempo の予測差分
L1 なので deposit / L1 cost / L1 block oracle なし、代わりに決済 primitives + FX oracle + stablecoin fee 市場。
答え合わせ
- L1 cost が executor のみに住む理由: precompile = tx 実行中の特定 CALL に応じる純粋関数(入出力)、executor = block 全体の制御(tx 実行前後、ホスト state 書き換え)。L1 cost は ① 全 tx に適用 + ② tx 実行前に控除 + ③ ホスト state 書き換え → 3 つとも precompile の責務外。
- Custom precompile が ChainSpec を経由する理由: 各 fork で異なる precompile set が必要(hardfork で precompile 追加 / 削除)→ EVM config が単独で hardfork 状態を知らないと正しい precompile set を渡せない → ChainSpec が「現 block での fork 状態」を提供 → EVM config が「fork に対応する precompile set」を選ぶ → revm が受け取る。
- mainnet と OP のメインループ差分: 3 行目(deposit tx 判定 + 署名検証スキップ)+ 9 行目(apply_l1_cost)の 2 行だけ。他のすべて(state load / configure_evm / transact / state.commit / receipts.push)は mainnet と同じ。extension model の最小差分 = consensus 互換性 + コード共有率最大。
合格基準
- 3 trait(ConfigureEvm / BlockExecutionStrategy / ExecutorBuilder)を即答できる。
- Optimism の 4 override を即答できる。
- Precompile vs Executor の判断軸を言える。
- Execution loop の mainnet との 2 行差分を言える。
- L1 cost が executor のみに住む理由を 3 つ言える。
まとheme(3行)
- 3 trait(ConfigureEvm / BlockExecutionStrategy / ExecutorBuilder)が execution layer の差し替え API、ChainSpec → EVM config → revm の配線。
- Optimism の 4 override(Custom precompile / Deposit tx / L1 cost / Pre-execution hook)= mainnet との最小差分(2 行)。
- Precompile = tx-scoped 純粋関数、Executor = block-scoped、ホスト state 任意書き換え可 — L1 cost は executor のみ。