fix: key the pricing quote cache by (chain_id, symbol) (RAI-2130) - #68
Open
hardyjosh wants to merge 1 commit into
Open
fix: key the pricing quote cache by (chain_id, symbol) (RAI-2130)#68hardyjosh wants to merge 1 commit into
hardyjosh wants to merge 1 commit into
Conversation
Contributor
Author
|
Warning This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
This stack of pull requests is managed by Graphite. Learn more about stacking. |
hardyjosh
force-pushed
the
fix/rai-2130-chain-scoped-quote-cache
branch
from
August 26, 2026 16:54
d8ada81 to
b9cb2be
Compare
st0x.pricing is about to publish one frame per (chain, symbol). Keyed by bare symbol, two chains' frames for the same asset were last-write-wins, so this server could sign an oracle context against another chain's rate. Nothing downstream catches it: every frame involved is individually fresh, in-expiry and correctly signed. Ingest now stores each frame under its own chain_id and halts evict only their own chain's entry; reads (latest, snapshot_many, missing, newest_source_ts_ms) are all scoped to config.chain_id, so other chains' frames are retained but never served. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
hardyjosh
force-pushed
the
feat/rai-1991-context-v7-chain-id
branch
from
August 26, 2026 16:55
1b61e1e to
44a1741
Compare
hardyjosh
force-pushed
the
fix/rai-2130-chain-scoped-quote-cache
branch
from
August 26, 2026 16:55
b9cb2be to
8c9c845
Compare
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.

Closes RAI-2130.
The hazard
src/pricing_client.rscached pricing quotes in aHashMap<Symbol, Quote>, inserting by bare symbol and looking up by bare symbol — discarding thechain_idthat everyPriceFramealready carries.st0x.pricing is about to publish one frame per (chain, symbol). Under a symbol-only key, two frames for the same symbol on different chains are last-write-wins across chains: whichever landed most recently occupies the single slot, and this server would sign an oracle context against another chain's rate.
That failure is silent. Every frame involved is individually fresh, in-expiry and correctly signed, so no staleness check, no expiry check and no signature check anywhere downstream can see the difference. The only observable symptom would be a mispriced fill.
The change
(u64 chain_id, Symbol)— aQuoteCachetype alias, since the type appears in four signatures.Priceframe under the frame's ownchain_id. Frames for other chains are retained, not dropped.latest,snapshot_many,missing,newest_source_ts_ms) are all scoped to this server's configured chain, so other chains' frames are never served. A symbol cached only for another chain reads as absent —/statusreports it undermissing_symbols.Halteviction is chain-scoped too: a halt on another chain must not fail-closed the quote this deployment serves.newest_source_ts_msexcludes other chains, so a fresh frame we would never serve cannot mask a frozen feed on the chain we do.The configured chain comes from
config.chain_id(default 8453), threaded throughLiveClientConfig::newas a required argument rather than a builder option — silently defaulting the serving chain is the exact class of bug being fixed here.No change to the signed-context schema or the wire format.
st0x-pricing-typesis unchanged —chain_idis already on the frame.Stacked on #65
This builds on #65 (
feat/rai-1991-context-v7-chain-id), which introducesconfig.chain_id. That field is reused as-is; #65's commit is untouched. Review/merge #65 first.Tests
Three new unit tests in
src/pricing_client.rs:frames_for_one_symbol_on_two_chains_do_not_clobber_each_other— both frames survive ingest, one entry per (chain, symbol).reads_serve_the_configured_chain_not_the_latest_frame— the chain-1 frame arrives last and would win under a symbol-only key;latest,snapshot_manyandmissingall serve/report the configured chain instead.halt_on_another_chain_does_not_evict_our_quote— halt eviction stays on its own chain.Verified these are discriminating, not just compile-time-new: re-keying the cache to a constant chain id (reproducing the old symbol-only collision) fails all three, with test 2 returning the last-arrived chain's quote.
Local checks
Both mirror CI, run in the nix shell:
nix develop -c cargo test— 99 passed, 0 failed (66 lib incl. the 3 new, 29 integration, 1 KMS smoke, 3 prod smoke).nix develop -c bash -c 'cargo fmt --all -- --check'— clean.Clippy caveat:
cargo clippycould not be run locally. It fails on this machine witherror[E0463]: can't find crate for num_enum_derive, inside the third-partynum_enum-0.7.6dependency. This reproduces on unmodifiedmainwith no diff applied — it is a local nix/proc-macro environment problem, not something this branch introduces. CI is the authority onoracle-rs-static.🤖 Generated with Claude Code