レッスン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, _>(ð_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 の値 | 同じ? |
|---|---|---|---|
| TxType | alloy_consensus::TxType | op_alloy_consensus::OpTxType | ❌ |
| TxEnvelope | alloy_consensus::TxEnvelope | op_alloy_consensus::OpTxEnvelope | ❌ |
| UnsignedTx | alloy_consensus::TypedTransaction | OP 類似 | ❌ |
| ReceiptEnvelope | alloy_consensus::ReceiptEnvelope | OP 類似 | ❌ |
| Header | alloy_consensus::Header | alloy_consensus::Header | ✅ |
| TransactionRequest | alloy_rpc_types_eth::TransactionRequest | OP 類似 | ❌ |
| TransactionResponse | alloy_rpc_types_eth::Transaction | OP 類似 | ❌ |
| ReceiptResponse | alloy_rpc_types_eth::TransactionReceipt | OP 類似 | ❌ |
| HeaderResponse | alloy_rpc_types_eth::Header | alloy_rpc_types_eth::Header | ✅ |
| BlockResponse | alloy_rpc_types_eth::Block | OP 類似 | ❌ |
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, _>(ð_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, _>(ð_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, _>(ð_provider, BlockId::latest()).await。
Step 4: Optimism mainnet で動かす
block_summary::<Optimism, _>(&op_provider, BlockId::latest()).await、同じ関数、違う型パラメータ。
Step 5: 混ぜると コンパイルエラー観察
block_summary::<Optimism, _>(ð_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, _>(ð_provider, ...)でP: Provider<Optimism>境界要求 →eth_provider: P_ethがProvider<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 のみ、関数本体不変 = 本番ブロックエクスプローラ / マルチチェーンインデクサのパターン。