レッスン15 — 作ったもの、まだ stub のもの、次に行く先
問い
16 レッスンで何を作り、何を まだ 作っていないか? そして「テストが pass する」から「real value を任せていい」まで、正直に何が残っているか?
原理(最小モデル)
- 空ディレクトリ → 動く single-validator BFT chain。
cargo initから、実 Reth EL を通じて実 block を ~0.02 秒で確定させる chain まで到達した。Consensus + EVM crate で約 1,400 行 + ~250 行の integration test。 - 4 つの
ConsensusBridgemethod が全て live。 build_payload / payload_ready / validate_payload / commit が real Reth コードパスに到達。CL クライアントが触る Reth public surface のほとんどに相当。 - L1 構築の最難関は engine を書くことではない。 Malachite がほとんどやってくれて、こちらは接続しただけ。最難関は 自分のコードに何ができて何ができないかに正直であること と、「できる」側を証明するテストを書くこと。全レッスンが happy-path + negative-path の assertion を持っていた。
具体例
4 method の最終状態:
| メソッド | 最初の impl | live impl | 到達する real Reth コード |
|---|---|---|---|
build_payload | L4 (in-memory) | L13 | HeaderProvider::sealed_header_by_hash, ChainSpec::next_block_base_fee |
payload_ready | L4 | L13 | (Reth call なし — bridge の pending map) |
validate_payload | L4 (stub Valid) | L13 | EthBeaconConsensus::validate_header_against_parent(4 sub-check) |
commit | L4 (HashMap) | L14 | ConsensusEngineHandle::fork_choice_updated(in-process Engine API) |
失敗例(誤解)
「workspace テスト 38 個 pass = production ready」は誤り。意図的に skip した scope が 6 つある(後述)。特に engine newPayload 統合が欠落 で、commit の forkchoice は今 SYNCING 応答を受ける。「テストが pass」と「real value を任せられる」の間には、コース本体より難しい long-pole 作業が並ぶ。
ここまでで「何を作り・最難関は scope への正直さ」は着地した。ここから全体像・残った placeholder・production checklist を振り返る。
🛑 セルフチェック。 次に進む前に:4 つの
ConsensusBridgeメソッドのうち、唯一データが EL → CL 方向 に流れる seam はどれか? そしてbuild_payloadとvalidate_payloadが同じ block ルールに合意できるのは、何を共有しているからか?(答え:payload_ready/Arc<ChainSpec>。即答できなければ L3・L13 を読み直す。)
作ったシステム(全体像)
~/code/my-openhl/
├── Cargo.toml reth-* 16 + malachite 8、すべて SHA pin
├── crates/types/ L2: CL↔EL 共通 contract 型(BlockHash, PayloadId, …)
├── crates/evm/ EL 側
│ ├── bridges/in_memory.rs L4: InMemoryEvmBridge(HashMap state)
│ ├── bridges/reth.rs L5: RethEvmBridge(alloy 型, real hash_slow)
│ ├── reth_node.rs L11: bootstrap 証明(test-only)
│ └── live_node.rs L12-14: LiveRethEvmBridge<P>
│ - L12: BlockNumReader 経由 parent lookup
│ - L13: EthBeaconConsensus validate
│ - L14: ConsensusEngineHandle forkchoice
└── crates/consensus/ CL 側(フル BFT engine)
├── bridge.rs L3: ConsensusBridge trait
├── types/ + context.rs L6: 10 Malachite Context sub-type + impl
├── signing*.rs L7: canonical encoding + SigningProvider
├── codec.rs L8: OpenHlCodec(real 1 + stub 7)
├── node.rs L9: OpenHlNode + start_engine
└── engine_app.rs L10: run_engine_app(12 AppMsg arm)
左右 2 世界は L3 の ConsensusBridge 4 メソッドだけで会話する。run_engine_app(L10)が B: ConsensusBridge generic なので Stub/InMemory/Reth/LiveReth の 4 bridge が同じ loop で走る。LiveRethEvmBridge 内の chain_spec: Arc<ChainSpec> が build/validate 両方で参照される共有 source of truth — ここが分かれた瞬間に self-fork する。
まだ placeholder のもの(意図的な scope cut)
- Engine
newPayload統合 — 欠落。commitの forkchoice に対し engine は body がないのでSYNCING。VALIDまで進めるには build_payload 出力をExecutionPayloadとして encode し forkchoice の前にnew_payloadで送る。ブロッカーは EVM-executable な tx がまだないこと — Hyperliquid 型は約定(fill)を protocol-initiated system tx / precompile injection としてユーザー署名なしで payload に差し込む(ステップ2 全体に相当)。 - Real
Codecimpl — real 1(ProposalPart)+ stub 7。2 番目の validator を足した瞬間に gossip/WAL/sync codec が発火する。wire format(protobuf 等)を選んで各型を実装。 - Multi-validator gossip — 未 exercise。libp2p は configure 済みだが peer discovery / vote propagation / sync は未テスト。
- 永続 WAL — エフェメラル tempdir。crash recovery 検証には real WAL codec + chaos test が要る。
- Slashing + double-sign 検知 — なし。value を扱うネットワークには危険。
- Custom Hyperliquid 型挙動 — vanilla Ethereum。precompile / CLOB 駆動 payload assembly は将来コース(ステップ2-3)。
Production-readiness チェックリスト
- 7 Codec stub を real impl に置換
-
engine_newPayload統合(engine の canonical view を bridge に合わせる) - N=2+ node 共有 chainspec の multi-validator integration test
- WAL crash-recovery test(commit 途中 kill → 再起動 → head 検証)
- 永続
home_dir(tempdir でない) - engine 応答を log(discard でなく)
- slashing/double-sign フック接続
- key rotation 手順
- 運用テレメトリ(round duration / build latency / validate failure)
- canonical encoding format の独立セキュリティレビュー(L7 のバイトレイアウトは wire spec の一部)
- 部分 partition 下の proposer manipulation の脅威モデル
16 レッスン前にはできなかった、今できること
- 実 EL に対してフル Rust BFT engine を bootstrap できる(mock でも FFI でもなく、同じ workspace で
EthereumNodeを走らせる)。 - producer/validator の自己整合性を推論できる(
chain_spec.next_block_base_feeが build/validate 両方を駆動)。 - incremental-stub パターン(trait bound が surface を強制、埋められない所は明確な failure mode で stub)。
- 2 つの汎用インフラ(Reth / Malachite)を handshake interface(
Node/ConsensusBridgetrait)で接続できる。 - プロトコルエラーと運用エラーを区別できる(
Rejected/Internal、Invalidと伝播)。 - live read が起きたことを証明するテストを書ける(L12 の
assert_eq!(block.number, 1)が load-bearing)。
次に行く先
- rethlab 内: Reth Expert(
reth-l1-architect、ステップ2 CLOB+ —BlockExecutor/ state-root / MDBX 深掘り)/ Reth Expert L11 — Running a Reth fork in production(devnet が動いたら次に来る、まさにこれ — build flag / monitoring / diff testing / upgrade 規律を fork に適用)/ Reth Consensus Engineering(slashing / vote extension / fault tolerance)。 - rethlab 外:
psyto/openhlStages 8-9(CLOB + custom precompile)/ Malachite spec docs / 実 Reth full node(cargo run --bin reth -- node --chain dev)/category-labs/monad-bft(もう 1 つの成熟 Rust BFT。Malachite は embeddability 最適、Monad-BFT は single-chain throughput 最適 — Rust BFT は単一の shape でないと腹落ちする。GPLv3 なので citation/読解 OK、code copy は NG)。
まとめ(3行)
cargo initから実 Reth EL で実 block を確定させる single-validator BFT chain を、約 1,400 行で作った(4 method 全て live)。- L1 構築の最難関は engine を書くことでなく、scope に正直であることと「できる」側を証明するテストを書くこと。
- production まではコース本体より難しい long-pole(newPayload / real codec / multi-validator / WAL / slashing)が残る — これを使って何か作りに行こう。