Route DFS has no global expansion budget — dense graph / unreachable goal = CPU blowup #286
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#286
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?
Severity: Medium
Reachability: Unauthenticated, via the solver (
/route/solve*), which enumerates paths through this DFS.Affected:
find_paths_top_k(indexer/src/api/route_paths.rs).Root cause: the DFS bounds depth (
max_hops) and result count (max_paths), but has no global expansion/visit budget, so an unreachable goal on a dense graph enumerates the whole reachable simple-path set up tomax_hops.Summary
find_paths_top_kstops once it has collectedmax_pathsresults and won't go deeper thanmax_hops, andon_pathblocks node revisits. But when the goal is unreachable (or only reachable via many routes),foundnever fills, so the DFS explores every simple path up tomax_hopsbefore returning — O(branching^max_hops). It runs synchronously inside the async handler, so it blocks the executor while it churns.The token graph is attacker-influenceable: anyone can create pairs, which add edges. A dense subgraph plus a goal token with no route forces worst-case enumeration on every request.
max_hopskeeps it from being unbounded, but there's no cap on total node expansions, which is what you want against an adversarial graph.Current codebase
route_paths.rsfind_paths_top_k/ innerdfs: caps arefound.len() >= max_pathsandpath.len() >= max_hops; no counter on totaldfsinvocations / expansions.Recommended direction
dfscalls (or edges relaxed), abort and return what's found when it's hit, and flag the result as truncated.Acceptance criteria
Test plan (abuse)
The pairs need to be stored in postrgres via factory with token indexes, so that all pairs associated with a token can be looked up qwuickly with 0 LCD/RPC calls. Once thats done, recheck if the recommended fixes are still needed or if correctly storing/indexing the data is sufficient.
mentioned in issue #285
mentioned in merge request !744
Fixed — added the global DFS expansion budget + truncated flag.
One correction to the triage premise: the path-enumeration graph is ALREADY built in-memory from Postgres (
db_pairs::get_all_pairsin best_execution.rs), sofind_paths_top_kdoes zero LCD/RPC. The factory/token-index Postgres storage you mentioned would shrink the candidate edge set but would NOT cap the worst-case enumeration — so the actual fix is a pure in-memory bound, independent of that storage work.Fix:
MAX_DFS_EXPANSIONS = 50_000global node-expansion budget inroute_paths::find_paths_top_k. When the budget is hit the DFS aborts and the function returns(paths, truncated=true); that surfaces as a newroute_search_truncated: Some(true)field onRouteSolveResponse(omitted otherwise). The sibling BFSfind_path(route_solver.rs) is already O(V+E) via a visited set, so it's untouched.Tests (route_paths.rs):
dense_unreachable_graph_truncates_within_budget— a complete graph on 50 nodes with an unreachable goal. Without the budget this enumerates ~N^3 simple paths; with it the search returns bounded +truncated=truein ~0.01s. This is the abuse case.normal_graph_finds_route_not_truncated— a real 3-hop route still resolves,truncated=false(no regression).small_unreachable_graph_completes_not_truncated— no false-positive truncation on a small absent-goal graph.3/3 pass. Branch
qa/286-route-dfs-expansion-budget, MR fork→main (no closing keyword). @PlasticDigitsmentioned in merge request !745
mentioned in issue #279
Strategy is denied as it may cause priority routes to be trauncated in the solver
(1) Need some basic checks like if the input/output token has no pairs then find_paths_top_k can exit immdiately
(2) Need a demonstration of the type of situation that could cause this memory issue
(3) Should add concurrency to
find_paths_top_kto reduce this issueIt is a serious problem if a path exists and is not discovered, as this will cause the user to incorrectly believe the dex is broken or may cause new projects to not list with rare/unusual intermediate tokens. If some type of limiting is needed, truncation is NOT acceptable as we do not know which paths would be truncated and which would not be.
Superseding my earlier "added the expansion budget + truncated flag" note above — you denied that and you were right, so the budget/truncation is gone entirely. Reworked the fix in !745 with no truncation; a route that exists is always discovered.
Short version (full detail + the four-point reply is on !745):
route_paths 8/8, full indexer lib suite green. @PlasticDigits
mentioned in commit
679cc56facmentioned in merge request !758
mentioned in issue #322
mentioned in issue #323
mentioned in issue #324
mentioned in merge request !787
mentioned in merge request !796
mentioned in merge request !799