mithril_era/
era_reader.rs

1use anyhow::anyhow;
2use async_trait::async_trait;
3use serde::{Deserialize, Serialize};
4use std::{str::FromStr, sync::Arc};
5use thiserror::Error;
6
7use mithril_common::entities::{Epoch, SupportedEra};
8use mithril_common::{StdError, StdResult};
9
10/// Value object that represents a tag of Era change.
11#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
12pub struct EraMarker {
13    /// Era name
14    pub name: String,
15
16    /// Eventual information that advertises the Epoch of transition.
17    pub epoch: Option<Epoch>,
18}
19
20impl EraMarker {
21    /// instantiate a new [EraMarker].
22    pub fn new(name: &str, epoch: Option<Epoch>) -> Self {
23        let name = name.to_string();
24
25        Self { name, epoch }
26    }
27}
28
29/// Adapters are responsible of technically reading the information of
30/// [EraMarker]s from a backend.
31#[async_trait]
32pub trait EraReaderAdapter: Sync + Send {
33    /// Read era markers from the underlying adapter.
34    async fn read(&self) -> StdResult<Vec<EraMarker>>;
35}
36
37/// This is a response from the [EraReader]. It contains [EraMarker]s read from
38/// the adapter. It can try to cast the given markers to [SupportedEra]s.
39#[derive(Debug, Clone, PartialEq, Eq)]
40pub struct EraEpochToken {
41    current_epoch: Epoch,
42    current_era: EraMarker,
43    next_era: Option<EraMarker>,
44}
45
46impl EraEpochToken {
47    /// Instantiate a new [EraMarker].
48    pub fn new(current_epoch: Epoch, current_era: EraMarker, next_era: Option<EraMarker>) -> Self {
49        Self {
50            current_epoch,
51            current_era,
52            next_era,
53        }
54    }
55
56    /// Try to cast the current [EraMarker] to a [SupportedEra]. If it fails,
57    /// that means the current Era is not supported by this version of the
58    /// software.
59    pub fn get_current_supported_era(&self) -> StdResult<SupportedEra> {
60        SupportedEra::from_str(&self.current_era.name)
61            .map_err(|_| anyhow!(format!("Unsupported era '{}'.", &self.current_era.name)))
62    }
63
64    /// Return the [EraMarker] of the current Era.
65    pub fn get_current_era_marker(&self) -> &EraMarker {
66        &self.current_era
67    }
68
69    /// Return the epoch the Token has been created at
70    pub fn get_current_epoch(&self) -> Epoch {
71        self.current_epoch
72    }
73
74    /// Try to cast the next [EraMarker] to a [SupportedEra]. If it fails, that
75    /// means the coming Era will not be supported by this version of the
76    /// software. This mechanism is used to issue a warning to the user asking
77    /// for upgrade.
78    pub fn get_next_supported_era(&self) -> StdResult<Option<SupportedEra>> {
79        match self.next_era.as_ref() {
80            Some(marker) => Ok(Some(
81                SupportedEra::from_str(&marker.name)
82                    .map_err(|_| anyhow!(format!("Unsupported era '{}'.", &marker.name)))?,
83            )),
84            None => Ok(None),
85        }
86    }
87
88    /// Return the [EraMarker] for the coming Era if any.
89    pub fn get_next_era_marker(&self) -> Option<&EraMarker> {
90        self.next_era.as_ref()
91    }
92}
93
94/// The EraReader is responsible of giving the current Era and the Era to come.
95/// It uses an [EraReaderAdapter] to read data from a backend.
96pub struct EraReader {
97    adapter: Arc<dyn EraReaderAdapter>,
98}
99
100/// Error type when [EraReader] fails to return a [EraEpochToken].
101#[derive(Debug, Error)]
102pub enum EraReaderError {
103    /// Underlying adapter fails to return data.
104    #[error("Adapter Error message: «{message}»")]
105    AdapterFailure {
106        /// context message
107        message: String,
108
109        /// nested underlying adapter error
110        #[source]
111        error: StdError,
112    },
113
114    /// Data returned from the adapter are inconsistent or incomplete.
115    #[error(
116        "Cannot determine the Era we are currently at epoch {epoch} using the adapter informations: {eras:?}"
117    )]
118    CurrentEraNotFound {
119        /// Current Epoch
120        epoch: Epoch,
121
122        /// Eras given by the adapter
123        eras: Vec<EraMarker>,
124    },
125}
126
127impl EraReader {
128    /// Instantiate the [EraReader] injecting the adapter.
129    pub fn new(adapter: Arc<dyn EraReaderAdapter>) -> Self {
130        Self { adapter }
131    }
132
133    /// This methods triggers the adapter to read the markers from the backend.
134    /// It tries to determine the current Era and the next Era if any from the
135    /// data returned from the adapter.
136    pub async fn read_era_epoch_token(
137        &self,
138        current_epoch: Epoch,
139    ) -> Result<EraEpochToken, EraReaderError> {
140        let eras = self
141            .adapter
142            .read()
143            .await
144            .map_err(|e| EraReaderError::AdapterFailure {
145                message: format!("Reading from EraReader adapter raised an error: '{}'.", &e),
146                error: e,
147            })?;
148
149        let current_marker = eras.iter().filter(|&f| f.epoch.is_some()).fold(
150            None,
151            |acc: Option<&EraMarker>, marker| {
152                if marker.epoch.unwrap() <= current_epoch
153                    && (acc.is_none() || marker.epoch.unwrap() > acc.unwrap().epoch.unwrap())
154                {
155                    Some(marker)
156                } else {
157                    acc
158                }
159            },
160        );
161        let current_era_marker =
162            current_marker.ok_or_else(|| EraReaderError::CurrentEraNotFound {
163                epoch: current_epoch,
164                eras: eras.clone(),
165            })?;
166
167        let next_era_marker = eras.last().filter(|&marker| marker != current_era_marker);
168
169        Ok(EraEpochToken::new(
170            current_epoch,
171            current_era_marker.to_owned(),
172            next_era_marker.cloned(),
173        ))
174    }
175}
176
177#[cfg(test)]
178mod tests {
179    use mithril_common::test::double::Dummy;
180
181    use super::super::adapters::EraReaderDummyAdapter as DummyAdapter;
182    use super::*;
183
184    fn get_basic_marker_sample() -> Vec<EraMarker> {
185        vec![
186            EraMarker {
187                name: "one".to_string(),
188                epoch: Some(Epoch(1)),
189            },
190            EraMarker {
191                name: SupportedEra::dummy().to_string(),
192                epoch: None,
193            },
194            EraMarker {
195                name: SupportedEra::dummy().to_string(),
196                epoch: Some(Epoch(10)),
197            },
198        ]
199    }
200
201    #[tokio::test]
202    async fn current_era_is_supported() {
203        let markers: Vec<EraMarker> = get_basic_marker_sample();
204        let adapter = DummyAdapter::default();
205        adapter.set_markers(markers);
206
207        let reader = EraReader::new(Arc::new(adapter));
208        let token = reader.read_era_epoch_token(Epoch(10)).await.unwrap();
209
210        assert_eq!(
211            EraEpochToken {
212                current_epoch: Epoch(10),
213                current_era: EraMarker {
214                    name: SupportedEra::dummy().to_string(),
215                    epoch: Some(Epoch(10))
216                },
217                next_era: None,
218            },
219            token
220        );
221    }
222
223    #[tokio::test]
224    async fn era_epoch_token() {
225        let markers: Vec<EraMarker> = get_basic_marker_sample();
226        let adapter = DummyAdapter::default();
227        adapter.set_markers(markers);
228
229        let reader = EraReader::new(Arc::new(adapter));
230        let token = reader.read_era_epoch_token(Epoch(10)).await.unwrap();
231        assert_eq!(
232            SupportedEra::dummy(),
233            token.get_current_supported_era().expect("the given era is supported")
234        );
235        assert!(token.get_next_era_marker().is_none());
236        assert!(
237            token
238                .get_next_supported_era()
239                .expect("None era shall not fail when asked.")
240                .is_none()
241        );
242    }
243
244    #[tokio::test]
245    async fn previous_era_is_not_supported() {
246        let markers: Vec<EraMarker> = get_basic_marker_sample();
247        let adapter = DummyAdapter::default();
248        adapter.set_markers(markers);
249
250        let reader = EraReader::new(Arc::new(adapter));
251        let token = reader.read_era_epoch_token(Epoch(9)).await.unwrap();
252
253        assert_eq!(
254            EraEpochToken {
255                current_epoch: Epoch(9),
256                current_era: EraMarker {
257                    name: "one".to_string(),
258                    epoch: Some(Epoch(1))
259                },
260                next_era: Some(EraMarker {
261                    name: SupportedEra::dummy().to_string(),
262                    epoch: Some(Epoch(10))
263                }),
264            },
265            token
266        );
267    }
268
269    #[tokio::test]
270    async fn error_when_no_current_era() {
271        let markers = vec![
272            EraMarker {
273                name: "one".to_string(),
274                epoch: None,
275            },
276            EraMarker {
277                name: "two".to_string(),
278                epoch: None,
279            },
280            EraMarker {
281                name: "three".to_string(),
282                epoch: Some(Epoch(100)),
283            },
284        ];
285
286        let adapter = DummyAdapter::default();
287        adapter.set_markers(markers);
288
289        let reader = EraReader::new(Arc::new(adapter));
290        let _ = reader
291            .read_era_epoch_token(Epoch(9))
292            .await
293            .expect_err("No current era must make the reader to fail.");
294    }
295
296    #[tokio::test]
297    async fn error_when_no_era() {
298        let adapter = DummyAdapter::default();
299
300        let reader = EraReader::new(Arc::new(adapter));
301        let _ = reader
302            .read_era_epoch_token(Epoch(9))
303            .await
304            .expect_err("The adapter gave no result hence the reader should fail.");
305    }
306
307    #[tokio::test]
308    async fn current_era_is_not_supported() {
309        let markers: Vec<EraMarker> = get_basic_marker_sample();
310        let adapter = DummyAdapter::default();
311        adapter.set_markers(markers);
312
313        let reader = EraReader::new(Arc::new(adapter));
314        let token = reader.read_era_epoch_token(Epoch(9)).await.unwrap();
315
316        token
317            .get_current_supported_era()
318            .expect_err("The era 'one' is not supported hence the token must issue an error.");
319
320        assert_eq!(
321            &EraMarker {
322                name: "one".to_string(),
323                epoch: Some(Epoch(1))
324            },
325            token.get_current_era_marker()
326        );
327        token
328            .get_next_supported_era()
329            .expect("The next era is supported hence this shall not fail.");
330    }
331
332    #[tokio::test]
333    async fn epoch_0_should_work() {
334        let markers = vec![EraMarker::new(&SupportedEra::dummy().to_string(), Some(Epoch(0)))];
335        let adapter = DummyAdapter::default();
336        adapter.set_markers(markers);
337        let reader = EraReader::new(Arc::new(adapter));
338        let token = reader.read_era_epoch_token(Epoch(9)).await.unwrap();
339
340        assert_eq!(
341            &EraMarker {
342                name: SupportedEra::dummy().to_string(),
343                epoch: Some(Epoch(0))
344            },
345            token.get_current_era_marker()
346        );
347    }
348}