mithril_signed_entity_preloader/
cardano_transactions_preloader.rs

1//! # Cardano Transaction Preloader
2//!
3//! This module provides a preload mechanism for Cardano Transaction signed entity, allowing
4//! to compute in advance the Transactions & Block Range Root to be signed.
5
6use std::sync::Arc;
7
8use anyhow::Context;
9use async_trait::async_trait;
10use mithril_signed_entity_lock::SignedEntityTypeLock;
11use slog::{Logger, debug, info};
12
13use mithril_cardano_node_chain::chain_observer::ChainObserver;
14use mithril_common::{
15    StdResult,
16    entities::{BlockNumber, SignedEntityTypeDiscriminants},
17    logging::LoggerExtensions,
18    signable_builder::TransactionsImporter,
19};
20
21#[cfg(test)]
22use mockall::automock;
23
24/// CardanoTransactionsPreloaderChecker gives the ability to determine
25/// if the Cardano Transactions Preloader should import the transactions.
26#[cfg_attr(test, automock)]
27#[async_trait]
28pub trait CardanoTransactionsPreloaderChecker: Send + Sync {
29    /// Determine if the Cardano Transactions Preloader should preload.
30    async fn is_activated(&self) -> StdResult<bool>;
31}
32
33/// CardanoTransactionsPreloaderActivation
34pub struct CardanoTransactionsPreloaderActivation {
35    activation: bool,
36}
37
38impl CardanoTransactionsPreloaderActivation {
39    /// Create a new instance of `CardanoTransactionsPreloaderActivation`
40    pub fn new(activation: bool) -> Self {
41        Self { activation }
42    }
43}
44
45#[async_trait]
46impl CardanoTransactionsPreloaderChecker for CardanoTransactionsPreloaderActivation {
47    async fn is_activated(&self) -> StdResult<bool> {
48        Ok(self.activation)
49    }
50}
51
52/// Preload mechanism for Cardano Transaction signed entity, allowing
53/// to compute in advance the Transactions & Block Range Root to be signed.
54pub struct CardanoTransactionsPreloader {
55    signed_entity_type_lock: Arc<SignedEntityTypeLock>,
56    importer: Arc<dyn TransactionsImporter>,
57    security_parameter: BlockNumber,
58    chain_observer: Arc<dyn ChainObserver>,
59    logger: Logger,
60    activation_state: Arc<dyn CardanoTransactionsPreloaderChecker>,
61}
62
63impl CardanoTransactionsPreloader {
64    /// Create a new instance of `CardanoTransactionPreloader`.
65    pub fn new(
66        signed_entity_type_lock: Arc<SignedEntityTypeLock>,
67        importer: Arc<dyn TransactionsImporter>,
68        security_parameter: BlockNumber,
69        chain_observer: Arc<dyn ChainObserver>,
70        logger: Logger,
71        activation_state: Arc<dyn CardanoTransactionsPreloaderChecker>,
72    ) -> Self {
73        Self {
74            signed_entity_type_lock,
75            importer,
76            security_parameter,
77            chain_observer,
78            logger: logger.new_with_component_name::<Self>(),
79            activation_state,
80        }
81    }
82
83    /// Preload the Cardano Transactions by running the importer up to the current chain block number.
84    pub async fn preload(&self) -> StdResult<()> {
85        if !self.is_activated().await? {
86            debug!(self.logger, "Not running, conditions not met");
87            return Ok(());
88        }
89
90        info!(self.logger, "Started");
91        debug!(self.logger, "Locking signed entity type"; "entity_type" => "CardanoTransactions");
92        self.signed_entity_type_lock
93            .lock(SignedEntityTypeDiscriminants::CardanoTransactions)
94            .await;
95
96        let preload_result = self.do_preload().await;
97
98        debug!(self.logger, "Releasing signed entity type"; "entity_type" => "CardanoTransactions");
99        self.signed_entity_type_lock
100            .release(SignedEntityTypeDiscriminants::CardanoTransactions)
101            .await;
102        info!(self.logger, "Finished");
103
104        preload_result
105    }
106
107    /// Return the activation state of the preloader.
108    pub async fn is_activated(&self) -> StdResult<bool> {
109        self.activation_state.is_activated().await
110    }
111
112    async fn do_preload(&self) -> StdResult<()> {
113        let chain_point = self.chain_observer.get_current_chain_point().await?.with_context(
114            || "No chain point yielded by the chain observer, is your cardano node ready?",
115        )?;
116        let up_to_block_number = chain_point.block_number - self.security_parameter;
117        self.importer.import(up_to_block_number).await?;
118
119        Ok(())
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    use async_trait::async_trait;
126    use mockall::mock;
127    use mockall::predicate::eq;
128
129    use mithril_cardano_node_chain::test::double::FakeChainObserver;
130    use mithril_common::entities::{BlockNumber, ChainPoint, TimePoint};
131    use mithril_common::test::double::Dummy;
132
133    use crate::test_tools::TestLogger;
134
135    use super::*;
136
137    mock! {
138        pub TransactionsImporterImpl { }
139
140        #[async_trait]
141        impl TransactionsImporter for TransactionsImporterImpl {
142            async fn import(&self, up_to_beacon: BlockNumber) -> StdResult<()>;
143        }
144    }
145
146    struct ImporterWithSignedEntityTypeLockCheck {
147        signed_entity_type_lock: Arc<SignedEntityTypeLock>,
148    }
149    #[async_trait]
150    impl TransactionsImporter for ImporterWithSignedEntityTypeLockCheck {
151        async fn import(&self, _up_to_beacon: BlockNumber) -> StdResult<()> {
152            assert!(
153                self.signed_entity_type_lock
154                    .is_locked(SignedEntityTypeDiscriminants::CardanoTransactions)
155                    .await
156            );
157            Ok(())
158        }
159    }
160
161    fn build_chain_observer() -> (FakeChainObserver, BlockNumber, BlockNumber) {
162        let chain_block_number = BlockNumber(5000);
163        let security_parameter = BlockNumber(542);
164        let chain_observer = FakeChainObserver::new(Some(TimePoint {
165            chain_point: ChainPoint {
166                block_number: chain_block_number,
167                ..ChainPoint::dummy()
168            },
169            ..TimePoint::dummy()
170        }));
171        (chain_observer, chain_block_number, security_parameter)
172    }
173
174    #[tokio::test]
175    async fn call_its_inner_importer_when_is_activated() {
176        let (chain_observer, chain_block_number, security_parameter) = build_chain_observer();
177        let expected_parsed_block_number = chain_block_number - security_parameter;
178
179        let mut importer = MockTransactionsImporterImpl::new();
180        importer
181            .expect_import()
182            .times(1)
183            .with(eq(expected_parsed_block_number))
184            .returning(|_| Ok(()));
185
186        let preloader = CardanoTransactionsPreloader::new(
187            Arc::new(SignedEntityTypeLock::default()),
188            Arc::new(importer),
189            security_parameter,
190            Arc::new(chain_observer),
191            TestLogger::stdout(),
192            Arc::new(CardanoTransactionsPreloaderActivation::new(true)),
193        );
194
195        preloader.preload().await.unwrap();
196    }
197
198    #[tokio::test]
199    async fn do_not_call_its_inner_importer_when_is_not_activated() {
200        let (chain_observer, _, _) = build_chain_observer();
201        let mut importer = MockTransactionsImporterImpl::new();
202        importer.expect_import().never();
203
204        let preloader = CardanoTransactionsPreloader::new(
205            Arc::new(SignedEntityTypeLock::default()),
206            Arc::new(importer),
207            BlockNumber(542),
208            Arc::new(chain_observer),
209            TestLogger::stdout(),
210            Arc::new(CardanoTransactionsPreloaderActivation::new(false)),
211        );
212
213        preloader.preload().await.unwrap();
214    }
215
216    #[tokio::test]
217    async fn return_error_when_is_activated_return_error() {
218        let (chain_observer, _, _) = build_chain_observer();
219        let mut importer = MockTransactionsImporterImpl::new();
220        importer.expect_import().never();
221
222        let mut preloader_checker = MockCardanoTransactionsPreloaderChecker::new();
223        preloader_checker
224            .expect_is_activated()
225            .returning(|| Err(anyhow::anyhow!("error")));
226
227        let preloader = CardanoTransactionsPreloader::new(
228            Arc::new(SignedEntityTypeLock::default()),
229            Arc::new(importer),
230            BlockNumber(542),
231            Arc::new(chain_observer),
232            TestLogger::stdout(),
233            Arc::new(preloader_checker),
234        );
235
236        preloader
237            .preload()
238            .await
239            .expect_err("should raise an error with error from the activation");
240    }
241
242    #[tokio::test]
243    async fn fail_if_chain_point_is_not_available() {
244        let chain_observer = FakeChainObserver::new(None);
245        let mut importer = MockTransactionsImporterImpl::new();
246        importer.expect_import().never();
247
248        let preloader = CardanoTransactionsPreloader::new(
249            Arc::new(SignedEntityTypeLock::default()),
250            Arc::new(importer),
251            BlockNumber(0),
252            Arc::new(chain_observer),
253            TestLogger::stdout(),
254            Arc::new(CardanoTransactionsPreloaderActivation::new(true)),
255        );
256
257        preloader
258            .preload()
259            .await
260            .expect_err("should raise an error when chain point is not available");
261    }
262
263    #[tokio::test]
264    async fn should_lock_entity_type_while_preloading() {
265        let signed_entity_type_lock = Arc::new(SignedEntityTypeLock::default());
266
267        let preloader = CardanoTransactionsPreloader::new(
268            signed_entity_type_lock.clone(),
269            Arc::new(ImporterWithSignedEntityTypeLockCheck {
270                signed_entity_type_lock: signed_entity_type_lock.clone(),
271            }),
272            BlockNumber(0),
273            Arc::new(FakeChainObserver::new(Some(TimePoint::dummy()))),
274            TestLogger::stdout(),
275            Arc::new(CardanoTransactionsPreloaderActivation::new(true)),
276        );
277
278        assert!(
279            !signed_entity_type_lock
280                .is_locked(SignedEntityTypeDiscriminants::CardanoTransactions)
281                .await
282        );
283
284        preloader.preload().await.unwrap();
285
286        assert!(
287            !signed_entity_type_lock
288                .is_locked(SignedEntityTypeDiscriminants::CardanoTransactions)
289                .await
290        );
291    }
292
293    #[tokio::test]
294    async fn should_release_locked_entity_type_when_preloading_fail() {
295        let signed_entity_type_lock = Arc::new(SignedEntityTypeLock::default());
296        let chain_observer = FakeChainObserver::new(None);
297
298        let preloader = CardanoTransactionsPreloader::new(
299            signed_entity_type_lock.clone(),
300            Arc::new(ImporterWithSignedEntityTypeLockCheck {
301                signed_entity_type_lock: signed_entity_type_lock.clone(),
302            }),
303            BlockNumber(0),
304            Arc::new(chain_observer),
305            TestLogger::stdout(),
306            Arc::new(CardanoTransactionsPreloaderActivation::new(true)),
307        );
308
309        preloader.preload().await.unwrap_err();
310
311        assert!(
312            !signed_entity_type_lock
313                .is_locked(SignedEntityTypeDiscriminants::CardanoTransactions)
314                .await
315        );
316    }
317}