feat(indexer): MR !43 Phase 1c — db_orderbook_sim, solver rewire, poisoned-mirror fidelity #319

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

Summary

Implement Phase 1c of the 0-LCD hybrid route solver program (MR !761 follow-up): port pool + resting-book math into a db_orderbook_sim module, rewire hybrid_route_opt / best_execution to price hops from Postgres (pair_reserves, resting_limit_orders) instead of per-grid HybridSimulation LCD calls, and harden against poisoned-mirror failures where stale or corrupt DB snapshots would mislead traders while still appearing “validated.”

Keep one LCD fidelity guard on the winning route only: maybe_simulate (simulate_swap_operations on the router) must reject or downgrade quotes that diverge from chain truth. Land the breaking quote_kind rename (*_lcd → DB-backed kinds) in the same increment and coordinate the dApp.

Hard dependency: Phase 1b (book_snapshot loop that populates pair_reserves + resting_limit_orders) must be merged and healthy before 1c goes live; 1c must not ship without a freshness contract for mirrored state.

Current codebase

Phase 1a (MR !761 — schema only, not wired to solver)

  • Migrations: pair_reserves (one row/pair: reserve_0/1, fee_bps, block_height, snapshot_at), resting_limit_orders (materialized book with live remaining, walk index price + FIFO order_id).
  • Query layer: indexer/src/db/queries/pair_reserves.rs (upsert / get → missing = None, degrade-not-error), indexer/src/db/queries/resting_orders.rs (replace_pair_resting_orders, get_pair_resting_book ordered bids DESC / asks ASC).
  • Tests: indexer/tests/db_orderbook_mirror.rs — CRUD + walk order only; no HTTP/solver paths.

MR !761 security review explicitly defers Phase 1c solver fidelity / poisoned-mirror to this work (tables are not read by the live solver yet).

