mithril_common/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 crate::entities::Epoch;
8use crate::{StdError, StdResult};
9
10use super::SupportedEra;
11
12/// Value object that represents a tag of Era change.
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
14pub struct EraMarker {
15    /// Era name
16    pub name: String,
17
18    /// Eventual information that advertises the Epoch of transition.
19    pub epoch: Option<Epoch>,
20}
21
22impl EraMarker {
23    /// instantiate a new [EraMarker].
24    pub fn new(name: &str, epoch: Option<Epoch>) -> Self {
25        let name = name.to_string();
26
27        Self { name, epoch }
28    }
29}
30
31/// Adapters are responsible of technically reading the information of
32/// [EraMarker]s from a backend.
33#[async_trait]
34pub trait EraReaderAdapter: Sync + Send {
35    /// Read era markers from the underlying adapter.
36    async fn read(&self) -> StdResult<Vec<EraMarker>>;
37}
38
39/// This is a response from the [EraReader]. It contains [EraMarker]s read from
40/// the adapter. It can try to cast the given markers to [SupportedEra]s.
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub struct EraEpochToken {
43    current_epoch: Epoch,
44    current_era: EraMarker,
45    next_era: Option<EraMarker>,
46}
47
48impl EraEpochToken {
49    /// Instantiate a new [EraMarker].
50    pub fn new(current_epoch: Epoch, current_era: EraMarker, next_era: Option<EraMarker>) -> Self {
51        Self {
52            current_epoch,
53            current_era,
54            next_era,
55        }
56    }
57
58    /// Try to cast the current [EraMarker] to a [SupportedEra]. If it fails,
59    /// that means the current Era is not supported by this version of the
60    /// software.
61    pub fn get_current_supported_era(&self) -> StdResult<SupportedEra> {
62        SupportedEra::from_str(&self.current_era.name)
63            .map_err(|_| anyhow!(format!("Unsupported era '{}'.", &self.current_era.name)))
64    }
65
66    /// Return the [EraMarker] of the current Era.
67    pub fn get_current_era_marker(&self) -> &EraMarker {
68        &self.current_era
69    }
70
71    /// Return the epoch the Token has been created at
72    pub fn get_current_epoch(&self) -> Epoch {
73        self.current_epoch
74    }
75
76    /// Try to cast the next [EraMarker] to a [SupportedEra]. If it fails, that
77    /// means the coming Era will not be supported by this version of the
78    /// software. This mechanism is used to issue a warning to the user asking
79    /// for upgrade.
80    pub fn get_next_supported_era(&self) -> StdResult<Option<SupportedEra>> {
81        match self.next_era.as_ref() {
82            Some(marker) => Ok(Some(
83                SupportedEra::from_str(&marker.name)
84                    .map_err(|_| anyhow!(format!("Unsupported era '{}'.", &marker.name)))?,
85            )),
86            None => Ok(None),
87        }
88    }
89
90    /// Return the [EraMarker] for the coming Era if any.
91    pub fn get_next_era_marker(&self) -> Option<&EraMarker> {
92        self.next_era.as_ref()
93    }
94}
95
96/// The EraReader is responsible of giving the current Era and the Era to come.
97/// It uses an [EraReaderAdapter] to read data from a backend.
98pub struct EraReader {
99    adapter: Arc<dyn EraReaderAdapter>,
100}
101
102/// Error type when [EraReader] fails to return a [EraEpochToken].
103#[derive(Debug, Error)]
104pub enum EraReaderError {
105    /// Underlying adapter fails to return data.
106    #[error("Adapter Error message: «{message}»")]
107    AdapterFailure {
108        /// context message
109        message: String,
110
111        /// nested underlying adapter error
112        #[source]
113        error: StdError,
114    },
115
116    /// Data returned from the adapter are inconsistent or incomplete.
117    #[error(
118        "Cannot determine the Era we are currently at epoch {epoch} using the adapter informations: {eras:?}"
119    )]
120    CurrentEraNotFound {
121        /// Current Epoch
122        epoch: Epoch,
123
124        /// Eras given by the adapter
125        eras: Vec<EraMarker>,
126    },
127}
128
129impl EraReader {
130    /// Instantiate the [EraReader] injecting the adapter.
131    pub fn new(adapter: Arc<dyn EraReaderAdapter>) -> Self {
132        Self { adapter }
133    }
134
135    /// This methods triggers the adapter to read the markers from the backend.
136    /// It tries to determine the current Era and the next Era if any from the
137    /// data returned from the adapter.
138    pub async fn read_era_epoch_token(
139        &self,
140        current_epoch: Epoch,
141    ) -> Result<EraEpochToken, EraReaderError> {
142        let eras = self
143            .adapter
144            .read()
145            .await
146            .map_err(|e| EraReaderError::AdapterFailure {
147                message: format!("Reading from EraReader adapter raised an error: '{}'.", &e),
148                error: e,
149            })?;
150
151        let current_marker = eras.iter().filter(|&f| f.epoch.is_some()).fold(
152            None,
153            |acc: Option<&EraMarker>, marker| {
154                if marker.epoch.unwrap() <= current_epoch
155                    && (acc.is_none() || marker.epoch.unwrap() > acc.unwrap().epoch.unwrap())
156                {
157                    Some(marker)
158                } else {
159                    acc
160                }
161            },
162        );
163        let current_era_marker =
164            current_marker.ok_or_else(|| EraReaderError::CurrentEraNotFound {
165                epoch: current_epoch,
166                eras: eras.clone(),
167            })?;
168
169        let next_era_marker = eras.last().filter(|&marker| marker != current_era_marker);
170
171        Ok(EraEpochToken::new(
172            current_epoch,
173            current_era_marker.to_owned(),
174            next_era_marker.cloned(),
175        ))
176    }
177}
178
179#[cfg(test)]
180mod tests {
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
234                .get_current_supported_era()
235                .expect("the given era is supported")
236        );
237        assert!(token.get_next_era_marker().is_none());
238        assert!(token
239            .get_next_supported_era()
240            .expect("None era shall not fail when asked.")
241            .is_none());
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(
335            &SupportedEra::dummy().to_string(),
336            Some(Epoch(0)),
337        )];
338        let adapter = DummyAdapter::default();
339        adapter.set_markers(markers);
340        let reader = EraReader::new(Arc::new(adapter));
341        let token = reader.read_era_epoch_token(Epoch(9)).await.unwrap();
342
343        assert_eq!(
344            &EraMarker {
345                name: SupportedEra::dummy().to_string(),
346                epoch: Some(Epoch(0))
347            },
348            token.get_current_era_marker()
349        );
350    }
351}