Skip to content

feat(cache-warmer): scope warming to recently-requested tokens#99

Draft
alastairong1 wants to merge 9 commits into
mainfrom
feat/adaptive-cache-warmer
Draft

feat(cache-warmer): scope warming to recently-requested tokens#99
alastairong1 wants to merge 9 commits into
mainfrom
feat/adaptive-cache-warmer

Conversation

@alastairong1
Copy link
Copy Markdown
Contributor

Summary

  • Adds an ActiveTokenCache (10-minute TTL) populated by the /v1/orders/token/{address} route handler.
  • The cache warmer consults the cache before refreshing each token and skips any not present.
  • Tokens nobody is hitting age out and stop being warmed; the next user request pays one singleflighted multicall (already de-duped by get_or_try_insert) and re-marks the token active for the next cycle.
  • Insertions only happen on the user-facing route, never from the warmer itself, so a warmed token doesn't perpetuate its own warming.

Why

The cache warmer previously refreshed every token in the registry every 5 minutes, regardless of demand. With 11 tokens that's a fixed RPC floor of one quote-batch multicall per token per cycle even with zero user traffic — wasted volume that contributes to public-RPC rate limiting (the underlying cause of the orderbook going empty earlier today).

Expected impact

  • Steady-state warmer RPC floor drops from `tokens_in_registry` (currently 11) per 5-min cycle to roughly the count of tokens hit by real users in the last 10 min. Likely 1–3 during quiet periods.
  • First user request on a cold token pays one singleflighted multicall (~1.5–2s); subsequent requests within 15s hit the existing OrdersByTokenCache.
  • No change to user-facing latency once a token is in the active set.

Verification

  • `cargo check` clean (only pre-existing dead-code warnings).
  • 208/208 tests pass.
  • `cargo fmt --check` clean on changed files.
  • `rainix-rs-static` shows no diffs in our `src/`.

Status

Draft — hold for now. Should be reviewed alongside the dRPC `config/rest-api.toml` change (currently a runtime hot-patch on the host at `/etc/rest-api/override.toml` that needs to be promoted to the repo). Both target the same broader goal of cutting RPC pressure off the public Base endpoints.

Test plan

  • Hit `/v1/orders/token/0xff05e1bd696900dc6a52ca35ca61bb1024eda8e2` once, watch warmer logs to confirm next cycle warms that address.
  • Wait 11+ minutes without hitting any address; confirm warmer logs show `skipped=11, ok=0`.
  • Hit a token mid-cycle, confirm singleflight on first cache miss (only one outbound multicall in journal).
  • Confirm `/health/detailed` still reports cache-warmer cycles completing.

alastairong1 and others added 9 commits April 22, 2026 10:52
Return the ABI-encoded order bytes in order list responses so the
frontend can decode OrderV4 directly without a separate Raindex
hydration round-trip. Matches albion.rest.api which already returns this.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…history

Enables the frontend to fetch trades for a specific token instead of
all trades globally. Uses GetOrdersTokenFilter to find orders where the
token appears as input or output, then batch-fetches trades via
DirectTradesFetcher with time-range filtering and pagination.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add max_output field to OrderSummary populated from the on-chain quote
simulation's formattedMaxOutput. Falls back to output_vault_balance when
no quote is available. This allows the frontend depth chart to show
accurate per-epoch availability for DCA/strategy orders instead of
the full vault balance.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The existing /health endpoint only returns {"status":"ok"} without
checking actual system health. The new GET /health/detailed endpoint
reports app DB connectivity, raindex DB connectivity, and per-orderbook
sync progress (last synced block, updated_at timestamp, latest trade
age). This enables diagnosing sync stalls like the Apr 17 stoppage.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit captures the working-tree state currently deployed to
api.preview.st0x.io, prior to splitting it into single-topic PRs.

Includes:
- cache_warmer + supporting caches (block_number, limit_ratio,
  stale_price_skip, swap_quote)
