feat(rate-limiter): enforce per-caller rate limiting on submission entry points - #26
Merged
Meshmulla merged 1 commit intoAug 18, 2026
Conversation
…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
4 tasks
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
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.RateLimitConfig { window_secs, max_calls }(admin-configurable) and per-callerRateLimitUsage { window_start, count }, stored under a newConfigDataKey::RateLimit(Address)persistent key — one fixed-size entry per distinct caller (storage footprint documented on the type).set_rate_limit_config(gated on theManageConfigpermission via the existingaclmodule — the contract admin and anySuperAdmin/Adminrole qualify), plus read-onlyget_rate_limit_config/get_rate_limit_usagegetters.enforce_rate_limit, a rolling-window counter that resets oncewindow_secselapses and reverts with a descriptive panic ("rate limit exceeded: too many submissions in the current window") oncemax_callsis hit in the current window.submit_health,submit_health_batch,submit_price(their_signedvariants are covered automatically since they delegate to the same shared_internalfunctions),submit_calculated_health,submit_health_multi_source,record_supply_mismatch,record_liquidity_depth, andrecord_pool_state.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.rsThat module is a separate,
#[cfg(test)]-only experimental contract (excluded from the production wasm build to avoid Wasm symbol conflicts withBridgeWatchContract, per the existing crate-level comment inlib.rs). Since it never ships in the deployed contract, this PR implements a focused limiter directly insideBridgeWatchContractso the enforcement actually applies to the production entry points, rather than depending on that test-only module.Acceptance criteria
RateLimitUsage)Testing / validation performed
soroban/src/lib.rs'smod tests: limit unset by default, admin config get/set, unauthorized config-set rejection, invalidwindow_secs/max_callsrejection, under-limit success, over-limit rejection, rolling-window reset, enforcement on an admin-only entry point (record_supply_mismatch), and independent per-caller budgets.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.relay_contract_fuzz,threshold_window_fuzz,escrow_contract_fuzz) — all green.Issue: #7