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 super::super::adapters::EraReaderDummyAdapter as DummyAdapter;
180    use super::*;
181
182    fn get_basic_marker_sample() -> Vec<EraMarker> {
183        vec![
184            EraMarker {
185                name: "one".to_string(),
186                epoch: Some(Epoch(1)),
187            },
188            EraMarker {
189                name: SupportedEra::dummy().to_string(),
190                epoch: None,
191            },
192            EraMarker {
193                name: SupportedEra::dummy().to_string(),
194                epoch: Some(Epoch(10)),
195            },
196        ]
197    }
198
199    #[tokio::test]
200    async fn current_era_is_supported() {
201        let markers: Vec<EraMarker> = get_basic_marker_sample();
202        let adapter = DummyAdapter::default();
203        adapter.set_markers(markers);
204
205        let reader = EraReader::new(Arc::new(adapter));
206        let token = reader.read_era_epoch_token(Epoch(10)).await.unwrap();
207
208        assert_eq!(
209            EraEpochToken {
210                current_epoch: Epoch(10),
211                current_era: EraMarker {
212                    name: SupportedEra::dummy().to_string(),
213                    epoch: Some(Epoch(10))
214                },
215                next_era: None,
216            },
217            token
218        );
219    }
220
221    #[tokio::test]
222    async fn era_epoch_token() {
223        let markers: Vec<EraMarker> = get_basic_marker_sample();
224        let adapter = DummyAdapter::default();
225        adapter.set_markers(markers);
226
227        let reader = EraReader::new(Arc::new(adapter));
228        let token = reader.read_era_epoch_token(Epoch(10)).await.unwrap();
229        assert_eq!(
230            SupportedEra::dummy(),
231            token
232                .get_current_supported_era()
233                .expect("the given era is supported")
234        );
235        assert!(token.get_next_era_marker().is_none());
236        assert!(token
237            .get_next_supported_era()
238            .expect("None era shall not fail when asked.")
239            .is_none());
240    }
241
242    #[tokio::test]
243    async fn previous_era_is_not_supported() {
244        let markers: Vec<EraMarker> = get_basic_marker_sample();
245        let adapter = DummyAdapter::default();
246        adapter.set_markers(markers);
247
248        let reader = EraReader::new(Arc::new(adapter));
249        let token = reader.read_era_epoch_token(Epoch(9)).await.unwrap();
250
251        assert_eq!(
252            EraEpochToken {
253                current_epoch: Epoch(9),
254                current_era: EraMarker {
255                    name: "one".to_string(),
256                    epoch: Some(Epoch(1))
257                },
258                next_era: Some(EraMarker {
259                    name: SupportedEra::dummy().to_string(),
260                    epoch: Some(Epoch(10))
261                }),
262            },
263            token
264        );
265    }
266
267    #[tokio::test]
268    async fn error_when_no_current_era() {
269        let markers = vec![
270            EraMarker {
271                name: "one".to_string(),
272                epoch: None,
273            },
274            EraMarker {
275                name: "two".to_string(),
276                epoch: None,
277            },
278            EraMarker {
279                name: "three".to_string(),
280                epoch: Some(Epoch(100)),
281            },
282        ];
283
284        let adapter = DummyAdapter::default();
285        adapter.set_markers(markers);
286
287        let reader = EraReader::new(Arc::new(adapter));
288        let _ = reader
289            .read_era_epoch_token(Epoch(9))
290            .await
291            .expect_err("No current era must make the reader to fail.");
292    }
293
294    #[tokio::test]
295    async fn error_when_no_era() {
296        let adapter = DummyAdapter::default();
297
298        let reader = EraReader::new(Arc::new(adapter));
299        let _ = reader
300            .read_era_epoch_token(Epoch(9))
301            .await
302            .expect_err("The adapter gave no result hence the reader should fail.");
303    }
304
305    #[tokio::test]
306    async fn current_era_is_not_supported() {
307        let markers: Vec<EraMarker> = get_basic_marker_sample();
308        let adapter = DummyAdapter::default();
309        adapter.set_markers(markers);
310
311        let reader = EraReader::new(Arc::new(adapter));
312        let token = reader.read_era_epoch_token(Epoch(9)).await.unwrap();
313
314        token
315            .get_current_supported_era()
316            .expect_err("The era 'one' is not supported hence the token must issue an error.");
317
318        assert_eq!(
319            &EraMarker {
320                name: "one".to_string(),
321                epoch: Some(Epoch(1))
322            },
323            token.get_current_era_marker()
324        );
325        token
326            .get_next_supported_era()
327            .expect("The next era is supported hence this shall not fail.");
328    }
329
330    #[tokio::test]
331    async fn epoch_0_should_work() {
332        let markers = vec![EraMarker::new(
333            &SupportedEra::dummy().to_string(),
334            Some(Epoch(0)),
335        )];
336        let adapter = DummyAdapter::default();
337        adapter.set_markers(markers);
338        let reader = EraReader::new(Arc::new(adapter));
339        let token = reader.read_era_epoch_token(Epoch(9)).await.unwrap();
340
341        assert_eq!(
342            &EraMarker {
343                name: SupportedEra::dummy().to_string(),
344                epoch: Some(Epoch(0))
345            },
346            token.get_current_era_marker()
347        );
348    }
349}