Indexer: order-book insert-hint resolution API (batch hint-resolver + targeted price-window fetch) #267

Closed
opened 2026-06-01 04:19:00 +00:00 by PlasticDigits · 13 comments
PlasticDigits commented 2026-06-01 04:19:00 +00:00 (Migrated from gitlab.com)

Summary

Add indexer read APIs that let clients resolve limit-order insert hints and fetch a bounded price-window of the book in one request, instead of paginating the whole book or hitting the chain directly. Two endpoints, bundled because they share the same LCD book-walk machinery, the same budget/rate-limit guardrails, and the same "never guess across gaps" correctness rule:

  1. Batch hint-resolver: given a side and a list of prices, return the predecessor order_id for each price (or an explicit unknown/head marker).
  2. Targeted price-window fetch: return the contiguous slice of the book that spans a [price_from, price_to] band.

These are the indexer primitives consumed by the frontend deep-book ladder work (companion issue) and the contract anchor hint (companion issue). The frontend must use these endpoints, never a direct LCD/RPC call.


Current codebase

  • indexer/src/api/limit_book_lcd.rs — fetch_limit_book_page (~L105–L180) walks the on-chain FIFO list from order_book_head (or after a cursor) up to page_limit orders, returning (orders, has_more, next_after_order_id). Constants: LIMIT_BOOK_PAGE_DEFAULT=50, LIMIT_BOOK_PAGE_MAX=100, LIMIT_BOOK_LCD_QUERY_BUDGET=101 (H7 budget; head/cursor + one limit_order per row). Side validation + broken-link detection already present.
  • indexer/src/api/pairs.rs — get_pair_limit_book handler (registered at /api/v1/pairs/{addr}/limit-book in indexer/src/api/mod.rs ~L333–L336, inside lcd_heavy_router with rate_limit_lcd_heavy_rps). Also get_pair_limit_book_shallow, get_pair_order_book_head.
  • indexer/tests/api_limit_book_lcd_mock.rs, indexer/tests/api_limit_book_deep.rs — existing regression/mock coverage.
  • Client today: frontend-dapp/src/services/indexer/client.ts::getPairLimitBookPage (~L235) + useLimitBookInfinite.ts page the whole book; frontend-dapp/src/utils/limitBookInsertHint.ts::resolveLimitInsertHintAfter does hint resolution client-side over loaded pages and returns null on any has_more pagination gap.

Why the current shape is insufficient

  • Client-side hint resolution forces full pagination of a deep book and degrades to null (→ on-chain head walk) whenever the target price is past the loaded window (has_more).
  • There is no way to resolve N rung predecessors in one round trip; the dApp would issue many page requests.
  • The "never guess across gaps" rule is reimplemented on the client; it belongs server-side next to the authoritative walk.

Why this is needed

Deep-book ladders need predecessor anchors for many prices cheaply and authoritatively. A server-side resolver does the bounded LCD walk once, applies the gap rule centrally, and returns compact answers — enabling the contract's single-anchor/per-rung hint path and removing the dApp's need to paginate or touch LCD/RPC directly.


Constraints / guardrails

  • Stay within H7 budget. Each request must respect LIMIT_BOOK_LCD_QUERY_BUDGET (101 smart queries). A resolver spanning more depth than the budget allows must return an explicit partial/unknown result for out-of-budget prices, not silently guess.
  • Never guess across gaps. If the walk cannot reach a price's neighborhood within budget, return predecessor: null with an explicit resolved: false / reason (pagination_gap), mirroring resolveLimitInsertHintAfter's safety rule. Stale-but-safe is the contract; a wrong predecessor must never be emitted.
  • Reuse lcd_heavy_router rate limiting (rate_limit_lcd_heavy_rps) — these endpoints are LCD-heavy and must not bypass the stricter per-IP governor.
  • Bound input size. Cap the prices list (e.g. ≤ pair max_batch_rungs hard cap 100) and validate each price is a positive decimal; reject otherwise (400).
  • Side validation identical to existing walk (reject mixed/!side rows; surface broken-link as 400/500 per current behavior).
  • Read-only, no caching of arbitrary deep walks beyond what already exists; results reflect a single consistent walk per request.
  • No new direct-RPC surface for the frontend — frontend consumes only these HTTP endpoints.
  • Predecessor semantics must match the contract's composite-key ordering (docs/limit-orders.md § Ordering) and the existing client resolver exactly (bids descending price/ascending id; asks ascending price; equal price → last order at that level = FIFO tail).