Live solver (LCD-heavy)

  • indexer/src/api/hybrid_route_opt.rs — optimize_multihop_hybrid_joint runs a 17-point book_input grid per hop via LCD hybrid_simulation (query_hybrid_sim); pool-only fallback when all grid points fail (degraded).
  • indexer/src/api/best_execution.rs — solve_global_best_execution evaluates up to 5 path candidates serially; each candidate: joint hybrid optimize + maybe_simulate on router.
  • indexer/src/api/route_solver.rs — GET cache (hybrid_cache_key + discount_tier per #283), RouteQuoteKind values indexer_pool_lcd / indexer_hybrid_lcd / indexer_hybrid_lcd_degraded, maybe_simulate as final amount.
  • Documented LCD budget: LCD_HYBRID_SIM_BUDGET in best_execution.rs; responses expose lcd_hybrid_queries, optimality_scope, hybrid_notes.

Existing pure-Rust sim (CG/CMC listing — reuse target)

  • indexer/src/api/orderbook_sim.rs — AMM curve walk (walk_amm_book, ceil_div, fee on gross).
  • indexer/src/api/hybrid_orderbook_sim.rs — merge pool levels + resting limits for listing depth (#220); still fetches limits via LCD today in simulate_orderbook_cached.
  • On-chain reference: smartcontracts/contracts/pair/src/contract.rs query_hybrid_simulation + orderbook::simulate_match_bids / simulate_match_asks — normative hybrid math.

Frontend contract

Why this is needed

  1. Performance / amplification (#279 class). Serial LCD fanout per candidate path makes /route/solve slow and cheap to amplify even under per-IP rate limits. DB-backed hybrid grid evaluation removes hundreds of pair-level LCD calls per request while preserving search bounds (top-5 paths, 17-point grid, joint coordinate passes).
  2. Fidelity boundary shift. Once quotes come from a mirror (Postgres) instead of LCD, correctness depends on snapshot freshness and parity with on-chain HybridSimulation. Without explicit poisoned-mirror handling, integrators and the dApp could show optimistic or stale estimated_amount_out that passes a weak final sim or none at all.
  3. Operational clarity. quote_kind names implying *_lcd become misleading; clients need honest labels (indexer_hybrid_db, degraded/fidelity-reject variants) and response metadata (snapshot height, drift flags).
  4. Program continuity. MR !761 deliberately scoped 1a to zero solver risk; 1c is the first increment where wrong mirror state becomes a user-facing security issue, not just a listing QA concern.

Constraints and guardrails

  • Prerequisite: Phase 1b snapshot loop must populate pair_reserves and resting_limit_orders from chain (modeled on oracle/tier-sync loops). Define max staleness (block lag / snapshot_at TTL); 1c must degrade (LCD fallback or explicit error) when mirror is missing or too stale — never silent best-effort with empty book.
  • Fidelity guard (non-negotiable): Keep maybe_simulate on the winning route only after DB optimization. If router sim is unavailable, response must use quote_kind / hybrid_notes that do not imply chain-validated output.
  • Poisoned-mirror policy: If DB-optimized estimated_amount_out exceeds LCD sim by more than a documented tolerance (or sim fails), do not return the DB amount as final — downgrade to indexer_*_degraded / indexer_route_only or reject with 400/502 per existing LCD gateway patterns; log structured drift metrics (pair, hop, block heights).
  • Math parity: Reuse or extract shared code from orderbook_sim / hybrid_orderbook_sim and align with pair query_hybrid_simulation (pool leg + book walk caps: max_maker_fills, scan steps, fee tier via QuoteTrader when mirrored tier data exists). Property tests against LCD mock on sampled grids before cutover.
  • Discount tiers (#238 / #283): DB sim must respect the same trader / sender / discount_tier inputs as LCD path; cache keys must include tier and solver generation bump (e.g. global_v2 or db_hybrid_v1).
  • Scope boundaries: Phase 2 (4-hop bump) and concurrent candidate evaluation (#279 performance item) are out of scope unless trivial; do not change global path enumeration in 1c.
  • CG/CMC listing: May later read DB mirror for /cg/orderbook (#278 rate-limit class) — optional follow-up; 1c focuses on route solver.
  • Breaking API: quote_kind rename is intentional; coordinate frontend + docs/indexer-invariants.md + skills/AGENTS_INDEXER_HYBRID_BEST_EXECUTION.md in the same release train.

Relevant files

Area Files
Schema / queries (1a) indexer/migrations/20260605010000_pair_reserves.sql, 20260605010100_resting_limit_orders.sql, indexer/src/db/queries/pair_reserves.rs, resting_orders.rs
Prerequisite 1b New snapshot loop (indexer background task — TBD path, mirror oracle / trader_tracker patterns)
New 1c core indexer/src/api/db_orderbook_sim.rs (or hybrid_sim_db.rs), exports used by hybrid_route_opt
Solver integration indexer/src/api/hybrid_route_opt.rs, best_execution.rs, route_solver.rs
Listing sim reuse orderbook_sim.rs, hybrid_orderbook_sim.rs, limit_book_lcd.rs (reference only)
On-chain reference smartcontracts/contracts/pair/src/contract.rs, orderbook.rs
Tests indexer/tests/db_orderbook_mirror.rs, api_route_solve.rs, common/lcd_mock.rs; new api_route_solve_db_hybrid.rs or extend existing
Frontend frontend-dapp/src/types/index.ts, utils/swapDisclosure.ts, services/indexer/__tests__/client.test.ts
Docs docs/indexer-invariants.md, docs/adr/0002-global-best-execution-route-solver.md, docs/integrators.md, skills/AGENTS_INDEXER_HYBRID_BEST_EXECUTION.md
  1. Extract db_orderbook_sim: Given pair_id, offer_token, pool_input / book_input, max_maker_fills, QuoteTrader, and rows from pair_reserves + resting_limit_orders, return return_amount matching HybridSimulationResponse semantics. Unit-test against walk_amm_book + book walk with seeded mirror rows (no LCD).
  2. Wire hybrid_route_opt: Replace query_hybrid_sim LCD calls in the grid / coordinate passes with DB sim when mirror is fresh; on missing/stale mirror per hop → pool-only DB leg if reserves exist, else mark degraded and optionally LCD fallback for that hop only (document which path is taken).
  3. Winning-route LCD guard: After global winner selected, run maybe_simulate once. Compare DB grid winner output to router sim; implement drift detection (absolute/relative threshold configurable). Set new quote_kind variants and extend hybrid_notes with snapshot block_height / age.
  4. Response metadata: Add fields (or extend hybrid_notes) for mirror_max_block_lag, db_hybrid_queries (replacing or supplementing lcd_hybrid_queries), fidelity_check: passed | drift | skipped.
  5. Bump solver_version: e.g. global_v2 / db_hybrid_v1 so caches and integrators can distinguish LCD-grid vs DB-grid eras.
  6. Frontend: Map new quote_kind values; update disclosure strings (“indexed mirror” vs “LCD snapshot”).
  7. Feature flag (optional): ROUTE_SOLVER_DB_HYBRID=1 for staged rollout on mainnet; default off until drift tests green in CI.

Acceptance criteria

  • With fresh mirror rows seeded in tests, GET /api/v1/route/solve?amount_in=… returns the same winning path/splits as LCD mock baseline within tolerance without pair-level hybrid_simulation LCD calls during optimization (assert via wiremock request counts).
  • maybe_simulate is invoked once per request on the winning route when ROUTER_ADDRESS is set.
  • Missing pair_reserves or empty resting_limit_orders for a hop → documented degrade (pool-only DB or explicit error), quote_kind reflects degraded state, not indexer_hybrid_lcd.
  • Stale mirror (over configured lag) → degrade or LCD fallback; response flags staleness.
  • Injected poisoned mirror (reserves or book rows that inflate return_amount vs LCD mock truth) → final response does not advertise the poisoned amount as validated; drift flagged or sim error returned.
  • quote_kind rename shipped with frontend + OpenAPI + docs; old *_lcd hybrid kinds removed or aliased with deprecation period documented.
  • solver_version and cache key generation updated; #283 tier isolation preserved.
  • cargo test route-solve + new db hybrid tests pass; make lint / indexer lib tests green.

Test plan (functional paths)

# Scenario Setup Expected
1 Happy path, fresh mirror Seed reserves + book; LCD mock for router sim only 200; optimized splits; fidelity_check=passed; zero hybrid_sim LCD during grid
2 Pool-only route (no book legs) Reserves only, empty resting book quote_kind pool DB variant; amount matches pool walk
3 Missing reserves row No pair_reserves for hop pair Degrade per policy; not labeled full hybrid
4 Missing resting book Reserves present, no resting rows Pool-only or hybrid-degraded; notes explain empty book
5 Stale snapshot_at / block lag Backdated snapshot Staleness flag; fallback or 502 per policy
6 Router sim disabled router_address unset estimated_amount_out absent; indexer_route_only; no false “validated” copy
7 Router sim failure Mock 500 on sim 400 with generic message (existing gateway behavior)
8 Multi-path global 2-hop A→B→C seed Best path still chosen; only winner simmed
9 trader / sender tier Two traders.tier_id seeds (#283) Different outputs; cache keys differ
10 max_maker_fills cap Book deeper than cap Same cap behavior as on-chain / LCD reference tests
11 POST hybrid_by_hop override Body with manual splits Unchanged or explicitly documented if POST stays LCD
12 pool_only=true Query flag No regression; still LCD or pool-only path as today
13 Frontend disclosure New quote_kind in API fixture swapDisclosure shows correct copy

Test plan (attack, hack, and abuse vectors)

Vector Attack Expected mitigation
Poisoned mirror (optimistic reserves) SQL or compromised indexer writes inflated reserve_* Drift vs maybe_simulate fails closed; metric + warn; no inflated estimated_amount_out
Poisoned mirror (fake book) Resting rows with impossible prices/qty Book walk bounded; drift reject; FIFO/order_id integrity checks on snapshot replace
Stale mirror trading Quote off old block during volatility Staleness TTL; hybrid_notes warn; optional reject
Cache poisoning cross-tier Two senders, different tiers (#283) Cache key includes discount_tier + new solver_version
Cache key spam Vary amount_in micro-units Existing amount bucketing still applies; document buckets
LCD amplification reduction Flood /route/solve Per-request LCD drops to ~1 sim; rate limit still applies
Missing sim + misleading kind Disable router indexer_route_only; frontend does not claim execution parity
Degraded grid masking All DB sims fail, silent pool-zero book indexer_hybrid_*_degraded + explicit notes
Integer overflow / bad numeric Huge remaining in mirror Use u128/bigdecimal bounds; reject row on parse failure
Pathogenic max_maker_fills Caller sets huge fills Clamp to on-chain max; grid work bounded

Add property or differential tests: random small grids comparing db_orderbook_sim vs wiremock LCD hybrid_simulation on identical mirror + chain state fixtures.

Verification criteria

  • CI: cd indexer && cargo test --test api_route_solve --test db_orderbook_mirror (+ new db hybrid test binary) all green.
  • CI: cd indexer && cargo test --lib green.
  • CI: make test-frontend / swapDisclosure tests updated for new kinds.
  • Manual (localnet): with 1b loop running, compare /route/solve output to pre-1c LCD baseline on same block; drift rate < agreed threshold on smoke pairs.
  • Docs: docs/indexer-invariants.md row for route GET updated (DB mirror, fidelity guard, new quote_kind table).
  • Observability: log lines for fidelity_drift, mirror_stale, db_hybrid_queries present in structured logs.
  • MR !761 linked; closing MR for 1c references this issue.
  • MR !761 — Phase 1a schema
  • #209 / ADR 0002 — global best execution bounds
  • #220 — hybrid orderbook sim (reuse)
  • #279 — serial LCD / cache performance (complementary)
  • #283 — discount-tier cache keys
## Summary Implement **Phase 1c** of the 0-LCD hybrid route solver program ([MR !761](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/merge_requests/43) follow-up): port pool + resting-book math into a **`db_orderbook_sim`** module, rewire `hybrid_route_opt` / `best_execution` to price hops from Postgres (`pair_reserves`, `resting_limit_orders`) instead of per-grid `HybridSimulation` LCD calls, and **harden against poisoned-mirror** failures where stale or corrupt DB snapshots would mislead traders while still appearing “validated.” Keep **one LCD fidelity guard** on the **winning** route only: `maybe_simulate` (`simulate_swap_operations` on the router) must reject or downgrade quotes that diverge from chain truth. Land the breaking **`quote_kind`** rename (`*_lcd` → DB-backed kinds) in the same increment and coordinate the dApp. **Hard dependency:** Phase **1b** (`book_snapshot` loop that populates `pair_reserves` + `resting_limit_orders`) must be merged and healthy before 1c goes live; 1c must not ship without a freshness contract for mirrored state. ## Current codebase ### Phase 1a (MR !761 — schema only, not wired to solver) - Migrations: `pair_reserves` (one row/pair: `reserve_0/1`, `fee_bps`, `block_height`, `snapshot_at`), `resting_limit_orders` (materialized book with live `remaining`, walk index `price` + FIFO `order_id`). - Query layer: `indexer/src/db/queries/pair_reserves.rs` (`upsert` / `get` → missing = `None`, degrade-not-error), `indexer/src/db/queries/resting_orders.rs` (`replace_pair_resting_orders`, `get_pair_resting_book` ordered bids DESC / asks ASC). - Tests: `indexer/tests/db_orderbook_mirror.rs` — CRUD + walk order only; **no HTTP/solver paths**. MR !761 security review explicitly defers **Phase 1c solver fidelity / poisoned-mirror** to this work (tables are not read by the live solver yet). ### Live solver (LCD-heavy) - [`indexer/src/api/hybrid_route_opt.rs`](indexer/src/api/hybrid_route_opt.rs) — `optimize_multihop_hybrid_joint` runs a **17-point** `book_input` grid per hop via LCD `hybrid_simulation` (`query_hybrid_sim`); pool-only fallback when all grid points fail (`degraded`). - [`indexer/src/api/best_execution.rs`](indexer/src/api/best_execution.rs) — `solve_global_best_execution` evaluates up to **5** path candidates **serially**; each candidate: joint hybrid optimize + `maybe_simulate` on router. - [`indexer/src/api/route_solver.rs`](indexer/src/api/route_solver.rs) — GET cache (`hybrid_cache_key` + `discount_tier` per #283), `RouteQuoteKind` values `indexer_pool_lcd` / `indexer_hybrid_lcd` / `indexer_hybrid_lcd_degraded`, `maybe_simulate` as final amount. - Documented LCD budget: `LCD_HYBRID_SIM_BUDGET` in `best_execution.rs`; responses expose `lcd_hybrid_queries`, `optimality_scope`, `hybrid_notes`. ### Existing pure-Rust sim (CG/CMC listing — reuse target) - [`indexer/src/api/orderbook_sim.rs`](indexer/src/api/orderbook_sim.rs) — AMM curve walk (`walk_amm_book`, `ceil_div`, fee on gross). - [`indexer/src/api/hybrid_orderbook_sim.rs`](indexer/src/api/hybrid_orderbook_sim.rs) — merge pool levels + resting limits for **listing** depth ([#220](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/220)); still fetches limits via LCD today in `simulate_orderbook_cached`. - On-chain reference: `smartcontracts/contracts/pair/src/contract.rs` `query_hybrid_simulation` + `orderbook::simulate_match_bids` / `simulate_match_asks` — normative hybrid math. ### Frontend contract - [`frontend-dapp/src/types/index.ts`](frontend-dapp/src/types/index.ts) — `IndexerRouteQuoteKind` mirrors indexer snake_case kinds. - [`frontend-dapp/src/utils/swapDisclosure.ts`](frontend-dapp/src/utils/swapDisclosure.ts) — disclosure copy keyed on `indexer_hybrid_lcd` / `indexer_hybrid_lcd_degraded`. ## Why this is needed 1. **Performance / amplification (#279 class).** Serial LCD fanout per candidate path makes `/route/solve` slow and cheap to amplify even under per-IP rate limits. DB-backed hybrid grid evaluation removes hundreds of pair-level LCD calls per request while preserving search bounds (top-5 paths, 17-point grid, joint coordinate passes). 2. **Fidelity boundary shift.** Once quotes come from a **mirror** (Postgres) instead of LCD, correctness depends on snapshot freshness and parity with on-chain `HybridSimulation`. Without explicit poisoned-mirror handling, integrators and the dApp could show optimistic or stale `estimated_amount_out` that passes a weak final sim or none at all. 3. **Operational clarity.** `quote_kind` names implying `*_lcd` become misleading; clients need honest labels (`indexer_hybrid_db`, degraded/fidelity-reject variants) and response metadata (snapshot height, drift flags). 4. **Program continuity.** MR !761 deliberately scoped 1a to zero solver risk; 1c is the first increment where wrong mirror state becomes a **user-facing security** issue, not just a listing QA concern. ## Constraints and guardrails - **Prerequisite:** Phase **1b** snapshot loop must populate `pair_reserves` and `resting_limit_orders` from chain (modeled on oracle/tier-sync loops). Define max staleness (block lag / `snapshot_at` TTL); 1c must **degrade** (LCD fallback or explicit error) when mirror is missing or too stale — never silent best-effort with empty book. - **Fidelity guard (non-negotiable):** Keep **`maybe_simulate` on the winning route only** after DB optimization. If router sim is unavailable, response must use `quote_kind` / `hybrid_notes` that do not imply chain-validated output. - **Poisoned-mirror policy:** If DB-optimized `estimated_amount_out` exceeds LCD sim by more than a documented tolerance (or sim fails), **do not** return the DB amount as final — downgrade to `indexer_*_degraded` / `indexer_route_only` or reject with **400/502** per existing LCD gateway patterns; log structured drift metrics (pair, hop, block heights). - **Math parity:** Reuse or extract shared code from `orderbook_sim` / `hybrid_orderbook_sim` and align with pair `query_hybrid_simulation` (pool leg + book walk caps: `max_maker_fills`, scan steps, fee tier via `QuoteTrader` when mirrored tier data exists). Property tests against LCD mock on sampled grids before cutover. - **Discount tiers (#238 / #283):** DB sim must respect the same `trader` / `sender` / `discount_tier` inputs as LCD path; cache keys must include tier and **solver generation** bump (e.g. `global_v2` or `db_hybrid_v1`). - **Scope boundaries:** Phase 2 (4-hop bump) and concurrent candidate evaluation ([#279](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/279) performance item) are **out of scope** unless trivial; do not change global path enumeration in 1c. - **CG/CMC listing:** May later read DB mirror for `/cg/orderbook` ([#278](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/278) rate-limit class) — optional follow-up; 1c focuses on **route solver**. - **Breaking API:** `quote_kind` rename is intentional; coordinate frontend + [`docs/indexer-invariants.md`](docs/indexer-invariants.md) + [`skills/AGENTS_INDEXER_HYBRID_BEST_EXECUTION.md`](skills/AGENTS_INDEXER_HYBRID_BEST_EXECUTION.md) in the same release train. ## Relevant files | Area | Files | |------|--------| | Schema / queries (1a) | `indexer/migrations/20260605010000_pair_reserves.sql`, `20260605010100_resting_limit_orders.sql`, `indexer/src/db/queries/pair_reserves.rs`, `resting_orders.rs` | | Prerequisite 1b | New snapshot loop (indexer background task — TBD path, mirror `oracle` / `trader_tracker` patterns) | | New 1c core | `indexer/src/api/db_orderbook_sim.rs` (or `hybrid_sim_db.rs`), exports used by `hybrid_route_opt` | | Solver integration | `indexer/src/api/hybrid_route_opt.rs`, `best_execution.rs`, `route_solver.rs` | | Listing sim reuse | `orderbook_sim.rs`, `hybrid_orderbook_sim.rs`, `limit_book_lcd.rs` (reference only) | | On-chain reference | `smartcontracts/contracts/pair/src/contract.rs`, `orderbook.rs` | | Tests | `indexer/tests/db_orderbook_mirror.rs`, `api_route_solve.rs`, `common/lcd_mock.rs`; new `api_route_solve_db_hybrid.rs` or extend existing | | Frontend | `frontend-dapp/src/types/index.ts`, `utils/swapDisclosure.ts`, `services/indexer/__tests__/client.test.ts` | | Docs | `docs/indexer-invariants.md`, `docs/adr/0002-global-best-execution-route-solver.md`, `docs/integrators.md`, `skills/AGENTS_INDEXER_HYBRID_BEST_EXECUTION.md` | ## Recommended direction 1. **Extract `db_orderbook_sim`:** Given `pair_id`, `offer_token`, `pool_input` / `book_input`, `max_maker_fills`, `QuoteTrader`, and rows from `pair_reserves` + `resting_limit_orders`, return `return_amount` matching `HybridSimulationResponse` semantics. Unit-test against `walk_amm_book` + book walk with seeded mirror rows (no LCD). 2. **Wire `hybrid_route_opt`:** Replace `query_hybrid_sim` LCD calls in the grid / coordinate passes with DB sim when mirror is fresh; on missing/stale mirror per hop → pool-only DB leg if reserves exist, else mark `degraded` and optionally LCD fallback for that hop only (document which path is taken). 3. **Winning-route LCD guard:** After global winner selected, run `maybe_simulate` once. Compare DB grid winner output to router sim; implement **drift detection** (absolute/relative threshold configurable). Set new `quote_kind` variants and extend `hybrid_notes` with snapshot `block_height` / age. 4. **Response metadata:** Add fields (or extend `hybrid_notes`) for `mirror_max_block_lag`, `db_hybrid_queries` (replacing or supplementing `lcd_hybrid_queries`), `fidelity_check: passed | drift | skipped`. 5. **Bump `solver_version`:** e.g. `global_v2` / `db_hybrid_v1` so caches and integrators can distinguish LCD-grid vs DB-grid eras. 6. **Frontend:** Map new `quote_kind` values; update disclosure strings (“indexed mirror” vs “LCD snapshot”). 7. **Feature flag (optional):** `ROUTE_SOLVER_DB_HYBRID=1` for staged rollout on mainnet; default off until drift tests green in CI. ## Acceptance criteria - [ ] With fresh mirror rows seeded in tests, `GET /api/v1/route/solve?amount_in=…` returns the same winning path/splits as LCD mock baseline within tolerance **without** pair-level `hybrid_simulation` LCD calls during optimization (assert via wiremock request counts). - [ ] `maybe_simulate` is invoked **once** per request on the winning route when `ROUTER_ADDRESS` is set. - [ ] Missing `pair_reserves` or empty `resting_limit_orders` for a hop → documented degrade (pool-only DB or explicit error), `quote_kind` reflects degraded state, not `indexer_hybrid_lcd`. - [ ] Stale mirror (over configured lag) → degrade or LCD fallback; response flags staleness. - [ ] Injected poisoned mirror (reserves or book rows that inflate `return_amount` vs LCD mock truth) → final response **does not** advertise the poisoned amount as validated; drift flagged or sim error returned. - [ ] `quote_kind` rename shipped with frontend + OpenAPI + docs; old `*_lcd` hybrid kinds removed or aliased with deprecation period documented. - [ ] `solver_version` and cache key generation updated; #283 tier isolation preserved. - [ ] `cargo test` route-solve + new db hybrid tests pass; `make lint` / indexer lib tests green. ## Test plan (functional paths) | # | Scenario | Setup | Expected | |---|----------|-------|----------| | 1 | Happy path, fresh mirror | Seed reserves + book; LCD mock for router sim only | 200; optimized splits; `fidelity_check=passed`; zero hybrid_sim LCD during grid | | 2 | Pool-only route (no book legs) | Reserves only, empty resting book | `quote_kind` pool DB variant; amount matches pool walk | | 3 | Missing reserves row | No `pair_reserves` for hop pair | Degrade per policy; not labeled full hybrid | | 4 | Missing resting book | Reserves present, no resting rows | Pool-only or hybrid-degraded; notes explain empty book | | 5 | Stale `snapshot_at` / block lag | Backdated snapshot | Staleness flag; fallback or 502 per policy | | 6 | Router sim disabled | `router_address` unset | `estimated_amount_out` absent; `indexer_route_only`; no false “validated” copy | | 7 | Router sim failure | Mock 500 on sim | **400** with generic message (existing gateway behavior) | | 8 | Multi-path global | 2-hop A→B→C seed | Best path still chosen; only winner simmed | | 9 | `trader` / `sender` tier | Two `traders.tier_id` seeds (#283) | Different outputs; cache keys differ | | 10 | `max_maker_fills` cap | Book deeper than cap | Same cap behavior as on-chain / LCD reference tests | | 11 | POST `hybrid_by_hop` override | Body with manual splits | Unchanged or explicitly documented if POST stays LCD | | 12 | `pool_only=true` | Query flag | No regression; still LCD or pool-only path as today | | 13 | Frontend disclosure | New `quote_kind` in API fixture | `swapDisclosure` shows correct copy | ## Test plan (attack, hack, and abuse vectors) | Vector | Attack | Expected mitigation | |--------|--------|---------------------| | **Poisoned mirror (optimistic reserves)** | SQL or compromised indexer writes inflated `reserve_*` | Drift vs `maybe_simulate` fails closed; metric + warn; no inflated `estimated_amount_out` | | **Poisoned mirror (fake book)** | Resting rows with impossible prices/qty | Book walk bounded; drift reject; FIFO/order_id integrity checks on snapshot replace | | **Stale mirror trading** | Quote off old block during volatility | Staleness TTL; `hybrid_notes` warn; optional reject | | **Cache poisoning cross-tier** | Two senders, different tiers ([#283](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/283)) | Cache key includes `discount_tier` + new `solver_version` | | **Cache key spam** | Vary `amount_in` micro-units | Existing amount bucketing still applies; document buckets | | **LCD amplification reduction** | Flood `/route/solve` | Per-request LCD drops to ~1 sim; rate limit still applies | | **Missing sim + misleading kind** | Disable router | `indexer_route_only`; frontend does not claim execution parity | | **Degraded grid masking** | All DB sims fail, silent pool-zero book | `indexer_hybrid_*_degraded` + explicit notes | | **Integer overflow / bad numeric** | Huge `remaining` in mirror | Use u128/bigdecimal bounds; reject row on parse failure | | **Pathogenic `max_maker_fills`** | Caller sets huge fills | Clamp to on-chain max; grid work bounded | Add property or differential tests: random small grids comparing `db_orderbook_sim` vs wiremock LCD `hybrid_simulation` on identical mirror + chain state fixtures. ## Verification criteria - [ ] CI: `cd indexer && cargo test --test api_route_solve --test db_orderbook_mirror` (+ new db hybrid test binary) all green. - [ ] CI: `cd indexer && cargo test --lib` green. - [ ] CI: `make test-frontend` / `swapDisclosure` tests updated for new kinds. - [ ] Manual (localnet): with 1b loop running, compare `/route/solve` output to pre-1c LCD baseline on same block; drift rate < agreed threshold on smoke pairs. - [ ] Docs: `docs/indexer-invariants.md` row for route GET updated (DB mirror, fidelity guard, new `quote_kind` table). - [ ] Observability: log lines for `fidelity_drift`, `mirror_stale`, `db_hybrid_queries` present in structured logs. - [ ] MR !761 linked; closing MR for 1c references this issue. ## Related - [MR !761](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/merge_requests/43) — Phase 1a schema - [#209](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/209) / [ADR 0002](docs/adr/0002-global-best-execution-route-solver.md) — global best execution bounds - [#220](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/220) — hybrid orderbook sim (reuse) - [#279](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/279) — serial LCD / cache performance (complementary) - [#283](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/283) — discount-tier cache keys
PlasticDigits commented 2026-06-05 04:19:52 +00:00 (Migrated from gitlab.com)

marked as related to #209

marked as related to #209
PlasticDigits commented 2026-06-05 04:19:53 +00:00 (Migrated from gitlab.com)

marked as related to #220

marked as related to #220
PlasticDigits commented 2026-06-05 04:19:53 +00:00 (Migrated from gitlab.com)

marked as related to #279

marked as related to #279
PlasticDigits commented 2026-06-05 04:19:53 +00:00 (Migrated from gitlab.com)

marked as related to #283

marked as related to #283
Brouie commented 2026-06-05 08:23:01 +00:00 (Migrated from gitlab.com)

mentioned in issue #322

mentioned in issue #322
Brouie commented 2026-06-05 08:23:02 +00:00 (Migrated from gitlab.com)

mentioned in issue #323

mentioned in issue #323
Brouie commented 2026-06-05 08:23:04 +00:00 (Migrated from gitlab.com)

mentioned in issue #324

mentioned in issue #324
Brouie commented 2026-06-05 08:23:46 +00:00 (Migrated from gitlab.com)

mentioned in issue #279

mentioned in issue #279
Brouie commented 2026-06-05 08:23:47 +00:00 (Migrated from gitlab.com)

Your hard dependency here — Phase 1b, the book_snapshot loop that populates pair_reserves + resting_limit_orders and defines the staleness contract — is now tracked as #322 (split out of #279 along with Phase 2 #323 and Phase 3 #324). 1c shouldn't go live until #322 is merged and healthy, per the freshness contract.

Your hard dependency here — Phase 1b, the book_snapshot loop that populates pair_reserves + resting_limit_orders and defines the staleness contract — is now tracked as #322 (split out of #279 along with Phase 2 #323 and Phase 3 #324). 1c shouldn't go live until #322 is merged and healthy, per the freshness contract.
Brouie commented 2026-06-05 08:24:27 +00:00 (Migrated from gitlab.com)

marked as related to #322

marked as related to #322
Brouie commented 2026-06-05 08:24:28 +00:00 (Migrated from gitlab.com)

marked as related to #323

marked as related to #323
Brouie commented 2026-06-05 08:24:28 +00:00 (Migrated from gitlab.com)

marked as related to #324

marked as related to #324
PlasticDigits commented 2026-06-05 10:14:47 +00:00 (Migrated from gitlab.com)

mentioned in merge request !793

mentioned in merge request !793
PlasticDigits commented 2026-06-05 11:09:55 +00:00 (Migrated from gitlab.com)

mentioned in merge request !796

mentioned in merge request !796
PlasticDigits commented 2026-06-05 11:20:03 +00:00 (Migrated from gitlab.com)

mentioned in merge request !798

mentioned in merge request !798
ghost1 commented 2026-06-05 11:20:03 +00:00 (Migrated from gitlab.com)

mentioned in commit 233eb6be64

mentioned in commit 233eb6be646ea4ab610a0744730815ce875f54ff
PlasticDigits commented 2026-06-05 11:20:08 +00:00 (Migrated from gitlab.com)

Phase 1c implementation pushed in !798.

Highlights: db_orderbook_sim, global_v2 behind ROUTE_SOLVER_DB_HYBRID=1, router fidelity_check drift guard, new indexer_*_db quote kinds + frontend disclosure.

Verify:

cd indexer && cargo test --test api_route_solve --test api_route_solve_db_hybrid --test db_orderbook_mirror --lib

Do not enable ROUTE_SOLVER_DB_HYBRID in prod until #322 snapshot loop is healthy.

Phase 1c implementation pushed in [!798](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/merge_requests/80). **Highlights:** `db_orderbook_sim`, `global_v2` behind `ROUTE_SOLVER_DB_HYBRID=1`, router `fidelity_check` drift guard, new `indexer_*_db` quote kinds + frontend disclosure. **Verify:** ```bash cd indexer && cargo test --test api_route_solve --test api_route_solve_db_hybrid --test db_orderbook_mirror --lib ``` Do not enable `ROUTE_SOLVER_DB_HYBRID` in prod until #322 snapshot loop is healthy.
ghost1 commented 2026-06-05 12:42:39 +00:00 (Migrated from gitlab.com)

mentioned in commit e0f9e00ca2

mentioned in commit e0f9e00ca2d570b80eb9b8510698f15d42e14efa
ghost1 commented 2026-06-05 13:13:17 +00:00 (Migrated from gitlab.com)

mentioned in commit 0dd92913f8

mentioned in commit 0dd92913f8652dcf627acb52f9d70e15d74d15f2
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-06-05 13:19:38 +00:00
PlasticDigits commented 2026-06-05 13:19:39 +00:00 (Migrated from gitlab.com)

mentioned in commit c0dd1104eb

mentioned in commit c0dd1104eb15bf92d2116364cec8ac944ac78b6c
PlasticDigits commented 2026-06-05 13:38:50 +00:00 (Migrated from gitlab.com)

mentioned in merge request !808

mentioned in merge request !808
ghost1 commented 2026-06-05 13:38:57 +00:00 (Migrated from gitlab.com)

mentioned in commit 666d7f4c4b

mentioned in commit 666d7f4c4bd7403d3b10f6195c3eed78e8e4faab
PlasticDigits commented 2026-06-05 14:10:16 +00:00 (Migrated from gitlab.com)

mentioned in merge request !818

mentioned in merge request !818
Brouie commented 2026-06-06 01:47:10 +00:00 (Migrated from gitlab.com)

mentioned in issue #332

mentioned in issue #332
PlasticDigits commented 2026-06-08 08:43:13 +00:00 (Migrated from gitlab.com)

mentioned in commit abe16fc6a7

mentioned in commit abe16fc6a746780e666343d5bf2e84de2d02db48
PlasticDigits commented 2026-06-08 08:43:13 +00:00 (Migrated from gitlab.com)

mentioned in commit 17b9bba754

mentioned in commit 17b9bba75477abcfd828b895c692fe7bbbe548f3
PlasticDigits commented 2026-06-25 14:12:56 +00:00 (Migrated from gitlab.com)

mentioned in issue #418

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

mentioned in issue #420

mentioned in issue #420
PlasticDigits commented 2026-07-13 10:33:13 +00:00 (Migrated from gitlab.com)

mentioned in issue #485

mentioned in issue #485
PlasticDigits commented 2026-07-15 03:49:13 +00:00 (Migrated from gitlab.com)

mentioned in issue #493

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