You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
solana-autopilot: ask native price sources by stage like the EVM autopilot - #5112
A native price lookup asks its sources the way the EVM autopilot does: by configured stage, a stage at once, until a token has results-required prices, and the best of them wins.
The sources were asked one after another, so a lookup cost the sum of every driver's probe (a /quote selling 0.01 wSOL into the token), and a slow driver held every quote on that token once #5068 forwards orderbook lookups to the autopilot under a 5s budget.
Stages come from config in the EVM [native-price-estimation] shape, estimators = [[{ type = "CoinGecko" }], [{ type = "Driver", name, url }]], with CoinGecko's url and key in [native-prices.coin-gecko]. A lookup starts as many stages as could reach results-required (default 2, as on EVM), drops the sources still in flight once every asked token has that many prices, and asks later stages only for the tokens still short.
Config and image ship together: cowprotocol/infrastructure#7288.
Changes
[native-prices] estimators lists stages, and results-required stops a lookup early
fetch_into_cache polls a stage on FuturesUnordered and drops it once every token has enough prices
squadgazzz
changed the title
solana-autopilot: ask the driver price probes together
solana-autopilot: ask native price sources by stage like the EVM autopilot
Oct 16, 2026
I traced the new staged fetch_into_cache loop against the EVM-style semantics described in the PR, checked the config plumbing, and read through the new tests.
Verdict: This looks solid and well-tested. I did not find a correctness bug worth a change request, so there are no inline comments. Summary of what I checked below.
Logic (prices.rs::fetch_into_cache)
Termination / no infinite loop: ✅ The outer loop is bounded by next_stage < stages.len(), and the inner stage-launcher always advances next_stage at least once per outer iteration (missing ≥ 1 since asked is non-empty and each asked token is !enough, so answers.len() = 0 < missing on entry). Progress is guaranteed even when results_required exceeds the number of sources — it degrades to "ask everyone, cache the best found".
No underflow: ✅ self.results_required - priced(&found, token) is safe because asked only retains tokens where priced < results_required.
Best-price accumulation: ✅ or_insert((price, 0)) then *best = (*best).min(price); *count += 1 correctly yields count = number of sources that priced the token and the min price, including the first insert.
Early-drop cancellation: ✅ Breaking the drain loop on asked.iter().all(enough) drops the in-flight FuturesUnordered, which cancels the pending reqwest requests — matching the comment and the a_lookup_stops_at_the_required_prices test.
Failure accounting: ✅ The fail-closed check (failures > 0 && failures == total sources) only fires when every configured source was actually asked and failed; a lookup that stops early can't reach it. Negative caching still only happens when failures == 0, preserving the "a failed source might know it, so retry next cut" behavior from main.
Partial pricing: ✅ A token reached by only one source (count < results_required) is still cached with its found price in the final loop, so single-source tokens aren't dropped.
Config (config.rs) & example.toml
estimators: Vec<Vec<...>> + results_required: NonZero<usize> (default 2) mirror the EVM [native-price-estimation] shape. NonZero rules out a zero that would make enough trivially true. new() filters empty stages and treats all-empty as Denominated. Example TOML, doc comments, and the loads_the_example_config test are all consistent.
Behavior note (not a defect — already implied by the PR)
With the shipped example (two single-source stages, results-required = 2), every token now always queries both CoinGecko and the driver on every (re)fetch — nothing is ever dropped, since 2 prices require 2 sources. This is the intended "ask together, best wins" design; operators wanting strict fallback would set results-required = 1. Flagging only so it's a conscious choice for driver load.
⚠️ I was unable to run cargo test -p autopilot-svm / clippy — cargo is blocked in this review sandbox. The new tests read correctly (timing assertions, atom-math, stop-early counts all check out by inspection), but please rely on CI for the actual pass.
· solana-autopilot/be-430-parallel-driver-probes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Labels
None yet
2 participants
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
A native price lookup asks its sources the way the EVM autopilot does: by configured stage, a stage at once, until a token has
results-requiredprices, and the best of them wins.The sources were asked one after another, so a lookup cost the sum of every driver's probe (a
/quoteselling 0.01 wSOL into the token), and a slow driver held every quote on that token once #5068 forwards orderbook lookups to the autopilot under a 5s budget.Stages come from config in the EVM
[native-price-estimation]shape,estimators = [[{ type = "CoinGecko" }], [{ type = "Driver", name, url }]], with CoinGecko's url and key in[native-prices.coin-gecko]. A lookup starts as many stages as could reachresults-required(default 2, as on EVM), drops the sources still in flight once every asked token has that many prices, and asks later stages only for the tokens still short.Config and image ship together: cowprotocol/infrastructure#7288.
Changes
[native-prices] estimatorslists stages, andresults-requiredstops a lookup earlyfetch_into_cachepolls a stage onFuturesUnorderedand drops it once every token has enough pricesHow to test
New unit tests.
Related issues
BE-430