Distant-pair route solve: routinely <15s hybrid search + live search progress UX #485

Closed
opened 2026-07-13 10:33:10 +00:00 by PlasticDigits · 10 comments
PlasticDigits commented 2026-07-13 10:33:10 +00:00 (Migrated from gitlab.com)

Summary

Bundle of two related follow-ups from the #484 Calculating… hang fix:

  1. Indexer: tighten hybrid search / cache so distant CW20 pairs (e.g. mainnet JADE↔RUBY, no direct pair) routinely solve in <15s.
  2. Frontend: show live indexer search status (e.g. Searching 3 of 12 pairs…) that updates about every second, so traders see progress instead of a static Calculating… state during long multihop solves.

These belong together: meaningful progress copy requires the indexer to expose progress, and a <15s p95 makes the UX tolerable even when progress is still in flight.


Current codebase

Indexer (GET /api/v1/route/solve global best execution)

When amount_in is set and pool_only is not true, the indexer runs global best execution:

Stage Behavior today
Graph load Loads all assets + pairs from Postgres (get_all_assets / get_all_pairs) per request
Path enum find_paths_top_k → up to 5 simple paths, ≤4 hops (MAX_PATH_CANDIDATES, GET_DEFAULT_MAX_HOPS)
Hybrid opt Per path: joint hybrid grid (17 book fractions × 2 coordinate passes) via DB mirror (global_v4) or LCD (global_v3)
Concurrency SOLVE_CONCURRENCY = 5 (#324)
Final validate Router simulate_swap_operations when ROUTER_ADDRESS set (fidelity check)
Response meta solver_version, paths_considered, lcd_hybrid_queries / db_hybrid_queries, optimality_scope, hybrid_notes — only on final JSON
Cache In-memory ROUTE_CACHE_TTL = **12s**, max 512 entries; key = `solver_version

Cold distant-pair solves often exceed 15s (and can approach the frontend’s 45s INDEXER_ROUTE_SOLVE_TIMEOUT_MS). There is no mid-solve progress channel (no SSE, no job/progress poll, no chunked status). Clients only learn paths_considered after the HTTP response completes.

Authoritative docs: docs/route-solver.md, ADR 0002, skills/AGENTS_INDEXER_HYBRID_BEST_EXECUTION.md.

Frontend (Swap + Trade market)

  • Sim quote uses React Query → getRouteSolve with 45s timeout and AbortSignal (#484).
  • simQuoteRefetchInterval avoids cancel/restart while fetchStatus === 'fetching'.
  • UI copy is binary: Calculating… (button / first-load receive) or the settled amount. No path/pair progress, no elapsed/stage hint.
  • Trade market panel mirrors the same sim refetch helpers.

Skill: skills/AGENTS_FRONTEND_SWAP_QUOTE_REFETCH.md explicitly lists this indexer speedup + product progress UX as out-of-scope follow-ups for #484.


Why this is needed

  1. Latency: Distant pairs are common on mainnet soft-launch graphs. A 15–45s opaque wait feels broken even when #484 correctly lets the request finish. Target: routine cold solves <15s (p95), cache hits much faster — ideally under the default indexer fetch budget where possible.
  2. Trust / UX: While a solve runs, traders need continuous feedback that work is happening (Searching x of y pairs… / path stage), updating ~1 Hz, not a frozen Calculating… label.
  3. Ops: Progress + timing metrics make it diagnosable whether slowness is graph load, candidate eval, mirror miss → LCD fallback, or final router sim.

Constraints / guardrails

  • Do not silently shrink optimality bounds (MAX_PATH_CANDIDATES, hop cap, grid points) without bumping solver_version, documenting the new optimality_scope, and updating ADR / docs/route-solver.md.
  • Do not lower frontend INDEXER_ROUTE_SOLVE_TIMEOUT_MS below production distant-pair latency until indexer p95 is proven <15s.
  • Preserve cache isolation: discount_bps / maker-fill buckets must not cross-contaminate quotes (#283/#324). Prefer longer TTL / warm cache for hot distant pairs without serving wrong-tier quotes.
  • Progress API must not become an unbounded fan-out or LCD-amplification vector; reuse existing RATE_LIMIT_LCD_HEAVY_RPS (and tighten if a new endpoint is added).
  • Progress is advisory — same liability boundary as today’s quotes (hybrid_notes / on-chain min_receive / max_spread remain authoritative).
  • Keep #484 invariants: no overlapping interval refetches; receive keeps prior quote on background refetch; submit stays gated on isFetching.
  • Prefer DB-hybrid (ROUTE_SOLVER_DB_HYBRID / global_v4) path; LCD grid remains fallback — do not reintroduce pair-level LCD grids as the happy path.
  • Accessibility: status text must be readable by screen readers (live region) without spamming announcements every second.

Relevant files

Indexer

  • indexer/src/api/best_execution.rs — top-K solve, concurrency, meta, budget constants
  • indexer/src/api/route_solver.rs — HTTP handlers, ROUTE_CACHE_TTL / hybrid_cache_key / execute_hybrid_route_solve
  • indexer/src/api/hybrid_route_opt.rs — joint hybrid grid
  • indexer/src/api/route_paths.rs — find_paths_top_k
  • indexer/src/api/db_orderbook_sim.rs — mirror load / pricing
  • indexer/tests/api_route_solve.rs, indexer/tests/api_route_solve_db_hybrid.rs
  • docs/route-solver.md, docs/adr/0002-global-best-execution-route-solver.md

Frontend

  • frontend-dapp/src/pages/SwapPage.tsx — sim query + Calculating copy
  • frontend-dapp/src/components/trade/TradeMarketOrderPanel.tsx — market sim query
  • frontend-dapp/src/services/indexer/client.ts — getRouteSolve, timeouts, AbortSignal
  • frontend-dapp/src/utils/quoteDebounce.ts (+ tests)
  • frontend-dapp/src/types/index.ts — IndexerRouteSolveResponse
  • docs/frontend.md (submit-quote / Calculating section)
  • skills/AGENTS_FRONTEND_SWAP_QUOTE_REFETCH.md

A. Indexer latency / cache (<15s)

Investigate and ship a combination of (order by expected impact):

  1. Hot distant-pair cache warm / longer TTL for sparse token pairs (or amount-bucket keys) without breaking tier isolation; consider negative-cache for 404/no-route with short TTL.
  2. Reuse graph snapshot across concurrent solves (avoid full get_all_pairs / assets reload per request when safe).
  3. Early exit / pruning when a high-quality short path already dominates remaining candidates (must stay within documented optimality or bump solver_version).
  4. Instrument stage timings (graph_ms, enum_ms, candidate_ms[], router_sim_ms, cache_hit) in logs/metrics and optionally response debug fields (gated).
  5. Optional: background warm for known soft-launch distant pairs after deploy / on pair-graph change.

Do not “fix” latency only by truncating search without documenting search_truncated / scope changes.

B. Progress reporting (indexer → frontend)

Pick one mechanism (prefer simplest that supports ~1 Hz UI updates):

Option Sketch
Preferred: SSE or chunked progress on solve Client opens solve; server emits { stage, done, total, label } events (~1/s or on each path/pair milestone), final event = full RouteSolveResponse
Alt: async job + poll POST starts solve → job_id; GET /route/solve/progress?job_id= returns counters; frontend polls every 1s; final GET returns body
Avoid: fake elapsed-only spinner with no indexer counters May be a temporary fallback, but not the acceptance bar

Counters should map to user-facing copy such as “Searching {x} of {y} pairs…” (or paths, if that is the accurate unit — prefer honest labels: pairs vs paths vs hops). Expose at least: done, total, stage (enumerating | evaluating | simulating | done), optional eta_ms.

C. Frontend display

  • Swap + Trade market: while sim is fetching and no settled quote (or during first distant-pair fetch), show live status from indexer progress (update ~every second).
  • Once a settled quote exists, keep #484 behavior (prior receive amount visible on background refetch); optional subtle “Refreshing route…” without wiping the amount.
  • On timeout/error: clear progress → existing Quote unavailable / outage paths (#326).
  • Unit-test formatting helpers; component tests for status updates; do not regress submit-stale gates.

Acceptance criteria

Indexer performance

  • Cold GET /api/v1/route/solve for representative distant pairs (no direct pool; ≥2–3 hops) completes in <15s p95 on LocalTerra / QA with DB-hybrid enabled (document exact pair fixtures).
  • Cache hit for same key returns substantially faster (target <200ms excluding slippage enrich) within TTL.
  • Cache still isolates discount tiers and maker-fill buckets (existing #283/#324 tests pass).
  • solver_version / optimality_scope remain accurate; any bound change is versioned + documented.
  • Stage timing is observable (logs or metrics) for graph / enum / candidates / router sim.

Progress API

  • During an in-flight solve, clients can observe progress that advances at least once per second when work is ongoing (or on each discrete milestone if milestones are rarer — document which).
  • Progress includes x of y style counters suitable for “Searching x of y pairs…” (or correctly labeled path/hop units).
  • Final result matches today’s response contract (plus any additive progress-related fields); POST override path behavior unchanged unless explicitly extended.
  • Rate limits / body limits still enforce; progress polling/SSE cannot bypass LCD-heavy caps.

Frontend UX

  • Swap and Trade market show live search status (~1s updates) instead of only static Calculating… while the first distant-pair quote is in flight.
  • Screen-reader live region announces status without excessive chatter.
  • #484 refetch / receive / submit invariants preserved.
  • Fast/direct pairs do not flash noisy progress for sub-second solves (debounce or show only after ~300–500ms).

Test plan (all paths)

Indexer — functional

  1. Direct pool pair: solve still fast; progress reaches done quickly; response quote_kind unchanged.
  2. Multihop DB-hybrid distant pair: cold <15s; progress advances through enum → evaluate → simulate → done.
  3. Cache hit: second identical request (same tier/amount bucket) hits cache; progress may be immediate/cached.
  4. pool_only=true / discovery-only (no amount_in): no global hybrid progress spam; existing semantics.
  5. POST /route/solve with hybrid_by_hop: unchanged merge + sim behavior.
  6. Degraded / zero-reserve candidate skip (#369): progress still completes; no 502 when a viable path exists.
  7. Fidelity drift / mirror stale: correct quote_kind + notes; progress ends in terminal state.
  8. 404 no route / 400 bad amount: progress terminates with error; no stuck jobs.
  9. Concurrent solves for different pairs: no cross-talk of progress IDs or cache keys.
  10. Regression suite: cargo test --test api_route_solve --test api_route_solve_db_hybrid -- --test-threads=1 (+ new progress/latency tests).

Frontend — functional

  1. Distant pair first quote: status updates ~1s with x/y; then amount appears; button leaves Calculating….
  2. Amount edit / debounce (#346/#356): progress resets for new key; stale submit still blocked.
  3. Background refetch with settled quote: receive amount stays; optional quiet refresh — no infinite Calculating (#484).
  4. Indexer timeout (45s) / abort on token switch: progress cleared; no orphan listeners/polls.
  5. Indexer 400 vs outage (#326): banners/CTA parity unchanged.
  6. Trade market panel parity with Swap.
  7. Direct / sub-second quotes: no flicker of “Searching 0 of N”.
  8. Unit tests for progress label helper + client progress consumer; component tests for live region.

E2E / QA

  • LocalTerra multihop fixture (see e2e/helpers/multihop-hybrid-e2e.ts / #422): assert quote settles and (if instrumented) progress events observed.
  • Manual mainnet-soft-launch style JADE→RUBY (or QA equivalent distant CW20s): cold <15s + visible searching status.

Test plan — attack / hack / abuse vectors

  1. Progress job spam: attacker opens many SSE/poll jobs without completing — require bind to in-flight solve, TTL, max concurrent jobs per IP, and cleanup on disconnect; enforce existing LCD-heavy RPS.
  2. Job ID oracle / IDOR: guess another client’s job_id — use unguessable IDs; do not leak quote bodies or trader/tier data on progress until authorized same as solve; prefer opaque secrets over sequential IDs.
  3. Cache poisoning via progress: ensure progress endpoint cannot write cache entries for arbitrary token_in/out without going through the real solver path and keying rules.
  4. Tier confusion: progress or premature partial quotes must not expose another wallet’s discounted estimated_amount_out (#283).
  5. LCD amplification: progress ticks must not trigger extra HybridSimulation / router sims beyond the single solve’s budget (LCD_HYBRID_SIM_BUDGET).
  6. Slowloris / hung clients: SSE or long polls must time out with the solve; abort cancels work where possible (align with frontend AbortSignal).
  7. Parameter bombs: huge max_maker_fills, huge amounts, identical pair flood — clamp (#379), rate-limit, cache bounded at 512 entries.
  8. UI spoofing: frontend must only render progress from indexer; do not invent “found better route” claims beyond optimality_scope.
  9. XSS via status strings: treat indexer stage labels as text, not HTML.
  10. Replay stale progress: after solve completes or errors, further polls return terminal state only; no resurrection of old counters for a new amount without a new job/solve.

Verification criteria

Check Pass condition
Latency Documented distant-pair fixture cold p95 <15s on QA/LocalTerra DB-hybrid; cache hit ≪ cold
Progress UI shows advancing x of y (or labeled equivalent) ~1 Hz during slow solve
Correctness Existing route-solve integration tests green; no tier/cache collisions
UX regression #484 Calculating hang does not return; submit-stale + receive-keep-prior still hold
Security Abuse cases above covered by tests or rate-limit/TTL assertions
Docs docs/route-solver.md, docs/frontend.md, and agent skills updated for progress contract + latency targets
Ship Optional make verify-issue-NNN script for latency + progress smoke
## Summary Bundle of two related follow-ups from the #484 Calculating… hang fix: 1. **Indexer:** tighten hybrid search / cache so distant CW20 pairs (e.g. mainnet JADE↔RUBY, no direct pair) routinely solve in **&lt;15s**. 2. **Frontend:** show live indexer search status (e.g. `Searching 3 of 12 pairs…`) that updates about **every second**, so traders see progress instead of a static Calculating… state during long multihop solves. These belong together: meaningful progress copy requires the indexer to expose progress, and a &lt;15s p95 makes the UX tolerable even when progress is still in flight. --- ## Current codebase ### Indexer (`GET /api/v1/route/solve` global best execution) When `amount_in` is set and `pool_only` is not true, the indexer runs **global best execution**: | Stage | Behavior today | |-------|----------------| | Graph load | Loads **all** assets + pairs from Postgres (`get_all_assets` / `get_all_pairs`) per request | | Path enum | `find_paths_top_k` → up to **5** simple paths, **≤4 hops** (`MAX_PATH_CANDIDATES`, `GET_DEFAULT_MAX_HOPS`) | | Hybrid opt | Per path: joint hybrid grid (17 book fractions × 2 coordinate passes) via DB mirror (`global_v4`) or LCD (`global_v3`) | | Concurrency | `SOLVE_CONCURRENCY = 5` (#324) | | Final validate | Router `simulate_swap_operations` when `ROUTER_ADDRESS` set (fidelity check) | | Response meta | `solver_version`, `paths_considered`, `lcd_hybrid_queries` / `db_hybrid_queries`, `optimality_scope`, `hybrid_notes` — **only on final JSON** | | Cache | In-memory `ROUTE_CACHE_TTL = **12s**`, max **512** entries; key = `solver_version|token_in|token_out|amount_bucket|mmf_bucket|discount_bps` (#283/#324) | Cold distant-pair solves often exceed **15s** (and can approach the frontend’s **45s** `INDEXER_ROUTE_SOLVE_TIMEOUT_MS`). There is **no** mid-solve progress channel (no SSE, no job/progress poll, no chunked status). Clients only learn `paths_considered` after the HTTP response completes. Authoritative docs: [`docs/route-solver.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/docs/route-solver.md), ADR 0002, [`skills/AGENTS_INDEXER_HYBRID_BEST_EXECUTION.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/skills/AGENTS_INDEXER_HYBRID_BEST_EXECUTION.md). ### Frontend (Swap + Trade market) - Sim quote uses React Query → `getRouteSolve` with **45s** timeout and AbortSignal (#484). - `simQuoteRefetchInterval` avoids cancel/restart while `fetchStatus === 'fetching'`. - UI copy is binary: **Calculating…** (button / first-load receive) or the settled amount. No path/pair progress, no elapsed/stage hint. - Trade market panel mirrors the same sim refetch helpers. Skill: [`skills/AGENTS_FRONTEND_SWAP_QUOTE_REFETCH.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/skills/AGENTS_FRONTEND_SWAP_QUOTE_REFETCH.md) explicitly lists this indexer speedup + product progress UX as out-of-scope follow-ups for #484. --- ## Why this is needed 1. **Latency:** Distant pairs are common on mainnet soft-launch graphs. A 15–45s opaque wait feels broken even when #484 correctly lets the request finish. Target: **routine cold solves &lt;15s** (p95), cache hits much faster — ideally under the default indexer fetch budget where possible. 2. **Trust / UX:** While a solve runs, traders need continuous feedback that work is happening (`Searching x of y pairs…` / path stage), updating ~**1 Hz**, not a frozen Calculating… label. 3. **Ops:** Progress + timing metrics make it diagnosable whether slowness is graph load, candidate eval, mirror miss → LCD fallback, or final router sim. --- ## Constraints / guardrails - **Do not** silently shrink optimality bounds (`MAX_PATH_CANDIDATES`, hop cap, grid points) without bumping `solver_version`, documenting the new `optimality_scope`, and updating ADR / `docs/route-solver.md`. - **Do not** lower frontend `INDEXER_ROUTE_SOLVE_TIMEOUT_MS` below production distant-pair latency until indexer p95 is proven &lt;15s. - Preserve cache isolation: **discount_bps / maker-fill buckets** must not cross-contaminate quotes (#283/#324). Prefer longer TTL / warm cache for hot distant pairs without serving wrong-tier quotes. - Progress API must not become an **unbounded fan-out** or LCD-amplification vector; reuse existing `RATE_LIMIT_LCD_HEAVY_RPS` (and tighten if a new endpoint is added). - Progress is **advisory** — same liability boundary as today’s quotes (`hybrid_notes` / on-chain `min_receive` / `max_spread` remain authoritative). - Keep #484 invariants: no overlapping interval refetches; receive keeps prior quote on background refetch; submit stays gated on `isFetching`. - Prefer DB-hybrid (`ROUTE_SOLVER_DB_HYBRID` / `global_v4`) path; LCD grid remains fallback — do not reintroduce pair-level LCD grids as the happy path. - Accessibility: status text must be readable by screen readers (live region) without spamming announcements every second. --- ## Relevant files ### Indexer - `indexer/src/api/best_execution.rs` — top-K solve, concurrency, meta, budget constants - `indexer/src/api/route_solver.rs` — HTTP handlers, `ROUTE_CACHE_TTL` / `hybrid_cache_key` / `execute_hybrid_route_solve` - `indexer/src/api/hybrid_route_opt.rs` — joint hybrid grid - `indexer/src/api/route_paths.rs` — `find_paths_top_k` - `indexer/src/api/db_orderbook_sim.rs` — mirror load / pricing - `indexer/tests/api_route_solve.rs`, `indexer/tests/api_route_solve_db_hybrid.rs` - `docs/route-solver.md`, `docs/adr/0002-global-best-execution-route-solver.md` ### Frontend - `frontend-dapp/src/pages/SwapPage.tsx` — sim query + Calculating copy - `frontend-dapp/src/components/trade/TradeMarketOrderPanel.tsx` — market sim query - `frontend-dapp/src/services/indexer/client.ts` — `getRouteSolve`, timeouts, AbortSignal - `frontend-dapp/src/utils/quoteDebounce.ts` (+ tests) - `frontend-dapp/src/types/index.ts` — `IndexerRouteSolveResponse` - `docs/frontend.md` (submit-quote / Calculating section) - `skills/AGENTS_FRONTEND_SWAP_QUOTE_REFETCH.md` --- ## Recommended direction ### A. Indexer latency / cache (&lt;15s) Investigate and ship a combination of (order by expected impact): 1. **Hot distant-pair cache warm / longer TTL** for sparse token pairs (or amount-bucket keys) without breaking tier isolation; consider negative-cache for 404/no-route with short TTL. 2. **Reuse graph snapshot** across concurrent solves (avoid full `get_all_pairs` / assets reload per request when safe). 3. **Early exit / pruning** when a high-quality short path already dominates remaining candidates (must stay within documented optimality or bump `solver_version`). 4. **Instrument** stage timings (`graph_ms`, `enum_ms`, `candidate_ms[]`, `router_sim_ms`, `cache_hit`) in logs/metrics and optionally response debug fields (gated). 5. Optional: **background warm** for known soft-launch distant pairs after deploy / on pair-graph change. Do **not** “fix” latency only by truncating search without documenting `search_truncated` / scope changes. ### B. Progress reporting (indexer → frontend) Pick one mechanism (prefer simplest that supports ~1 Hz UI updates): | Option | Sketch | |--------|--------| | **Preferred:** SSE or chunked progress on solve | Client opens solve; server emits `{ stage, done, total, label }` events (~1/s or on each path/pair milestone), final event = full `RouteSolveResponse` | | **Alt:** async job + poll | `POST` starts solve → `job_id`; `GET /route/solve/progress?job_id=` returns counters; frontend polls every 1s; final GET returns body | | **Avoid:** fake elapsed-only spinner with no indexer counters | May be a temporary fallback, but not the acceptance bar | Counters should map to user-facing copy such as **“Searching {x} of {y} pairs…”** (or paths, if that is the accurate unit — prefer honest labels: pairs vs paths vs hops). Expose at least: `done`, `total`, `stage` (`enumerating` | `evaluating` | `simulating` | `done`), optional `eta_ms`. ### C. Frontend display - Swap + Trade market: while sim is fetching and no settled quote (or during first distant-pair fetch), show live status from indexer progress (update ~every second). - Once a settled quote exists, keep #484 behavior (prior receive amount visible on background refetch); optional subtle “Refreshing route…” without wiping the amount. - On timeout/error: clear progress → existing **Quote unavailable** / outage paths (#326). - Unit-test formatting helpers; component tests for status updates; do not regress submit-stale gates. --- ## Acceptance criteria ### Indexer performance - [ ] Cold `GET /api/v1/route/solve` for representative distant pairs (no direct pool; ≥2–3 hops) completes in **&lt;15s p95** on LocalTerra / QA with DB-hybrid enabled (document exact pair fixtures). - [ ] Cache hit for same key returns substantially faster (target **&lt;200ms** excluding slippage enrich) within TTL. - [ ] Cache still isolates discount tiers and maker-fill buckets (existing #283/#324 tests pass). - [ ] `solver_version` / `optimality_scope` remain accurate; any bound change is versioned + documented. - [ ] Stage timing is observable (logs or metrics) for graph / enum / candidates / router sim. ### Progress API - [ ] During an in-flight solve, clients can observe progress that advances at least once per second when work is ongoing (or on each discrete milestone if milestones are rarer — document which). - [ ] Progress includes **x of y** style counters suitable for “Searching x of y pairs…” (or correctly labeled path/hop units). - [ ] Final result matches today’s response contract (plus any additive progress-related fields); POST override path behavior unchanged unless explicitly extended. - [ ] Rate limits / body limits still enforce; progress polling/SSE cannot bypass LCD-heavy caps. ### Frontend UX - [ ] Swap and Trade market show live search status (~1s updates) instead of only static Calculating… while the first distant-pair quote is in flight. - [ ] Screen-reader live region announces status without excessive chatter. - [ ] #484 refetch / receive / submit invariants preserved. - [ ] Fast/direct pairs do not flash noisy progress for sub-second solves (debounce or show only after ~300–500ms). --- ## Test plan (all paths) ### Indexer — functional 1. Direct pool pair: solve still fast; progress reaches `done` quickly; response `quote_kind` unchanged. 2. Multihop DB-hybrid distant pair: cold &lt;15s; progress advances through enum → evaluate → simulate → done. 3. Cache hit: second identical request (same tier/amount bucket) hits cache; progress may be immediate/`cached`. 4. `pool_only=true` / discovery-only (no `amount_in`): no global hybrid progress spam; existing semantics. 5. `POST /route/solve` with `hybrid_by_hop`: unchanged merge + sim behavior. 6. Degraded / zero-reserve candidate skip (#369): progress still completes; no 502 when a viable path exists. 7. Fidelity drift / mirror stale: correct `quote_kind` + notes; progress ends in terminal state. 8. 404 no route / 400 bad amount: progress terminates with error; no stuck jobs. 9. Concurrent solves for different pairs: no cross-talk of progress IDs or cache keys. 10. Regression suite: `cargo test --test api_route_solve --test api_route_solve_db_hybrid -- --test-threads=1` (+ new progress/latency tests). ### Frontend — functional 1. Distant pair first quote: status updates ~1s with x/y; then amount appears; button leaves Calculating…. 2. Amount edit / debounce (#346/#356): progress resets for new key; stale submit still blocked. 3. Background refetch with settled quote: receive amount stays; optional quiet refresh — no infinite Calculating (#484). 4. Indexer timeout (45s) / abort on token switch: progress cleared; no orphan listeners/polls. 5. Indexer 400 vs outage (#326): banners/CTA parity unchanged. 6. Trade market panel parity with Swap. 7. Direct / sub-second quotes: no flicker of “Searching 0 of N”. 8. Unit tests for progress label helper + client progress consumer; component tests for live region. ### E2E / QA - LocalTerra multihop fixture (see `e2e/helpers/multihop-hybrid-e2e.ts` / #422): assert quote settles and (if instrumented) progress events observed. - Manual mainnet-soft-launch style JADE→RUBY (or QA equivalent distant CW20s): cold &lt;15s + visible searching status. --- ## Test plan — attack / hack / abuse vectors 1. **Progress job spam:** attacker opens many SSE/poll jobs without completing — require bind to in-flight solve, TTL, max concurrent jobs per IP, and cleanup on disconnect; enforce existing LCD-heavy RPS. 2. **Job ID oracle / IDOR:** guess another client’s `job_id` — use unguessable IDs; do not leak quote bodies or trader/tier data on progress until authorized same as solve; prefer opaque secrets over sequential IDs. 3. **Cache poisoning via progress:** ensure progress endpoint cannot write cache entries for arbitrary `token_in/out` without going through the real solver path and keying rules. 4. **Tier confusion:** progress or premature partial quotes must not expose another wallet’s discounted `estimated_amount_out` (#283). 5. **LCD amplification:** progress ticks must not trigger extra HybridSimulation / router sims beyond the single solve’s budget (`LCD_HYBRID_SIM_BUDGET`). 6. **Slowloris / hung clients:** SSE or long polls must time out with the solve; abort cancels work where possible (align with frontend AbortSignal). 7. **Parameter bombs:** huge `max_maker_fills`, huge amounts, identical pair flood — clamp (#379), rate-limit, cache bounded at 512 entries. 8. **UI spoofing:** frontend must only render progress from indexer; do not invent “found better route” claims beyond `optimality_scope`. 9. **XSS via status strings:** treat indexer stage labels as text, not HTML. 10. **Replay stale progress:** after solve completes or errors, further polls return terminal state only; no resurrection of old counters for a new amount without a new job/solve. --- ## Verification criteria | Check | Pass condition | |-------|----------------| | Latency | Documented distant-pair fixture cold p95 **&lt;15s** on QA/LocalTerra DB-hybrid; cache hit ≪ cold | | Progress | UI shows advancing **x of y** (or labeled equivalent) ~1 Hz during slow solve | | Correctness | Existing route-solve integration tests green; no tier/cache collisions | | UX regression | #484 Calculating hang does not return; submit-stale + receive-keep-prior still hold | | Security | Abuse cases above covered by tests or rate-limit/TTL assertions | | Docs | `docs/route-solver.md`, `docs/frontend.md`, and agent skills updated for progress contract + latency targets | | Ship | Optional `make verify-issue-NNN` script for latency + progress smoke | ## Related - Follow-up called out in #484 / `AGENTS_FRONTEND_SWAP_QUOTE_REFETCH.md` - Prior solver work: #209, #319, #323, #324, #369, #379
PlasticDigits commented 2026-07-13 10:33:12 +00:00 (Migrated from gitlab.com)

