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 {
68 Self {
69 address,
70 kind: DestinationKind::Validator,
71 }
72 }
73
74 pub fn address(&self) -> &str {
76 &self.address
77 }
78
79 pub fn kind(&self) -> DestinationKind {
81 self.kind
82 }
83}
84
85#[derive(Debug, Clone, PartialEq, Eq)]
87pub enum Destination {
88 Indexer {
90 tls: TlsConfig,
92 endpoint: String,
94 port: u16,
96 },
97 Validator {
99 endpoint: String,
101 port: u16,
103 },
104 Logging {
106 file_name: String,
108 },
109}
110
111#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Copy, Hash)]
114pub enum DestinationKind {
115 Indexer,
117 Validator,
119 Logging,
121}
122
123impl Serialize for Destination {
124 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
125 where
126 S: Serializer,
127 {
128 use serde::ser::SerializeMap;
129
130 match self {
131 Destination::Indexer {
132 tls,
133 endpoint,
134 port,
135 } => {
136 let mut map = serializer.serialize_map(Some(4))?;
137 map.serialize_entry("kind", "Indexer")?;
138 map.serialize_entry("tls", tls)?;
139 map.serialize_entry("endpoint", endpoint)?;
140 map.serialize_entry("port", port)?;
141 map.end()
142 }
143 Destination::Validator { endpoint, port } => {
144 let mut map = serializer.serialize_map(Some(3))?;
145 map.serialize_entry("kind", "Validator")?;
146 map.serialize_entry("endpoint", endpoint)?;
147 map.serialize_entry("port", port)?;
148 map.end()
149 }
150 Destination::Logging { file_name } => {
151 let mut map = serializer.serialize_map(Some(2))?;
152 map.serialize_entry("kind", "Logging")?;
153 map.serialize_entry("file_name", file_name)?;
154 map.end()
155 }
156 }
157 }
158}
159
160struct DestinationVisitor;
161
162impl<'de> Visitor<'de> for DestinationVisitor {
163 type Value = Destination;
164
165 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
166 formatter.write_str("a map with a 'kind' field")
167 }
168
169 fn visit_map<V>(self, mut map: V) -> Result<Destination, V::Error>
170 where
171 V: MapAccess<'de>,
172 {
173 let mut kind: Option<String> = None;
174 let mut tls: Option<TlsConfig> = None;
175 let mut endpoint: Option<String> = None;
176 let mut port: Option<u16> = None;
177 let mut file_name: Option<String> = None;
178
179 while let Some(key) = map.next_key::<String>()? {
180 match key.as_str() {
181 "kind" => {
182 if kind.is_some() {
183 return Err(V::Error::duplicate_field("kind"));
184 }
185 kind = Some(map.next_value()?);
186 }
187 "tls" => {
188 if tls.is_some() {
189 return Err(V::Error::duplicate_field("tls"));
190 }
191 tls = Some(map.next_value()?);
192 }
193 "endpoint" => {
194 if endpoint.is_some() {
195 return Err(V::Error::duplicate_field("endpoint"));
196 }
197 endpoint = Some(map.next_value()?);
198 }
199 "port" => {
200 if port.is_some() {
201 return Err(V::Error::duplicate_field("port"));
202 }
203 port = Some(map.next_value()?);
204 }
205 "file_name" => {
206 if file_name.is_some() {
207 return Err(V::Error::duplicate_field("file_name"));
208 }
209 file_name = Some(map.next_value()?);
210 }
211 _ => {
212 let _: serde::de::IgnoredAny = map.next_value()?;
214 }
215 }
216 }
217
218 let kind = kind.ok_or_else(|| V::Error::missing_field("kind"))?;
219
220 match kind.as_str() {
221 "Indexer" => {
222 let tls = tls.ok_or_else(|| V::Error::missing_field("tls"))?;
223 let endpoint = endpoint.ok_or_else(|| V::Error::missing_field("endpoint"))?;
224 let port = port.ok_or_else(|| V::Error::missing_field("port"))?;
225 Ok(Destination::Indexer {
226 tls,
227 endpoint,
228 port,
229 })
230 }
231 "Validator" => {
232 let endpoint = endpoint.ok_or_else(|| V::Error::missing_field("endpoint"))?;
233 let port = port.ok_or_else(|| V::Error::missing_field("port"))?;
234 Ok(Destination::Validator { endpoint, port })
235 }
236 "Logging" => {
237 let file_name = file_name.ok_or_else(|| V::Error::missing_field("file_name"))?;
238 Ok(Destination::Logging { file_name })
239 }
240 _ => Err(V::Error::unknown_variant(
241 &kind,
242 &["Indexer", "Validator", "Logging"],
243 )),
244 }
245 }
246}
247impl<'de> Deserialize<'de> for Destination {
248 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
249 where
250 D: Deserializer<'de>,
251 {
252 deserializer.deserialize_map(DestinationVisitor)
253 }
254}
255#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
258pub struct LimitsConfig {
259 pub persistence_period_ms: u32,
262 pub work_queue_size: u16,
265 pub blob_cache_weight_mb: u16,
267 pub blob_cache_items_capacity: u16,
269 pub block_cache_weight_mb: u16,
271 pub block_cache_items_capacity: u16,
273 pub auxiliary_cache_size_mb: u16,
276}
277
278impl Default for LimitsConfig {
279 fn default() -> Self {
280 Self {
281 persistence_period_ms: 299 * 1000,
282 work_queue_size: 256,
283 blob_cache_weight_mb: 1024,
284 blob_cache_items_capacity: 8192,
285 block_cache_weight_mb: 1024,
286 block_cache_items_capacity: 8192,
287 auxiliary_cache_size_mb: 1024,
288 }
289 }
290}
291
292impl Destination {
293 pub fn address(&self) -> String {
295 match &self {
296 Destination::Indexer {
297 tls,
298 endpoint,
299 port,
300 } => {
301 let tls = match tls {
302 TlsConfig::ClearText => "http",
303 TlsConfig::Tls => "https",
304 };
305
306 format!("{tls}://{endpoint}:{port}")
307 }
308
309 Destination::Validator { endpoint, port } => {
310 format!("{}:{}:{}", "grpc", endpoint, port)
311 }
312
313 Destination::Logging { file_name } => file_name.to_string(),
314 }
315 }
316
317 pub fn id(&self) -> DestinationId {
319 let kind = match self {
320 Destination::Indexer { .. } => DestinationKind::Indexer,
321 Destination::Validator { .. } => DestinationKind::Validator,
322 Destination::Logging { .. } => DestinationKind::Logging,
323 };
324 DestinationId {
325 address: self.address(),
326 kind,
327 }
328 }
329}
330
331#[cfg(test)]
332mod tests {
333 use super::*;
334
335 #[test]
336 fn parse_from_str() {
337 let input = r#"
338 tls = "ClearText"
339 endpoint = "127.0.0.1"
340 port = 8080
341 kind = "Indexer"
342 "#
343 .to_string();
344
345 let destination: Destination = toml::from_str(&input).unwrap();
346 assert_eq!(
347 destination,
348 Destination::Indexer {
349 tls: TlsConfig::ClearText,
350 endpoint: "127.0.0.1".to_owned(),
351 port: 8080,
352 }
353 );
354
355 let input = r#"
356 endpoint = "127.0.0.1"
357 port = 8080
358 kind = "Validator"
359 "#
360 .to_string();
361 let destination: Destination = toml::from_str(&input).unwrap();
362 assert_eq!(
363 destination,
364 Destination::Validator {
365 endpoint: "127.0.0.1".to_owned(),
366 port: 8080,
367 }
368 );
369
370 let input = r#"
371 file_name = "export.log"
372 kind = "Logging"
373 "#
374 .to_string();
375 let destination: Destination = toml::from_str(&input).unwrap();
376 assert_eq!(
377 destination,
378 Destination::Logging {
379 file_name: "export.log".to_owned(),
380 }
381 );
382 }
383}