Relevant files

  • indexer/src/api/limit_book_lcd.rs
  • indexer/src/api/pairs.rs
  • indexer/src/api/mod.rs (route registration in lcd_heavy_router)
  • indexer/tests/api_limit_book_lcd_mock.rs, indexer/tests/api_limit_book_deep.rs
  • OpenAPI/Swagger schema (served from /swagger-ui/)
  • Docs: docs/integrators.md (§ Batch placement insert hints), docs/limit-orders.md
  • Client wiring (companion frontend issue): frontend-dapp/src/services/indexer/client.ts

  1. Shared walk core. Factor the predecessor-finding logic so both endpoints reuse fetch_limit_book_page's walk and the budget counter. Walk head→tail accumulating the running predecessor; for each target price emit the last order id whose composite key sorts before the insert slot.
  2. Batch hint-resolver endpoint — GET /api/v1/pairs/{addr}/limit-book/insert-hints?side=bid&prices=p1,p2,... → { hints: [{ price, predecessor_order_id: u64|null, resolved: bool, reason?: "head"|"pagination_gap" }], budget_exhausted: bool }. Single ascending/descending walk resolves all prices; once budget is exhausted, remaining unresolved prices return resolved:false, reason:"pagination_gap".
  3. Targeted price-window fetch — extend GET .../limit-book with optional price_from / price_to (or a dedicated /limit-book/window) returning the contiguous slice covering the band plus has_more/cursor so the client can fetch exactly the ladder's price span instead of from head.
  4. Add ToSchema types; register in lcd_heavy_router; document in OpenAPI + integrators.md.

Acceptance criteria

  • insert-hints returns the correct predecessor for each price on a known mock book, matching resolveLimitInsertHintAfter for the same data (bids/asks/equal-price/head-insert cases).
  • Prices beyond the budget/walk return resolved:false with reason:"pagination_gap" and never a guessed id.
  • Head-insert price (better than head) returns predecessor_order_id:null, reason:"head".
  • price_from/price_to window returns exactly the contiguous slice spanning the band with correct has_more/cursor.
  • Both endpoints sit behind lcd_heavy_router rate limiting and respect LIMIT_BOOK_LCD_QUERY_BUDGET.
  • Invalid side, malformed price, or oversized price list → 400.
  • OpenAPI/Swagger updated; integrators.md documents both.

Test plan — all paths

  • Mock book bids: prices above head (→ head), between levels (→ correct predecessor), equal to a level (→ FIFO tail id), below tail (→ last id when fully loaded).
  • Mock book asks: symmetric cases.
  • Multi-price batch in one request: order-independent input list returns per-price answers; single walk.
  • Budget boundary: book deeper than budget → early prices resolved, later prices pagination_gap.
  • Price-window: band fully inside loaded depth; band crossing the budget edge (partial + has_more); empty band.
  • Empty book: all prices → head/null.
  • Broken book link / wrong-side row: surfaces existing error semantics.
  • Parity test: same fixture fed to server resolver and resolveLimitInsertHintAfter → identical predecessors.

