FABRKNT
Step 2. CLOB — マッチングエンジンの追加とステートマシンの統合
Capstone
レッスン 13 / 13·CONTENT15 分50 XP
コース
Step 2. CLOB — マッチングエンジンの追加とステートマシンの統合
レッスンの役割
CONTENT
順序
13 / 13

レッスン12 — 作ったもの、まだ stub のもの、次に行く先

問い

11 レッスンで何を作り、何がまだ stub か? そして「price-time-priority CLOB を作った」という選択は、perp DEX 設計の広い landscape のどこに位置するのか?

原理(最小モデル)

  • CLOB は pure crate。 openhl-clob は EVM/consensus/async runtime に何も依存しない(レッスン1-8 でその境界を引いた)。約 800 行の Rust で、実 Reth-backed bridge に組み込まれた動く matching engine。
  • bridge が 2 つの非対称世界を仲介。 pure な matching engine と、Reth が回す async + I/O ヘビーな EVM 基盤を Mutex<...> 越しに繋ぐ(レッスン9-11)。
  • fill はまだ EVM 本流を横切らない parallel レーン。 pending HashMap に座り payload に attach されるだけで、BlockExecutor は fill を知らない。この破線が Step 3(precompile) で実線になる。
  • 最難関は matching でなく determinism。 submit の任意順序で同じ答えを生む保証。レッスン8 の proptest がテストしようと思わないバグを catch し、それが consensus に plug して safe な理由になる。

具体例

操作は 2 つ・観察可能な結果は 1 つ:

操作method内部
Limit submitBook::submit(Limit)反対側を price 以下/以上で辿りマッチ、残りを rest
Market submitBook::submit(Market)反対側を任意価格で辿りマッチ、残りを破棄
cancelBook::cancel(id)両 side を scan、削除、空 level を drop
inspectbest_bid/best_ask/depth_*read-only

データフロー: submit_order → clob.submit → pending_fills.push(caller に FillResult)/ build_payload → drain pending_fills → payload に attach → payload_fills(id)。drain は forward-only。

失敗例(誤解)

「動く matching engine = 製品」は誤り。EVM-encoded fill / precompile / multi-market / 永続 state / cancel index / pre-trade risk / multi-validator agreement / liquidation — どれも未着手。動く matching engine は基礎であって製品ではない。


ここまでで「pure crate・bridge 仲介・determinism」は着地した。ここから全体像・市場構造・残った placeholder を振り返る。

🛑 セルフチェック。 次に進む前に 1 文で言えるか:maker bid@100 と taker sell@95 が cross したとき、約定価格はいくらで、それはなぜか?(答え: 100。fill は常に resting=maker の価格。price-time priority は resting 側に決定権がある。即答できなければ レッスン4・11 を読み直す。)

作ったシステム(全体像)

~/code/my-openhl/crates/
├── clob/                          ← NEW crate(EVM/consensus/async 非依存の pure state machine)
│   └── src/
│       ├── types.rs               L1-L2: newtype + record(~109 LOC)
│       └── book.rs                L3-L8: Book + submit/cancel + unit×9 + proptest×3
└── evm/src/live_node.rs           L9-L11: bridge が clob/pending_fills を持ち build で drain

[Reth EthereumNode]  Engine API / Payload Builder / BlockExecutor(EVM 本流レーン)
        ▲(まだ未接続 — Step 3 で precompile 接続)
[LiveRethEvmBridge]  submit_order → Mutex<Book> → Mutex<Vec<Fill>> → pending HashMap → payload_fills(id)
        ▲
[openhl-clob]  Side/Price/Qty/Order/Fill + Book(BTreeMap<Reverse<Price>,VecDeque>) — pure

新規テスト 15 個(hand-trace 9 + proptest 3=768 シナリオ + integration 1)。workspace 計 39 テスト。fill は EVM 本流の 隣を並走するレーンで、BlockExecutorVec<Fill> を知らない(同じ PayloadId で identifier 紐づけのみ)。Step 3 がこの並走レーンを EVM execution path に交差させる。

