レッスン13 — Chaos engineering を Rust EVM ノードに
問い
カスタム Reth フォークを出荷しているチームの大半は upstream のテストスイートを回して終わる。それは間違い。upstream のテストはハッピーパス(全ピア正直 / 全ディスク健全 / 全クロック正確)の正しさ。敵対的条件(DoS / 破損ページ / ずれたクロック / 嘘をつくピア)で生き残ることは別物 — どう検証するか?
原理(最小モデル)
- Differential fuzzing + Chaos は補完. Fuzz = 「有効入力で正しい答えか」、Chaos = 「乱れた条件下で正しい答えが返らなくなるか」。両方必要。
- 4 chaos カテゴリ. Network(パケット損失 / 分断)+ Disk(MDBX 破損 / write 失敗)+ Time(クロックずれ)+ Byzantine(嘘をつくピア)。各カテゴリにツール + 失敗シグネチャ + 対応パターン。
- Network =
tc/ Toxiproxy / Pumba. Linux カーネルレベル / アプリレベルプロキシ / Docker ラッパ。 - Disk = chaosfs / カーネル fault injection. FUSE で破損バイトを返す +
fail_io_timeoutで syscall 失敗注入。 - Time = libfaketime.
LD_PRELOADでクロックずれ注入。単調クロック逆行は VM pause / resume が必要。 - Byzantine = カスタム Reth フォーク. 意図的に間違った state root を提案するペイロードビルダー。
tcやchaosfsでは注入不可。 - 3 実践レベル. CI 内 chaos(毎 PR)+ Game day(四半期)+ Production chaos(週次、Netflix 風)。
- 信頼性トライアングル. Fuzzing(正しさ)+ Chaos(耐性)+ Auditing(潜在的バグ)= 3 つそろって production-grade。
具体例
4 chaos カテゴリ表:
| カテゴリ | 何を注入 | 現実世界の等価 |
|---|---|---|
| Network chaos | パケット損失、レイテンシ、分断、ピア排除 | クラウドリージョン障害、BGP、DDoS |
| Disk chaos | MDBX ページ破損、write 失敗、レイテンシ | 故障 SSD、ビット腐敗、FS バグ |
| Time chaos | クロックずれ、NTP ドリフト、単調逆行 | クロックドリフト、閏秒、VM 仮想化 |
| Byzantine chaos | 敵対ピアが無効ブロック / 矛盾投票 / 嘘 | 悪意バリデータ、鍵漏洩、MitM |
Network chaos:
# tc で 30% パケット損失
tc qdisc add dev eth0 root netem loss 30%
# 200ms レイテンシ
tc qdisc add dev eth0 root netem delay 200ms
# Docker コンテナ向け(Pumba)
pumba netem --duration 5m loss --percent 30 my-reth-validator
Disk chaos:
# MDBX データディレクトリに chaosfs をマウント
chaosfs --backend ./reth-data --mount ./reth-mdbx --corrupt-rate 0.001
# カーネル fault injection
echo 1 > /sys/kernel/debug/fail_io_timeout/probability
echo 100 > /sys/kernel/debug/fail_io_timeout/interval
Time chaos:
LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libfaketime.so.1 FAKETIME=+30s reth node
Byzantine chaos(カスタム Reth フォーク):
// byzantine-reth フォークで: 標準のペイロードビルダーを、
// state root の 1 bit を反転させたブロックを提案するものに差し替える。
impl PayloadBuilder for ByzantinePayloadBuilder {
fn build(&self, attrs: PayloadAttributes) -> ExecutionPayload {
let mut block = self.honest.build(attrs);
block.state_root ^= 1; // 1 bit 反転
block
}
}
3 実践レベル:
- CI 内 Chaos: chaos サブセット毎 PR、4 ノードテストネットで network loss + clock skew + disk fault、毎 commit
- Game day: 四半期半日、チームが手動 realistic 失敗を staging chain に注入、バグ + runbook ギャップを発見
- Production chaos: Netflix 風週 1、コントロールウィンドウで本番バリデータ意図的 1 台落とす。最も規律ある(Tempo / OP / Hyperliquid)チームが実行。
失敗例(誤解)
「tc で Byzantine も注入できる」— 間違い。tc はネットワーク層、Byzantine ピアはアプリケーション層(実 Reth コードを動かして能動的に誤動作)。カスタム Reth フォーク が必要。
「サイレントなディスク破損は気づける」— 間違い。サイレント = MDBX のチェックサムが捕まえない種類の破損(1 ビット反転で意味的に valid だが間違った値)→ ノードが間違った state を serve → 他ノードと分岐 → フォーク。「サイレント」 = レッスン1にとって最悪の失敗モード。
「Production chaos は危険、staging で十分」— 半分間違い。Production には staging で再現できない条件(実トラフィック / 実クライアント / 実ハードウェア)がある。規律あるチームは production も chaos する、ただし on-call が revert 準備済み + コントロールウィンドウ。
🛑 予測。 4 バリデータ BFT テストネット、1 バリデータに 80% パケット損失を 30 秒注入。起きるべき 3 つと 起きるべきでない 1 つ?(答え: 起きるべき: ① 残り 3 ノード quorum がブロック生成継続(BFT safety: f=1 に対して 3 of 4 ≥ 2f+1)、② 落ちたバリデータが復旧時に手動介入なしでキャッチアップ、③ 落ちたバリデータが inactivity でスラッシュ(policy 次第)。起きるべきでない: チェーン停止(safety 維持されているのに liveness が止まる = BFT バグ)。)
ステップで組み立てる
Step 1: 4 chaos カテゴリを即答
Network / Disk / Time / Byzantine。各カテゴリのツール 1-2 つ言える。
Step 2: tc で network chaos を 1 回実行
tc qdisc add dev eth0 root netem loss 30% → 4 バリデータテストネットで観察。
Step 3: chaosfs で disk chaos
FUSE でマウント → MDBX が破損バイトを返したとき Reth が検出するか + 優雅に停止するかサイレント不正 state serve か。
Step 4: libfaketime で time chaos
LD_PRELOAD で 30s 進める → 「未来の」ブロック提案 → ネットワークが reject + スラッシュ。
Step 5: Byzantine フォークで意図 1 bit 反転
PayloadBuilder を override → state root XOR 1 → 正直ノードが 1 スロット以内に reject すべき。
Step 6: 3 実践レベルに進化
CI 内 → Game day → Production。最後は文化、ツールではない。
Step 7: 信頼性トライアングルを完成
Fuzz(正しさ)+ Chaos(耐性)+ Auditing(潜在的)= production-grade。1 つ ship なし = 既知の壊れたコード ship。
答え合わせ
- Byzantine chaos がカスタムフォーク必要な理由: Byzantine ピアは Reth コードを動かして能動的誤動作(state root 1 bit 反転 / 矛盾投票 / 嘘な state)。
tc(ネットワーク層)/ chaosfs(ファイル層)では「アプリが嘘をつく」を表現不能。コードレベルの override が必須。 - 「サイレント破損」のカスケード: ① MDBX が間違った値を返す(チェックサムも一致してしまう種類)→ ② Reth が間違った state を計算 + serve → ③ stateRoot 検証が他ノードと一致しない → ④ ピアから「あなたのブロックは無効」とアナウンス → ⑤ ノードが分岐したフォークに居る → ⑥ 復旧は手動診断 + DB 再 sync。fork は 1 ノードから始まる。
libfaketimeでできないこと: 単調クロック逆行(Rust のInstantは単調保証 + プロセスがサスペンド/resume したり VM がマイグレートすると見える)→LD_PRELOADは実時計は嘘つけるがInstantは嘘つけない。カーネル時間ストレッチ or VM pause/resume が必要。
合格基準
- 4 chaos カテゴリを即答できる。
- 各カテゴリのツール 1-2 を言える。
- Byzantine chaos がカスタムフォーク必要な理由を説明できる。
- 「サイレント破損」のカスケードを 4 ステップで辿れる。
- 信頼性トライアングルの 3 柱を即答できる。
まとめ(3行)
- Chaos engineering = 4 カテゴリ(Network / Disk / Time / Byzantine)の失敗注入、ハッピーパステストでは見えない耐性を検証。
- Network =
tc/ Toxiproxy、Disk = chaosfs、Time = libfaketime、Byzantine = カスタム Reth フォーク(コードレベル override 必須)。 - 信頼性トライアングル(Fuzz + Chaos + Auditing)= production-grade、3 実践レベル(CI / Game day / Production)で文化として実装。