1use 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#[derive(Serialize, Deserialize, Debug, Clone)]
14pub struct BlockExporterConfig {
15 pub id: u32,
17
18 pub service_config: ExporterServiceConfig,
20
21 #[serde(default)]
23 pub destination_config: DestinationConfig,
24
25 #[serde(default)]
28 pub limits: LimitsConfig,
29
30 pub metrics_port: u16,
32}
33
34impl BlockExporterConfig {
35 pub fn metrics_address(&self) -> SocketAddr {
37 SocketAddr::from(([0, 0, 0, 0], self.metrics_port))
38 }
39}
40
41#[derive(Serialize, Deserialize, Debug, Clone, Default)]
43pub struct DestinationConfig {
44 pub destinations: Vec<Destination>,
46 #[serde(default)]
48 pub committee_destination: bool,
49}
50
51#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
53pub struct DestinationId {
54 address: String,
55 kind: DestinationKind,
56}
57
58impl DestinationId {
59 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 pub fn address(&self) -> &str {
73 &self.address
74 }
75
76 pub fn kind(&self) -> DestinationKind {
78 self.kind
79 }
80}
81
82#[derive(Debug, Clone, PartialEq, Eq)]
84pub enum Destination {
85 Indexer {
86 tls: TlsConfig,
88 endpoint: String,
90 port: u16,
92 },
93 Validator {
94 endpoint: String,
96 port: u16,
98 },
99 Logging {
100 file_name: String,
102 },
103}
104
105#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Copy, Hash)]
108pub enum DestinationKind {
109 Indexer,
111 Validator,
113 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 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#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
252pub struct LimitsConfig {
253 pub persistence_period_ms: u32,
256 pub work_queue_size: u16,
259 pub blob_cache_weight_mb: u16,
261 pub blob_cache_items_capacity: u16,
263 pub block_cache_weight_mb: u16,
265 pub block_cache_items_capacity: u16,
267 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}