レッスン7 — ドリル: reorg-safe なインデクサを作る
問い
読むのはリハーサル、実装するのが記憶。reorg を正しく乗り越える HashMap インデクサを書く。holesky で reorg を実際に観測、tx_count の不変条件を保つには?
原理(最小モデル)
- セットアップ.
reth-exex-examplesを clone + minimal で build。 - holesky テストネット. 初期同期速い + reorg 頻度高い(ハッシュパワー低、contested fork 多い)= 学習に最適。
- トランザクションカウンター追加.
ChainCommittedで tx 数を sum + log。 - HashMap インデックスの reorg-safe 設計. ChainCommitted += / ChainReorged old -= then new += / ChainReverted -=。
- 3 アーム不変条件. 各 Address のカウント = (canonical チェーンで送られた tx 数) − (commit 後 revert されたセグメントで送られた tx 数)。
ChainReorgedの操作順序. old を先に undo してから new を apply(Reth が reorg を処理する時系列順と一致)。- 観察ポイント. Reorg ログ前後で高 tx アドレスの
tx_countをスポットチェック、古い/新しいチェーンセグメントの差と一致確認。
具体例
セットアップ:
git clone https://github.com/paradigmxyz/reth-exex-examples
cd reth-exex-examples/minimal
cargo build
holesky で実行:
cargo run -- node --chain holesky
トランザクションカウンター(ChainCommitted アーム拡張):
ExExNotification::ChainCommitted { new } => {
let total: usize = new.blocks().values()
.map(|b| b.body.transactions.len())
.sum();
info!(committed_chain = ?new.range(), tx_count = total, "Received commit");
}
実測値: holesky 5-20 tx/block(たまに 0)、mainnet 100-300(混雑度次第)= ゼロレイテンシで本物のチェーンデータ読み。
Reorg-safe HashMap(フル実装):
use std::collections::HashMap;
use alloy_primitives::Address;
let mut tx_count: HashMap<Address, u64> = HashMap::new();
while let Some(notification) = ctx.notifications.try_next().await? {
match ¬ification {
ExExNotification::ChainCommitted { new } => {
for (_, block) in new.blocks() {
for tx in block.body.transactions() {
*tx_count.entry(tx.signer()).or_insert(0) += 1;
}
}
}
ExExNotification::ChainReorged { old, new } => {
// old を undo してから new を apply — 順序が大事
for (_, block) in old.blocks() {
for tx in block.body.transactions() {
*tx_count.entry(tx.signer()).or_insert(0) -= 1;
}
}
for (_, block) in new.blocks() {
for tx in block.body.transactions() {
*tx_count.entry(tx.signer()).or_insert(0) += 1;
}
}
}
ExExNotification::ChainReverted { old } => {
// old を undo、置換なし
for (_, block) in old.blocks() {
for tx in block.body.transactions() {
*tx_count.entry(tx.signer()).or_insert(0) -= 1;
}
}
}
};
if let Some(committed_chain) = notification.committed_chain() {
ctx.events.send(ExExEvent::FinishedHeight(committed_chain.tip().num_hash()))?;
}
}
3 アーム不変条件: 各 Address のカウント = (canonical で送られた tx 数) − (commit 後 revert で送られた tx 数)。
操作順序が重要: old を先に undo してから new を apply(Reth の reorg 時系列順と一致)。new 先 + old 後 の場合、共通プレフィックスがあると一旦二重カウント → old undo で復旧、ただし慣習は old-first。
Reorg 検証手順:
from_chain/to_chain範囲メモ- Reorg ログ前に高 tx アドレスの
tx_countスポットチェック → 記録 - Reorg ログ後に再記録
- 手動確認: 古い/新しいチェーンセグメントの差と一致するか
一致すれば 本番グレードインデクサ(goldsky、The Graph 等が出荷するコード)相当。
失敗例(誤解)
「ChainReverted で -= しなくても ChainCommitted で正しくなる」— 間違い。Revert された tx は canonical ではなくなった、-= 忘れるとカウント永久膨張。
「順序は new 先で良い」— 危険。共通プレフィックスがある場合、new 先 apply で一旦二重カウント → old undo で復旧、ただし中間状態が不正。Reth と同じ time-line 順(old-first)が慣習。
「再起動後にカウントは再構築できる」— 半分間違い。在庫が永続化されていないと再起動で全消失 → 再 sync 必要。tracking-state パターン(別 DB 永続化)を見る。
ステップで組み立てる
Step 1: セットアップ
reth-exex-examples clone + minimal build。
Step 2: holesky で実行
reorg 頻度が高い + 初期同期速い = 学習に最適。
Step 3: tx カウンター追加
ChainCommitted で sum + log、実測値を確認(holesky 5-20、mainnet 100-300)。
Step 4: 3 アーム HashMap 実装
ChainCommitted += / ChainReorged old -= then new += / ChainReverted -=。
Step 5: 不変条件を理解
各 Address のカウント = canonical で送られた tx 数 − revert されたセグメントで送られた tx 数。
Step 6: 操作順序を守る
old を先に undo、new を後で apply(Reth の reorg 時系列順)。
Step 7: Reorg を検証
ログ前後でスポットチェック、差と一致確認 → 一致すれば本番グレード。
答え合わせ
ChainReorgedで old/new を同通知で扱う論理的根拠: アトミック swap、reorg を 1 トランザクションとして扱う。old を先に undo + new を後で apply で 1 通知の処理を Reth の time-line 順と一致させる。new先だと共通プレフィックスで一旦二重カウント。- 3 アームの不変条件: 各 Address のカウント = (canonical で送られた tx 数) − (commit 後 revert で送られた tx 数)。revert アームで
-=を忘れると永久膨張、reorg アームで-=を忘れると古い + 新しいチェーンの和になる。 - 本番グレードの定義: holesky で実際の reorg を検知 + 前後の
tx_count差が古い/新しいチェーンセグメントの差と一致 → goldsky や The Graph が出荷するコード相当。MEV ボット、リアルタイムリスクエンジン、ロールアップも同じ道具で実装可能。
合格基準
- holesky で reorg 観測の理由(頻度高 + 同期速い)を即答できる。
- 3 アーム HashMap の += / -= パターンを書ける。
- 不変条件(canonical − revert)を 1 文で言える。
- 操作順序(old undo first)の理由を Reth time-line で説明できる。
- Reorg 検証手順(前後スポットチェック)を辿れる。
まとめ(3行)
- holesky で 3 アーム HashMap インデクサ実装(ChainCommitted += / ChainReorged old -= then new += / ChainReverted -=)、不変条件 = canonical − revert。
- 操作順序は old を先に undo + new を後で apply(Reth time-line 順)、
new先だと共通プレフィックスで二重カウント可能。 - Reorg 観測 + スポットチェックで一致確認 → 本番グレード(goldsky / The Graph 相当)、MEV / リスクエンジン / ロールアップも同じ道具で実装。