FABRKNT
Step 1. Consensus — cargo init から single-validator devnet 構築
Capstone
レッスン 16 / 16·CONTENT25 分60 XP
コース
Step 1. Consensus — cargo init から single-validator devnet 構築
レッスンの役割
CONTENT
順序
16 / 16

レッスン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 つの ConsensusBridge method が全て live。 build_payload / payload_ready / validate_payload / commit が real Reth コードパスに到達。CL クライアントが触る Reth public surface のほとんどに相当。
  • L1 構築の最難関は engine を書くことではない。 Malachite がほとんどやってくれて、こちらは接続しただけ。最難関は 自分のコードに何ができて何ができないかに正直であること と、「できる」側を証明するテストを書くこと。全レッスンが happy-path + negative-path の assertion を持っていた。

具体例

4 method の最終状態:

メソッド最初の impllive impl到達する real Reth コード
build_payloadL4 (in-memory)L13HeaderProvider::sealed_header_by_hash, ChainSpec::next_block_base_fee
payload_readyL4L13(Reth call なし — bridge の pending map)
validate_payloadL4 (stub Valid)L13EthBeaconConsensus::validate_header_against_parent(4 sub-check)
commitL4 (HashMap)L14ConsensusEngineHandle::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_payloadvalidate_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)

  1. Engine newPayload 統合 — 欠落。commit の forkchoice に対し engine は body がないので SYNCINGVALID まで進めるには build_payload 出力を ExecutionPayload として encode し forkchoice の前に new_payload で送る。ブロッカーは EVM-executable な tx がまだないこと — Hyperliquid 型は約定(fill)を protocol-initiated system tx / precompile injection としてユーザー署名なしで payload に差し込む(ステップ2 全体に相当)。
  2. Real Codec impl — real 1(ProposalPart)+ stub 7。2 番目の validator を足した瞬間に gossip/WAL/sync codec が発火する。wire format(protobuf 等)を選んで各型を実装。
  3. Multi-validator gossip — 未 exercise。libp2p は configure 済みだが peer discovery / vote propagation / sync は未テスト。
  4. 永続 WAL — エフェメラル tempdir。crash recovery 検証には real WAL codec + chaos test が要る。
  5. Slashing + double-sign 検知 — なし。value を扱うネットワークには危険。
  6. 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/ConsensusBridge trait)で接続できる。
  • プロトコルエラーと運用エラーを区別できる(Rejected / InternalInvalid と伝播)。
  • 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/openhl Stages 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)が残る — これを使って何か作りに行こう。