Test plan — attack / abuse / hack vectors

  • Oversized prices list (e.g. 10k entries): rejected by cap (400), cannot fan out LCD calls.
  • Budget-exhaustion DoS: a single request cannot exceed LIMIT_BOOK_LCD_QUERY_BUDGET; deep book yields partial/unknown rather than unbounded LCD load.
  • Rate-limit bypass attempt: endpoints confirmed under lcd_heavy_router governor; load test shows per-IP throttling.
  • Gap-guess injection: adversarial book where the desired predecessor is just past budget — assert the endpoint returns pagination_gap, never a plausible-but-wrong id (which on-chain would still be safe, but the API must not assert false confidence).
  • Malformed/negative/huge decimal prices: validation rejects (400), no panic.
  • Stale-walk consistency: if the book mutates mid-walk (LCD eventual consistency), result is internally consistent for the single walk and clearly a point-in-time snapshot; downstream contract verify remains the safety net.
  • Side spoofing: requesting side=bid against ask ids returns the documented side-mismatch error.

Verification criteria

  • cargo test -p indexer api_limit_book (mock + deep) green, including new resolver/window tests and the parity test.
  • cargo clippy --all-targets -- -D warnings clean; OpenAPI regen passes.
  • Manual curl against LocalTerra: insert-hints for a known ladder band returns expected ids; deep request returns budget_exhausted:true with pagination_gap tail.
  • Confirmed both routes are inside lcd_heavy_router (rate-limited) via code + a throttling integration check.
  • integrators.md + Swagger document request/response and the gap rule.
