Skip to content

Commit 4647f0e

Browse files
committed
docs: clarify critbit is depth-bounded, not self-balancing
"Balanced-by-construction" lumped critbit in with red-black/AVL trees, which it isn't — critbit never rotates or recolours. Reframe the §8 heading and prose around the accurate property: a radix trie whose depth is bounded by the key's bit width (<=128), so it cannot degenerate under adversarial insert order. The security conclusion is unchanged. https://claude.ai/code/session_01G6iaAjzg8aoFwe8ZWWG9VR
1 parent 21ff523 commit 4647f0e

1 file changed

Lines changed: 27 additions & 22 deletions

File tree

defi/order-book/anchor/README.md

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
895895
The specific data structure used here is a
896896
[critbit tree](https://cr.yp.to/critbit.html) (short for *critical-bit
897897
tree*) — 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
903904
approach. 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
15561557
search tree only keeps a roughly-balanced shape when its inputs arrive
15571558
in random order. In an order book an attacker chooses the inputs — the
15581559
prices 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
15641568
monotonically increasing prices ($100, $101, $102, $103, …). Each new
@@ -1568,20 +1572,21 @@ degenerated into a linked list of length N. Lookups, inserts, and
15681572
matches 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
15721576
and exceeds the CU limit mid-match, the transaction aborts and the
15731577
placer 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
15851590
Openbook v2 (`src/state/slab/`).
15861591

15871592
### Harder

0 commit comments

Comments
 (0)