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::{debug, info, Logger};
12
13use mithril_cardano_node_chain::chain_observer::ChainObserver;
14use mithril_common::{
15    entities::{BlockNumber, SignedEntityTypeDiscriminants},
16    logging::LoggerExtensions,
17    signable_builder::TransactionsImporter,
18    StdResult,
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
114            .chain_observer
115            .get_current_chain_point()
116            .await?
117            .with_context(|| {
118                "No chain point yielded by the chain observer, is your cardano node ready?"
119            })?;
120        let up_to_block_number = chain_point.block_number - self.security_parameter;
121        self.importer.import(up_to_block_number).await?;
122
123        Ok(())
124    }
125}
126
127#[cfg(test)]
128mod tests {
129    use async_trait::async_trait;
130    use mockall::mock;
131    use mockall::predicate::eq;
132
133    use mithril_cardano_node_chain::test::double::FakeChainObserver;
134    use mithril_common::entities::{BlockNumber, ChainPoint, TimePoint};
135
136    use crate::test_tools::TestLogger;
137
138    use super::*;
139
140    mock! {
141        pub TransactionsImporterImpl { }
142
143        #[async_trait]
144        impl TransactionsImporter for TransactionsImporterImpl {
145            async fn import(&self, up_to_beacon: BlockNumber) -> StdResult<()>;
146        }
147    }
148
149    struct ImporterWithSignedEntityTypeLockCheck {
150        signed_entity_type_lock: Arc<SignedEntityTypeLock>,
151    }
152    #[async_trait]
153    impl TransactionsImporter for ImporterWithSignedEntityTypeLockCheck {
154        async fn import(&self, _up_to_beacon: BlockNumber) -> StdResult<()> {
155            assert!(
156                self.signed_entity_type_lock
157                    .is_locked(SignedEntityTypeDiscriminants::CardanoTransactions)
158                    .await
159            );
160            Ok(())
161        }
162    }
163
164    fn build_chain_observer() -> (FakeChainObserver, BlockNumber, BlockNumber) {
165        let chain_block_number = BlockNumber(5000);
166        let security_parameter = BlockNumber(542);
167        let chain_observer = FakeChainObserver::new(Some(TimePoint {
168            chain_point: ChainPoint {
169                block_number: chain_block_number,
170                ..ChainPoint::dummy()
171            },
172            ..TimePoint::dummy()
173        }));
174        (chain_observer, chain_block_number, security_parameter)
175    }
176
177    #[tokio::test]
178    async fn call_its_inner_importer_when_is_activated() {
179        let (chain_observer, chain_block_number, security_parameter) = build_chain_observer();
180        let expected_parsed_block_number = chain_block_number - security_parameter;
181
182        let mut importer = MockTransactionsImporterImpl::new();
183        importer
184            .expect_import()
185            .times(1)
186            .with(eq(expected_parsed_block_number))
187            .returning(|_| Ok(()));
188
189        let preloader = CardanoTransactionsPreloader::new(
190            Arc::new(SignedEntityTypeLock::default()),
191            Arc::new(importer),
192            security_parameter,
193            Arc::new(chain_observer),
194            TestLogger::stdout(),
195            Arc::new(CardanoTransactionsPreloaderActivation::new(true)),
196        );
197
198        preloader.preload().await.unwrap();
199    }
200
201    #[tokio::test]
202    async fn do_not_call_its_inner_importer_when_is_not_activated() {
203        let (chain_observer, _, _) = build_chain_observer();
204        let mut importer = MockTransactionsImporterImpl::new();
205        importer.expect_import().never();
206
207        let preloader = CardanoTransactionsPreloader::new(
208            Arc::new(SignedEntityTypeLock::default()),
209            Arc::new(importer),
210            BlockNumber(542),
211            Arc::new(chain_observer),
212            TestLogger::stdout(),
213            Arc::new(CardanoTransactionsPreloaderActivation::new(false)),
214        );
215
216        preloader.preload().await.unwrap();
217    }
218
219    #[tokio::test]
220    async fn return_error_when_is_activated_return_error() {
221        let (chain_observer, _, _) = build_chain_observer();
222        let mut importer = MockTransactionsImporterImpl::new();
223        importer.expect_import().never();
224
225        let mut preloader_checker = MockCardanoTransactionsPreloaderChecker::new();
226        preloader_checker
227            .expect_is_activated()
228            .returning(|| Err(anyhow::anyhow!("error")));
229
230        let preloader = CardanoTransactionsPreloader::new(
231            Arc::new(SignedEntityTypeLock::default()),
232            Arc::new(importer),
233            BlockNumber(542),
234            Arc::new(chain_observer),
235            TestLogger::stdout(),
236            Arc::new(preloader_checker),
237        );
238
239        preloader
240            .preload()
241            .await
242            .expect_err("should raise an error with error from the activation");
243    }
244
245    #[tokio::test]
246    async fn fail_if_chain_point_is_not_available() {
247        let chain_observer = FakeChainObserver::new(None);
248        let mut importer = MockTransactionsImporterImpl::new();
249        importer.expect_import().never();
250
251        let preloader = CardanoTransactionsPreloader::new(
252            Arc::new(SignedEntityTypeLock::default()),
253            Arc::new(importer),
254            BlockNumber(0),
255            Arc::new(chain_observer),
256            TestLogger::stdout(),
257            Arc::new(CardanoTransactionsPreloaderActivation::new(true)),
258        );
259
260        preloader
261            .preload()
262            .await
263            .expect_err("should raise an error when chain point is not available");
264    }
265
266    #[tokio::test]
267    async fn should_lock_entity_type_while_preloading() {
268        let signed_entity_type_lock = Arc::new(SignedEntityTypeLock::default());
269
270        let preloader = CardanoTransactionsPreloader::new(
271            signed_entity_type_lock.clone(),
272            Arc::new(ImporterWithSignedEntityTypeLockCheck {
273                signed_entity_type_lock: signed_entity_type_lock.clone(),
274            }),
275            BlockNumber(0),
276            Arc::new(FakeChainObserver::new(Some(TimePoint::dummy()))),
277            TestLogger::stdout(),
278            Arc::new(CardanoTransactionsPreloaderActivation::new(true)),
279        );
280
281        assert!(
282            !signed_entity_type_lock
283                .is_locked(SignedEntityTypeDiscriminants::CardanoTransactions)
284                .await
285        );
286
287        preloader.preload().await.unwrap();
288
289        assert!(
290            !signed_entity_type_lock
291                .is_locked(SignedEntityTypeDiscriminants::CardanoTransactions)
292                .await
293        );
294    }
295
296    #[tokio::test]
297    async fn should_release_locked_entity_type_when_preloading_fail() {
298        let signed_entity_type_lock = Arc::new(SignedEntityTypeLock::default());
299        let chain_observer = FakeChainObserver::new(None);
300
301        let preloader = CardanoTransactionsPreloader::new(
302            signed_entity_type_lock.clone(),
303            Arc::new(ImporterWithSignedEntityTypeLockCheck {
304                signed_entity_type_lock: signed_entity_type_lock.clone(),
305            }),
306            BlockNumber(0),
307            Arc::new(chain_observer),
308            TestLogger::stdout(),
309            Arc::new(CardanoTransactionsPreloaderActivation::new(true)),
310        );
311
312        preloader.preload().await.unwrap_err();
313
314        assert!(
315            !signed_entity_type_lock
316                .is_locked(SignedEntityTypeDiscriminants::CardanoTransactions)
317                .await
318        );
319    }
320}