FABRKNT
Inside Reth — シンク・拡張・SDK
Reth スタック — シンク・拡張・SDK
レッスン 10 / 17·CONTENT12 分25 XP
コース
Inside Reth — シンク・拡張・SDK
レッスンの役割
CONTENT
順序
10 / 17

レッスン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 &notification {
        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 検証手順:

  1. from_chain / to_chain 範囲メモ
  2. Reorg ログ前に高 tx アドレスの tx_count スポットチェック → 記録
  3. Reorg ログ後に再記録
  4. 手動確認: 古い/新しいチェーンセグメントの差と一致するか

一致すれば 本番グレードインデクサ(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 / リスクエンジン / ロールアップも同じ道具で実装。