FABRKNT
Inside Alloy — Rust Ethereum ライブラリを読む
Alloyの内側
レッスン 9 / 15·CONTENT12 分25 XP
コース
Inside Alloy — Rust Ethereum ライブラリを読む
レッスンの役割
CONTENT
順序
9 / 15

レッスン6 — ドリル: Ethereum Optimism で動く N 上ジェネリックなコード

問い

読むだけではリハーサル、手を動かすことで記憶。チェーンごとのコードを書かずに、Ethereum でも Optimism でも動く 1 関数を書く。本番のブロックエクスプローラ / インデクサ / MEV ボットが投入するコード相当 — 何が「同じコード、違う型パラメータ」を可能にするか?

原理(最小モデル)

  • セットアップ. Anvil(ローカル)+ Optimism mainnet RPC + 新規 cargo + alloy + op-alloy
  • op-alloy の Network 実装を読む. 10 関連型のうち 8 がオーバーライド、Header と HeaderResponse のみ共有。
  • N 上ジェネリック関数. fn block_summary<N, P>(provider: &P, block_id: BlockId) -> Result<String> where N: Network, P: Provider<N> で本体 1 つ。
  • コンパイラの特殊化. Ethereum / Optimism で具体化 → 2 つの特殊化コピー出力、各々最適化済。
  • コンパイル時混ぜ防止. block_summary::<Optimism, _>(&eth_provider, ...) は関連型不一致でコンパイルエラー、一貫性の性質がコンパイル時に守る。
  • 3 つ目チェーン追加 = 新 struct + impl Network のみ. struct PolygonZkEvm; impl Network for PolygonZkEvm { ... } で関数本体は変えず。

具体例

セットアップ:

# ターミナル 1: Anvil
anvil

# ターミナル 2: プロジェクト
cargo new alloy-network-drill --bin
cd alloy-network-drill

Cargo.toml:

[dependencies]
alloy = { version = "0.x", features = ["full", "provider-http"] }
op-alloy = { version = "0.x" }
tokio = { version = "1", features = ["full"] }
eyre = "0.6"

10 スロット並列比較:

スロットEthereum の値Optimism の値同じ?
TxTypealloy_consensus::TxTypeop_alloy_consensus::OpTxType
TxEnvelopealloy_consensus::TxEnvelopeop_alloy_consensus::OpTxEnvelope
UnsignedTxalloy_consensus::TypedTransactionOP 類似
ReceiptEnvelopealloy_consensus::ReceiptEnvelopeOP 類似
Headeralloy_consensus::Headeralloy_consensus::Header
TransactionRequestalloy_rpc_types_eth::TransactionRequestOP 類似
TransactionResponsealloy_rpc_types_eth::TransactionOP 類似
ReceiptResponsealloy_rpc_types_eth::TransactionReceiptOP 類似
HeaderResponsealloy_rpc_types_eth::Headeralloy_rpc_types_eth::Header
BlockResponsealloy_rpc_types_eth::BlockOP 類似

8 違う、2 共有(Header と HeaderResponse)。一貫性の性質がオーバーライドを強制。

N 上ジェネリックなブロックサマリ:

use alloy::network::{primitives::BlockTransactionsKind, Network};
use alloy::providers::Provider;
use alloy::rpc::types::BlockId;

async fn block_summary<N, P>(provider: &P, block_id: BlockId) -> eyre::Result<String>
where
    N: Network,
    P: Provider<N>,
{
    let block = provider
        .get_block(block_id)
        .kind(BlockTransactionsKind::Hashes)
        .await?
        .ok_or_else(|| eyre::eyre!("block not found"))?;

    use alloy::network::BlockResponse;
    let header = block.header();

    use alloy::network::primitives::HeaderResponse;
    Ok(format!(
        "block {} on chain — hash={:?}",
        header.number(),
        header.hash(),
    ))
}

Ethereum + Optimism main:

use alloy::network::Ethereum;
use alloy::providers::ProviderBuilder;
use op_alloy::network::Optimism;

