レッスン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 本番実装.
InMemoryDB(HashMap群、~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 本番実装:
| 実装 | 場所 | バッキング | 行数 |
|---|---|---|---|
InMemoryDB | crates/database/src/in_memory_db.rs | HashMap 群 | 約50 |
AlloyDB | crates/database/src/alloydb.rs | ネットワーク経由の JSON-RPC | 約150 |
StateProviderDatabase | reth: crates/storage/storage-api/src/database_provider.rs | MDBX、スパースマークル | 数千 |
失敗例(誤解)
「Database だけで Arc<MyDb> も動く」— 間違い。Database メソッド &mut self + Arc<T> は &T のみ → 共有並列で Database 使用不可。DatabaseRef 仲間トレイト + auto_impl 5 種 で解決。
「commit を Database のメソッドに追加で済む」— 間違い。読み専用 Database(フォークメインネット = RPC から読むだけ、バッキングストアなし)で commit 強制 = panic スタブ or 嘘メソッド、型汚染。std::io::Read と Write の分離と同じパターン。
「メインネットフォークは 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 行のグルーコードがフォーク機構の全て。
答え合わせ
DatabaseRefauto_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_id3 追加ピースの役割を即答できる。auto_implリスト非対称(5 種 vs 2 種)の機械的根拠を即答できる。DatabaseCommit分離 2 理由(読み専用存在 + ライフサイクル違い)を即答できる。- 3 本番実装(
InMemoryDB/AlloyDB/StateProviderDatabase)の用途と行数感を即答できる。 - メインネットフォーク用に
AlloyDBを選ぶ理由を 1 文で説明できる。
まとめ(3行)
- 3 追加ピース =
DatabaseRef(&self読み専用 +auto_impl5 種でArc対応)+DatabaseCommit(書き戻し分離)+storage_by_account_id最適化(パフォーマンスがトレイト API に住む)。 - 3 本番実装(
InMemoryDB50 行 /AlloyDB150 行 /StateProviderDatabase数千行)= 同じ 4 メソッドトレイトで 3 つの全く違う世界、メインネットフォークはAlloyDB(RPC 遅延取得 + キャッシュ)。 auto_implリスト非対称は設計でなく機械的帰結(&self<&mut self制約強度)、次クイズで全体定着。