marked as related to #484

marked as related to #484
PlasticDigits commented 2026-07-13 10:57:31 +00:00 (Migrated from gitlab.com)

mentioned in commit 24594c5f4c

mentioned in commit 24594c5f4c2fb45268959371e7f646c3be54914b
PlasticDigits commented 2026-07-13 10:57:33 +00:00 (Migrated from gitlab.com)

mentioned in merge request !1021

mentioned in merge request !1021
PlasticDigits commented 2026-07-13 11:50:13 +00:00 (Migrated from gitlab.com)

mentioned in commit 9925165dde

mentioned in commit 9925165dde67b1cd70b8d62c881b94b2f37b6699
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-07-13 11:50:13 +00:00
PlasticDigits commented 2026-07-15 03:49:13 +00:00 (Migrated from gitlab.com)

mentioned in issue #493

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

marked as related to #493

marked as related to #493
PlasticDigits commented 2026-08-22 03:10:04 +00:00 (Migrated from gitlab.com)

mentioned in issue #589

mentioned in issue #589
PlasticDigits commented 2026-08-22 11:02:34 +00:00 (Migrated from gitlab.com)

mentioned in issue #595

mentioned in issue #595
PlasticDigits commented 2026-08-28 09:24:17 +00:00 (Migrated from gitlab.com)

mentioned in issue #694

mentioned in issue #694
PlasticDigits commented 2026-08-28 09:24:18 +00:00 (Migrated from gitlab.com)

marked as related to #694

marked as related to #694
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#485
No description provided.