Build OpenHL — from cargo init to a single-validator devnet
Question
Build OpenHL, an open-source Hyperliquid-shaped chain, in Rust: from cargo init to a single-validator Malachite BFT round wired to an in-process Reth node.
Principle (minimum model)
- 16 lessons across 8 modules: Orientation → Foundations → Contract types → EL test double → CL types → Engine integration → Live Reth → Capstone.
- Two upstream engines plus your glue. Reth v2.2.0 (execution) and Malachite v0.5.0 (Tendermint-style BFT consensus), joined by a four-method
ConsensusBridgetrait that you write. - One answer key. Each lesson cites the
psyto/openhlcommit where the same code first appeared — e.g.75be9de/5fc7ca1(workspace + pins),13113db(contract types + trait),3b43586(in-memory bridge),708472c(first block through the engine actors),0cac571(commit via Reth's Engine API). - Milestone, then live Reth. Lesson 10:
first_block_via_engine_actorsdecides one block through Malachite's real actor pipeline, with a stub bridge in place of the EVM. Lessons 11–14 put a live in-process Reth dev node behind the same trait. Lesson 15 reviews both proofs and what is still missing. - Scope. The consensus substrate only — no CLOB, precompiles, funding/oracle/liquidation or vaults (and so no working perp DEX yet).
- Prerequisites. Rust 1.95+, cargo workspaces, async/await and trait impls. No prior Reth, Malachite or consensus knowledge needed.
- Design analogy. Hyperliquid also pairs a BFT consensus (HyperBFT) with an EVM (HyperEVM). Its node is closed source and not Reth-based, so OpenHL shows a Hyperliquid-shaped design, not how Hyperliquid is built.
Worked example + steps
Build OpenHL — from cargo init to a single-validator devnet
This is not a course you read. This is a course you build.
Over the next 16 lessons, you'll start from cargo init on an empty directory and end with a Rust workspace that compiles, decides a block through Malachite's real engine actors, and builds, validates and commits blocks against a real in-process Reth node. The codebase you end with is your own — written by you, line by line — and it will look almost exactly like psyto/openhl at the same Stage. That repo is your answer key.
Hyperliquid runs on a closed-source stack — HyperBFT consensus, HyperCore matching engine, HyperEVM execution. There is no public Rust reference. OpenHL is what the open-source version looks like, and this course is how you build the Module 1 substrate of it yourself.
Why a CLOB? Hyperliquid's design choice is price-time-priority order book matching because its target market — top-tier crypto-native perps — has enough continuous retail flow for the order book to do real local price discovery. RFQ systems (Variational, Paradigm) win the long tail of assets by quoting just-in-time and hedging on a primary venue; pool-based designs (GMX-style) optimize cold-start at the cost of tail economics. You're about to build the engine for the slice of the market where CLOB is the right answer. The openhl CLOB course's capstone reflects on this tradeoff in depth — for now, the design context is enough to start.
1. What you'll have at the end
A 30-second BFT primer first. BFT consensus runs in rounds of three phases:
- propose — one chosen validator broadcasts a block proposal
- prevote — every validator broadcasts a yes/no/nil vote on the proposal
- precommit — validators lock their vote
A block is decided (= final) once ≥ 2/3 of validators have precommitted it. Within each round the proposer is selected deterministically from the validator set; if the round fails (no quorum), the protocol advances to the next round with a different proposer.
Malachite is the Rust BFT engine that drives this state machine; your job in this course is to wire your application (header construction, EVM execution) into it via a Context trait.
Keep five words in hand — propose, prevote, precommit, decided, proposer — and the rest of the course vocabulary lands cleanly.
By the end of Lesson 10, on your own machine, cargo test first_block_via_engine_actors passes a single-validator BFT round through Malachite's real engine actors — about 0.02 seconds, per the openhl commit that introduced it (708472c). At that point a stub bridge stands in for the EVM. Lessons 11–14 put a live in-process Reth dev node behind the same bridge. The two are proven separately:
Proof 1 (Lesson 10, stub EVM):
your run_engine_app →
Malachite engine actors (proposer election, prevote, precommit) →
build_payload / payload_ready (StubBridge) →
decided block
Proof 2 (Lessons 12–14, live Reth, called from tests):
LiveRethEvmBridge::build_payload → Reth dev-node provider → header construction
LiveRethEvmBridge::validate_payload → EthBeaconConsensus header checks
LiveRethEvmBridge::commit → fork_choice_updated to Reth's Engine API
Joining the two — run_engine_app driving LiveRethEvmBridge in one test — is left as the next step after the course. Every line of both paths is code you wrote. None of it is magic; all of it is open. By the time you finish, you can:
- Read any line of
psyto/openhlModule 1 code and explain why it's there - Modify any part of the bridge contract and run the tests to see what breaks
- Fork the substrate to start your own Hyperliquid-shape chain —
psyto/openhlbecomes your reference implementation, not your dependency
2. What you won't have at the end
This course covers openhl Build arc Module 1 only — the consensus substrate. It does NOT cover:
- Module 2: the CLOB matching engine
- Module 3: custom EVM precompiles that read CLOB state
- Module 4: funding, oracle, liquidations
- Module 5: protocol-native vault primitive
Modules 2 and 3, plus Module 4's funding and liquidation (including ADL), each have their own course later in this curriculum; the oracle and the vault are not covered by a course. When you finish this course you have the substrate — the BFT-EVM contract, the actor wiring, the live-Reth integration. You do not have a working perp DEX. The perp DEX is Modules 2 through 5 on top of what you build here.
This is honest scoping. A "build your own Hyperliquid" course that promises everything in 16 lessons would be lying to you.
3. How this course works
Every lesson has the same shape:
- Goal. "By the end of this lesson,
cargo test <name>will pass." That test does not pass right now. You will make it pass. - Recap. Where you are in the workspace. What the last lesson built. What the test landscape looks like as of now.
- Plan. What you're about to build, and what design choices the openhl maintainers made when they built it the first time.
- Walk-through. Step-by-step code. Type it, save it, run
cargo checkafter each step. - Test. Run
cargo test <name>. It should pass. If it doesn't, here are the typical mistakes. - Design reflection. One or two load-bearing decisions you just encoded. We come back to these in later lessons.
- Answer key. Git-checkout point in
psyto/openhlwhere the same code lives. Diff your code against that SHA if you want to verify. - Next lesson. What gets built next, and why.
The lesson is the instruction set. The code you write is the artifact. psyto/openhl at the matching SHA is the answer key.
4. Prerequisites
You need:
- Rust 1.95+.
rustup default 1.95.0or newer. - Git. You'll clone
psyto/openhlonce as the answer key. - Basic comfort with cargo workspaces, async/await, and trait impls. If
#[async_trait]andimpl Trait for Foo { ... }are new vocabulary, this course will move too fast. Take the introductory Reth courses first. - An editor that handles Rust well. VS Code + rust-analyzer is fine. Vim/Helix/Emacs are fine.
- Plenty of free disk space — 20 GB or more. Reth's compile graph is large, and you'll build it in two workspaces (yours and the reference); a check-only build of a subset of Reth already fills about 4 GB of
target/.
You do not need:
- Any prior consensus-protocol knowledge (we explain BFT as we go)
- Any prior Reth knowledge (lesson 1 introduces the dep)
- Any prior Malachite knowledge (lesson 1 introduces it too)
- A multi-machine setup (everything runs single-process on your laptop)
5. Setup (do this now)
You will have two directories on your machine:
~/code/my-openhl/— your workspace. You write code here. This is yours.~/code/openhl-reference/— a clone ofpsyto/openhl. You read code here when you want to compare. This is read-only.
# Your workspace
mkdir -p ~/code/my-openhl && cd ~/code/my-openhl
cargo init --lib
# (the package name will default to `my-openhl` from the directory name. Lesson 1
# restructures this into a workspace where the inner crates are `openhl-types`
# / `openhl-consensus` / …, so the root package name disappears at that point.
# We'll also delete the default lib.rs in Lesson 1 — this `cargo init` only exists
# to give git a starting commit to track against.)
# Pin the same Rust toolchain in your own workspace too
printf '[toolchain]\nchannel = "1.95.0"\n' > rust-toolchain.toml
# Answer-key reference
mkdir -p ~/code && cd ~/code
git clone https://github.com/psyto/openhl.git openhl-reference
cd openhl-reference
cargo check # this WILL take a long time the first time — Reth is big
psyto/openhl is archived (read-only), and its main goes far beyond this course — at main some Module 1 tests, including first_block_via_engine_actors, are marked #[ignore]. Always compare against the SHA each lesson cites, not against main.
If cargo check in openhl-reference passes, you have the right toolchain. Move on. If it fails, fix toolchain version first — rust-toolchain.toml in that repo pins Rust 1.95.0, and you've just dropped the same pin into my-openhl/, so rustup should auto-install the required toolchain for both.
6. The 16-lesson map
Each row is one lesson. Each lesson ends with a passing cargo test.
| # | Module | What you build | End-of-lesson test |
|---|---|---|---|
| Lesson 0 | Orientation | (this lesson) | setup confirmed |
| Lesson 1 | Foundations | workspace + Reth & Malachite pinned | cargo check --workspace clean |
| Lesson 2 | Contract types | openhl-types primitives (BlockHash, PayloadId, ...) | cargo test -p openhl-types |
| Lesson 3 | Contract trait | ConsensusBridge trait — 4 messages as async fns | cargo check -p openhl-consensus |
| Lesson 4 | EL test double | InMemoryEvmBridge — fake EVM for testing | InMemoryEvmBridge tests pass |
| Lesson 5 | Reth-typed bridge | RethEvmBridge — same contract, real Reth types | RethEvmBridge tests pass |
| Lesson 6 | CL types | OpenHlContext + 10 Context sub-types | context compiles |
| Lesson 7 | Signing | OpenHlSigningProvider — Ed25519 sign/verify | sign/verify round-trip |
| Lesson 8 | Codec | OpenHlCodec — the codec slot the engine demands | codec round-trip |
| Lesson 9 | Node | OpenHlNode + the first start_engine call | engine start/stop smoke |
| Lesson 10 | App loop | run_engine_app — the actor pipeline that ties it all together | first_block_via_engine_actors — Module 1 milestone, BFT round closes |
| Lesson 11 | Live Reth | bootstrap a real Reth dev-node in a test | reth_dev_node_bootstraps |
| Lesson 12 | Live bridge — build path | LiveRethEvmBridge (build_payload side) reads parent from a live provider | live_bridge_builds_on_real_genesis |
| Lesson 13 | Live bridge — validate path | LiveRethEvmBridge (validate_payload side) wires EthBeaconConsensus for real header validation | validate-path tests |
| Lesson 14 | Live bridge — commit path | LiveRethEvmBridge (commit side) wires fork_choice_updated via Reth's in-process Engine API | commit_sends_forkchoice_to_engine_when_handle_installed |
| Lesson 15 | Capstone | review what you built and what is still missing (codec stubs, newPayload, multi-validator, WAL, slashing) | no new code |
Lesson 10 is the major milestone. Finishing Lesson 10, you have BFT consensus producing a block end-to-end through your actor system. Lessons 11–14 swap your stub Reth for real Reth. Lesson 15 takes stock: the Module 1 commits of psyto/openhl (through 0cac571) have no single test that runs run_engine_app against LiveRethEvmBridge, so the two halves are proven separately, and joining them is the natural next step beyond the course. (Later openhl stages wire the two together in bin/openhl, from 7a52443 on.)
7. The answer-key discipline
Every lesson cites a psyto/openhl SHA — the commit where the same code first appeared. After you finish the lesson and your test passes:
cd ~/code/openhl-reference
git checkout <SHA-from-lesson>
# Now compare. Your code in ~/code/my-openhl/ should be ~equivalent.
diff -ru ~/code/my-openhl/crates/types ./crates/types
Your code will differ in trivial ways (whitespace, variable names, comment wording). What matters: types, signatures, control flow are equivalent. If those diverge meaningfully, the lesson didn't land; re-read the design-reflection section and adjust.
8. Setup confirmation — the actual Lesson 0 exercise
Before you move to Lesson 1, run all of this and confirm it all passes:
# 1. Rust version
rustc --version # expect: rustc 1.95.x or later
# 2. Your workspace exists
ls ~/code/my-openhl # expect: Cargo.toml, rust-toolchain.toml, src/
# 3. Reference exists and compiles
cd ~/code/openhl-reference && cargo check # expect: "Finished" eventually
If all three pass, you are set up correctly. Move to Lesson 1.
💡 Self-check before moving on
In one sentence, can you state the difference between
~/code/my-openhland~/code/openhl-reference?If you can't say in your own words "one is the real workspace I write line by line, the other is the mirror I only consult when I'm stuck," re-read §5 before starting Lesson 1. If you blur this distinction now, you'll eventually write code into
openhl-referenceby mistake and lose the boundary between what you wrote and what you borrowed. Make the boundary muscle memory before moving on.
Summary (3 lines)
- Build openhl's consensus substrate = Malachite BFT + Reth behind a
ConsensusBridgetrait. 16 lessons / 8 modules. - Each lesson cites the
psyto/openhlSHA to diff against; milestone isfirst_block_via_engine_actors(Lesson 10), then live Reth (Lessons 11–14). - Prerequisites: Rust async + traits; no prior Reth/Malachite needed. Design analogy: a Hyperliquid-shaped BFT + EVM chain (Hyperliquid's own node is closed source).