agglayer_config/
settlement_service.rs

1use std::time::Duration;
2
3use agglayer_primitives::U256;
4use serde::{Deserialize, Serialize};
5use serde_with::{serde_as, serde_conv};
6
7use crate::{with::HumanDuration, Multiplier};
8
9/// Policy for considering the transactions as settled on Ethereum.
10///
11/// Defines when a transaction should be considered settled on the Ethereum
12/// network, determining the security guarantees for the settlement operation.
13///
14/// This matches the `latest`, `safe`, and `finalized` block concepts from
15/// Ethereum clients, exposed over the JSON-RPC API.
16#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
17pub enum SettlementPolicy {
18    /// Transaction is considered settled immediately after the specified
19    /// number of confirmation blocks.
20    ///
21    /// **Security**: Vulnerable to chain reorganizations beyond the
22    /// confirmation count.
23    LatestBlock,
24
25    /// Transaction is considered settled when the containing block has been
26    /// considered "safe."
27    ///
28    /// **Security**: Very strong. Reversing a safe block would require
29    /// a significant portion of validators to be slashed.
30    ///
31    /// **Time**: Typically up to ~7 minutes on mainnet. Worst case scenario
32    /// 12-13 minutes.
33    #[default]
34    SafeBlock,
35
36    /// Transaction is considered settled only when the containing block has
37    /// been fully finalized.
38    ///
39    /// **Time**: Typically between 7-13 minutes on mainnet. Worst case scenario
40    /// ~19 minutes.
41    FinalizedBlock,
42}
43
44/// Transaction retry policy.
45#[serde_as]
46#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Default)]
47#[serde(rename_all = "kebab-case")]
48struct ConfigTxRetryPolicy {
49    /// Initial retry interval.
50    #[serde(default, skip_serializing_if = "crate::is_default")]
51    #[serde_as(as = "Option<HumanDuration>")]
52    initial_interval: Option<Duration>,
53
54    /// Interval multiplier for each subsequent retry.
55    #[serde(default, skip_serializing_if = "crate::is_default")]
56    interval_multiplier_factor: Option<Multiplier>,
57
58    /// Maximum interval between retries.
59    #[serde(default, skip_serializing_if = "crate::is_default")]
60    #[serde_as(as = "Option<HumanDuration>")]
61    max_interval: Option<Duration>,
62
63    /// Jitter factor to add randomness to retry intervals.
64    #[serde(default, skip_serializing_if = "crate::is_default")]
65    #[serde_as(as = "Option<HumanDuration>")]
66    jitter: Option<Duration>,
67}
68
69impl From<&TxRetryPolicy> for ConfigTxRetryPolicy {
70    fn from(value: &TxRetryPolicy) -> Self {
71        ConfigTxRetryPolicy {
72            initial_interval: Some(value.initial_interval),
73            interval_multiplier_factor: Some(value.interval_multiplier_factor),
74            max_interval: Some(value.max_interval),
75            jitter: Some(value.jitter),
76        }
77    }
78}
79
80serde_conv!(pub TransientRetryPolicy, TxRetryPolicy, TransientRetryPolicyImpl::new, TransientRetryPolicyImpl::get);
81
82#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
83struct TransientRetryPolicyImpl(ConfigTxRetryPolicy);
84
85impl TransientRetryPolicyImpl {
86    fn new(value: &TxRetryPolicy) -> Self {
87        TransientRetryPolicyImpl(ConfigTxRetryPolicy::from(value))
88    }
89
90    pub fn get(self) -> Result<TxRetryPolicy, std::convert::Infallible> {
91        Ok(TxRetryPolicy {
92            initial_interval: self.0.initial_interval.unwrap_or(Duration::from_secs(10)),
93            interval_multiplier_factor: self
94                .0
95                .interval_multiplier_factor
96                .unwrap_or(Multiplier::from_u64_per_1000(1500)),
97            max_interval: self.0.max_interval.unwrap_or(Duration::from_secs(120)),
98            jitter: self.0.jitter.unwrap_or(Duration::from_secs(1)),
99        })
100    }
101}
102
103serde_conv!(pub NonInclusionRetryPolicy, TxRetryPolicy, NonInclusionRetryPolicyImpl::new, NonInclusionRetryPolicyImpl::get);
104
105#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
106struct NonInclusionRetryPolicyImpl(ConfigTxRetryPolicy);
107
108impl NonInclusionRetryPolicyImpl {
109    fn new(value: &TxRetryPolicy) -> Self {
110        NonInclusionRetryPolicyImpl(ConfigTxRetryPolicy::from(value))
111    }
112
113    pub fn get(self) -> Result<TxRetryPolicy, std::convert::Infallible> {
114        Ok(TxRetryPolicy {
115            initial_interval: self.0.initial_interval.unwrap_or(Duration::from_secs(60)),
116            interval_multiplier_factor: self
117                .0
118                .interval_multiplier_factor
119                .unwrap_or(Multiplier::from_u64_per_1000(2000)),
120            max_interval: self.0.max_interval.unwrap_or(Duration::from_secs(600)),
121            jitter: self.0.jitter.unwrap_or(Duration::from_secs(10)),
122        })
123    }
124}
125
126/// Transaction retry policy for failed or pending settlement transactions.
127///
128/// Defines the strategy used when retrying failed or pending transactions.
129///
130/// Future versions may include additional policies such as:
131/// - **Exponential**: Exponential backoff with increasing intervals
132/// - **Jittered**: Random jitter to avoid thundering herd issues
133#[derive(Debug, Clone, PartialEq, Eq, Default, Deserialize, Serialize)]
134pub struct TxRetryPolicy {
135    /// Initial retry interval.
136    pub initial_interval: Duration,
137
138    /// Interval multiplier for each subsequent retry.
139    pub interval_multiplier_factor: Multiplier,
140
141    /// Maximum interval between retries.
142    pub max_interval: Duration,
143
144    /// Jitter factor to add randomness to retry intervals.
145    pub jitter: Duration,
146}
147
148impl TxRetryPolicy {
149    /// Default retry policy for transient failures.
150    pub fn default_on_transient_failure() -> Self {
151        TransientRetryPolicyImpl(ConfigTxRetryPolicy::default())
152            .get()
153            .unwrap()
154    }
155
156    /// Default retry policy for non-inclusion on L1.
157    pub fn default_non_inclusion_on_l1() -> Self {
158        NonInclusionRetryPolicyImpl(ConfigTxRetryPolicy::default())
159            .get()
160            .unwrap()
161    }
162}
163
164/// The settlement transaction configuration.
165#[serde_as]
166#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
167#[serde(rename_all = "kebab-case")]
168pub struct SettlementTransactionConfig {
169    /// Retry policy for the transaction when there is a transient failure.
170    #[serde(default = "TxRetryPolicy::default_on_transient_failure")]
171    #[serde_as(as = "TransientRetryPolicy")]
172    pub retry_on_transient_failure: TxRetryPolicy,
173
174    /// Retry policy for the transaction when it is not included on L1.
175    #[serde(default = "TxRetryPolicy::default_non_inclusion_on_l1")]
176    #[serde_as(as = "NonInclusionRetryPolicy")]
177    pub retry_on_not_included_on_l1: TxRetryPolicy,
178
179    /// Number of block confirmations required for
180    /// the transaction to resolve a receipt.
181    #[serde(default = "default_confirmations")]
182    pub confirmations: usize,
183
184    /// Finality level required for the transaction to be considered settled.
185    #[serde(default)]
186    pub settlement_policy: SettlementPolicy,
187
188    /// Gas limit multiplier factor for the transaction.
189    /// The gas is calculated as follows:
190    /// `gas = estimated_gas * gas_multiplier_factor`
191    #[serde(default, skip_serializing_if = "crate::is_default")]
192    pub gas_limit_multiplier_factor: Multiplier,
193
194    /// Ceiling for the gas limit for the transaction.
195    #[serde(default = "default_gas_limit_ceiling")]
196    pub gas_limit_ceiling: U256,
197
198    /// Minimum `max_fee_per_gas` (in wei) for the transaction.
199    /// Can be specified with units: "1gwei", "0.1eth", "1000000000wei".
200    /// Used for EIP1559 fee `max_fee_per_gas`.
201    #[serde(default, skip_serializing_if = "crate::is_default")]
202    #[serde_as(as = "crate::with::EthAmount")]
203    pub max_fee_per_gas_floor: u128,
204
205    /// Maximum `max_fee_per_gas` (in wei) for the transaction.
206    /// Use for EIP1559 `max_fee_per_gas`.
207    /// Can be specified with units: "100gwei", "0.01eth", "10000000000wei"
208    #[serde(default = "default_max_fee_per_gas_ceiling")]
209    #[serde_as(as = "crate::with::EthAmount")]
210    pub max_fee_per_gas_ceiling: u128,
211
212    /// Increase rate for the `max_fee_per_gas` for each retry attempt.
213    #[serde(default, skip_serializing_if = "crate::is_default")]
214    pub max_fee_per_gas_multiplier_factor: Multiplier,
215
216    /// Minimum `max_priority_fee_per_gas` (in wei) for the transaction.
217    /// Can be specified with units: "1gwei", "0.1eth", "1000000000wei".
218    /// Used for EIP1559 fee `max_priority_fee_per_gas`.
219    #[serde(default, skip_serializing_if = "crate::is_default")]
220    #[serde_as(as = "crate::with::EthAmount")]
221    pub max_priority_fee_per_gas_floor: u128,
222
223    /// Maximum `max_priority_fee_per_gas` (in wei) for the transaction.
224    /// Use for EIP1559 `max_priority_fee_per_gas`.
225    /// Can be specified with units: "100gwei", "0.01eth", "10000000000wei"
226    #[serde(default = "default_max_priority_fee_per_gas_ceiling")]
227    #[serde_as(as = "crate::with::EthAmount")]
228    pub max_priority_fee_per_gas_ceiling: u128,
229
230    /// Increase rate for the `max_priority_fee_per_gas` for each retry attempt.
231    #[serde(default, skip_serializing_if = "crate::is_default")]
232    pub max_priority_fee_per_gas_multiplier_factor: Multiplier,
233}
234
235impl Default for SettlementTransactionConfig {
236    fn default() -> Self {
237        Self {
238            retry_on_transient_failure: TransientRetryPolicyImpl(ConfigTxRetryPolicy::default())
239                .get()
240                .unwrap(),
241            retry_on_not_included_on_l1:
242                NonInclusionRetryPolicyImpl(ConfigTxRetryPolicy::default())
243                    .get()
244                    .unwrap(),
245            confirmations: default_confirmations(),
246            settlement_policy: SettlementPolicy::default(),
247            gas_limit_multiplier_factor: Multiplier::default(),
248            gas_limit_ceiling: default_gas_limit_ceiling(),
249            max_fee_per_gas_multiplier_factor: Multiplier::default(),
250            max_fee_per_gas_floor: 0,
251            max_fee_per_gas_ceiling: default_max_fee_per_gas_ceiling(),
252            max_priority_fee_per_gas_multiplier_factor: Multiplier::default(),
253            max_priority_fee_per_gas_floor: 0,
254            max_priority_fee_per_gas_ceiling: default_max_priority_fee_per_gas_ceiling(),
255        }
256    }
257}
258
259/// The settlement service configuration.
260///
261/// Contains service-wide configuration options for the Agglayer settlement
262/// service. This configuration is separate from transaction-specific settings
263/// and focuses on overall service behavior and integration points.
264///
265/// This structure is designed to hold settlement service-specific values that
266/// are not related to individual transactions.
267#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
268#[serde(rename_all = "kebab-case")]
269pub struct SettlementServiceConfig {
270    // TODO: introduce regexes / error numbers to identify "nonce already used" errors
271    // The exact format of this config will depend on the implementation and tests of RPC error
272    // handling in the settlement service.
273}
274
275/// The Agglayer settlement configuration.
276///
277/// This configuration controls how the Agglayer settlement service interacts
278/// with the L1 blockchain for settling certificates and validium transactions.
279/// It provides separate transaction configurations for certificate settlements
280/// and validium settlements, allowing fine-grained control over gas prices,
281/// retries, and confirmation requirements.
282///
283/// # Configuration Structure
284///
285/// The settlement configuration is organized into three main components:
286///
287/// - **Certificate Transaction Config**: Controls settlement transactions for
288///   certificates with pessimistic proofs (proofs of state transitions).
289/// - **Validium Transaction Config**: Controls settlement transactions for
290///   validium data (off-chain data availability).
291/// - **Settlement Service Config**: General settlement service configuration.
292///
293/// # Gas Price Configuration
294///
295/// Gas prices can be specified with units for readability:
296/// - `"1gwei"` = 1,000,000,000 wei
297/// - `"0.1eth"` = 100,000,000,000,000,000 wei
298/// - `"1000000000wei"` = 1,000,000,000 wei
299///
300/// The final gas price is calculated as:
301/// ```text
302/// gas_price = max(
303///     floor,
304///     min(ceiling, estimate_gas_price * gas_price_multiplier_factor)
305/// )
306/// ```
307///
308/// Each retry can increase the price by the multiplier factor.
309///
310/// # Security Considerations
311///
312/// - **Settlement policy**: Choose the appropriate finality level based on
313///   security requirements.
314/// - **Gas Price Ceiling**: Acts as a safety mechanism to prevent excessive
315///   transaction costs during network congestion.
316/// - **Confirmations**: Higher confirmation counts increase security for
317///   receipt retrieval but do not replace proper finality guarantees.
318/// - **Max Retries**: Should be set high enough to handle temporary network
319///   issues.
320#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
321#[serde(rename_all = "kebab-case")]
322pub struct SettlementConfig {
323    /// Configuration for certificate settlement transactions.
324    ///
325    /// This controls how pessimistic proofs (proofs of state transitions for
326    /// certificates) are submitted to the L1 settlement layer.
327    #[serde(default)]
328    pub pessimistic_proof_tx_config: SettlementTransactionConfig,
329
330    /// Configuration for validium settlement transactions.
331    ///
332    /// This controls how validium data (off-chain data availability proofs)
333    /// are submitted to the L1 settlement layer. Validium transactions may
334    /// have different gas and retry requirements than certificate transactions.
335    #[serde(default)]
336    pub validium_tx_config: SettlementTransactionConfig,
337
338    /// General settlement service configuration.
339    ///
340    /// Contains service-wide settings that apply to the overall settlement
341    /// service operation (beyond individual transaction parameters).
342    #[serde(default)]
343    pub settlement_service_config: SettlementServiceConfig,
344}
345
346/// Default number of confirmations required
347/// for the transaction to resolve a receipt.
348const fn default_confirmations() -> usize {
349    12
350}
351
352fn default_gas_limit_ceiling() -> U256 {
353    U256::from(60_000_000_u64)
354}
355
356/// Default gas price ceiling for the transaction.
357const fn default_max_fee_per_gas_ceiling() -> u128 {
358    // 100 gwei
359    100_000_000_000_u128
360}
361
362/// Default priority fee ceiling for the transaction.
363const fn default_max_priority_fee_per_gas_ceiling() -> u128 {
364    // 100 gwei
365    100_000_000_000_u128
366}