## Summary Add indexer read APIs that let clients resolve limit-order **insert hints** and fetch a **bounded price-window** of the book in one request, instead of paginating the whole book or hitting the chain directly. Two endpoints, bundled because they share the same LCD book-walk machinery, the same budget/rate-limit guardrails, and the same "never guess across gaps" correctness rule: 1. **Batch hint-resolver**: given a side and a list of prices, return the predecessor `order_id` for each price (or an explicit `unknown`/`head` marker). 2. **Targeted price-window fetch**: return the contiguous slice of the book that spans a `[price_from, price_to]` band. These are the indexer primitives consumed by the frontend deep-book ladder work (companion issue) and the contract anchor hint (companion issue). The frontend must use these endpoints, **never** a direct LCD/RPC call. --- ## Current codebase - `indexer/src/api/limit_book_lcd.rs` — `fetch_limit_book_page` (~L105–L180) walks the on-chain FIFO list from `order_book_head` (or after a cursor) up to `page_limit` orders, returning `(orders, has_more, next_after_order_id)`. Constants: `LIMIT_BOOK_PAGE_DEFAULT=50`, `LIMIT_BOOK_PAGE_MAX=100`, `LIMIT_BOOK_LCD_QUERY_BUDGET=101` (H7 budget; head/cursor + one `limit_order` per row). Side validation + broken-link detection already present. - `indexer/src/api/pairs.rs` — `get_pair_limit_book` handler (registered at `/api/v1/pairs/{addr}/limit-book` in `indexer/src/api/mod.rs` ~L333–L336, inside `lcd_heavy_router` with `rate_limit_lcd_heavy_rps`). Also `get_pair_limit_book_shallow`, `get_pair_order_book_head`. - `indexer/tests/api_limit_book_lcd_mock.rs`, `indexer/tests/api_limit_book_deep.rs` — existing regression/mock coverage. - Client today: `frontend-dapp/src/services/indexer/client.ts::getPairLimitBookPage` (~L235) + `useLimitBookInfinite.ts` page the whole book; `frontend-dapp/src/utils/limitBookInsertHint.ts::resolveLimitInsertHintAfter` does hint resolution **client-side** over loaded pages and returns `null` on any `has_more` pagination gap. ### Why the current shape is insufficient - Client-side hint resolution forces full pagination of a deep book and degrades to `null` (→ on-chain head walk) whenever the target price is past the loaded window (`has_more`). - There is no way to resolve N rung predecessors in one round trip; the dApp would issue many page requests. - The "never guess across gaps" rule is reimplemented on the client; it belongs server-side next to the authoritative walk. --- ## Why this is needed Deep-book ladders need predecessor anchors for many prices cheaply and authoritatively. A server-side resolver does the bounded LCD walk once, applies the gap rule centrally, and returns compact answers — enabling the contract's single-anchor/per-rung hint path and removing the dApp's need to paginate or touch LCD/RPC directly. --- ## Constraints / guardrails - **Stay within H7 budget.** Each request must respect `LIMIT_BOOK_LCD_QUERY_BUDGET` (101 smart queries). A resolver spanning more depth than the budget allows must return an explicit **partial/unknown** result for out-of-budget prices, not silently guess. - **Never guess across gaps.** If the walk cannot reach a price's neighborhood within budget, return `predecessor: null` with an explicit `resolved: false` / reason (`pagination_gap`), mirroring `resolveLimitInsertHintAfter`'s safety rule. Stale-but-safe is the contract; a wrong predecessor must never be emitted. - **Reuse `lcd_heavy_router` rate limiting** (`rate_limit_lcd_heavy_rps`) — these endpoints are LCD-heavy and must not bypass the stricter per-IP governor. - **Bound input size.** Cap the prices list (e.g. ≤ pair `max_batch_rungs` hard cap 100) and validate each price is a positive decimal; reject otherwise (400). - **Side validation** identical to existing walk (reject mixed/!side rows; surface broken-link as 400/500 per current behavior). - **Read-only**, no caching of arbitrary deep walks beyond what already exists; results reflect a single consistent walk per request. - **No new direct-RPC surface for the frontend** — frontend consumes only these HTTP endpoints. - Predecessor semantics must match the contract's composite-key ordering (`docs/limit-orders.md § Ordering`) and the existing client resolver exactly (bids descending price/ascending id; asks ascending price; equal price → last order at that level = FIFO tail). --- ## Relevant files - `indexer/src/api/limit_book_lcd.rs` - `indexer/src/api/pairs.rs` - `indexer/src/api/mod.rs` (route registration in `lcd_heavy_router`) - `indexer/tests/api_limit_book_lcd_mock.rs`, `indexer/tests/api_limit_book_deep.rs` - OpenAPI/Swagger schema (served from `/swagger-ui/`) - Docs: `docs/integrators.md` (§ Batch placement insert hints), `docs/limit-orders.md` - Client wiring (companion frontend issue): `frontend-dapp/src/services/indexer/client.ts` --- ## Recommended direction 1. **Shared walk core.** Factor the predecessor-finding logic so both endpoints reuse `fetch_limit_book_page`'s walk and the budget counter. Walk head→tail accumulating the running predecessor; for each target price emit the last order id whose composite key sorts before the insert slot. 2. **Batch hint-resolver endpoint** — `GET /api/v1/pairs/{addr}/limit-book/insert-hints?side=bid&prices=p1,p2,...` → `{ hints: [{ price, predecessor_order_id: u64|null, resolved: bool, reason?: "head"|"pagination_gap" }], budget_exhausted: bool }`. Single ascending/descending walk resolves all prices; once budget is exhausted, remaining unresolved prices return `resolved:false, reason:"pagination_gap"`. 3. **Targeted price-window fetch** — extend `GET .../limit-book` with optional `price_from` / `price_to` (or a dedicated `/limit-book/window`) returning the contiguous slice covering the band plus `has_more`/cursor so the client can fetch exactly the ladder's price span instead of from head. 4. Add `ToSchema` types; register in `lcd_heavy_router`; document in OpenAPI + `integrators.md`. --- ## Acceptance criteria - `insert-hints` returns the correct predecessor for each price on a known mock book, matching `resolveLimitInsertHintAfter` for the same data (bids/asks/equal-price/head-insert cases). - Prices beyond the budget/walk return `resolved:false` with `reason:"pagination_gap"` and **never** a guessed id. - Head-insert price (better than head) returns `predecessor_order_id:null, reason:"head"`. - `price_from/price_to` window returns exactly the contiguous slice spanning the band with correct `has_more`/cursor. - Both endpoints sit behind `lcd_heavy_router` rate limiting and respect `LIMIT_BOOK_LCD_QUERY_BUDGET`. - Invalid side, malformed price, or oversized price list → 400. - OpenAPI/Swagger updated; `integrators.md` documents both. --- ## Test plan — all paths - **Mock book bids**: prices above head (→ head), between levels (→ correct predecessor), equal to a level (→ FIFO tail id), below tail (→ last id when fully loaded). - **Mock book asks**: symmetric cases. - **Multi-price batch in one request**: order-independent input list returns per-price answers; single walk. - **Budget boundary**: book deeper than budget → early prices resolved, later prices `pagination_gap`. - **Price-window**: band fully inside loaded depth; band crossing the budget edge (partial + `has_more`); empty band. - **Empty book**: all prices → head/null. - **Broken book link / wrong-side row**: surfaces existing error semantics. - **Parity test**: same fixture fed to server resolver and `resolveLimitInsertHintAfter` → identical predecessors. ## Test plan — attack / abuse / hack vectors - **Oversized prices list** (e.g. 10k entries): rejected by cap (400), cannot fan out LCD calls. - **Budget-exhaustion DoS**: a single request cannot exceed `LIMIT_BOOK_LCD_QUERY_BUDGET`; deep book yields partial/unknown rather than unbounded LCD load. - **Rate-limit bypass attempt**: endpoints confirmed under `lcd_heavy_router` governor; load test shows per-IP throttling. - **Gap-guess injection**: adversarial book where the desired predecessor is just past budget — assert the endpoint returns `pagination_gap`, never a plausible-but-wrong id (which on-chain would still be safe, but the API must not assert false confidence). - **Malformed/negative/huge decimal prices**: validation rejects (400), no panic. - **Stale-walk consistency**: if the book mutates mid-walk (LCD eventual consistency), result is internally consistent for the single walk and clearly a point-in-time snapshot; downstream contract verify remains the safety net. - **Side spoofing**: requesting `side=bid` against ask ids returns the documented side-mismatch error. ## Verification criteria - `cargo test -p indexer api_limit_book` (mock + deep) green, including new resolver/window tests and the parity test. - `cargo clippy --all-targets -- -D warnings` clean; OpenAPI regen passes. - Manual `curl` against LocalTerra: `insert-hints` for a known ladder band returns expected ids; deep request returns `budget_exhausted:true` with `pagination_gap` tail. - Confirmed both routes are inside `lcd_heavy_router` (rate-limited) via code + a throttling integration check. - `integrators.md` + Swagger document request/response and the gap rule.
PlasticDigits commented 2026-06-01 04:19:16 +00:00 (Migrated from gitlab.com)

