ostium_rust_sdk/
rate_limit.rs1use std::sync::Arc;
7use std::time::{Duration, Instant};
8use tokio::sync::Mutex;
9use tokio::time::sleep;
10use tracing::{debug, warn};
11
12#[derive(Debug, Clone)]
14pub struct RateLimitConfig {
15 pub max_requests: u32,
17 pub time_window: Duration,
19 pub burst_size: u32,
21}
22
23impl Default for RateLimitConfig {
24 fn default() -> Self {
25 Self {
26 max_requests: 100,
27 time_window: Duration::from_secs(60), burst_size: 10,
29 }
30 }
31}
32
33impl RateLimitConfig {
34 pub fn graphql() -> Self {
36 Self {
37 max_requests: 60,
38 time_window: Duration::from_secs(60), burst_size: 5,
40 }
41 }
42
43 pub fn rest_api() -> Self {
45 Self {
46 max_requests: 120,
47 time_window: Duration::from_secs(60), burst_size: 10,
49 }
50 }
51
52 pub fn blockchain() -> Self {
54 Self {
55 max_requests: 200,
56 time_window: Duration::from_secs(60), burst_size: 20,
58 }
59 }
60
61 pub fn conservative() -> Self {
63 Self {
64 max_requests: 30,
65 time_window: Duration::from_secs(60), burst_size: 3,
67 }
68 }
69}
70
71#[derive(Debug)]
73pub struct RateLimiter {
74 config: RateLimitConfig,
75 state: Arc<Mutex<RateLimiterState>>,
76}
77
78#[derive(Debug)]
79struct RateLimiterState {
80 tokens: f64,
81 last_refill: Instant,
82}
83
84impl RateLimiter {
85 pub fn new(config: RateLimitConfig) -> Self {
87 let burst_size = config.burst_size;
88 Self {
89 state: Arc::new(Mutex::new(RateLimiterState {
90 tokens: burst_size as f64,
91 last_refill: Instant::now(),
92 })),
93 config,
94 }
95 }
96
97 pub async fn acquire(&self) -> Result<(), RateLimitError> {
100 loop {
101 {
102 let mut state = self.state.lock().await;
103 self.refill_tokens(&mut state);
104
105 if state.tokens >= 1.0 {
106 state.tokens -= 1.0;
107 debug!(
108 "Rate limit token acquired, {} tokens remaining",
109 state.tokens
110 );
111 return Ok(());
112 }
113 }
114
115 let wait_time = self.calculate_wait_time().await;
117 debug!(
118 "Rate limit exceeded, waiting {:?} for next token",
119 wait_time
120 );
121
122 if wait_time > Duration::from_secs(30) {
123 warn!("Rate limit wait time exceeds 30 seconds, rejecting request");
124 return Err(RateLimitError::ExcessiveWait(wait_time));
125 }
126
127 sleep(wait_time).await;
128 }
129 }
130
131 pub async fn try_acquire(&self) -> bool {
134 let mut state = self.state.lock().await;
135 self.refill_tokens(&mut state);
136
137 if state.tokens >= 1.0 {
138 state.tokens -= 1.0;
139 debug!(
140 "Rate limit token acquired, {} tokens remaining",
141 state.tokens
142 );
143 true
144 } else {
145 debug!("Rate limit exceeded, no tokens available");
146 false
147 }
148 }
149
150 pub async fn available_tokens(&self) -> f64 {
152 let mut state = self.state.lock().await;
153 self.refill_tokens(&mut state);
154 state.tokens
155 }
156
157 async fn calculate_wait_time(&self) -> Duration {
159 let state = self.state.lock().await;
160 let tokens_per_second =
161 self.config.max_requests as f64 / self.config.time_window.as_secs_f64();
162 let time_per_token = Duration::from_secs_f64(1.0 / tokens_per_second);
163
164 if state.tokens <= 0.0 {
166 time_per_token
167 } else {
168 Duration::from_millis(100)
170 }
171 }
172
173 fn refill_tokens(&self, state: &mut RateLimiterState) {
175 let now = Instant::now();
176 let elapsed = now.duration_since(state.last_refill);
177
178 let tokens_per_second =
179 self.config.max_requests as f64 / self.config.time_window.as_secs_f64();
180 let tokens_to_add = elapsed.as_secs_f64() * tokens_per_second;
181
182 state.tokens = (state.tokens + tokens_to_add).min(self.config.burst_size as f64);
183 state.last_refill = now;
184 }
185}
186
187#[derive(Debug, thiserror::Error)]
189pub enum RateLimitError {
190 #[error("Rate limit wait time would be excessive: {0:?}")]
192 ExcessiveWait(Duration),
193}
194
195#[derive(Debug)]
197pub struct RateLimiterManager {
198 graphql_limiter: Option<RateLimiter>,
199 rest_limiter: Option<RateLimiter>,
200 blockchain_limiter: Option<RateLimiter>,
201}
202
203impl Default for RateLimiterManager {
204 fn default() -> Self {
205 Self::new()
206 }
207}
208
209impl RateLimiterManager {
210 pub fn new() -> Self {
212 Self {
213 graphql_limiter: None,
214 rest_limiter: None,
215 blockchain_limiter: None,
216 }
217 }
218
219 pub fn with_graphql_rate_limit(mut self, config: RateLimitConfig) -> Self {
221 self.graphql_limiter = Some(RateLimiter::new(config));
222 self
223 }
224
225 pub fn with_rest_rate_limit(mut self, config: RateLimitConfig) -> Self {
227 self.rest_limiter = Some(RateLimiter::new(config));
228 self
229 }
230
231 pub fn with_blockchain_rate_limit(mut self, config: RateLimitConfig) -> Self {
233 self.blockchain_limiter = Some(RateLimiter::new(config));
234 self
235 }
236
237 pub fn with_default_limits(mut self) -> Self {
239 self.graphql_limiter = Some(RateLimiter::new(RateLimitConfig::graphql()));
240 self.rest_limiter = Some(RateLimiter::new(RateLimitConfig::rest_api()));
241 self.blockchain_limiter = Some(RateLimiter::new(RateLimitConfig::blockchain()));
242 self
243 }
244
245 pub async fn acquire_graphql(&self) -> Result<(), RateLimitError> {
247 if let Some(limiter) = &self.graphql_limiter {
248 limiter.acquire().await
249 } else {
250 Ok(())
251 }
252 }
253
254 pub async fn acquire_rest(&self) -> Result<(), RateLimitError> {
256 if let Some(limiter) = &self.rest_limiter {
257 limiter.acquire().await
258 } else {
259 Ok(())
260 }
261 }
262
263 pub async fn acquire_blockchain(&self) -> Result<(), RateLimitError> {
265 if let Some(limiter) = &self.blockchain_limiter {
266 limiter.acquire().await
267 } else {
268 Ok(())
269 }
270 }
271
272 pub async fn try_acquire_graphql(&self) -> bool {
274 if let Some(limiter) = &self.graphql_limiter {
275 limiter.try_acquire().await
276 } else {
277 true
278 }
279 }
280
281 pub async fn try_acquire_rest(&self) -> bool {
283 if let Some(limiter) = &self.rest_limiter {
284 limiter.try_acquire().await
285 } else {
286 true
287 }
288 }
289
290 pub async fn try_acquire_blockchain(&self) -> bool {
292 if let Some(limiter) = &self.blockchain_limiter {
293 limiter.try_acquire().await
294 } else {
295 true
296 }
297 }
298}
299
300#[cfg(test)]
301mod tests {
302 use super::*;
303 use tokio::time::{sleep, Duration};
304
305 #[tokio::test]
306 async fn test_rate_limiter_basic() {
307 let config = RateLimitConfig {
308 max_requests: 5,
309 time_window: Duration::from_secs(1),
310 burst_size: 2,
311 };
312
313 let limiter = RateLimiter::new(config);
314
315 assert!(limiter.try_acquire().await);
317 assert!(limiter.try_acquire().await);
318
319 assert!(!limiter.try_acquire().await);
321 }
322
323 #[tokio::test]
324 async fn test_rate_limiter_refill() {
325 let config = RateLimitConfig {
326 max_requests: 10,
327 time_window: Duration::from_secs(1),
328 burst_size: 1,
329 };
330
331 let limiter = RateLimiter::new(config);
332
333 assert!(limiter.try_acquire().await);
335 assert!(!limiter.try_acquire().await);
336
337 sleep(Duration::from_millis(150)).await;
339
340 assert!(limiter.try_acquire().await);
342 }
343
344 #[tokio::test]
345 async fn test_rate_limiter_manager() {
346 let manager = RateLimiterManager::new().with_graphql_rate_limit(RateLimitConfig {
347 max_requests: 2,
348 time_window: Duration::from_secs(1),
349 burst_size: 1,
350 });
351
352 assert!(manager.acquire_graphql().await.is_ok());
354
355 assert!(manager.try_acquire_rest().await);
357 assert!(manager.try_acquire_rest().await);
358 }
359}