agglayer_config/
outbound.rs

1use std::time::Duration;
2
3use serde::{Deserialize, Deserializer, Serialize};
4use serde_with::serde_as;
5use tracing::warn;
6
7use crate::Multiplier;
8
9/// Outbound configuration.
10/// After introduction of the `agglayer-settlement-service`, `OutboundConfig`
11/// will be deprecated and removed.
12#[derive(Serialize, Default, Debug, Deserialize, PartialEq, Eq)]
13#[serde(rename = "outbound", rename_all = "kebab-case")]
14pub struct OutboundConfig {
15    pub rpc: OutboundRpcConfig,
16}
17
18/// Outbound RPC configuration that is used to configure the outbound RPC
19/// clients and their RPC calls.
20#[derive(Serialize, Default, Debug, PartialEq, Eq)]
21#[serde(rename = "rpc", rename_all = "kebab-case")]
22pub struct OutboundRpcConfig {
23    /// Outbound configuration of the RPC settle function call.
24    pub settle_tx: OutboundRpcSettleConfig,
25    pub settle_cert: OutboundRpcSettleConfig,
26}
27
28impl<'de> Deserialize<'de> for OutboundRpcConfig {
29    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
30    where
31        D: Deserializer<'de>,
32    {
33        #[derive(Deserialize)]
34        #[serde(rename = "rpc", rename_all = "kebab-case")]
35        pub struct Intermediate {
36            pub settle_tx: Option<OutboundRpcSettleConfig>,
37            pub settle_cert: Option<OutboundRpcSettleConfig>,
38            pub settle: Option<OutboundRpcSettleConfig>,
39        }
40
41        let deserialized = Intermediate::deserialize(deserializer)?;
42
43        let (settle_tx, settle_cert) = match (
44            deserialized.settle_tx,
45            deserialized.settle_cert,
46            deserialized.settle,
47        ) {
48            (Some(tx), Some(cert), _) => (tx, cert),
49            (None, None, Some(settle)) => {
50                warn!("'settle' is deprecated. Please use 'settle-tx' and 'settle-cert' instead.");
51                (settle.clone(), settle)
52            }
53            _ => {
54                return Err(serde::de::Error::custom(
55                    "Either both ['settle-tx','settle-cert'] or 'settle' must be specified",
56                ));
57            }
58        };
59
60        Ok(OutboundRpcConfig {
61            settle_tx,
62            settle_cert,
63        })
64    }
65}
66
67/// Outbound RPC settle configuration that is used to configure the outbound
68/// RPC settle function call.
69#[serde_as]
70#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
71#[serde(rename = "settle", rename_all = "kebab-case")]
72pub struct OutboundRpcSettleConfig {
73    /// Maximum number of retries for the transaction.
74    #[serde(default = "default_rpc_retries")]
75    pub max_retries: usize,
76
77    /// Interval for the polling of the transaction.
78    #[serde(default = "default_rpc_retry_interval")]
79    #[serde(with = "crate::with::HumanDuration")]
80    pub retry_interval: Duration,
81
82    /// Number of confirmations required for the transaction to resolve a
83    /// receipt.
84    #[serde(default = "default_rpc_confirmations")]
85    pub confirmations: usize,
86
87    /// Gas multiplier factor for the transaction.
88    /// The gas is calculated as follows:
89    /// `gas = estimate_gas * (gas_multiplier / 100)
90    #[serde(
91        default = "default_gas_multiplier_factor",
92        skip_serializing_if = "same_as_default_gas_multiplier_factor"
93    )]
94    pub gas_multiplier_factor: u32,
95
96    /// Gas price configuration.
97    #[serde(default)]
98    pub gas_price: GasPriceConfig,
99}
100
101impl Default for OutboundRpcSettleConfig {
102    fn default() -> Self {
103        OutboundRpcSettleConfig {
104            max_retries: default_rpc_retries(),
105            retry_interval: default_rpc_retry_interval(),
106            confirmations: default_rpc_confirmations(),
107            gas_multiplier_factor: default_gas_multiplier_factor(),
108            gas_price: GasPriceConfig::default(),
109        }
110    }
111}
112
113/// Gas price configuration for settlement transactions.
114#[serde_as]
115#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
116#[serde(rename_all = "kebab-case")]
117pub struct GasPriceConfig {
118    /// Gas price multiplier for the transaction.
119    /// The gas price is calculated as follows:
120    /// `gas_price = estimate_gas_price * multiplier`
121    #[serde(default, skip_serializing_if = "crate::is_default")]
122    pub multiplier: Multiplier,
123
124    /// Minimum gas price floor (in wei) for the transaction.
125    /// Can be specified with units: "1gwei", "0.1eth", "1000000000wei"
126    #[serde(default, skip_serializing_if = "crate::is_default")]
127    #[serde_as(as = "crate::with::EthAmount")]
128    pub floor: u128,
129
130    /// Maximum gas price ceiling (in wei) for the transaction.
131    /// Can be specified with units: "100gwei", "0.01eth", "10000000000wei"
132    #[serde(default = "default_gas_price_ceiling")]
133    #[serde_as(as = "crate::with::EthAmount")]
134    pub ceiling: u128,
135}
136
137impl Default for GasPriceConfig {
138    fn default() -> Self {
139        GasPriceConfig {
140            multiplier: Multiplier::default(),
141            floor: 0,
142            ceiling: default_gas_price_ceiling(),
143        }
144    }
145}
146
147/// Default gas price multiplier for the transaction.
148const fn default_gas_multiplier_factor() -> u32 {
149    100
150}
151
152const fn same_as_default_gas_multiplier_factor(v: &u32) -> bool {
153    *v == default_gas_multiplier_factor()
154}
155
156/// Default number of retries for the transaction.
157const fn default_rpc_retries() -> usize {
158    30
159}
160
161/// Default interval for the polling of the transaction.
162const fn default_rpc_retry_interval() -> Duration {
163    Duration::from_secs(10)
164}
165
166/// Default number of confirmations required for the transaction to resolve a
167/// receipt.
168const fn default_rpc_confirmations() -> usize {
169    12
170}
171
172/// Default gas price ceiling for the transaction.
173const fn default_gas_price_ceiling() -> u128 {
174    // 100 gwei
175    100_000_000_000_u128
176}
177
178#[cfg(test)]
179mod tests {
180    mod outbound {
181        use serde::Deserialize;
182
183        use crate::outbound::OutboundConfig;
184
185        #[test]
186        #[allow(deprecated)]
187        fn expected_namespace() {
188            #[derive(Debug, Deserialize)]
189            struct DummyContainer {
190                outbound: OutboundConfig,
191            }
192
193            let toml = r#"
194                [outbound.rpc.settle-tx]
195                max-retries = 10
196                [outbound.rpc.settle-cert]
197                max-retries = 11
198                [outbound.rpc.settle]
199                max-retries = 12
200                "#;
201
202            let config = toml::from_str::<DummyContainer>(toml).unwrap();
203
204            assert_eq!(config.outbound.rpc.settle_tx.max_retries, 10);
205            assert_eq!(config.outbound.rpc.settle_cert.max_retries, 11);
206        }
207
208        mod rpc {
209            mod settle {
210                use std::time::Duration;
211
212                use crate::outbound::OutboundRpcSettleConfig;
213
214                #[test]
215                fn test_default() {
216                    let toml = r#"
217                        "#;
218
219                    let config = toml::from_str::<OutboundRpcSettleConfig>(toml).unwrap();
220
221                    assert_eq!(config.max_retries, 30);
222                    assert_eq!(config.retry_interval, Duration::from_secs(10));
223                    assert_eq!(config.confirmations, 12);
224                }
225
226                #[test]
227                fn test_custom() {
228                    let toml = r#"
229                        max-retries = 10
230                        retry-interval = 1
231                        confirmations = 5
232                        "#;
233
234                    let config = toml::from_str::<OutboundRpcSettleConfig>(toml).unwrap();
235
236                    assert_eq!(config.max_retries, 10);
237                    assert_eq!(config.retry_interval, Duration::from_secs(1));
238                    assert_eq!(config.confirmations, 5);
239                }
240            }
241        }
242    }
243}