fix(indexer): CG/CMC orderbook depth — Openware total-across-book semantics #221

Closed
opened 2026-05-29 05:37:02 +00:00 by PlasticDigits · 9 comments
PlasticDigits commented 2026-05-29 05:37:02 +00:00 (Migrated from gitlab.com)

Summary

Align depth query parameter semantics on /cg/orderbook and /cmc/orderbook/* with Openware / CoinMarketCap exchange integration wording: depth is total levels across the book, split equally per side (e.g. depth=100 → 50 bids + 50 asks), not Kujira-style “levels per side.”

Related: #210, spot-check on Openware CMC spec.


Current codebase

Piece Behavior today
indexer/src/api/cg.rs cg_orderbook let depth = q.depth.unwrap_or(20).min(100); passed to sim → N levels per side
indexer/src/api/cmc.rs cmc_orderbook Same
indexer/src/api/orderbook_sim.rs walk_amm_book Loop for step in 1..=depth → depth bids and depth asks
indexer/tests/api_orderbook_lcd_mock.rs depth=50 asserts 50 bids and 50 asks
docs/CG_CMC_COMPLIANCE.md CG: “Number of bid/ask levels”; CMC: “levels per side” — inconsistent with Openware

Reference (Openware): depth ∈ [0,5,10,20,50,100,500]; “Depth = 100 means 50 for each bid/ask side.”

Reference (Kujira FIN — different): depth=200 → 200 per side (Kujira Coingecko API).

CL8Y currently implements Kujira-style per-side depth, not Openware total-across-book.


Why this is needed

  1. CMC listing compliance: Official exchange integration spec defines total depth; crawlers may assume depth=100 ⇒ 50+50, not 100+100 (2× expected payload).
  2. DoS / payload size: Per-side interpretation doubles response size at max depth vs Openware expectation.
  3. Doc accuracy: Internal compliance doc must not contradict the spec CMC links during application.
  4. CG alignment choice: Product decision: adopt Openware for both CG and CMC (this issue) or split semantics by endpoint (document explicitly if split — default recommendation: unify to Openware for CMC; CG follows same rule for one mental model unless listing team requires Kujira for CG only).

Constraints and guardrails

Area Guardrail
Mapping levels_per_side = depth / 2 (integer floor); depth=1 → document behavior (0 or 1 per side — must not panic)
Cap Keep max depth query ≤ 100 (per docs/indexer-invariants.md); at most 50+50 levels at cap
Allowed values Optionally snap to Openware ladder [5,10,20,50,100] or accept any integer 1–100 with computed per-side count — document choice
Odd depth e.g. depth=21 → 10+10 or 11+10 — pick rule, test, document
Cache key Cache on requested depth query param, not internal per-side count
#210 math Step schedule unchanged; only number of steps per side changes
Breaking change Responses shrink for same depth query — note in changelog for listing teams

Relevant files

File Role
indexer/src/api/cg.rs, cmc.rs Parse depth, compute per-side
indexer/src/api/orderbook_sim.rs walk_amm_book(..., levels_per_side, ...)
indexer/tests/api_orderbook_lcd_mock.rs Update assertions (depth=50 → 25+25)
indexer/tests/api_cg.rs, api_cmc.rs Depth cap tests
docs/CG_CMC_COMPLIANCE.md, docs/indexer-invariants.md Normative depth semantics

  1. Add fn levels_per_side(requested_depth: usize) -> usize with max(1, requested_depth / 2) or stricter Openware mapping.
  2. Handlers pass per-side count into walk_amm_book / future hybrid merge.
  3. Update all integration tests and compliance tables.
  4. OpenAPI: “depth — total levels across book (split evenly between bids and asks); max 100.”
  5. If CG listing requires Kujira per-side semantics, add ?depth_style=kujira|openware only if listing team insists — otherwise one rule for both.

Acceptance criteria

  • depth=100 returns at most 50 bids and 50 asks.
  • depth=20 returns 10 bids and 10 asks.
  • depth=1 defined and tested (no panic).
  • depth=9999 still capped at 100 total → 50+50.
  • docs/CG_CMC_COMPLIANCE.md describes Openware semantics explicitly.
  • Cache tests updated for new level counts.

Test plan — functional paths

# Scenario Expected
1 depth=100 ≤50 bids, ≤50 asks
2 depth=50 ≤25 each side
3 depth=1 Documented split
4 depth omitted Default 20 total → 10+10
5 Monotonicity (from #210) Still holds within per-side count

Test plan — attack vectors / abuse

# Vector Mitigation
A1 depth=100 payload DoS 50+50 cap enforced
A2 Odd depth confusion Test + doc
A3 Cache key vs internal count mismatch Cache uses query depth

Verification criteria

  1. Unit test table for levels_per_side mapping.
  2. Integration tests green.
  3. Compare response level count before/after for depth=50 (expect half).
  4. CMC listing QA template row updated.
## Summary Align **`depth`** query parameter semantics on **`/cg/orderbook`** and **`/cmc/orderbook/*`** with **Openware / CoinMarketCap exchange integration** wording: **`depth` is total levels across the book**, split **equally per side** (e.g. `depth=100` → **50 bids + 50 asks**), **not** Kujira-style “levels per side.” Related: [**#210**](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/210), spot-check on [Openware CMC spec](https://openware.com/sdk/2.6/docs/peatio/peatio/coin-market-cap). --- ## Current codebase | Piece | Behavior today | |-------|----------------| | [`indexer/src/api/cg.rs`](indexer/src/api/cg.rs) `cg_orderbook` | `let depth = q.depth.unwrap_or(20).min(100);` passed to sim → **N levels per side** | | [`indexer/src/api/cmc.rs`](indexer/src/api/cmc.rs) `cmc_orderbook` | Same | | [`indexer/src/api/orderbook_sim.rs`](indexer/src/api/orderbook_sim.rs) `walk_amm_book` | Loop `for step in 1..=depth` → **`depth` bids and `depth` asks** | | [`indexer/tests/api_orderbook_lcd_mock.rs`](indexer/tests/api_orderbook_lcd_mock.rs) | `depth=50` asserts **50 bids and 50 asks** | | [`docs/CG_CMC_COMPLIANCE.md`](docs/CG_CMC_COMPLIANCE.md) | CG: “Number of bid/ask levels”; CMC: “levels per side” — **inconsistent** with Openware | **Reference (Openware):** `depth` ∈ `[0,5,10,20,50,100,500]`; “Depth = 100 means **50 for each** bid/ask side.” **Reference (Kujira FIN — different):** `depth=200` → 200 per side ([Kujira Coingecko API](https://docs.kujira.app/dapps-and-infrastructure/fin/coingecko-api.md)). CL8Y currently implements **Kujira-style per-side** depth, not Openware **total-across-book**. --- ## Why this is needed 1. **CMC listing compliance**: Official exchange integration spec defines **total** depth; crawlers may assume `depth=100` ⇒ 50+50, not 100+100 (**2× expected payload**). 2. **DoS / payload size**: Per-side interpretation doubles response size at max depth vs Openware expectation. 3. **Doc accuracy**: Internal compliance doc must not contradict the spec CMC links during application. 4. **CG alignment choice**: Product decision: adopt **Openware for both CG and CMC** (this issue) **or** split semantics by endpoint (document explicitly if split — default recommendation: **unify to Openware** for CMC; CG follows same rule for one mental model unless listing team requires Kujira for CG only). --- ## Constraints and guardrails | Area | Guardrail | |------|-----------| | **Mapping** | `levels_per_side = depth / 2` (integer floor); `depth=1` → document behavior (0 or 1 per side — **must not panic**) | | **Cap** | Keep max **`depth` query ≤ 100** (per [`docs/indexer-invariants.md`](docs/indexer-invariants.md)); at most **50+50** levels at cap | | **Allowed values** | Optionally snap to Openware ladder `[5,10,20,50,100]` or accept any integer 1–100 with computed per-side count — document choice | | **Odd depth** | e.g. `depth=21` → 10+10 or 11+10 — pick rule, test, document | | **Cache key** | Cache on **requested `depth` query param**, not internal per-side count | | **#210 math** | Step schedule unchanged; only **number of steps** per side changes | | **Breaking change** | Responses shrink for same `depth` query — note in changelog for listing teams | --- ## Relevant files | File | Role | |------|------| | [`indexer/src/api/cg.rs`](indexer/src/api/cg.rs), [`cmc.rs`](indexer/src/api/cmc.rs) | Parse `depth`, compute per-side | | [`indexer/src/api/orderbook_sim.rs`](indexer/src/api/orderbook_sim.rs) | `walk_amm_book(..., levels_per_side, ...)` | | [`indexer/tests/api_orderbook_lcd_mock.rs`](indexer/tests/api_orderbook_lcd_mock.rs) | Update assertions (`depth=50` → 25+25) | | [`indexer/tests/api_cg.rs`](indexer/tests/api_cg.rs), [`api_cmc.rs`](indexer/tests/api_cmc.rs) | Depth cap tests | | [`docs/CG_CMC_COMPLIANCE.md`](docs/CG_CMC_COMPLIANCE.md), [`docs/indexer-invariants.md`](docs/indexer-invariants.md) | Normative depth semantics | --- ## Recommended direction 1. Add `fn levels_per_side(requested_depth: usize) -> usize` with `max(1, requested_depth / 2)` or stricter Openware mapping. 2. Handlers pass **per-side** count into `walk_amm_book` / future hybrid merge. 3. Update all integration tests and compliance tables. 4. OpenAPI: “`depth` — total levels across book (split evenly between bids and asks); max 100.” 5. If CG listing **requires** Kujira per-side semantics, add `?depth_style=kujira|openware` **only** if listing team insists — otherwise **one rule for both**. --- ## Acceptance criteria - [ ] `depth=100` returns **at most 50** bids and **50** asks. - [ ] `depth=20` returns **10** bids and **10** asks. - [ ] `depth=1` defined and tested (no panic). - [ ] `depth=9999` still capped at **100** total → **50+50**. - [ ] [`docs/CG_CMC_COMPLIANCE.md`](docs/CG_CMC_COMPLIANCE.md) describes Openware semantics explicitly. - [ ] Cache tests updated for new level counts. --- ## Test plan — functional paths | # | Scenario | Expected | |---|----------|----------| | 1 | `depth=100` | ≤50 bids, ≤50 asks | | 2 | `depth=50` | ≤25 each side | | 3 | `depth=1` | Documented split | | 4 | `depth` omitted | Default 20 total → 10+10 | | 5 | Monotonicity (from #210) | Still holds within per-side count | --- ## Test plan — attack vectors / abuse | # | Vector | Mitigation | |---|--------|------------| | A1 | `depth=100` payload DoS | 50+50 cap enforced | | A2 | Odd `depth` confusion | Test + doc | | A3 | Cache key vs internal count mismatch | Cache uses query `depth` | --- ## Verification criteria 1. Unit test table for `levels_per_side` mapping. 2. Integration tests green. 3. Compare response level count before/after for `depth=50` (expect half). 4. CMC listing QA template row updated.
PlasticDigits commented 2026-05-29 05:37:03 +00:00 (Migrated from gitlab.com)

marked as related to #210

marked as related to #210
PlasticDigits commented 2026-05-29 05:37:11 +00:00 (Migrated from gitlab.com)

mentioned in issue #210

mentioned in issue #210
PlasticDigits commented 2026-05-29 06:50:24 +00:00 (Migrated from gitlab.com)

mentioned in commit 70e4b1a0c2

mentioned in commit 70e4b1a0c2f23664def26fc9a87f0c724300eaad
PlasticDigits commented 2026-05-29 06:50:24 +00:00 (Migrated from gitlab.com)

mentioned in commit de01bd5acd

mentioned in commit de01bd5acd046fdae7c48775baaa3083f12c2def
PlasticDigits commented 2026-05-29 06:51:15 +00:00 (Migrated from gitlab.com)

Implementation complete (merged to main)

@brouie — please verify when you have a moment.

Summary

CG/CMC depth now follows Openware / CMC exchange integration semantics: the query value is total levels across the book, split evenly per side (levels_per_side = max(1, depth / 2)).

Examples:

  • depth=100 (cap) → 50 bids + 50 asks (was 100+100)
  • depth=20 (default) → 10 + 10
  • depth=1 → 1 + 1 (no panic)
  • Odd totals floor (e.g. depth=21 → 10+10)

Cache keys still use the requested depth query param (not per-side count).

Code / docs

Verification checklist

  • GET /cg/orderbook?ticker_id=<pair>&depth=100 → ≤50 bids and ≤50 asks
  • GET /cg/orderbook?...&depth=50 → ≤25 each side
  • GET /cg/orderbook?...&depth=1 → 1 bid + 1 ask
  • Omitted depth → 10 bids + 10 asks (default total 20)
  • depth=9999 capped at 100 total → ≤50 per side
  • CMC /cmc/orderbook/<pair>?depth=15 → 7+7 (floor split)
  • Second identical request within 30s hits cache (LCD not re-queried)
  • Bid/ask price monotonicity unchanged (#210)
  • Listing QA doc row matches live behavior

Tests run

cd indexer && cargo test --lib orderbook_sim
cd indexer && cargo test --test api_orderbook_lcd_mock -- --test-threads=1

Issue left open pending your sign-off.

## Implementation complete (merged to `main`) @brouie — please verify when you have a moment. ### Summary CG/CMC `depth` now follows **Openware / CMC exchange integration** semantics: the query value is **total levels across the book**, split evenly per side (`levels_per_side = max(1, depth / 2)`). Examples: - `depth=100` (cap) → **50 bids + 50 asks** (was 100+100) - `depth=20` (default) → **10 + 10** - `depth=1` → **1 + 1** (no panic) - Odd totals floor (e.g. `depth=21` → 10+10) Cache keys still use the **requested** `depth` query param (not per-side count). ### Code / docs - `indexer/src/api/orderbook_sim.rs` — `cap_orderbook_depth`, `levels_per_side` - `indexer/src/api/cg.rs`, `cmc.rs` — handlers use cap + cached sim - Tests: `api_orderbook_lcd_mock.rs`, `api_cg.rs`, `api_cmc.rs` - [`docs/CG_CMC_COMPLIANCE.md`](docs/CG_CMC_COMPLIANCE.md), [`docs/indexer-invariants.md`](docs/indexer-invariants.md) - [`skills/AGENTS_INDEXER_AMM_ORDERBOOK_SIM.md`](skills/AGENTS_INDEXER_AMM_ORDERBOOK_SIM.md) ### Verification checklist - [ ] `GET /cg/orderbook?ticker_id=<pair>&depth=100` → ≤50 bids and ≤50 asks - [ ] `GET /cg/orderbook?...&depth=50` → ≤25 each side - [ ] `GET /cg/orderbook?...&depth=1` → 1 bid + 1 ask - [ ] Omitted `depth` → 10 bids + 10 asks (default total 20) - [ ] `depth=9999` capped at 100 total → ≤50 per side - [ ] CMC `/cmc/orderbook/<pair>?depth=15` → 7+7 (floor split) - [ ] Second identical request within 30s hits cache (LCD not re-queried) - [ ] Bid/ask price monotonicity unchanged (#210) - [ ] Listing QA doc row matches live behavior ### Tests run ```bash cd indexer && cargo test --lib orderbook_sim cd indexer && cargo test --test api_orderbook_lcd_mock -- --test-threads=1 ``` Issue left **open** pending your sign-off.
PlasticDigits commented 2026-05-29 06:58:58 +00:00 (Migrated from gitlab.com)

mentioned in issue #220

mentioned in issue #220
PlasticDigits commented 2026-05-29 06:59:05 +00:00 (Migrated from gitlab.com)

mentioned in commit 8b595c89f3

mentioned in commit 8b595c89f32dcbf2f6d18d13cdf7d412d7c1760f
PlasticDigits commented 2026-05-30 06:26:19 +00:00 (Migrated from gitlab.com)

Verification complete (agent, 2026-05-30)

Verified GitLab #221 on main @ 9f78dcd using worktree verify/issue-221 (no code delta vs origin/main). LocalTerra indexer at http://127.0.0.1:3001 left running; no infra restart.

Implementation (already on main)

Tests (worktree)

cd indexer && cargo test --lib orderbook_sim -- --test-threads=1
cd indexer && cargo test --test api_orderbook_lcd_mock -- --test-threads=1
cd indexer && cargo test cg_orderbook --test api_cg -- --test-threads=1

All #221-scoped tests passed (19 unit + 10 lcd_mock + 2 cg orderbook).

Live checks (ticker_id=EMBER_CORAL)

Query Bids Asks Pass
depth=100 50 50 yes
depth=50 25 25 yes
depth=1 1 1 yes
omitted (default 20) 10 10 yes
depth=9999 (cap 100) 50 50 yes
CMC depth=15 7 7 yes
Monotonicity @ depth=50 bids desc, asks asc yes

Checklist for @brouie (spot-check)

  • GET /cg/orderbook?ticker_id=<pair>&depth=100 → ≤50 bids, ≤50 asks
  • GET /cg/orderbook?...&depth=50 → ≤25 each side
  • GET /cg/orderbook?...&depth=1 → 1+1
  • Omitted depth → 10+10
  • depth=9999 → ≤50 per side
  • GET /cmc/orderbook/<pair>?depth=15 → 7+7
  • Repeat identical request within 30s does not re-hit LCD (cache)
  • Bid/ask monotonicity unchanged (#210)

All acceptance criteria from the issue body satisfied in this pass — closing.

## Verification complete (agent, 2026-05-30) Verified GitLab **#221** on `main` @ `9f78dcd` using worktree `verify/issue-221` (no code delta vs `origin/main`). LocalTerra indexer at `http://127.0.0.1:3001` left running; no infra restart. ### Implementation (already on main) - `indexer/src/api/orderbook_sim.rs` — `cap_orderbook_depth`, `levels_per_side` (`max(1, depth/2)`) - `indexer/src/api/cg.rs`, `cmc.rs` — capped total depth passed to hybrid sim - Docs: [`docs/CG_CMC_COMPLIANCE.md`](docs/CG_CMC_COMPLIANCE.md) § AMM Orderbook Simulation, [`docs/indexer-invariants.md`](docs/indexer-invariants.md) - Agents: [`skills/AGENTS_INDEXER_AMM_ORDERBOOK_SIM.md`](skills/AGENTS_INDEXER_AMM_ORDERBOOK_SIM.md) § Depth query (#221) ### Tests (worktree) ```bash cd indexer && cargo test --lib orderbook_sim -- --test-threads=1 cd indexer && cargo test --test api_orderbook_lcd_mock -- --test-threads=1 cd indexer && cargo test cg_orderbook --test api_cg -- --test-threads=1 ``` All #221-scoped tests passed (19 unit + 10 lcd_mock + 2 cg orderbook). ### Live checks (`ticker_id=EMBER_CORAL`) | Query | Bids | Asks | Pass | |-------|------|------|------| | `depth=100` | 50 | 50 | yes | | `depth=50` | 25 | 25 | yes | | `depth=1` | 1 | 1 | yes | | omitted (default 20) | 10 | 10 | yes | | `depth=9999` (cap 100) | 50 | 50 | yes | | CMC `depth=15` | 7 | 7 | yes | | Monotonicity @ `depth=50` | bids desc, asks asc | yes | ### Checklist for @brouie (spot-check) - [ ] `GET /cg/orderbook?ticker_id=<pair>&depth=100` → ≤50 bids, ≤50 asks - [ ] `GET /cg/orderbook?...&depth=50` → ≤25 each side - [ ] `GET /cg/orderbook?...&depth=1` → 1+1 - [ ] Omitted `depth` → 10+10 - [ ] `depth=9999` → ≤50 per side - [ ] `GET /cmc/orderbook/<pair>?depth=15` → 7+7 - [ ] Repeat identical request within 30s does not re-hit LCD (cache) - [ ] Bid/ask monotonicity unchanged (#210) All acceptance criteria from the issue body satisfied in this pass — closing.
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-05-30 06:26:30 +00:00
PlasticDigits commented 2026-05-30 06:32:31 +00:00 (Migrated from gitlab.com)

mentioned in issue #224

mentioned in issue #224
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
code/cl8y-dex-terraclassic#221
No description provided.