#[tokio::main]
async fn main() -> eyre::Result<()> {
    // Ethereum (Anvil)
    let eth_provider = ProviderBuilder::new()
        .on_http("http://localhost:8545".parse()?);
    let s = block_summary::<Ethereum, _>(&eth_provider, BlockId::latest()).await?;
    println!("ETH: {s}");

    // Optimism mainnet
    let op_provider = ProviderBuilder::<_, _, Optimism>::default()
        .on_http("https://mainnet.optimism.io".parse()?);
    let s = block_summary::<Optimism, _>(&op_provider, BlockId::latest()).await?;
    println!(" OP: {s}");

    Ok(())
}

混ぜると コンパイルエラー:

// コンパイルエラー
let s = block_summary::<Optimism, _>(&eth_provider, BlockId::latest()).await?;

エラー: "expected Optimism::TransactionRequest, found Ethereum::TransactionRequest"。一貫性の性質がコンパイル時にあなたを守る

失敗例(誤解)

「Network = ランタイム設定で切り替え」— 間違いコンパイル時の型レベル選択、ランタイム切り替えではない。各 chain に対して特殊化コピーがコンパイルされる。

「ジェネリック関数だから動的ディスパッチ」— 間違い。N: Network はトレイト境界、関連型は静的ディスパッチ → コンパイラがチェーンごとに特殊化コピー出力、vtable なし、性能ペナルティなし。

「Polygon zkEVM 追加には alloy 自体を改造」— 間違い。新 struct + impl Network for PolygonZkEvm { ... } のみ、関数本体は変えず。

ステップで組み立てる

Step 1: 10 スロット並列比較表を読む

8 違う + 2 共有を理解。

Step 2: N 上ジェネリック関数を書く

fn block_summary<N, P>(provider: &P, block_id: BlockId) where N: Network, P: Provider<N>

Step 3: Ethereum (Anvil) で動かす

block_summary::<Ethereum, _>(&eth_provider, BlockId::latest()).await

Step 4: Optimism mainnet で動かす

block_summary::<Optimism, _>(&op_provider, BlockId::latest()).await同じ関数、違う型パラメータ

Step 5: 混ぜると コンパイルエラー観察

block_summary::<Optimism, _>(&eth_provider, ...) で関連型不一致エラー。

Step 6: Polygon zkEVM 追加方法

新 struct + impl Network for PolygonZkEvm { ... } のみ、関数本体は変えず。

答え合わせ

  • コンパイラの特殊化コピー数: Ethereum と Optimism の 2 種類で具体化 → 2 つの特殊化コピー出力、各々最適化済。性能上重要 = vtable 経由の動的ディスパッチなし、静的ディスパッチで関連型解決 → ホットループでも遅くならない。
  • Header 共有 + BlockResponse 違いの差: Header = コンセンサスヘッダレベル(OP は EVM 互換)→ 共有。BlockResponse = ブロックの tx リストに OP 型が含まれる → Ethereum Block 再利用すると OP deposit が誤シリアライズ → 別物必須。「tx リスト含むもの = 波及、ヘッダレベル = 共有可能」
  • 混ぜたときのコンパイルエラー経路: block_summary::<Optimism, _>(&eth_provider, ...)P: Provider<Optimism> 境界要求 → eth_provider: P_ethProvider<Ethereum> 実装 → Provider<Optimism> 未実装 → 関連型不一致エラー("expected Optimism::TransactionRequest, found Ethereum::TransactionRequest")→ コンパイル拒否。

合格基準

  • 10 スロット並列比較表を即答できる。
  • N 上ジェネリック関数 signature を書ける。
  • 「同じ関数、違う型パラメータ」を 1 文で説明できる。
  • コンパイラの特殊化コピー数を即答できる。
  • Polygon zkEVM 追加方法(新 struct + impl Network のみ)を言える。

まとめ(3行)

  • N 上ジェネリック関数(fn block_summary<N, P>(provider: &P) where N: Network, P: Provider<N>)で Ethereum / Optimism / 任意のチェーンで動く 1 本体。
  • コンパイラが各 chain に対して特殊化コピー出力(静的ディスパッチ、vtable なし、性能ペナルティなし)、混ぜたら関連型不一致でコンパイルエラー。
  • Polygon zkEVM 追加 = 新 struct + impl Network のみ、関数本体不変 = 本番ブロックエクスプローラ / マルチチェーンインデクサのパターン。