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 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}