agglayer_config/
rate_limiting.rs

1use std::{collections::BTreeMap, time::Duration};
2
3use serde::{Deserialize, Serialize};
4use serde_with::DisplayFromStr;
5
6pub type NetworkId = u32;
7
8/// Time-based rate limiting
9#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
10#[serde(rename_all = "kebab-case")]
11pub enum TimeRateLimit {
12    /// Don't apply any rate limiting, allowing the client to make requests as
13    /// often as desired.
14    Unlimited,
15
16    /// Apply rate limit of `max_per_interval` events in given `time_interval`.
17    #[serde(untagged, rename_all = "kebab-case")]
18    Limited {
19        max_per_interval: u32,
20
21        #[serde(with = "crate::with::HumanDuration")]
22        time_interval: Duration,
23    },
24}
25
26impl TimeRateLimit {
27    /// Default rate limiting for the `sendTx` call.
28    pub const fn send_tx_default() -> Self {
29        Self::Unlimited
30    }
31
32    /// Create a time-based rate limiting
33    pub const fn limited(max_per_interval: u32, time_interval: Duration) -> Self {
34        Self::Limited {
35            max_per_interval,
36            time_interval,
37        }
38    }
39}
40
41/// Rate limiting override for each endpoint
42#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
43#[serde(rename_all = "kebab-case")]
44struct RateLimitOverride {
45    send_tx: Option<TimeRateLimit>,
46}
47
48/// Rate limiting configuration for a single network.
49#[derive(Debug, Clone, PartialEq, Eq)]
50pub struct NetworkRateLimitingConfig<'a> {
51    /// Rate limit for `sendTx` for given network.
52    pub send_tx: &'a TimeRateLimit,
53}
54
55/// Full rate limiting config.
56/// Contains the defaults and the per-network overrides.
57#[serde_with::serde_as]
58#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
59#[serde(rename_all = "kebab-case")]
60pub struct RateLimitingConfig {
61    /// Settlement rate limiting for the `sendTx` call.
62    #[serde(default = "TimeRateLimit::send_tx_default")]
63    send_tx: TimeRateLimit,
64
65    /// Per-network rate limiting overrides.
66    #[serde(default)]
67    #[serde_as(as = "BTreeMap<DisplayFromStr, _>")]
68    network: BTreeMap<NetworkId, RateLimitOverride>,
69}
70
71impl RateLimitingConfig {
72    /// Default rate limiting configuration.
73    pub const DEFAULT: Self = Self::new(TimeRateLimit::send_tx_default());
74
75    /// New rate limiting config with no network-specific settings.
76    pub const fn new(send_tx: TimeRateLimit) -> Self {
77        let network = BTreeMap::new();
78        Self { send_tx, network }
79    }
80
81    /// Override `sendTx`setting for given network.
82    pub fn with_send_tx_override(mut self, nid: NetworkId, limit: TimeRateLimit) -> Self {
83        self.network.entry(nid).or_default().send_tx = Some(limit);
84        self
85    }
86
87    /// Get rate limiting configuration for given network.
88    pub fn config_for(&self, network_id: NetworkId) -> NetworkRateLimitingConfig<'_> {
89        let overrides = self.override_for(network_id);
90        let send_tx = overrides
91            .and_then(|l| l.send_tx.as_ref())
92            .unwrap_or(&self.send_tx);
93        NetworkRateLimitingConfig { send_tx }
94    }
95
96    fn override_for(&self, nid: NetworkId) -> Option<&RateLimitOverride> {
97        self.network.get(&nid)
98    }
99}
100
101impl Default for RateLimitingConfig {
102    fn default() -> Self {
103        Self::DEFAULT
104    }
105}
106
107#[cfg(test)]
108mod test {
109    use super::*;
110
111    #[test]
112    fn default_config() {
113        let config_str = "send-tx = \"unlimited\"";
114        let parsed_default_config: RateLimitingConfig = toml::from_str(config_str).unwrap();
115        assert_eq!(parsed_default_config, RateLimitingConfig::DEFAULT);
116
117        let empty_config: RateLimitingConfig = toml::from_str("").unwrap();
118        assert_eq!(empty_config, RateLimitingConfig::DEFAULT);
119    }
120
121    #[test]
122    fn unlimited() {
123        let config_str = "send-tx = \"unlimited\"";
124        let config: RateLimitingConfig = toml::from_str(config_str).unwrap();
125        let expected = RateLimitingConfig::new(TimeRateLimit::Unlimited);
126        assert_eq!(config, expected);
127    }
128
129    #[rstest::rstest]
130    #[case(4, "1h 20min", 80 * 60)]
131    #[case(2, "30min", 1800)]
132    #[case(50, "90s", 90)]
133    #[case(0, "2min", 120)]
134    fn basic(#[case] limit: u32, #[case] interval_str: String, #[case] interval_secs: u64) {
135        #[rustfmt::skip]
136        let config_str = format!(
137            "[send-tx]\n\
138            max-per-interval = {limit}\n\
139            time-interval = {interval_str:?}\n"
140        );
141        let config: RateLimitingConfig = toml::from_str(&config_str).unwrap();
142        let expected = RateLimitingConfig::new(TimeRateLimit::Limited {
143            max_per_interval: limit,
144            time_interval: Duration::from_secs(interval_secs),
145        });
146        assert_eq!(config, expected);
147    }
148
149    #[test]
150    fn top_level_and_override() {
151        #[rustfmt::skip]
152        let config_str = "[send-tx]\n\
153            max-per-interval = 3\n\
154            time-interval = \"30min\"\n\
155            [network.1.send-tx]\n\
156            max-per-interval = 4\n\
157            time-interval = \"40min\"\n";
158        let config: RateLimitingConfig = toml::from_str(config_str).unwrap();
159
160        let default_send_tx_limit = TimeRateLimit::limited(3, Duration::from_secs(1800));
161        let network_1_send_tx_limit = TimeRateLimit::limited(4, Duration::from_secs(2400));
162        let network_1_override = RateLimitOverride {
163            send_tx: Some(network_1_send_tx_limit.clone()),
164        };
165
166        let expected = RateLimitingConfig {
167            send_tx: default_send_tx_limit.clone(),
168            network: BTreeMap::from_iter([(1, network_1_override)]),
169        };
170
171        assert_eq!(config, expected);
172        assert_eq!(config.config_for(1).send_tx, &network_1_send_tx_limit);
173        assert_eq!(config.config_for(2).send_tx, &default_send_tx_limit);
174        assert_eq!(config.config_for(1337).send_tx, &default_send_tx_limit);
175    }
176}