mithril_ticker/
ticker_service.rs1use anyhow::Context;
7use async_trait::async_trait;
8use std::sync::Arc;
9use thiserror::Error;
10
11use mithril_cardano_node_chain::chain_observer::ChainObserver;
12use mithril_cardano_node_internal_database::ImmutableFileObserver;
13use mithril_common::StdResult;
14use mithril_common::entities::{Epoch, TimePoint};
15
16#[async_trait]
22pub trait TickerService
23where
24 Self: Sync + Send,
25{
26 async fn get_current_epoch(&self) -> StdResult<Epoch> {
28 self.get_current_time_point().await.map(|time_point| time_point.epoch)
29 }
30
31 async fn get_current_time_point(&self) -> StdResult<TimePoint>;
33}
34
35#[derive(Error, Debug)]
37pub enum TickerServiceError {
38 #[error("No epoch yielded by the chain observer, is your cardano node ready?")]
40 NoEpoch,
41
42 #[error("No chain point yielded by the chain observer, is your cardano node ready?")]
44 NoChainPoint,
45}
46
47pub struct MithrilTickerService {
49 chain_observer: Arc<dyn ChainObserver>,
50 immutable_observer: Arc<dyn ImmutableFileObserver>,
51}
52
53impl MithrilTickerService {
54 pub fn new(
56 chain_observer: Arc<dyn ChainObserver>,
57 immutable_observer: Arc<dyn ImmutableFileObserver>,
58 ) -> Self {
59 Self {
60 chain_observer,
61 immutable_observer,
62 }
63 }
64}
65
66#[async_trait]
67impl TickerService for MithrilTickerService {
68 async fn get_current_time_point(&self) -> StdResult<TimePoint> {
69 let epoch = self
70 .chain_observer
71 .get_current_epoch()
72 .await
73 .with_context(|| "TimePoint Provider can not get current epoch")?
74 .ok_or(TickerServiceError::NoEpoch)?;
75
76 let immutable_file_number = self
77 .immutable_observer
78 .get_last_immutable_number()
79 .await
80 .with_context(|| {
81 format!(
82 "TimePoint Provider can not get last immutable file number for epoch: '{epoch}'"
83 )
84 })?;
85
86 let chain_point = self
87 .chain_observer
88 .get_current_chain_point()
89 .await
90 .with_context(|| "TimePoint Provider can not get current chain point")?
91 .ok_or(TickerServiceError::NoChainPoint)?;
92
93 Ok(TimePoint {
94 epoch,
95 immutable_file_number,
96 chain_point,
97 })
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use anyhow::anyhow;
104
105 use mithril_cardano_node_chain::chain_observer::{ChainObserver, ChainObserverError};
106 use mithril_cardano_node_chain::entities::{ChainAddress, TxDatum};
107 use mithril_cardano_node_internal_database::test::double::DumbImmutableFileObserver;
108 use mithril_common::entities::{BlockNumber, ChainPoint, Epoch, SlotNumber, StakeDistribution};
109
110 use super::*;
111
112 struct DumbChainObserver {}
113
114 #[async_trait]
115 impl ChainObserver for DumbChainObserver {
116 async fn get_current_datums(
117 &self,
118 _address: &ChainAddress,
119 ) -> Result<Vec<TxDatum>, ChainObserverError> {
120 Ok(Vec::new())
121 }
122
123 async fn get_current_era(&self) -> Result<Option<String>, ChainObserverError> {
124 Ok(Some(String::new()))
125 }
126
127 async fn get_current_epoch(&self) -> Result<Option<Epoch>, ChainObserverError> {
128 Ok(Some(Epoch(42)))
129 }
130
131 async fn get_current_chain_point(&self) -> Result<Option<ChainPoint>, ChainObserverError> {
132 Ok(Some(ChainPoint {
133 slot_number: SlotNumber(800),
134 block_number: BlockNumber(51),
135 block_hash: "1b69b3202fbe500".to_string(),
136 }))
137 }
138
139 async fn get_current_stake_distribution(
140 &self,
141 ) -> Result<Option<StakeDistribution>, ChainObserverError> {
142 Err(ChainObserverError::General(anyhow!(
143 "this should not be called in the TimePointProvider"
144 )))
145 }
146 }
147
148 #[tokio::test]
149 async fn test_get_current_epoch() {
150 let ticker_service = MithrilTickerService::new(
151 Arc::new(DumbChainObserver {}),
152 Arc::new(DumbImmutableFileObserver::default()),
153 );
154 let epoch = ticker_service.get_current_epoch().await.unwrap();
155
156 assert_eq!(Epoch(42), epoch);
157 }
158
159 #[tokio::test]
160 async fn test_happy_path() {
161 let ticker_service = MithrilTickerService::new(
162 Arc::new(DumbChainObserver {}),
163 Arc::new(DumbImmutableFileObserver::default()),
164 );
165 let time_point = ticker_service.get_current_time_point().await.unwrap();
166
167 assert_eq!(
168 TimePoint::new(
169 42,
170 500,
171 ChainPoint {
172 slot_number: SlotNumber(800),
173 block_number: BlockNumber(51),
174 block_hash: "1b69b3202fbe500".to_string(),
175 },
176 ),
177 time_point
178 );
179 }
180
181 #[tokio::test]
182 async fn test_error_from_dependency() {
183 let immutable_observer = DumbImmutableFileObserver::default();
184 immutable_observer.shall_return(None).await;
185 let ticker_service =
186 MithrilTickerService::new(Arc::new(DumbChainObserver {}), Arc::new(immutable_observer));
187
188 let result = ticker_service.get_current_time_point().await;
189 assert!(result.is_err());
190 }
191}