mentioned in issue #266

mentioned in issue #266
PlasticDigits commented 2026-06-01 04:19:17 +00:00 (Migrated from gitlab.com)

Companion issues:

  • #266 — Contract book-order insertion + ladder anchor hint (consumer of these endpoints' values)
  • #268 — Frontend deep-book ladder placement (primary consumer; must use these endpoints, never direct LCD/RPC)

These endpoints are foundational for #268 and supply the anchor values used by #266.

Companion issues: - #266 — Contract book-order insertion + ladder anchor hint (consumer of these endpoints' values) - #268 — Frontend deep-book ladder placement (primary consumer; must use these endpoints, never direct LCD/RPC) These endpoints are foundational for #268 and supply the anchor values used by #266.
PlasticDigits commented 2026-06-01 04:19:19 +00:00 (Migrated from gitlab.com)

mentioned in issue #268

mentioned in issue #268
PlasticDigits commented 2026-06-01 04:41:56 +00:00 (Migrated from gitlab.com)

mentioned in commit fead8edd04

mentioned in commit fead8edd04a85bd16ffb556533c48b72a8e28951
PlasticDigits commented 2026-06-01 04:42:10 +00:00 (Migrated from gitlab.com)

Shipped on main (fead8ed)

Indexer read APIs for GitLab #267 are implemented, tested, and documented. The issue stays open for QA verification.

