Skip to content

Fix/api hardening followups - #131

Merged
valoryyaa-byte merged 6 commits into
RWA-ToolKit:mainfrom
deeejaymu-afk:fix/api-hardening-followups
Jul 28, 2026
Merged

Fix/api hardening followups#131
valoryyaa-byte merged 6 commits into
RWA-ToolKit:mainfrom
deeejaymu-afk:fix/api-hardening-followups

Conversation

deeejaymu-afk and others added 6 commits July 25, 2026 16:29
PrometheusBuilder, PrometheusHandle and the metrics::counter! macro were
used in main.rs and indexer/mod.rs without either crate being declared in
Cargo.toml, and without the corresponding use imports. The crate did not
compile on main.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
cache_headers already called state.last_indexed_ledger() to derive the
ETag, but AppState never defined it. Add it as a plain field read off the
ArcSwap so the ETag middleware doesn't need to clone the whole snapshot.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
ApiError only had NotFound, so there was no way to return a 500/503 with a
structured body when the snapshot itself wasn't usable (e.g. before the
indexer's first successful refresh) -- routes just served zero-value
defaults as if they were real data.

Add ServiceUnavailable (503) and Internal (500) variants, plus a
require_ready() check used by every snapshot-backed route to fail fast
with 503 until the indexer has completed at least one refresh.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The router only had CORS and trace layers -- no timeout, no concurrency
cap, no request-size limit -- so a slow backend read or a burst of
concurrent/abusive clients had nothing bounding resource usage.

Add, globally, ahead of the existing per-IP governor rate limit:
- TimeoutLayer: aborts a request after REQUEST_TIMEOUT (10s).
- ConcurrencyLimitLayer + LoadShedLayer: caps in-flight requests at
  MAX_CONCURRENT_REQUESTS (256) and rejects overflow immediately with 503
  instead of queuing it unboundedly.
- RequestBodyLimitLayer: caps request bodies at 16 KiB (no route accepts
  one).

Errors from the timeout/load-shed layers are converted to the same
ApiErrorBody JSON shape as the rest of the API via HandleErrorLayer,
using the ServiceUnavailable/Internal variants added in the previous
commit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
with_graceful_shutdown was already wired up, but shutdown_signal only
resolved on Ctrl-C. Container runtimes stop a process with SIGTERM, not
SIGINT, so on `docker stop`/pod termination the server kept accepting
connections until the runtime's kill timeout forcibly cut them.

Race Ctrl-C against SIGTERM (tokio::signal::unix::signal) so either
triggers graceful shutdown.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
parse_i128 did s.parse().unwrap_or(0), so any decode/format drift on
supply, valuation, a holder balance, or a distribution amount silently
turned a real on-chain value into 0 -- understating TVL, supply and
balances with no signal anything went wrong.

Add parse_i128_flagged, used at every contract-data call site: it logs a
warn (asset id, field, raw value, parse error) on failure and returns
whether it happened. Thread that per-call flag up into a new
Asset::decode_errors field so API consumers can tell a genuine zero from
"we couldn't decode this and defaulted to zero." The existing parse_i128
stays as-is for re-parsing strings this process already produced (e.g.
sorting by a balance it just formatted), where failure isn't possible.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@drips-wave

drips-wave Bot commented Jul 25, 2026

Copy link
Copy Markdown

@deeejaymu-afk Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@valoryyaa-byte
valoryyaa-byte merged commit 4820655 into RWA-ToolKit:main Jul 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment