Indexer route/solve 502s ('mirror simulation failed') on a healthy pair when a candidate path touches a zero-reserve pair (DB-hybrid) #369
Labels
No labels
agent:fix_bugfix
agent:fix_conflicts
agent:fix_security
agent:gap_analysis
agent:implement
agent:implement
agent:implement
agent:open_issues
agent:ready
agent:research
agent:security_audit
agent:verify
architecture
backend
blocker:hybrid
blocker:launch
blocker:limit-orders
blocker:v2
block:log_only
block:security
bug
ci
contracts
correctness
deploy
dev
devops
docs
documentation
duplicate
e2e
enhancement
epic
feature
frontend
functional-completion
gas
good first issue
governance
help wanted
high-risk
hooks
hybrid
indexer
infra
infrastructure
integrators
invalid
launch-blocker
limit-orders
localnet
localterra
low priority
missing-implementation
needs-design
ops
performance
priority
high
priority
medium
product
qa
QA
question
ready
ready
research
scripts
security
security-hardening
smartcontracts
tech-debt
testing
ux
UX
v2
verification
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
code/cl8y-dex-terraclassic#369
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Symptom
GET /api/v1/route/solve(and/best) returns 502 "Route mirror simulation failed" for EMBER↔CORAL, both directions, every size (1 EMBER through pool-capacity), on main8408689withROUTE_SOLVER_DB_HYBRID=1.Indexer
/healthis ok and the indexer is caught up (last_indexed 1463243 vs chain 1463246).Root cause
best_execution.rs:161-171mapsHybridSimError::Db→ 502 "Route mirror simulation failed". Log detail:db hybrid sim error detail=insufficient pool liquidity.db_orderbook_sim.rs::simulate_pool_legreturnsInsufficientLiquiditywheninput_reserve == 0 || output_reserve == 0.pair_reserves=96883935727 / 95441935304, matching live LCD{pool}exactly, snapshot age 0. So the zero-reserve leg is not the direct pair.pair_reserves: pair_id 152 and 205 (reserve_0=0, reserve_1=0). The solver evaluates a candidate path that includes one of these and hard-fails the whole request instead of (a) skipping that path for the viable direct route, or (b) degrading. Notequote_kind_fordoes degrade gracefully formirror_stale/mirror_missing, but a zero-reserve mirror is treated as a hard error.A viable direct route demonstrably exists (pair 1 has deep liquidity and the dapp swaps EMBER→CORAL fine via LCD sim, e.g. tx 1D82DB21… code 0), yet route/solve 502s.
Why this matters beyond QA state
Empty/unfunded pairs are a normal state — every
create_pairstarts at zero reserves until liquidity is added (CP-00-02). UnderROUTE_SOLVER_DB_HYBRID, any such pair can poison route/solve for other pairs that share a candidate path, returning a hard 502 instead of the working direct route. Impact: indexer route-compare, the dapp's >30% retail-slippage warning UX (it reads route/solveslippage_percent), and integrator/route/solve/bestall degrade. On-chain swaps are unaffected (dapp + on-chainmin_returnstill protect funds).How it surfaced here
ROUTE_SOLVER_DB_HYBRID=1is set on the QA indexer. Pairs 152/205 were left at zero by abook_snapshot"All LCD endpoints failed" transient (2026-06-11), but the same poisoning reproduces for any genuinely empty pair, so the transient is incidental.Suggested direction
ROUTE_SOLVER_DB_HYBRID; if so this is launch-relevant for any pair set containing empty pairs.Found during #337 browser pass (SEC-00-05 re-confirm: the >30% guard didn't fire precisely because route/solve was 502-ing).
mentioned in issue #337
mentioned in commit
d847275c89mentioned in merge request !878
mentioned in commit
d64052c857mentioned in commit
2d46702d23mentioned in merge request !880
mentioned in commit
faf127ce0amentioned in commit
15551045b6mentioned in merge request !885
mentioned in issue #363
mentioned in commit
d269d11312mentioned in commit
9e4582b52fmentioned in merge request !887
mentioned in commit
83ae37217amentioned in merge request !890
mentioned in commit
43853ec6a4Verified on my side on main
87b6f9a(indexer rebuilt + restarted off it).The hard 502 is gone. EMBER↔CORAL both directions, /route/solve/best, and a pool-capacity-size request all come back 200 now — and they do it with pairs 152 and 205 still sitting at zero reserves in pair_reserves, so the skip path is genuinely being exercised, not masked by clean data. The solve returns a real route (3-hop, paths_considered 5, estimated_amount_out populated, quote_kind indexer_pool_db).
Source matches the symptom: hybrid_route_opt.rs treats an unusable (zero-reserve) pool hop as skip/degrade-to-LCD-fallback instead of failing the whole request, while genuinely missing reserves still error.
verify-issue-369.sh: 5/5 green
Good to close from my side. @PlasticDigits
mentioned in commit
bfd30abba9mentioned in issue #485