What changed

  1. GET /api/v1/pairs/{addr}/limit-book/insert-hints?side=bid|ask&prices=p1,p2,...

    • Single head→tail LCD walk resolves up to 100 prices per request.
    • Response: { side, hints[{ price, predecessor_order_id, resolved, reason? }], budget_exhausted }.
    • reason: "head" | "pagination_gap"; never emits a guessed predecessor when the walk cannot reach the slot.
    • Respects LIMIT_BOOK_LCD_QUERY_BUDGET (101); sets budget_exhausted when the cap stops the walk.
  2. GET /api/v1/pairs/{addr}/limit-book — optional price_from + price_to (both required) return the contiguous in-band slice with the same pagination/cursor semantics as the existing endpoint.

  3. Core modules: indexer/src/api/limit_book_lcd.rs, limit_book_price.rs (decimal compare aligned with limitBookInsertHint.ts).

  4. Routing: both paths registered on lcd_heavy_router (LCD-heavy rate limit).

  5. Tests: indexer/tests/api_limit_book_insert_hints.rs (HTTP + parity + budget boundary); existing api_limit_book_deep / api_limit_book_lcd_mock still green.

  6. Docs / skills: docs/integrators.md (§ Insert hints & price window), docs/limit-orders.md, docs/indexer-invariants.md, ADR 0002, and cross-links in skills/AGENTS_* playbooks for frontend/indexer agents.

QA verification checklist

  • cd indexer && cargo test --test api_limit_book_insert_hints --test api_limit_book_deep --test api_limit_book_lcd_mock
  • Swagger UI lists insert-hints and updated limit-book params (price_from / price_to).
  • Against LocalTerra (or wiremock): bid book insert-hints for prices above head, between levels, equal-price FIFO tail, and below tail — predecessors match on-chain ordering.
  • Deep book / budget: request where tail is past 101 LCD queries → budget_exhausted: true, tail prices resolved: false, reason: "pagination_gap", no fabricated predecessor_order_id.
  • limit-book?price_from=&price_to= returns only orders in the band; has_more / next_after_order_id behave when the band continues past limit.
  • Oversized prices list (>100) → 400; malformed decimals → 400; only one of price_from/price_to → 400.
  • Confirm 429 under sustained load on insert-hints (LCD-heavy governor, same as limit-book).
  • Companion #268 can wire the dApp to these endpoints (no direct LCD book walks).

Follow-ups (for companion issues, not blockers here)

  • #268 — frontend: call insert-hints + price-window limit-book from client.ts / ladder placement.
  • #266 — already consumes hint values on-chain; no contract change required for this indexer slice.

Please run the checklist above on a staging indexer before closing. Requesting verification from the QA agent team.

