fix: retry price ranges left empty by a transient decimals failure#67
Merged
Conversation
When a token's decimals() call failed while scanning a block range, the affected swaps were dropped and the resulting zeroed/partial price was cached for both the gap and the whole query window. Every later call for that range then returned the empty result, freezing the transient failure in even after the RPC recovered. A gap whose swaps were dropped by a decimals fetch failure is now treated as degraded and left out of the cache, and the full-range write-back is skipped whenever any gap degraded. A later call rescans the affected range instead of serving the stale empty aggregate. Genuinely empty ranges are still cached, so the retry only applies to failures.
Contributor
Author
Self-review passPre-commit /code-review (high effort, 5 angles) + post-PR /pr-review, findings triaged below.
No blockers remaining. Confident to ship. |
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 free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
A persistent
decimals()fetch failure while scanning a block range causedthe affected swaps to be dropped and the resulting zeroed/partial price to be
cached as authoritative — for both the gap and the whole query window. Every
later call for that range then returned the empty result, so a transient RPC
hiccup was frozen in and downstream consumers concluded the token had no swaps.
This makes the calculator decline to cache a range degraded by a decimals
failure, so it rescans on a later call.
Closes #66.What's in the diff
src/price/calculator.rsprocess_gap_for_pricenow returns a privateGapScan { result, degraded };degradedis set whenever a swap is dropped because its decimals couldn't befetched. A genuinely-empty gap returns
degraded: false.calculate_price_between_blocksskips the per-gap cache insert for a degradedgap, and skips the full-range covering write-back whenever any gap degraded
(the covering write would otherwise collapse the disjoint segments into one
entry that re-caches the degraded data for the whole window). Each case logs
at
warn.How it preserves the cache invariants
Clean gaps in the same call are still cached as disjoint segments; only the
degraded gap is left out. On the next call,
calculate_gapsmerges the cachedclean segments and reports the degraded range as a gap, so just that range is
rescanned — no over-counting, no stale empty aggregate.
Acceptance check (from #66)
fetch failure" —
relevant.is_empty()returnsdegraded: falseand is cached;a dropped-swap gap returns
degraded: trueand is not cached.full-range write-back are both skipped when degraded.
disjoint; the degraded range resurfaces as a gap for rescanning.
Test plan
cargo test --all-features— 97/97 (price + cache suites) passcargo test(default) andcargo test --no-default-featurespasscargo clippy --all-targets --all-features -- -D warningscleancargo fmtcleandecimals(); per the project convention (provider-dependent calculator pathslive in
examples/, not tests) this isn't covered by a unit test. The cachingmechanics it relies on are covered by the existing
PriceCachetests.Note (deliberately out of scope)
The returned
TokenPriceResultfor a degraded range is still the partial/zeroedaggregate with no partial-failure flag on it — a caller doing its own caching
could re-freeze it. Surfacing such a signal is the alternative the issue framed
("mirror the combined-retrieval partial-failure reporting"); this PR takes the
narrower skip-the-cache approach. Happy to file a follow-up if the signal is wanted.