- market_calendar
- ops tooling (docs/ops.md, scripts/smoke.sh, uptimerobot-setup.sh)
- RPC override config
- direct_trades_fetcher extensions
- Health detailed endpoint additions for cache_warmer status
- Various route + infra adjustments

NOTE: lib/rain.orderbook submodule has local modifications that are
not committed inside the submodule itself; the submodule pointer in
this commit is unchanged. Those upstream changes need separate
handling before any per-topic PR can build cleanly against main.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two changes that together let signed-context oracle orders (e.g. SPYM)
return real `ioRatio` values from `/v1/orders/token/*` and
`/v1/orders/owner/*` instead of the `"-"` sentinel:

1. **`fetch_oracle_context` now sends the right body.** The previous
   implementation POSTed `abi.encode(empty bytes)` (just `0x20` || `0x00`)
   to the maker's oracle URL, which the server (st0x-oracle-server) 400'd
   with `Invalid ABI-encoded body`. As a result every signed-context
   `quote()` call fell through with empty `signedContext` and the order's
   Rainlang reverted with `signed-context out of bounds` — the API then
   reported `ioRatio: "-"`, which the frontend would helpfully synthesise
   from oracle price plus phantom liquidity.

   The fix matches what the oracle server's own smoke test does: it
   ABI-encodes the tuple `(OrderV4, U256 inputIOIndex, U256 outputIOIndex,
   address counterparty)` via `SolValue::abi_encode`, with `(0, 0,
   address(0))` for the IO indices and counterparty. The orderbook
   contract then runs the order's full Rainlang including `× baseline-
   multiplier`, so the returned `io_ratio` is the exact ratio the order
   would fill at right now.

   Stale-oracle reverts (`Oracle data stale`) still surface as `"-"` —
   that's correct, the order legitimately can't fill outside market hours.
   Old orders with deprecated URLs (`/context` instead of `/context/v1`)
    404 and also surface as `"-"` — also correct, they can't fill.

   Function signature changed from `(oracle_url: &str)` to
   `(order: &RaindexOrder, oracle_url: &str)` so we can build the request
   body. Success path now logs at INFO so deploys can verify the wiring
   without bumping `RUST_LOG`.

2. **`OrderSummary` exposes `parsed_meta: Vec<ParsedMeta>`.** Since the
   server now does the oracle plumbing internally, frontends don't need
   this field to *quote* — but exposing it lets clients tag oracle-driven
   orders in the UI, read `DotrainSourceV1` for strategy configuration,
   etc. Mirrors the Rain SDK's `ParsedMeta` enum shape; schema declared
   as `Vec<serde_json::Value>` because `ParsedMeta` lives outside this
   crate.

Verified on api.preview.st0x.io — INFO logs show
`fetched oracle signed context oracle_url=...context/v1 count=1` for
SPYM orders. `ioRatio` populates when markets are open / oracle data is
fresh; `"-"` only appears for orders that genuinely can't fill.
The marker is meant as a brief debounce against repeated StalePrice reverts,
not a week-long quarantine. The 7-day TTL combined with NYSE-closed gating
caused orders backed by pre-market (.PRE) Pyth feeds to be locked out of
quoting all weekend after a single transient stale-price hit, surfacing as
ioRatio: '-' on /v1/orders/token responses.
The cache warmer previously refreshed every token in the registry every
5 minutes, regardless of whether anyone was looking at them. With 11
tokens in the registry that's a fixed RPC floor of one quote-batch
multicall per token per cycle even with zero user traffic — wasted
volume that contributes to public-RPC rate limiting.

Add ActiveTokenCache (10-minute TTL): the user-facing
`/v1/orders/token/{address}` handler inserts the requested address on
every call. The warmer consults the cache before refreshing each token
and skips any not present. Tokens nobody is hitting age out and stop
being warmed; the next user request pays one singleflighted multicall
and re-marks the token active for the next cycle.

Insertions only happen on the user-facing route, never from the warmer
itself, so a warmed token doesn't perpetuate its own warming.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f2a6d618-a3fe-49cf-ab28-a3009f44c7ed

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/adaptive-cache-warmer

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant