FABRKNT
Inside Revm — EVM エンジンを読む
Revmの心臓部
レッスン 11 / 17·CONTENT10 分25 XP
コース
Inside Revm — EVM エンジンを読む
レッスンの役割
CONTENT
順序
11 / 17

レッスン10 — 仲間トレイト・最適化・本物の実装

問い

前レッスンで &mut self を取る 4 メソッド Database を組み立てた + 宙ぶらりんの問題: Arc<MyDb>&T しか出さない = 並列リーダーは Database を共有できない。Revm の 3 追加ピースで解決 — どう?

原理(最小モデル)

  • DatabaseRef (読み専用仲間). 4 メソッド同じ + 違い 2 つ: &self 化 + auto_impl(&, &mut, Box, Rc, Arc) 5 種(Database の 2 種に対し)。
  • auto_impl リスト非対称の根拠. &self アクセスは &mut self より厳密に弱い制約 = Arc<T> / Rc<T>&T 出すが &mut T は出さない、機械的帰結。
  • DatabaseCommit (書き戻し別トレイト). fn commit(&mut self, changes: AddressMap<Account>);Database から分離する 2 理由。
  • DatabaseCommit 分離 2 理由. ① 読み専用 Database(フォークメインネット)が存在 = commit 実装強制が panic スタブ要求、② ライフサイクル違う(読みは呼び出しごと、commit は tx 終了時)。
  • storage_by_account_id 最適化. デフォルト実装が account_id 無視で storage に転送、MDBX バックエンド等内部アカウントインデックス持つ実装はオーバーライドでアドレス→ID 検索省略。
  • 3 本番実装. InMemoryDBHashMap 群、~50 行)/ AlloyDB(JSON-RPC ネットワーク、~150 行)/ StateProviderDatabase(reth の MDBX、数千行)。
  • メインネットフォークの選択. AlloyDB、RPC 遅延取得 + キャッシュ、フルアーカイブダウンロード不要。

具体例

DatabaseRef:

#[auto_impl(&, &mut, Box, Rc, Arc)]
pub trait DatabaseRef {
    type Error: DBErrorMarker;
    fn basic_ref(&self, address: Address) -> Result<Option<AccountInfo>, Self::Error>;
    fn code_by_hash_ref(&self, code_hash: B256) -> Result<Bytecode, Self::Error>;
    fn storage_ref(&self, address: Address, index: StorageKey)
        -> Result<StorageValue, Self::Error>;
    fn block_hash_ref(&self, number: u64) -> Result<B256, Self::Error>;
}

DatabaseCommit:

#[auto_impl(&mut, Box)]
pub trait DatabaseCommit {
    fn commit(&mut self, changes: AddressMap<Account>);
}

storage_by_account_id デフォルト実装:

#[inline]
fn storage_by_account_id(
    &mut self,
    address: Address,
    account_id: AccountId,
    storage_key: StorageKey,
) -> Result<StorageValue, Self::Error> {
    let _ = account_id;
    self.storage(address, storage_key)
}

3 本番実装:

実装場所バッキング行数
InMemoryDBcrates/database/src/in_memory_db.rsHashMap約50
AlloyDBcrates/database/src/alloydb.rsネットワーク経由の JSON-RPC約150
StateProviderDatabasereth: crates/storage/storage-api/src/database_provider.rsMDBX、スパースマークル数千

失敗例(誤解)

Database だけで Arc<MyDb> も動く」— 間違いDatabase メソッド &mut self + Arc<T>&T のみ → 共有並列で Database 使用不可。DatabaseRef 仲間トレイト + auto_impl 5 種 で解決。

commitDatabase のメソッドに追加で済む」— 間違い。読み専用 Database(フォークメインネット = RPC から読むだけ、バッキングストアなし)で commit 強制 = panic スタブ or 嘘メソッド、型汚染。std::io::ReadWrite の分離と同じパターン。

「メインネットフォークは InMemoryDB で十分」— 間違い。メインネット全状態を事前ロード = 非実用的(TB 級アーカイブ)。AlloyDB が遅延取得 + キャッシュ、tx が初触れスロットを上流ノードに問い合わせ、以降キャッシュから返る。

ステップで組み立てる

Step 1: DatabaseRef で読み専用アクセス

4 メソッド同じ + &self + auto_impl(&, &mut, Box, Rc, Arc) 5 種で Arc 対応。

Step 2: auto_impl リスト非対称の機械的帰結

&self < &mut self 制約強度、Arc<T> / Rc<T>&T のみ。

Step 3: DatabaseCommit で書き戻し分離

読み専用 Database 存在 + ライフサイクル違い(読み = 呼び出しごと、commit = tx 終了時)。

Step 4: storage_by_account_id 最適化

デフォルト実装で安全フォールバック、MDBX 等内部 ID 持つ実装はオーバーライドで検索省略 = パフォーマンスがトレイト API に住む

Step 5: 3 本番実装を読む

InMemoryDB(50 行 / HashMap)/ AlloyDB(150 行 / RPC)/ StateProviderDatabase(数千行 / MDBX)= 同形 3 世界。

Step 6: メインネットフォーク用に AlloyDB 選択

遅延取得 + キャッシュ、フルアーカイブ不要、150 行のグルーコードがフォーク機構の全て。

答え合わせ

  • DatabaseRef auto_impl リスト 5 種の機械的根拠: &self アクセスは &mut self より 厳密に弱い制約Arc<T> / Rc<T> は安価で共有可能な &T を出すが &mut T は決して出さない → DatabaseRef はこれら経由で動くが Database は動かない、設計選択でなく機械的帰結。
  • DatabaseCommit 分離の 2 理由: ① 読み専用 Database 存在(フォークメインネット = RPC から読むだけ、バッキングストアなし)= commit 強制で panic スタブ要求 or 型汚染、② ライフサイクル違い(読み = 呼び出しごと、commit = tx 終了時)= トレイト分離で型システムに強制。std::io::Read / Write パターン、混ぜると全読み手が書きを考える羽目。
  • メインネットフォーク用の AlloyDB 選択: RPC 経由遅延取得(tx が初触れスロットを上流ノード問い合わせ)+ インメモリキャッシュ = フルアーカイブダウンロード不要。メインネットフォークの仕組みは結局 Database を 150 行で実装したグルーコードがすべてInMemoryDB だとメインネット全状態事前ロード(非実用)、StateProviderDatabase はローカルフル Reth アーカイブ必要。

合格基準

  • DatabaseRef / DatabaseCommit / storage_by_account_id 3 追加ピースの役割を即答できる。
  • auto_impl リスト非対称(5 種 vs 2 種)の機械的根拠を即答できる。
  • DatabaseCommit 分離 2 理由(読み専用存在 + ライフサイクル違い)を即答できる。
  • 3 本番実装(InMemoryDB / AlloyDB / StateProviderDatabase)の用途と行数感を即答できる。
  • メインネットフォーク用に AlloyDB を選ぶ理由を 1 文で説明できる。

まとめ(3行)

  • 3 追加ピース = DatabaseRef&self 読み専用 + auto_impl 5 種で Arc 対応)+ DatabaseCommit(書き戻し分離)+ storage_by_account_id 最適化(パフォーマンスがトレイト API に住む)。
  • 3 本番実装(InMemoryDB 50 行 / AlloyDB 150 行 / StateProviderDatabase 数千行)= 同じ 4 メソッドトレイトで 3 つの全く違う世界、メインネットフォークは AlloyDB(RPC 遅延取得 + キャッシュ)。
  • auto_impl リスト非対称は設計でなく機械的帰結(&self < &mut self 制約強度)、次クイズで全体定着。