A standalone, deterministic trading matching engine in Rust.
The same ordered command sequence produces byte-identical state and output — on any platform, any future build of the same engine-core version. Everything else (performance, features) is subordinate to that.
| Crate | Type | Responsibility |
|---|---|---|
engine-core |
lib | All matching logic: book, order types, matching loop, and the canonical state snapshot. No I/O, async, networking, SQL, or clock. The reusable jewel. |
engine-wire |
lib | The wire protocol: length-prefixed bincode frames, order-entry + market-data vocab. Versioned independently of the journal. |
engine-server |
bin | Wraps the core: append-only journal, replay/recovery + snapshot-accelerated recovery, the sequencer + clock, the durability watermark, the durable-gated market-data channel, the SQL projection, and the networked service. |
engine-client |
lib | The Rust SDK: order entry + market data, the provisional/committed contract, and an auto-reconnecting transport with exactly-once resubmit. |
cargo build
cargo test --workspace
cargo clippy --workspace --all-targets # determinism lints (float ban)cargo run -p engine-server --example serveBoots a real server (sequencer thread + order-entry/market-data over loopback TCP) and drives it end-to-end: register, rest, subscribe, cross the book, see the pushed update, clean shutdown.
In-memory, single-threaded, integer-only — and benchmarked, not asserted. Tiers are reported separately, never blended. Numbers are indicative (unpinned Ryzen 9 6900HX dev laptop; official figures want a pinned runner).
cargo bench -p engine-core. Throughput is batch-timed; latency is sampled per-op
(so it includes timer overhead — read it as an upper bound).
| command | throughput | p50 | p99 | p99.9 (ns) |
|---|---|---|---|---|
| add (rest on a ladder) | ~1.8 M orders/sec | ~280 ns | ~880 ns | ~1.4 µs |
| cancel (by id) | ~5.1 M cancels/sec | ~140 ns | ~920 ns | ~1.7 µs |
| marketable, 1 fill | ~2.8 M matches/sec | ~310 ns | ~1.15 µs | ~1.9 µs |
| deep sweep (64 fills) | ~14 M matches/sec | — | — | — |
| add, expiry sweep active | ~12 K orders/sec | ~82 µs | ~158 µs | ~203 µs |
The first four rows are the fast path (cost dominated by the canonical BTreeMap
price ladders). The last row was a ~150× cliff — a single GTD order armed an
O(book) expiry sweep on every command; a lazy O(due) index fixed it, byte-identically.
cargo bench -p engine-server. The two latencies are reported separately: the
provisional ack (no fsync except PerEvent) and the commit watermark (per durability
knob). File journal on tmpfs — fsync cost is storage-dependent.
| latency | p50 | p99 |
|---|---|---|
| provisional-ack (open-loop, coordinated-omission-resistant) | ~54 µs | ~139 µs |
| provisional-ack (closed-loop round-trip) | ~43 µs | ~90 µs |
commit — PerEvent / OsFlushOnly (window 0) |
~20 ns | ~40 ns |
commit — GroupCommit (2 ms tick) |
~2.0 ms | ~2.1 ms |
GroupCommit keeps the ack fast but pays ~one tick at commit; PerEvent folds
durability into the ack. (This bench caught a real bug: a missing server-side
TCP_NODELAY → ~40 ms Nagle stalls, now fixed.)
cargo bench --bench stress (both crates) drives a mixed workload for millions of
commands. The honest finding: sustained core throughput degrades (~1.07 M → 475 K
cmds/sec over 10 M commands) and RSS climbs to ~1 GB — not the matching loop
(per-command stays sub-µs) but the unbounded dedup table (see Limits). The
networked service saturates near ~110 K acks/sec (single sequencer + fsync), with
sub-ms→low-ms p99 below that.
engine-core/tests/golden_replay.rs runs a fixed corpus, canonically encodes the
(Seq, SubSeq) event stream, and pins it by hash — asserting two runs are
byte-identical and match the pin. Integer-only math makes that pin a true
cross-platform fingerprint, exercised on x86_64 + aarch64 in CI.
Feature-complete, built and verified phase by phase (each phase = a feature commit + a multi-agent verification commit):
- deterministic core + matching, with a golden replay-hash gate + a differential suite vs an independent reference matcher;
- durable journal + replay/recovery, the sequencer + durability watermark, the wire
protocol, the durable-gated market-data channel, and a runnable server (order entry
- market-data push over UDS/TCP);
- SQL projection (journal → SQLite), snapshots + accelerated recovery, and the
full SDK (both channels, the provisional/committed contract, auto-reconnecting
transport with
client_order_iddedup); - Tier-1/2/3 + recovery benchmarks; CI runs the suite + clippy on x86_64 and the
determinism gates on aarch64 (via
cross/QEMU).
Remaining is polish, not features: the cascade-depth breaker for hostile/seedable books (v1-deferred) and O(1) intrusive-list order cancel (benchmark-gated).
Built for a trusted, single-economy session (a 1–15-player floor); pre-1.0, the API may change. None of these are determinism bugs — they are v1's documented edges.
- Trusted single peer. Framing is DoS-bounded, but the server isn't hardened against a hostile multi-tenant client. UDS uses file-permission auth; TCP needs the per-session secret.
- Bounded sessions. The dedup table and journal grow with distinct
client_order_ids / command count — fine for a game session, but a long-lived, high-throughput deployment needs windowing/compaction (not yet built). Recovery reads the whole journal into memory; snapshots skip the replay, not the bytes. - Single process, recover-on-restart. No sharding or failover; availability is restart + replay/snapshot recovery.
- No cascade-depth breaker yet. A non-adversarial book can't cascade unbounded, so
v1 ships uncapped; a seedable public book wants the (designed)
max_cascade_depthbreaker first. - Determinism scope. Guaranteed within one
engine-coreversion (mismatches refused). Floats are compiler-forbidden; the cross-platform hash runs on x86_64 + aarch64 in CI.