Build OpenHL CLOB — adding the matching engine on top of the Reth substrate
Question
Build openhl's central limit order book (CLOB) matching engine in Rust. Hyperliquid runs perp markets on a CLOB; openhl is its open reference. Pinned to openhl SHAs 55a9dff (Stage 8a) / 428cc26 (Stage 8d).
Principle (minimum model)
- 13 lessons / 6 modules. Orientation → CLOB types → Matching engine (4 lessons) → Testing (2 lessons) → Bridge integration (3 lessons) → Capstone.
- The matching engine. A
Bookper market, holding bids + asks; orders submitted viasubmit, cancelled viacancel. The book matches incoming orders against resting ones; emitsFills. - Pinned to SHAs
55a9dff(Stage 8a) /428cc26(Stage 8d). Byte-for-byte reproducible against the openhl reference. - Prerequisites. The openhl consensus course (the Reth substrate this course builds on) + perp primer (market structure).
- Pure compute everywhere. No I/O, no async, no state mutation outside the book. Reusable in tests + production.
- Three operations. Limit order, Market order, Cancel.
Book::submitroutes Limit / Market to the privatesubmit_limit/submit_market; cancel isBook::cancel. - Production parallel. Hyperliquid HyperCore uses this exact discipline (different code, same shape).
Worked example + steps
Build OpenHL CLOB — adding the matching engine on top of the Reth substrate
The openhl consensus course ended with a single-validator BFT chain that decides blocks through a real Reth EVM in 0.02 seconds. It decides empty blocks. No transactions. No matching. No price discovery — the mechanism by which a trade's price emerges from the competing best-bid and best-ask of the order book, rather than being dictated by one party.
This course adds the CLOB matching engine — the part of Hyperliquid that turns "I want to buy 10 HYPE at $25" + "I want to sell 5 HYPE at $25" into a real fill. Stage 8a (701 lines) builds the pure state machine; Stage 8d (171 lines) wires it into the bridge so committed blocks now carry the fills the matching engine produced.
By the end of this course, cargo test clob_fills_flow_into_payload passes — a real fill flows from the matching engine through LiveRethEvmBridge::build_payload and into a payload that consensus then commits.
1. What you'll have at the end
A new crates/clob/ crate with:
- A price-time-priority matching engine that runs in microseconds — pure state machine, no I/O, fully deterministic.
Book+Order+Filltypes that match what a CEX would call its order book.- 12 tests passing: 9 hand-traced scenarios (empty book, FIFO priority, market-order liquidity exhaustion, partial fills, cancellation, no-crossed-book post-match) and 3 proptest invariants exercising 768 random scenarios (quantity conservation, no-crossed-book always, determinism = replayability).
And a new integration test in crates/evm/:
clob_fills_flow_into_payload— bootstraps a real Reth node, submits a maker bid + crossing taker sell (maker = a resting order placed earlier; taker = the incoming order that consumes that liquidity) to the bridge's CLOB, asserts the resulting fill appears in the nextbuild_payloadoutput, and asserts that earlier payloads weren't retroactively filled (drain semantics are forward-only).
By the time you finish, you can:
- Explain why a price-time-priority CLOB is the canonical structure for on-chain perpetual exchanges
- Reason about the trade-offs between matching engines that buffer fills (this one) vs those that emit them synchronously
- Reproduce the matching logic from scratch — and modify it (e.g., to support stop orders, post-only orders, or pro-rata matching) by knowing where to cut into the code
2. What you won't have at the end
This course covers Stage 8a + 8d only. It does NOT cover:
- Stage 9: custom EVM precompiles that read/write CLOB state (= the openhl precompiles course)
- Stage 8b: funding rate state machine (= the openhl funding course)
- Encoding fills as EVM-executable transactions (= future work past Stage 9 in openhl itself)
- Liquidations, mark-vs-index pricing, leverage limits
When you finish this course you have a working matching engine producing fills into committed blocks, but those fills are still a parallel list — not yet executable as Ethereum transactions readable by smart contracts. That's what the openhl precompiles course adds via custom EVM precompiles.
This is honest scoping. A CLOB engine without execution wiring is half the story; the other half (precompiles) is the openhl precompiles course.
3. Prerequisites
You need:
- The openhl consensus course complete — or, equivalently, a workspace at the state where the openhl consensus course ends. Your
crates/evm/src/live_node.rsshould already haveLiveRethEvmBridge<P>withprovider,chain_spec,validator, and optionalengine_handlefields. If yours doesn't, work through the openhl consensus course first. - Rust 1.95+ — openhl's
rust-toolchain.tomlpins1.95.0. Same prerequisite as the openhl consensus course. - Comfort with
BTreeMap,VecDeque,Reverse<T>, and proptest. If "natural ordering" and "reverse-ordering trick to walk highest-first" are unfamiliar, skim thestd::collections::BTreeMapdocs first.
You do not need:
- Any prior matching-engine experience (we'll build the data structures from scratch)
- Any prior order-book reading skill (the test scenarios walk every step)
- Multi-validator setup (still single-validator throughout)
4. Setup confirmation (do this now)
You should already have the two-directory workflow from the openhl consensus course:
~/code/my-openhl/— your workspace~/code/openhl-reference/— read-onlypsyto/openhlclone
Bring the reference repo up to date in case Stage 8 commits are newer than your clone:
cd ~/code/openhl-reference
git fetch origin
git log --oneline | head -15
# You should see commits up to and including SHA 0cac571 (Stage 7d) and
# 428cc26 (Stage 8d).
Then confirm your workspace is at the end-of-consensus-course state:
cd ~/code/my-openhl
cargo test -p openhl-evm --release 2>&1 | tail -10
# Expect: the openhl-evm tests pass, including
# - reth_dev_node_bootstraps (consensus course: booting a live Reth node)
# - live_bridge_builds_on_real_genesis (consensus course: LiveRethEvmBridge + validate_payload)
# - commit_sends_forkchoice_to_engine_when_handle_installed (consensus course: commit → forkchoice)
If those tests pass, you're at the right starting point. If they don't, finish the openhl consensus course first.
5. The 13-lesson map
| # | Module | What you build | End-of-lesson test |
|---|---|---|---|
| Lesson 0 | Orientation | (this lesson) | setup confirmed |
| Lesson 1 | CLOB types | newtypes — AccountId, OrderId, Price, Qty, Side, OrderType | cargo check -p openhl-clob |
| Lesson 2 | CLOB types | Order, Fill, FillResult | types compile |
| Lesson 3 | Matching engine | Book struct + Reverse<Price> trick + accessors | cargo check -p openhl-clob |
| Lesson 4 | Matching engine | submit → submit_limit + match_at_level — Limit orders, in-book matching | matches resting orders |
| Lesson 5 | Matching engine | submit_market — Market orders + crossing + partial fills | edge-case behaviour |
| Lesson 6 | Matching engine | cancel + empty-level cleanup | cancel-by-id works |
| Lesson 7 | Testing | 9 hand-traced unit tests | all 9 pass |
| Lesson 8 | Testing | 3 proptest invariants (qty conservation, no-crossed-book, determinism) | 768 random scenarios pass |
| Lesson 9 | Bridge integration | Add clob + pending_fills to LiveRethEvmBridge; submit_order method | bridge compiles |
| Lesson 10 | Bridge integration | build_payload drains pending fills; payload_fills(id) inspector | fills appear in payload |
| Lesson 11 | Bridge integration | clob_fills_flow_into_payload integration test | full pipeline test passes |
| Lesson 12 | Capstone | recap, what's next (precompiles via the openhl precompiles course) | (no test — recap) |
Lesson 11 is the milestone. Finishing Lesson 11, you have a fill produced by the matching engine flowing through the BFT engine into a real block. Lesson 12 names what's still missing (the fills aren't yet readable from smart contracts — that's the openhl precompiles course).
6. The answer-key discipline (same as the openhl consensus course)
Every lesson from Lesson 1 through Lesson 11 cites either SHA 55a9dff (Stage 8a) or 428cc26 (Stage 8d). After your lesson test passes:
cd ~/code/openhl-reference
git checkout 55a9dff # or 428cc26 for Lessons 9–11
diff -u ~/code/my-openhl/crates/clob/src/types.rs ./crates/clob/src/types.rs
# (etc.)
Match meaningfully — same types, same control flow. Whitespace and naming will differ.
One caveat for Lessons 9–11. In openhl's history, Stage 8a/8d (55a9dff, 428cc26) sit on top of Stage 7c (0844d58); Stage 7d (0cac571, commit → forkchoice) was committed later. So live_node.rs at 428cc26 has no engine_handle field, no with_engine_handle, no forkchoice call in commit, and no commit_sends_forkchoice_to_engine_when_handle_installed test. If your workspace has the consensus course's Stage 7d work, those lines show up in the diff; ignore them.
7. Setup confirmation — the actual Lesson 0 exercise
Before Lesson 1, run all of these and confirm they pass:
# 1. Rust version
rustc --version # expect: rustc 1.95.x or later
# 2. End-of-consensus-course state
cd ~/code/my-openhl && cargo test -p openhl-evm --release
# Expect: every `test result:` line reads `ok` with 0 failed
# 3. Reference repo has Stage 8 commits
cd ~/code/openhl-reference && git log --oneline | grep -E "(55a9dff|428cc26)"
# Expect: both SHAs appear
If all three pass, you're ready for Lesson 1.
Final check. In one sentence, what does this course add that the openhl consensus course didn't have? If your answer doesn't mention "a matching engine producing fills that flow into committed blocks", re-read §1.
Summary (3 lines)
- Build openhl CLOB = limit-order-book matching engine. 13 lessons / 6 modules. Pinned to SHAs
55a9dff(Stage 8a) /428cc26(Stage 8d). - Prerequisites: the openhl consensus course + perp primer. Pure compute throughout.
- Production parallel: Hyperliquid HyperCore. Three operations (limit / market / cancel).