## Shipped on `main` (`fead8ed`) Indexer read APIs for GitLab **#267** are implemented, tested, and documented. The issue stays **open** for QA verification. ### What changed 1. **`GET /api/v1/pairs/{addr}/limit-book/insert-hints?side=bid|ask&prices=p1,p2,...`** - Single head→tail LCD walk resolves up to **100** prices per request. - Response: `{ side, hints[{ price, predecessor_order_id, resolved, reason? }], budget_exhausted }`. - `reason`: `"head"` | `"pagination_gap"`; never emits a guessed predecessor when the walk cannot reach the slot. - Respects **`LIMIT_BOOK_LCD_QUERY_BUDGET` (101)**; sets `budget_exhausted` when the cap stops the walk. 2. **`GET /api/v1/pairs/{addr}/limit-book`** — optional **`price_from`** + **`price_to`** (both required) return the contiguous in-band slice with the same pagination/cursor semantics as the existing endpoint. 3. **Core modules:** `indexer/src/api/limit_book_lcd.rs`, `limit_book_price.rs` (decimal compare aligned with `limitBookInsertHint.ts`). 4. **Routing:** both paths registered on **`lcd_heavy_router`** (LCD-heavy rate limit). 5. **Tests:** `indexer/tests/api_limit_book_insert_hints.rs` (HTTP + parity + budget boundary); existing `api_limit_book_deep` / `api_limit_book_lcd_mock` still green. 6. **Docs / skills:** `docs/integrators.md` (§ Insert hints & price window), `docs/limit-orders.md`, `docs/indexer-invariants.md`, ADR 0002, and cross-links in `skills/AGENTS_*` playbooks for frontend/indexer agents. ### QA verification checklist - [ ] `cd indexer && cargo test --test api_limit_book_insert_hints --test api_limit_book_deep --test api_limit_book_lcd_mock` - [ ] Swagger UI lists `insert-hints` and updated `limit-book` params (`price_from` / `price_to`). - [ ] Against LocalTerra (or wiremock): bid book `insert-hints` for prices above head, between levels, equal-price FIFO tail, and below tail — predecessors match on-chain ordering. - [ ] Deep book / budget: request where tail is past **101** LCD queries → `budget_exhausted: true`, tail prices `resolved: false`, `reason: "pagination_gap"`, no fabricated `predecessor_order_id`. - [ ] `limit-book?price_from=&price_to=` returns only orders in the band; `has_more` / `next_after_order_id` behave when the band continues past `limit`. - [ ] Oversized `prices` list (>100) → **400**; malformed decimals → **400**; only one of `price_from`/`price_to` → **400**. - [ ] Confirm **429** under sustained load on `insert-hints` (LCD-heavy governor, same as `limit-book`). - [ ] Companion **#268** can wire the dApp to these endpoints (no direct LCD book walks). ### Follow-ups (for companion issues, not blockers here) - **#268** — frontend: call `insert-hints` + price-window `limit-book` from `client.ts` / ladder placement. - **#266** — already consumes hint values on-chain; no contract change required for this indexer slice. Please run the checklist above on a staging indexer before closing. Requesting verification from the QA agent team.
Brouie commented 2026-06-01 15:38:44 +00:00 (Migrated from gitlab.com)

mentioned in issue #264

mentioned in issue #264
Brouie commented 2026-06-01 16:16:28 +00:00 (Migrated from gitlab.com)

Verified #267 on d6701c4 (indexer insert-hint resolver + price-window). Acceptance + checklist + attack vectors:

Tests (cargo test --test api_limit_book_insert_hints --test api_limit_book_deep --test api_limit_book_lcd_mock — all green):

  • insert_hints_parity_with_client_resolver (matches resolveLimitInsertHintAfter), insert_hints_budget_exhausted_pagination_gap (budget cap → pagination_gap, never a guessed id), insert_hints_and_price_window_http, limit_book_side_mismatch_400, limit_book_invalid_cursor_400, limit_book_paginates_deep_chain, limit_book_concurrent_pages_stress.

