Skip to content

feat(rate-limiter): enforce per-caller rate limiting on submission entry points - #26

Merged
Meshmulla merged 1 commit into
stellar-kracken:mainfrom
priscaenoch:feature/7-enforce-rate-limiter-on-submissions
Aug 18, 2026
Merged

feat(rate-limiter): enforce per-caller rate limiting on submission entry points#26
Meshmulla merged 1 commit into
stellar-kracken:mainfrom
priscaenoch:feature/7-enforce-rate-limiter-on-submissions

Conversation

@priscaenoch

Copy link
Copy Markdown
Contributor

Summary

Closes #7

Wires an admin-configurable, per-caller rate limit into BridgeWatchContract's submission/report entry points to protect against spam and abuse (e.g. a single operator flooding submissions), as requested in #7.

  • Adds RateLimitConfig { window_secs, max_calls } (admin-configurable) and per-caller RateLimitUsage { window_start, count }, stored under a new ConfigDataKey::RateLimit(Address) persistent key — one fixed-size entry per distinct caller (storage footprint documented on the type).
  • Adds set_rate_limit_config (gated on the ManageConfig permission via the existing acl module — the contract admin and any SuperAdmin/Admin role qualify), plus read-only get_rate_limit_config / get_rate_limit_usage getters.
  • Adds enforce_rate_limit, a rolling-window counter that resets once window_secs elapses and reverts with a descriptive panic ("rate limit exceeded: too many submissions in the current window") once max_calls is hit in the current window.
  • Wires enforcement into every submission/report entry point: submit_health, submit_health_batch, submit_price (their _signed variants are covered automatically since they delegate to the same shared _internal functions), submit_calculated_health, submit_health_multi_source, record_supply_mismatch, record_liquidity_depth, and record_pool_state.
  • The limiter is a no-op until an admin explicitly calls set_rate_limit_config, so default/existing behavior for already-deployed or freshly-initialized contracts is unchanged unless an admin opts in.

Note on soroban/src/rate_limiter.rs

That module is a separate, #[cfg(test)]-only experimental contract (excluded from the production wasm build to avoid Wasm symbol conflicts with BridgeWatchContract, per the existing crate-level comment in lib.rs). Since it never ships in the deployed contract, this PR implements a focused limiter directly inside BridgeWatchContract so the enforcement actually applies to the production entry points, rather than depending on that test-only module.

Acceptance criteria

  • Submission entry points enforce the rate limiter
  • Limits are admin-configurable (window + max)
  • Over-limit calls revert with a descriptive error
  • Tests cover under-limit success, over-limit rejection, and window reset
  • Storage footprint documented (see doc comment on RateLimitUsage)

Testing / validation performed

  • 10 new unit tests in soroban/src/lib.rs's mod tests: limit unset by default, admin config get/set, unauthorized config-set rejection, invalid window_secs/max_calls rejection, under-limit success, over-limit rejection, rolling-window reset, enforcement on an admin-only entry point (record_supply_mismatch), and independent per-caller budgets.
  • Full existing test suite passes unchanged (535 lib tests, up from 525, plus all integration and fuzz targets) — confirms the opt-in design introduces zero regressions.
  • cargo fmt --all -- --check — clean.
  • cargo clippy --all-targets --all-features -- -D warnings — clean, zero warnings.
  • cargo build --release --target wasm32-unknown-unknown — builds successfully.
  • cargo test (full workspace) — all green.
  • Bounded fuzz targets (relay_contract_fuzz, threshold_window_fuzz, escrow_contract_fuzz) — all green.

Issue: #7

…try points

Wires an admin-configurable, per-caller rate limit into BridgeWatchContract's
submission/report entry points to protect against spam and abuse (e.g. a
single operator flooding submissions).

What changed:
- Add `RateLimitConfig { window_secs, max_calls }` and per-caller
  `RateLimitUsage { window_start, count }`, stored under a new
  `ConfigDataKey::RateLimit(Address)` persistent key (one fixed-size entry
  per distinct caller — footprint documented on the type).
- Add `set_rate_limit_config` (admin/`ManageConfig`-gated), plus read-only
  `get_rate_limit_config` and `get_rate_limit_usage` getters.
- Add `enforce_rate_limit`, a rolling-window counter that resets once
  `window_secs` elapses and panics with a descriptive error
  ("rate limit exceeded: too many submissions in the current window")
  once `max_calls` is reached in the current window.
- Wire enforcement into `submit_health`, `submit_health_batch`,
  `submit_price` (covering their `_signed` variants via the shared
  `_internal` functions), `submit_calculated_health`,
  `submit_health_multi_source`, `record_supply_mismatch`,
  `record_liquidity_depth`, and `record_pool_state`.
- The limiter is a no-op until an admin explicitly configures it via
  `set_rate_limit_config`, so existing/default behavior is unchanged
  until opted into.

Note: the existing `soroban/src/rate_limiter.rs` module is a standalone,
`#[cfg(test)]`-only experimental contract (excluded from the production
wasm build to avoid symbol conflicts with BridgeWatchContract, per the
existing crate-level comment). This change implements a focused limiter
directly in BridgeWatchContract so it actually ships in the deployed
contract, rather than depending on that test-only module.

Testing performed:
- 10 new unit tests covering: limit unset by default (no regression),
  admin config get/set, unauthorized config-set rejection, invalid
  window/max_calls rejection, under-limit success, over-limit rejection,
  rolling-window reset, enforcement on an admin-only entry point, and
  independent per-caller budgets.
- Full existing suite (535 lib tests + all integration/fuzz targets)
  passes unchanged, confirming no regressions from the opt-in default.
- `cargo fmt --all -- --check`, `cargo clippy --all-targets --all-features
  -- -D warnings`, and `cargo build --release --target
  wasm32-unknown-unknown` all pass.

Closes stellar-kracken#7
@Meshmulla
Meshmulla merged commit 482b3d1 into stellar-kracken:main Aug 18, 2026
3 checks passed
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.

Enforce the rate limiter on submission entry points

2 participants