まだ placeholder のもの(意図的な scope cut)

  1. EVM-executable transaction encoding — fill は parallel Vec<Fill> のまま、block body の tx でない(= Step 3)。
  2. Custom EVM precompile — contract が CLOB state を read(clob_read_best_bid)/ on-chain submit(clob_place_order)(= Step 3)。
  3. Funding rate state machine — mark vs index、定期 rebalancing(= Step 4)。
  4. 複数 market — 現状 orderbook 1 個。HashMap<MarketId, Book> で機械的に拡張可。
  5. 永続 CLOB state — in-memory のみ。再起動で resting order 消失(snapshot/event-sourcing 要)。
  6. Cancel-by-id index — O(n) scan(レッスン6 で明示的に simplicity を選択)。~10k order 超で HashMap<OrderId,(Side,Price)> を入れる。

市場構造 — あなたが本当に作ったもの

price-time-priority CLOB は perp DEX の 3 モデルの 1 つ:

  • CLOB(作ったもの): MM が resting order を置き taker が約定。価格はこの venue 上の需給で決まる。銘柄ごとに継続 quoting が要る → retail flow がある銘柄で機能。
  • RFQ(Variational, Paradigm): taker が quote request、dealer が JIT 応答し primary venue(CME/NYSE/別 CLOB)で hedge。価格を source venue から 持ってくる。idle 時の quoting コストがないので long tail でも economics が成立。
  • AMM(GMX, 旧 dYdX vAMM): 流動性を curve に集約。cold-start に強いが tail で破綻。

CLOB が勝つのはローカルな需給が存在する場合に限る。 BTC/ETH/SOL/HYPE のような TradFi 的 primary venue を持たない資産では HL の CLOB が真に価格を決める。WTI/NVDA/SPY perp は取引時間中 primary 市場の arbitrage shadow にすぎない(RFQ も同様、両者とも CME/NYSE order book の downstream)。cold-start 非対称は構造的 — 200 RWA 銘柄のうち 10 銘柄にしか flow がなければ残り 190 は薄い板になる。RFQ は dealer が需要時だけ quote するのでこれを回避する。

「last look」: 「RFQ には last look があり CLOB にはない」は半分正しい。CLOB でも MM は taker が hit するより速く quote を cancel できる(HL docs の cancel prioritization)。あなたの CLOB はこれを実装していない(submit/cancel は first-come-first-served)。追加したいなら変更箇所は BFT engine の ordering rule であって matching engine でない

位置づけ: あなたが作ったのは HL が crypto-native の top tier 銘柄 を pricing する engine — 現実に存在し経済的に重要な slice。RWA perp の long tail 用ではない(そちらは RFQ が構造的に向く)。問いは「CLOB か RFQ か」でなく「どの asset class のどの slice を、どの liquidity source で」。

Production-readiness チェックリスト

  • Fill を EVM tx として encode し BlockExecutor に route
  • custom precompile(clob_read_best_bid / clob_place_order
  • multi-market(HashMap<MarketId, Book>
  • 永続 state(snapshot + replay)
  • cancel index(HashMap<OrderId,(Side,Price)> で O(1))
  • order-id 衝突チェック(現状 caller の unique 性を信頼)
  • pre-trade risk チェック(margin 以下に追い込む order を matching 前に拒否)
  • telemetry(throughput / fill latency / depth-of-book)
  • multi-validator agreement(proptest determinism はローカル証明、ネット上は integration test が要る)
  • liquidation engine(= Step 4 領域)

次に行く先

  • rethlab 内: Step 3(precompile — clob_read_best_bid+clob_place_order)/ Step 4(funding state machine)。
  • rethlab 外: psyto/openhl Stage 9 ソース(crates/evm/src/precompiles/mod.rs)/ 参考 production engine(Project Serum, dYdX v4)/ proptest + QuickCheck 文献。

まとめ(3行)

  • 約 800 行で、実 Reth-backed bridge に組み込まれた pure な price-time-priority CLOB を作った(EVM/consensus 非依存)。
  • 最難関は matching でなく determinism — proptest がそれを守り、consensus に plug して safe な理由になる。
  • fill はまだ EVM 本流を並走する parallel レーン。precompile で交差させるのが Step 3 — 動く matching engine は基礎であって製品ではない。