Live against the indexer on :3001 (real book from my #266 ladders):

  • insert-hints side=bid: price 2.0 → predecessor null, reason head; 0.93 → predecessor 105 (FIFO tail of the 0.93 level); 0.50 → predecessor 107 (true tail, walk reached it, resolved=true); budget_exhausted=false. All match the actual book's composite-key ordering (bids descending, ascending-id at equal price — 106 before 126 @ 0.94).
  • Validation: oversized prices list (101) → 400; malformed/negative decimal → 400; invalid side → 400; only price_from → 400. Server stayed up (no panic).
  • Price-window price_from=0.94 price_to=0.92 → 200, exact in-band slice; reversed band (ascending for a bid) correctly rejected → 400 ("not a valid band for this side").
  • Rate limit: 60 rapid insert-hints → 20×200 + 40×429 (lcd_heavy_router governor). Covers rate-limit-bypass + budget-DoS vectors.
  • Swagger/OpenAPI served spec lists insert-hints + price_from/price_to.

Attack vectors all map to a test or live evidence (oversized→400, gap-guess→pagination_gap test, side-spoof→400, malformed→400 no panic, rate-limit→429).

Two honest caveats:

  1. Minor: price-window returns has_more=true at the band floor even when the full in-band slice fits one page (book continues below the band). The slice itself is exactly correct — flagging the has_more semantics, not a wrong result.
  2. cargo clippy --all-targets -- -D warnings is NOT clean on my host toolchain (rustc 1.94 / clippy 0.1.94): 23 crate-wide lints (route_solver, best_execution, block_indexer, etc.), incl. newer lints like is_multiple_of. ZERO are in #267's files (limit_book_lcd.rs / limit_book_price.rs / api/pairs.rs) — toolchain drift (my clippy is newer than the pinned build), not a #267 regression. Worth a separate crate-wide clippy cleanup.

Good to close from my side once the proptest fix in !733 merges. @PlasticDigits

Verified #267 on d6701c4 (indexer insert-hint resolver + price-window). Acceptance + checklist + attack vectors: Tests (`cargo test --test api_limit_book_insert_hints --test api_limit_book_deep --test api_limit_book_lcd_mock` — all green): - `insert_hints_parity_with_client_resolver` (matches `resolveLimitInsertHintAfter`), `insert_hints_budget_exhausted_pagination_gap` (budget cap → `pagination_gap`, never a guessed id), `insert_hints_and_price_window_http`, `limit_book_side_mismatch_400`, `limit_book_invalid_cursor_400`, `limit_book_paginates_deep_chain`, `limit_book_concurrent_pages_stress`. Live against the indexer on :3001 (real book from my #266 ladders): - `insert-hints` side=bid: price `2.0` → predecessor null, reason `head`; `0.93` → predecessor `105` (FIFO tail of the 0.93 level); `0.50` → predecessor `107` (true tail, walk reached it, `resolved=true`); `budget_exhausted=false`. All match the actual book's composite-key ordering (bids descending, ascending-id at equal price — 106 before 126 @ 0.94). - Validation: oversized prices list (101) → 400; malformed/negative decimal → 400; invalid side → 400; only `price_from` → 400. Server stayed up (no panic). - Price-window `price_from=0.94 price_to=0.92` → 200, exact in-band slice; reversed band (ascending for a bid) correctly rejected → 400 ("not a valid band for this side"). - Rate limit: 60 rapid `insert-hints` → 20×200 + 40×429 (`lcd_heavy_router` governor). Covers rate-limit-bypass + budget-DoS vectors. - Swagger/OpenAPI served spec lists `insert-hints` + `price_from`/`price_to`. Attack vectors all map to a test or live evidence (oversized→400, gap-guess→`pagination_gap` test, side-spoof→400, malformed→400 no panic, rate-limit→429). Two honest caveats: 1. Minor: price-window returns `has_more=true` at the band floor even when the full in-band slice fits one page (book continues below the band). The slice itself is exactly correct — flagging the `has_more` semantics, not a wrong result. 2. `cargo clippy --all-targets -- -D warnings` is NOT clean on my host toolchain (rustc 1.94 / clippy 0.1.94): 23 crate-wide lints (route_solver, best_execution, block_indexer, etc.), incl. newer lints like `is_multiple_of`. ZERO are in #267's files (`limit_book_lcd.rs` / `limit_book_price.rs` / `api/pairs.rs`) — toolchain drift (my clippy is newer than the pinned build), not a #267 regression. Worth a separate crate-wide clippy cleanup. Good to close from my side once the proptest fix in !733 merges. @PlasticDigits
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-06-02 06:56:30 +00:00
PlasticDigits commented 2026-06-02 06:59:58 +00:00 (Migrated from gitlab.com)

mentioned in issue #270

mentioned in issue #270
PlasticDigits commented 2026-06-07 12:14:14 +00:00 (Migrated from gitlab.com)

mentioned in issue #337

mentioned in issue #337
PlasticDigits commented 2026-08-17 10:26:07 +00:00 (Migrated from gitlab.com)

mentioned in issue #546

mentioned in issue #546
PlasticDigits commented 2026-08-22 12:26:36 +00:00 (Migrated from gitlab.com)

mentioned in issue #597

mentioned in issue #597
PlasticDigits commented 2026-08-24 03:15:38 +00:00 (Migrated from gitlab.com)

mentioned in issue #618

mentioned in issue #618
PlasticDigits commented 2026-09-01 08:14:37 +00:00 (Migrated from gitlab.com)

mentioned in issue #717

mentioned in issue #717
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#267
No description provided.