@@ -70,8 +70,8 @@ call `settle_funds` to pull their balances out.
7070 that can withdraw accumulated fees.
7171- An ** OrderBook** account — two stores: bids sorted highest-first,
7272 asks sorted lowest-first, each holding up to 1024 entries. Rather
73- than a plain list of orders, each side uses a balanced tree for fast
74- lookup — see [ Ensuring fast order matching performance] ( #ensuring-fast-order-matching-performance ) .
73+ than a plain list of orders, each side uses a depth-bounded tree (a
74+ critbit trie) for fast lookup — see [ Ensuring fast order matching performance] ( #ensuring-fast-order-matching-performance ) .
7575 Each entry stores enough to drive matching (price, quantity,
7676 ` order_id ` ); the full ` Order ` PDA holds the authoritative state.
7777- A ** MarketUser** PDA — one per ` (market, wallet) ` pair. Tracks the
@@ -895,9 +895,10 @@ instead of 1 024.
895895The specific data structure used here is a
896896[critbit tree](https://cr.yp.to/critbit.html) (short for *critical-bit
897897tree*) — a compact binary radix trie where each internal node splits on
898- the first bit where two keys disagree. It has the same O(log n) bounds
899- as other balanced trees but operates on fixed-width integer keys, so no
900- rebalancing rotations are needed. This implementation is ported from
898+ the first bit where two keys disagree. Unlike a self-balancing BST it
899+ never rotates or recolours nodes; its depth is instead bounded by the
900+ *bit width of the key* rather than the number of orders, so it stays
901+ shallow no matter what order keys arrive in. This implementation is ported from
901902[Openbook v2](https://github.com/openbook-dex/openbook-v2);
902903[Phoenix](https://github.com/Ellipsis-Labs/phoenix-v1) uses the same
903904approach. Both are production Solana CLOBs worth reading alongside this
@@ -1550,15 +1551,18 @@ Ordered by difficulty.
15501551 ` place_order ` , skip resting entries whose ` expires_at ` is past;
15511552 add a permissionless ` sweep_expired ` instruction.
15521553
1553- ### Why a balanced tree (critbit)?
1554+ ### Why a depth-bounded tree (critbit)?
15541555
1555- ** Tree balancing must be guaranteed , not assumed.** A plain binary
1556+ ** Worst-case depth must be bounded , not assumed.** A plain binary
15561557search tree only keeps a roughly-balanced shape when its inputs arrive
15571558in random order. In an order book an attacker chooses the inputs — the
15581559prices of their orders — so nothing they choose can be allowed to
1559- determine the tree's shape. A * balanced-by-construction* tree
1560- (red-black, critbit, AVL, …) enforces a bounded shape via invariants
1561- maintained on every insert and delete, regardless of input order.
1560+ inflate the tree's depth. Two families of structure defend against
1561+ this: * self-balancing* BSTs (red-black, AVL, …) that restore a bounded
1562+ height with rotations on every insert and delete, and * radix tries*
1563+ like critbit whose depth is capped by the key's bit width no matter
1564+ which keys are present. Both keep every operation cheap regardless of
1565+ input order; this example uses the second.
15621566
15631567** Concrete attack on a plain BST.** An attacker posts orders at
15641568monotonically increasing prices ($100, $101, $102, $103, …). Each new
@@ -1568,20 +1572,21 @@ degenerated into a linked list of length N. Lookups, inserts, and
15681572matches all walk O(N) instead of O(log N).
15691573
15701574** Why this matters on Solana specifically.** Solana transactions have
1571- a ~ 1.4M compute-unit budget. If ` place_order ` walks an unbalanced book
1575+ a ~ 1.4M compute-unit budget. If ` place_order ` walks a degenerate book
15721576and exceeds the CU limit mid-match, the transaction aborts and the
15731577placer pays fees for nothing. Worse, * legitimate users' orders fail
1574- because an adversary skewed the tree shape* . A balanced-by-construction
1575- tree bounds every operation at O(log N) regardless of input, so the
1576- attack is structurally impossible.
1577-
1578- ** Why critbit specifically.** Critbit (a binary radix trie keyed on
1579- the price bits) is balanced-by-construction in a different way from a
1580- red-black tree: tree depth is bounded by the * bit width of the sort
1581- key* (128 bits here — price in the high 64, sequence number in the
1582- low 64), not by insertion order. Inserts and deletes don't need
1583- rotations or recolouring; the trie shape is a deterministic function
1584- of which keys are present. This example uses the critbit slab from
1578+ because an adversary skewed the tree shape* . A depth-bounded tree keeps
1579+ every operation cheap regardless of input, so the attack is
1580+ structurally impossible.
1581+
1582+ ** Why critbit specifically.** Critbit is a binary radix trie keyed on
1583+ the order's sort bits — * not* a self-balancing BST, so it never rotates
1584+ or recolours nodes. Its shape is a deterministic function of which keys
1585+ are present, and its depth can never exceed the * bit width of the sort
1586+ key* (128 bits here — price in the high 64, sequence number in the low
1587+ 64), so it cannot degenerate into a long chain under any insert order.
1588+ An insert splits exactly one leaf and adds exactly one inner node; a
1589+ delete splices one out. This example uses the critbit slab from
15851590Openbook v2 (` src/state/slab/ ` ).
15861591
15871592### Harder
0 commit comments