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#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
12pub struct EraMarker {
13 pub name: String,
15
16 pub epoch: Option<Epoch>,
18}
19
20impl EraMarker {
21 pub fn new(name: &str, epoch: Option<Epoch>) -> Self {
23 let name = name.to_string();
24
25 Self { name, epoch }
26 }
27}
28
29#[async_trait]
32pub trait EraReaderAdapter: Sync + Send {
33 async fn read(&self) -> StdResult<Vec<EraMarker>>;
35}
36
37#[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 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 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 pub fn get_current_era_marker(&self) -> &EraMarker {
66 &self.current_era
67 }
68
69 pub fn get_current_epoch(&self) -> Epoch {
71 self.current_epoch
72 }
73
74 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 pub fn get_next_era_marker(&self) -> Option<&EraMarker> {
90 self.next_era.as_ref()
91 }
92}
93
94pub struct EraReader {
97 adapter: Arc<dyn EraReaderAdapter>,
98}
99
100#[derive(Debug, Error)]
102pub enum EraReaderError {
103 #[error("Adapter Error message: «{message}»")]
105 AdapterFailure {
106 message: String,
108
109 #[source]
111 error: StdError,
112 },
113
114 #[error(
116 "Cannot determine the Era we are currently at epoch {epoch} using the adapter informations: {eras:?}"
117 )]
118 CurrentEraNotFound {
119 epoch: Epoch,
121
122 eras: Vec<EraMarker>,
124 },
125}
126
127impl EraReader {
128 pub fn new(adapter: Arc<dyn EraReaderAdapter>) -> Self {
130 Self { adapter }
131 }
132
133 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}