1use std::{fmt, net::SocketAddr};
7
8use linera_rpc::config::{ExporterServiceConfig, TlsConfig};
9use serde::{
10 de::{Error, MapAccess, Visitor},
11 Deserialize, Deserializer, Serialize, Serializer,
12};
13
14#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
16pub struct BlockExporterConfig {
17 pub id: u32,
19
20 pub service_config: ExporterServiceConfig,
22
23 #[serde(default)]
25 pub destination_config: DestinationConfig,
26
27 #[serde(default)]
30 pub limits: LimitsConfig,
31
32 pub metrics_port: u16,
34}
35
36impl BlockExporterConfig {
37 pub fn metrics_address(&self) -> SocketAddr {
39 SocketAddr::from(([0, 0, 0, 0], self.metrics_port))
40 }
41}
42
43#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq)]
45pub struct DestinationConfig {
46 pub destinations: Vec<Destination>,
48 #[serde(default)]
50 pub committee_destination: bool,
51}
52
53#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
55pub struct DestinationId {
56 address: String,
57 kind: DestinationKind,
58}
59
60impl DestinationId {
61 pub fn new(address: String, kind: DestinationKind) -> Self {
63 Self { address, kind }
64 }
65
66 pub fn validator(address: String) -> Self {
67 Self {
68 address,
69 kind: DestinationKind::Validator,
70 }
71 }
72
73 pub fn address(&self) -> &str {
75 &self.address
76 }
77
78 pub fn kind(&self) -> DestinationKind {
80 self.kind
81 }
82}
83
84#[derive(Debug, Clone, PartialEq, Eq)]
86pub enum Destination {
87 Indexer {
88 tls: TlsConfig,
90 endpoint: String,
92 port: u16,
94 },
95 Validator {
96 endpoint: String,
98 port: u16,
100 },
101 Logging {
102 file_name: String,
104 },
105}
106
107#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Copy, Hash)]
110pub enum DestinationKind {
111 Indexer,
113 Validator,
115 Logging,
117}
118
119impl Serialize for Destination {
120 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
121 where
122 S: Serializer,
123 {
124 use serde::ser::SerializeMap;
125
126 match self {
127 Destination::Indexer {
128 tls,
129 endpoint,
130 port,
131 } => {
132 let mut map = serializer.serialize_map(Some(4))?;
133 map.serialize_entry("kind", "Indexer")?;
134 map.serialize_entry("tls", tls)?;
135 map.serialize_entry("endpoint", endpoint)?;
136 map.serialize_entry("port", port)?;
137 map.end()
138 }
139 Destination::Validator { endpoint, port } => {
140 let mut map = serializer.serialize_map(Some(3))?;
141 map.serialize_entry("kind", "Validator")?;
142 map.serialize_entry("endpoint", endpoint)?;
143 map.serialize_entry("port", port)?;
144 map.end()
145 }
146 Destination::Logging { file_name } => {
147 let mut map = serializer.serialize_map(Some(2))?;
148 map.serialize_entry("kind", "Logging")?;
149 map.serialize_entry("file_name", file_name)?;
150 map.end()
151 }
152 }
153 }
154}
155
156struct DestinationVisitor;
157
158impl<'de> Visitor<'de> for DestinationVisitor {
159 type Value = Destination;
160
161 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
162 formatter.write_str("a map with a 'kind' field")
163 }
164
165 fn visit_map<V>(self, mut map: V) -> Result<Destination, V::Error>
166 where
167 V: MapAccess<'de>,
168 {
169 let mut kind: Option<String> = None;
170 let mut tls: Option<TlsConfig> = None;
171 let mut endpoint: Option<String> = None;
172 let mut port: Option<u16> = None;
173 let mut file_name: Option<String> = None;
174
175 while let Some(key) = map.next_key::<String>()? {
176 match key.as_str() {
177 "kind" => {
178 if kind.is_some() {
179 return Err(V::Error::duplicate_field("kind"));
180 }
181 kind = Some(map.next_value()?);
182 }
183 "tls" => {
184 if tls.is_some() {
185 return Err(V::Error::duplicate_field("tls"));
186 }
187 tls = Some(map.next_value()?);
188 }
189 "endpoint" => {
190 if endpoint.is_some() {
191 return Err(V::Error::duplicate_field("endpoint"));
192 }
193 endpoint = Some(map.next_value()?);
194 }
195 "port" => {
196 if port.is_some() {
197 return Err(V::Error::duplicate_field("port"));
198 }
199 port = Some(map.next_value()?);
200 }
201 "file_name" => {
202 if file_name.is_some() {
203 return Err(V::Error::duplicate_field("file_name"));
204 }
205 file_name = Some(map.next_value()?);
206 }
207 _ => {
208 let _: serde::de::IgnoredAny = map.next_value()?;
210 }
211 }
212 }
213
214 let kind = kind.ok_or_else(|| V::Error::missing_field("kind"))?;
215
216 match kind.as_str() {
217 "Indexer" => {
218 let tls = tls.ok_or_else(|| V::Error::missing_field("tls"))?;
219 let endpoint = endpoint.ok_or_else(|| V::Error::missing_field("endpoint"))?;
220 let port = port.ok_or_else(|| V::Error::missing_field("port"))?;
221 Ok(Destination::Indexer {
222 tls,
223 endpoint,
224 port,
225 })
226 }
227 "Validator" => {
228 let endpoint = endpoint.ok_or_else(|| V::Error::missing_field("endpoint"))?;
229 let port = port.ok_or_else(|| V::Error::missing_field("port"))?;
230 Ok(Destination::Validator { endpoint, port })
231 }
232 "Logging" => {
233 let file_name = file_name.ok_or_else(|| V::Error::missing_field("file_name"))?;
234 Ok(Destination::Logging { file_name })
235 }
236 _ => Err(V::Error::unknown_variant(
237 &kind,
238 &["Indexer", "Validator", "Logging"],
239 )),
240 }
241 }
242}
243impl<'de> Deserialize<'de> for Destination {
244 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
245 where
246 D: Deserializer<'de>,
247 {
248 deserializer.deserialize_map(DestinationVisitor)
249 }
250}
251#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
254pub struct LimitsConfig {
255 pub persistence_period_ms: u32,
258 pub work_queue_size: u16,
261 pub blob_cache_weight_mb: u16,
263 pub blob_cache_items_capacity: u16,
265 pub block_cache_weight_mb: u16,
267 pub block_cache_items_capacity: u16,
269 pub auxiliary_cache_size_mb: u16,
272}
273
274impl Default for LimitsConfig {
275 fn default() -> Self {
276 Self {
277 persistence_period_ms: 299 * 1000,
278 work_queue_size: 256,
279 blob_cache_weight_mb: 1024,
280 blob_cache_items_capacity: 8192,
281 block_cache_weight_mb: 1024,
282 block_cache_items_capacity: 8192,
283 auxiliary_cache_size_mb: 1024,
284 }
285 }
286}
287
288impl Destination {
289 pub fn address(&self) -> String {
290 match &self {
291 Destination::Indexer {
292 tls,
293 endpoint,
294 port,
295 } => {
296 let tls = match tls {
297 TlsConfig::ClearText => "http",
298 TlsConfig::Tls => "https",
299 };
300
301 format!("{tls}://{endpoint}:{port}")
302 }
303
304 Destination::Validator { endpoint, port } => {
305 format!("{}:{}:{}", "grpc", endpoint, port)
306 }
307
308 Destination::Logging { file_name } => file_name.to_string(),
309 }
310 }
311
312 pub fn id(&self) -> DestinationId {
313 let kind = match self {
314 Destination::Indexer { .. } => DestinationKind::Indexer,
315 Destination::Validator { .. } => DestinationKind::Validator,
316 Destination::Logging { .. } => DestinationKind::Logging,
317 };
318 DestinationId {
319 address: self.address(),
320 kind,
321 }
322 }
323}
324
325#[cfg(test)]
326mod tests {
327 use super::*;
328
329 #[test]
330 fn parse_from_str() {
331 let input = r#"
332 tls = "ClearText"
333 endpoint = "127.0.0.1"
334 port = 8080
335 kind = "Indexer"
336 "#
337 .to_string();
338
339 let destination: Destination = toml::from_str(&input).unwrap();
340 assert_eq!(
341 destination,
342 Destination::Indexer {
343 tls: TlsConfig::ClearText,
344 endpoint: "127.0.0.1".to_owned(),
345 port: 8080,
346 }
347 );
348
349 let input = r#"
350 endpoint = "127.0.0.1"
351 port = 8080
352 kind = "Validator"
353 "#
354 .to_string();
355 let destination: Destination = toml::from_str(&input).unwrap();
356 assert_eq!(
357 destination,
358 Destination::Validator {
359 endpoint: "127.0.0.1".to_owned(),
360 port: 8080,
361 }
362 );
363
364 let input = r#"
365 file_name = "export.log"
366 kind = "Logging"
367 "#
368 .to_string();
369 let destination: Destination = toml::from_str(&input).unwrap();
370 assert_eq!(
371 destination,
372 Destination::Logging {
373 file_name: "export.log".to_owned(),
374 }
375 );
376 }
377}