Skip to main content

ostium_rust_sdk/
rate_limit.rs

1//! Rate limiting functionality for the Ostium SDK
2//!
3//! This module provides rate limiting capabilities to ensure we don't exceed
4//! API rate limits. It uses a token bucket algorithm for flexible rate limiting.
5
6use std::sync::Arc;
7use std::time::{Duration, Instant};
8use tokio::sync::Mutex;
9use tokio::time::sleep;
10use tracing::{debug, warn};
11
12/// Configuration for rate limiting
13#[derive(Debug, Clone)]
14pub struct RateLimitConfig {
15    /// Maximum number of requests per time window
16    pub max_requests: u32,
17    /// Time window for the rate limit
18    pub time_window: Duration,
19    /// Maximum burst size (number of requests that can be made instantly)
20    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), // 100 requests per minute
28            burst_size: 10,
29        }
30    }
31}
32
33impl RateLimitConfig {
34    /// Create a config for GraphQL API rate limiting
35    pub fn graphql() -> Self {
36        Self {
37            max_requests: 60,
38            time_window: Duration::from_secs(60), // 60 requests per minute
39            burst_size: 5,
40        }
41    }
42
43    /// Create a config for REST API rate limiting  
44    pub fn rest_api() -> Self {
45        Self {
46            max_requests: 120,
47            time_window: Duration::from_secs(60), // 120 requests per minute
48            burst_size: 10,
49        }
50    }
51
52    /// Create a config for blockchain RPC rate limiting
53    pub fn blockchain() -> Self {
54        Self {
55            max_requests: 200,
56            time_window: Duration::from_secs(60), // 200 requests per minute
57            burst_size: 20,
58        }
59    }
60
61    /// Create a conservative rate limit config
62    pub fn conservative() -> Self {
63        Self {
64            max_requests: 30,
65            time_window: Duration::from_secs(60), // 30 requests per minute
66            burst_size: 3,
67        }
68    }
69}
70
71/// Token bucket rate limiter
72#[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    /// Create a new rate limiter with the given configuration
86    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    /// Wait for permission to make a request
98    /// Returns immediately if a token is available, otherwise waits
99    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            // Calculate how long to wait for the next token
116            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    /// Try to acquire a token without waiting
132    /// Returns true if successful, false if rate limited
133    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    /// Get the current number of available tokens
151    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    /// Calculate how long until the next token is available
158    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 we have no tokens, wait for one token duration
165        if state.tokens <= 0.0 {
166            time_per_token
167        } else {
168            // Otherwise wait a shorter time
169            Duration::from_millis(100)
170        }
171    }
172
173    /// Refill tokens based on elapsed time
174    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/// Rate limiting errors
188#[derive(Debug, thiserror::Error)]
189pub enum RateLimitError {
190    /// Wait time would be excessive
191    #[error("Rate limit wait time would be excessive: {0:?}")]
192    ExcessiveWait(Duration),
193}
194
195/// Rate limiter manager that handles multiple rate limiters
196#[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    /// Create a new rate limiter manager with no rate limiting
211    pub fn new() -> Self {
212        Self {
213            graphql_limiter: None,
214            rest_limiter: None,
215            blockchain_limiter: None,
216        }
217    }
218
219    /// Enable GraphQL rate limiting
220    pub fn with_graphql_rate_limit(mut self, config: RateLimitConfig) -> Self {
221        self.graphql_limiter = Some(RateLimiter::new(config));
222        self
223    }
224
225    /// Enable REST API rate limiting  
226    pub fn with_rest_rate_limit(mut self, config: RateLimitConfig) -> Self {
227        self.rest_limiter = Some(RateLimiter::new(config));
228        self
229    }
230
231    /// Enable blockchain RPC rate limiting
232    pub fn with_blockchain_rate_limit(mut self, config: RateLimitConfig) -> Self {
233        self.blockchain_limiter = Some(RateLimiter::new(config));
234        self
235    }
236
237    /// Enable all rate limiters with default configs
238    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    /// Wait for permission to make a GraphQL request
246    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    /// Wait for permission to make a REST API request
255    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    /// Wait for permission to make a blockchain RPC request
264    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    /// Try to acquire permission for GraphQL without waiting
273    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    /// Try to acquire permission for REST API without waiting  
282    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    /// Try to acquire permission for blockchain RPC without waiting
291    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        // Should be able to make 2 requests immediately (burst)
316        assert!(limiter.try_acquire().await);
317        assert!(limiter.try_acquire().await);
318
319        // Third request should be rate limited
320        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        // Use up the token
334        assert!(limiter.try_acquire().await);
335        assert!(!limiter.try_acquire().await);
336
337        // Wait for refill (should get a token every 100ms with 10 req/sec)
338        sleep(Duration::from_millis(150)).await;
339
340        // Should have a token now
341        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        // Should work for GraphQL
353        assert!(manager.acquire_graphql().await.is_ok());
354
355        // REST should work without limits
356        assert!(manager.try_acquire_rest().await);
357        assert!(manager.try_acquire_rest().await);
358    }
359}