linera_service/
config.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::{fmt, net::SocketAddr};
5
6use linera_rpc::config::{ExporterServiceConfig, TlsConfig};
7use serde::{
8    de::{Error, MapAccess, Visitor},
9    Deserialize, Deserializer, Serialize, Serializer,
10};
11
12/// The configuration file for the linera-exporter.
13#[derive(Serialize, Deserialize, Debug, Clone)]
14pub struct BlockExporterConfig {
15    /// Identity for the block exporter state.
16    pub id: u32,
17
18    /// The server configuration for the linera-exporter.
19    pub service_config: ExporterServiceConfig,
20
21    /// The configuration file for the export destinations.
22    #[serde(default)]
23    pub destination_config: DestinationConfig,
24
25    /// The configuration file to impose various limits
26    /// on the resources used by the linera-exporter.
27    #[serde(default)]
28    pub limits: LimitsConfig,
29
30    /// The address to expose the `/metrics` endpoint on.
31    pub metrics_port: u16,
32}
33
34impl BlockExporterConfig {
35    /// Returns the address to expose the `/metrics` endpoint on.
36    pub fn metrics_address(&self) -> SocketAddr {
37        SocketAddr::from(([0, 0, 0, 0], self.metrics_port))
38    }
39}
40
41/// Configuration file for the exports.
42#[derive(Serialize, Deserialize, Debug, Clone, Default)]
43pub struct DestinationConfig {
44    /// The destination URIs to export to.
45    pub destinations: Vec<Destination>,
46    /// Export blocks to the current committee.
47    #[serde(default)]
48    pub committee_destination: bool,
49}
50
51// Each destination has an ID and a configuration.
52#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
53pub struct DestinationId {
54    address: String,
55    kind: DestinationKind,
56}
57
58impl DestinationId {
59    /// Creates a new destination ID from the address and kind.
60    pub fn new(address: String, kind: DestinationKind) -> Self {
61        Self { address, kind }
62    }
63
64    pub fn validator(address: String) -> Self {
65        Self {
66            address,
67            kind: DestinationKind::Validator,
68        }
69    }
70
71    /// Returns the address of the destination.
72    pub fn address(&self) -> &str {
73        &self.address
74    }
75
76    /// Returns the kind of the destination.
77    pub fn kind(&self) -> DestinationKind {
78        self.kind
79    }
80}
81
82/// The uri to provide export services to.
83#[derive(Debug, Clone, PartialEq, Eq)]
84pub enum Destination {
85    Indexer {
86        /// The gRPC network protocol.
87        tls: TlsConfig,
88        /// The host name of the target destination (IP or hostname).
89        endpoint: String,
90        /// The port number of the target destination.
91        port: u16,
92    },
93    Validator {
94        /// The host name of the target destination (IP or hostname).
95        endpoint: String,
96        /// The port number of the target destination.
97        port: u16,
98    },
99    Logging {
100        /// The host name of the target destination (IP or hostname).
101        file_name: String,
102    },
103}
104
105/// The description for the gRPC based destination.
106/// Discriminates the export mode and the client to use.
107#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Copy, Hash)]
108pub enum DestinationKind {
109    /// The indexer description.
110    Indexer,
111    /// The validator description.
112    Validator,
113    /// The logging target.
114    Logging,
115}
116
117impl Serialize for Destination {
118    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
119    where
120        S: Serializer,
121    {
122        use serde::ser::SerializeMap;
123
124        match self {
125            Destination::Indexer {
126                tls,
127                endpoint,
128                port,
129            } => {
130                let mut map = serializer.serialize_map(Some(4))?;
131                map.serialize_entry("kind", "Indexer")?;
132                map.serialize_entry("tls", tls)?;
133                map.serialize_entry("endpoint", endpoint)?;
134                map.serialize_entry("port", port)?;
135                map.end()
136            }
137            Destination::Validator { endpoint, port } => {
138                let mut map = serializer.serialize_map(Some(3))?;
139                map.serialize_entry("kind", "Validator")?;
140                map.serialize_entry("endpoint", endpoint)?;
141                map.serialize_entry("port", port)?;
142                map.end()
143            }
144            Destination::Logging { file_name } => {
145                let mut map = serializer.serialize_map(Some(2))?;
146                map.serialize_entry("kind", "Logging")?;
147                map.serialize_entry("file_name", file_name)?;
148                map.end()
149            }
150        }
151    }
152}
153
154struct DestinationVisitor;
155
156impl<'de> Visitor<'de> for DestinationVisitor {
157    type Value = Destination;
158
159    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
160        formatter.write_str("a map with a 'kind' field")
161    }
162
163    fn visit_map<V>(self, mut map: V) -> Result<Destination, V::Error>
164    where
165        V: MapAccess<'de>,
166    {
167        let mut kind: Option<String> = None;
168        let mut tls: Option<TlsConfig> = None;
169        let mut endpoint: Option<String> = None;
170        let mut port: Option<u16> = None;
171        let mut file_name: Option<String> = None;
172
173        while let Some(key) = map.next_key::<String>()? {
174            match key.as_str() {
175                "kind" => {
176                    if kind.is_some() {
177                        return Err(V::Error::duplicate_field("kind"));
178                    }
179                    kind = Some(map.next_value()?);
180                }
181                "tls" => {
182                    if tls.is_some() {
183                        return Err(V::Error::duplicate_field("tls"));
184                    }
185                    tls = Some(map.next_value()?);
186                }
187                "endpoint" => {
188                    if endpoint.is_some() {
189                        return Err(V::Error::duplicate_field("endpoint"));
190                    }
191                    endpoint = Some(map.next_value()?);
192                }
193                "port" => {
194                    if port.is_some() {
195                        return Err(V::Error::duplicate_field("port"));
196                    }
197                    port = Some(map.next_value()?);
198                }
199                "file_name" => {
200                    if file_name.is_some() {
201                        return Err(V::Error::duplicate_field("file_name"));
202                    }
203                    file_name = Some(map.next_value()?);
204                }
205                _ => {
206                    // Ignore unknown fields
207                    let _: serde::de::IgnoredAny = map.next_value()?;
208                }
209            }
210        }
211
212        let kind = kind.ok_or_else(|| V::Error::missing_field("kind"))?;
213
214        match kind.as_str() {
215            "Indexer" => {
216                let tls = tls.ok_or_else(|| V::Error::missing_field("tls"))?;
217                let endpoint = endpoint.ok_or_else(|| V::Error::missing_field("endpoint"))?;
218                let port = port.ok_or_else(|| V::Error::missing_field("port"))?;
219                Ok(Destination::Indexer {
220                    tls,
221                    endpoint,
222                    port,
223                })
224            }
225            "Validator" => {
226                let endpoint = endpoint.ok_or_else(|| V::Error::missing_field("endpoint"))?;
227                let port = port.ok_or_else(|| V::Error::missing_field("port"))?;
228                Ok(Destination::Validator { endpoint, port })
229            }
230            "Logging" => {
231                let file_name = file_name.ok_or_else(|| V::Error::missing_field("file_name"))?;
232                Ok(Destination::Logging { file_name })
233            }
234            _ => Err(V::Error::unknown_variant(
235                &kind,
236                &["Indexer", "Validator", "Logging"],
237            )),
238        }
239    }
240}
241impl<'de> Deserialize<'de> for Destination {
242    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
243    where
244        D: Deserializer<'de>,
245    {
246        deserializer.deserialize_map(DestinationVisitor)
247    }
248}
249/// The configuration file to impose various limits
250/// on the resources used by the linera-exporter.
251#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
252pub struct LimitsConfig {
253    /// Time period in milliseconds between periodic persistence
254    /// to the shared storage.
255    pub persistence_period_ms: u32,
256    /// Maximum size of the work queue i.e. maximum number
257    /// of blocks queued up for exports per destination.
258    pub work_queue_size: u16,
259    /// Maximum weight of the blob cache in megabytes.
260    pub blob_cache_weight_mb: u16,
261    /// Estimated number of elements for the blob cache.
262    pub blob_cache_items_capacity: u16,
263    /// Maximum weight of the block cache in megabytes.
264    pub block_cache_weight_mb: u16,
265    /// Estimated number of elements for the block cache.
266    pub block_cache_items_capacity: u16,
267    /// Maximum weight in megabytes for the combined
268    /// cache, consisting of small miscellaneous items.
269    pub auxiliary_cache_size_mb: u16,
270}
271
272impl Default for LimitsConfig {
273    fn default() -> Self {
274        Self {
275            persistence_period_ms: 299 * 1000,
276            work_queue_size: 256,
277            blob_cache_weight_mb: 1024,
278            blob_cache_items_capacity: 8192,
279            block_cache_weight_mb: 1024,
280            block_cache_items_capacity: 8192,
281            auxiliary_cache_size_mb: 1024,
282        }
283    }
284}
285
286impl Destination {
287    pub fn address(&self) -> String {
288        match &self {
289            Destination::Indexer {
290                tls,
291                endpoint,
292                port,
293            } => {
294                let tls = match tls {
295                    TlsConfig::ClearText => "http",
296                    TlsConfig::Tls => "https",
297                };
298
299                format!("{}://{}:{}", tls, endpoint, port)
300            }
301
302            Destination::Validator { endpoint, port } => {
303                format!("{}:{}:{}", "grpc", endpoint, port)
304            }
305
306            Destination::Logging { file_name } => file_name.to_string(),
307        }
308    }
309
310    pub fn id(&self) -> DestinationId {
311        let kind = match self {
312            Destination::Indexer { .. } => DestinationKind::Indexer,
313            Destination::Validator { .. } => DestinationKind::Validator,
314            Destination::Logging { .. } => DestinationKind::Logging,
315        };
316        DestinationId {
317            address: self.address(),
318            kind,
319        }
320    }
321}
322
323#[cfg(test)]
324mod tests {
325    use super::*;
326
327    #[test]
328    fn parse_from_str() {
329        let input = r#"
330                        tls = "ClearText"
331                        endpoint = "127.0.0.1"
332                        port = 8080
333                        kind = "Indexer"
334            "#
335        .to_string();
336
337        let destination: Destination = toml::from_str(&input).unwrap();
338        assert_eq!(
339            destination,
340            Destination::Indexer {
341                tls: TlsConfig::ClearText,
342                endpoint: "127.0.0.1".to_owned(),
343                port: 8080,
344            }
345        );
346
347        let input = r#"
348                        endpoint = "127.0.0.1"
349                        port = 8080
350                        kind = "Validator"
351        "#
352        .to_string();
353        let destination: Destination = toml::from_str(&input).unwrap();
354        assert_eq!(
355            destination,
356            Destination::Validator {
357                endpoint: "127.0.0.1".to_owned(),
358                port: 8080,
359            }
360        );
361
362        let input = r#"
363                        file_name = "export.log"
364                        kind = "Logging"
365        "#
366        .to_string();
367        let destination: Destination = toml::from_str(&input).unwrap();
368        assert_eq!(
369            destination,
370            Destination::Logging {
371                file_name: "export.log".to_owned(),
372            }
373        );
374    }
375}