agglayer_config/
lib.rs

1//! Agglayer configuration.
2//!
3//! The agglayer is configured via its TOML configuration file, `agglayer.toml`
4//! by default, which is deserialized into the [`Config`] struct.
5
6use std::{collections::HashMap, path::Path};
7
8use agglayer_primitives::Address;
9use outbound::OutboundConfig;
10use serde::{de::DeserializeSeed, Deserialize, Serialize};
11use serde_with::DisplayFromStr;
12use shutdown::ShutdownConfig;
13use url::Url;
14
15pub use self::telemetry::TelemetryConfig;
16
17pub(crate) const DEFAULT_IP: std::net::Ipv4Addr = std::net::Ipv4Addr::new(0, 0, 0, 0);
18
19pub(crate) mod auth;
20pub mod certificate_orchestrator;
21pub mod epoch;
22pub mod grpc;
23pub(crate) mod l1;
24pub(crate) mod l2;
25pub mod log;
26mod multiplier;
27pub mod outbound;
28mod port;
29pub mod rate_limiting;
30pub(crate) mod rpc;
31pub mod settlement_service;
32pub mod shutdown;
33pub mod storage;
34pub(crate) mod telemetry;
35mod with;
36
37pub use auth::{AuthConfig, GcpKmsConfig, LocalConfig, PrivateKey};
38pub use epoch::Epoch;
39pub use l1::L1;
40pub use l2::L2;
41pub use log::Log;
42pub use multiplier::Multiplier;
43use port::{Port, PortDefaults};
44pub use rate_limiting::RateLimitingConfig;
45pub use rpc::RpcConfig;
46
47/// The Agglayer configuration.
48#[serde_with::serde_as]
49#[derive(Default, Deserialize, Serialize, Debug, PartialEq, Eq)]
50#[serde(rename_all = "kebab-case")]
51#[serde(deny_unknown_fields)]
52#[serde(default)]
53pub struct Config {
54    /// A map of Zkevm node RPC endpoints for each rollup.
55    ///
56    /// The key is the rollup ID, and the value is the URL of the associated RPC
57    /// endpoint.
58    #[serde_as(as = "HashMap<DisplayFromStr, _>")]
59    pub full_node_rpcs: HashMap<u32, Url>,
60
61    #[serde(default)]
62    pub l2: L2,
63
64    #[serde_as(as = "HashMap<DisplayFromStr, _>")]
65    #[serde(default)]
66    pub proof_signers: HashMap<u32, Address>,
67
68    /// The log configuration.
69    #[serde(default)]
70    pub log: Log,
71
72    /// The local RPC server configuration.
73    #[serde(default)]
74    pub rpc: RpcConfig,
75
76    /// Rate limiting configuration.
77    #[serde(default)]
78    pub rate_limiting: RateLimitingConfig,
79
80    /// The configuration for every outbound network component.
81    #[serde(default)]
82    pub outbound: OutboundConfig,
83
84    /// The L1 configuration.
85    #[serde(default)]
86    pub l1: L1,
87
88    /// The authentication configuration.
89    #[serde(default)]
90    pub auth: AuthConfig,
91
92    /// Telemetry configuration.
93    #[serde(default)]
94    pub telemetry: TelemetryConfig,
95
96    /// The Epoch configuration.
97    #[serde(default)]
98    pub epoch: Epoch,
99
100    /// The list of configuration options used during shutdown.
101    #[serde(default)]
102    pub shutdown: ShutdownConfig,
103
104    /// The certificate orchestrator configuration.
105    #[serde(default)]
106    pub certificate_orchestrator: certificate_orchestrator::CertificateOrchestrator,
107
108    /// The storage configuration.
109    #[serde(default)]
110    pub storage: storage::StorageConfig,
111
112    /// The prover config
113    #[serde(default)]
114    pub prover: prover_config::ProverType,
115
116    #[serde(default = "default_prover_buffer_size")]
117    pub prover_buffer_size: usize,
118
119    #[serde(default)]
120    #[serde(skip_serializing_if = "is_false")]
121    pub debug_mode: bool,
122
123    #[serde(default)]
124    #[serde(skip_serializing_if = "is_false")]
125    pub mock_verifier: bool,
126
127    #[serde(default)]
128    pub grpc: grpc::GrpcConfig,
129}
130
131impl Config {
132    pub fn try_load(path: &Path) -> Result<Self, ConfigurationError> {
133        let reader = std::fs::read_to_string(path).map_err(|source| {
134            ConfigurationError::UnableToReadConfigFile {
135                path: path.to_path_buf(),
136                source,
137            }
138        })?;
139
140        let path = path
141            .parent()
142            .ok_or_else(|| ConfigurationError::UnableToReadConfigFile {
143                path: path.to_path_buf(),
144                source: std::io::Error::other(
145                    "Unable to determine the parent folder of the configuration file",
146                ),
147            })?;
148
149        let config_with_path = ConfigDeserializer { path };
150
151        let deserializer = toml::de::Deserializer::parse(&reader)
152            .map_err(ConfigurationError::DeserializationError)?;
153
154        config_with_path
155            .deserialize(deserializer)
156            .map_err(ConfigurationError::DeserializationError)
157    }
158}
159
160impl Config {
161    pub fn new(base_path: &Path) -> Self {
162        Self {
163            storage: storage::StorageConfig::new_from_path(base_path),
164            full_node_rpcs: Default::default(),
165            proof_signers: Default::default(),
166            log: Default::default(),
167            rpc: Default::default(),
168            rate_limiting: Default::default(),
169            outbound: Default::default(),
170            l1: Default::default(),
171            l2: Default::default(),
172            auth: Default::default(),
173            telemetry: Default::default(),
174            epoch: Default::default(),
175            shutdown: Default::default(),
176            certificate_orchestrator: Default::default(),
177            prover: prover_config::ProverType::NetworkProver(
178                prover_config::NetworkProverConfig::default(),
179            ),
180            prover_buffer_size: default_prover_buffer_size(),
181            debug_mode: false,
182            mock_verifier: false,
183            grpc: Default::default(),
184        }
185    }
186
187    /// Get the target ReadRPC socket address from the configuration.
188    pub fn readrpc_addr(&self) -> std::net::SocketAddr {
189        std::net::SocketAddr::from((self.rpc.host, self.rpc.readrpc_port.as_u16()))
190    }
191
192    /// Get the target gRPC socket address from the configuration.
193    pub fn public_grpc_addr(&self) -> std::net::SocketAddr {
194        std::net::SocketAddr::from((self.rpc.host, self.rpc.grpc_port.as_u16()))
195    }
196
197    /// Get the admin RPC socket address from the configuration.
198    pub fn admin_rpc_addr(&self) -> std::net::SocketAddr {
199        std::net::SocketAddr::from((self.rpc.host, self.rpc.admin_port.as_u16()))
200    }
201
202    pub fn path_contextualized(mut self, base_path: &Path) -> Self {
203        self.storage = self.storage.path_contextualized(base_path);
204
205        self
206    }
207
208    pub(crate) fn validate(self) -> Result<Self, ConfigurationError> {
209        Ok(self)
210    }
211}
212
213#[derive(Debug, thiserror::Error)]
214pub enum ConfigurationError {
215    #[error("Unable to read the configuration file: {source}")]
216    UnableToReadConfigFile {
217        path: std::path::PathBuf,
218        #[source]
219        source: std::io::Error,
220    },
221
222    #[error("Failed to deserialize the configuration: {0}")]
223    DeserializationError(#[from] toml::de::Error),
224}
225
226#[cfg(feature = "testutils")]
227impl Config {
228    pub fn new_for_test() -> Self {
229        Config::new(Path::new("/tmp/agglayer"))
230    }
231}
232
233struct ConfigDeserializer<'a> {
234    path: &'a Path,
235}
236
237impl<'de> DeserializeSeed<'de> for ConfigDeserializer<'_> {
238    type Value = Config;
239
240    fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
241    where
242        D: serde::Deserializer<'de>,
243    {
244        let mut config_candidate: Config = serde::Deserialize::deserialize(deserializer)?;
245
246        config_candidate.storage =
247            config_candidate
248                .storage
249                .path_contextualized(&self.path.canonicalize().map_err(|error| {
250                    serde::de::Error::custom(format!(
251                        "Unable to canonicalize the storage path: {error}"
252                    ))
253                })?);
254
255        config_candidate
256            .validate()
257            .map_err(|e| serde::de::Error::custom(e.to_string()))
258    }
259}
260
261/// Default prover buffer size.
262const fn default_prover_buffer_size() -> usize {
263    100
264}
265
266fn is_false(b: &bool) -> bool {
267    !*b
268}
269
270pub(crate) fn is_default<T: Default + PartialEq>(t: &T) -> bool {
271    *t == Default::default()
272}
273
274#[cfg(feature = "testutils")]
275pub fn redact_storage_path() -> insta::internals::Redaction {
276    use insta::internals::Content;
277    insta::dynamic_redaction(|value, path| {
278        if path.to_string() != "storage.db-path" {
279            if let Content::String(path) = value {
280                let cur_dir = Path::new("./").canonicalize().unwrap();
281                return Content::String(path.replace(cur_dir.to_str().unwrap(), "/tmp/agglayer"));
282            }
283        }
284
285        value
286    })
287}