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