mithril_aggregator/services/certificate_chain_synchronizer/
synchronizer_service.rs

1//! # Certificate chain synchronizer
2//!
3//! Behavior:
4//! 1. Check force:
5//!    - If false, fetch the latest local genesis certificate in database
6//!       - If it's found, fetch the remote Genesis certificate
7//!          - If it's different from the local genesis, continue synchronization
8//!          - If it's the same, abort with an `Ok`
9//!       - If it's not found, continue synchronization
10//!    - If true, skip the remote Genesis certificate check and synchronize
11//! 2. Fetch then validate the latest remote certificate
12//!    - if valid, store it in an in-memory FIFO list
13//!    - if invalid, abort with an `Err`
14//! 3. Repeat step 2. with each parent of the certificate until the genesis certificate is reached
15//! 4. Store the fetched certificates in the database, from genesis to latest, for each certificate:
16//!    - if it exists in the database, it is replaced
17//!    - if it doesn't exist, it is inserted
18//! 5. Create a certified open message in the database, based on the latest certificate (the first
19//!    of the last epoch synchronized), only if the corresponding epoch settings exist (to avoid
20//!    foreign key constraint violations when synchronizing a gapped certificate chain)
21//! 6. End
22//!
23use anyhow::{Context, anyhow};
24use async_trait::async_trait;
25use chrono::Utc;
26use slog::{Logger, debug, info, warn};
27use std::collections::VecDeque;
28use std::sync::Arc;
29
30use mithril_common::StdResult;
31use mithril_common::certificate_chain::CertificateVerifier;
32use mithril_common::crypto_helper::ProtocolGenesisVerifier;
33use mithril_common::entities::{Certificate, SignedEntityType};
34use mithril_common::logging::LoggerExtensions;
35
36use crate::EpochSettingsStorer;
37use crate::entities::OpenMessage;
38
39use super::{
40    CertificateChainSynchronizer, OpenMessageStorer, RemoteCertificateRetriever,
41    SynchronizedCertificateStorer,
42};
43
44/// Service that synchronizes the certificate chain with a remote aggregator
45pub struct MithrilCertificateChainSynchronizer {
46    remote_certificate_retriever: Arc<dyn RemoteCertificateRetriever>,
47    certificate_storer: Arc<dyn SynchronizedCertificateStorer>,
48    certificate_verifier: Arc<dyn CertificateVerifier>,
49    genesis_verifier: Arc<ProtocolGenesisVerifier>,
50    open_message_storer: Arc<dyn OpenMessageStorer>,
51    epoch_settings_storer: Arc<dyn EpochSettingsStorer>,
52    logger: Logger,
53}
54
55#[derive(Debug, Copy, Clone, PartialEq, Eq)]
56enum SyncStatus {
57    Forced,
58    NoLocalGenesis,
59    RemoteGenesisMatchesLocalGenesis,
60    RemoteGenesisDoesntMatchLocalGenesis,
61}
62
63impl SyncStatus {
64    fn should_sync(&self) -> bool {
65        match self {
66            SyncStatus::Forced => true,
67            SyncStatus::NoLocalGenesis => true,
68            SyncStatus::RemoteGenesisMatchesLocalGenesis => false,
69            SyncStatus::RemoteGenesisDoesntMatchLocalGenesis => true,
70        }
71    }
72}
73
74impl MithrilCertificateChainSynchronizer {
75    /// Create a new `MithrilCertificateChainSynchronizer` instance
76    pub fn new(
77        remote_certificate_retriever: Arc<dyn RemoteCertificateRetriever>,
78        certificate_storer: Arc<dyn SynchronizedCertificateStorer>,
79        certificate_verifier: Arc<dyn CertificateVerifier>,
80        genesis_verifier: Arc<ProtocolGenesisVerifier>,
81        open_message_storer: Arc<dyn OpenMessageStorer>,
82        epoch_settings_storer: Arc<dyn EpochSettingsStorer>,
83        logger: Logger,
84    ) -> Self {
85        Self {
86            remote_certificate_retriever,
87            certificate_storer,
88            certificate_verifier,
89            genesis_verifier,
90            open_message_storer,
91            epoch_settings_storer,
92            logger: logger.new_with_component_name::<Self>(),
93        }
94    }
95
96    async fn check_sync_state(&self, force: bool) -> StdResult<SyncStatus> {
97        if force {
98            return Ok(SyncStatus::Forced);
99        }
100
101        match self.certificate_storer.get_latest_genesis().await? {
102            Some(local_genesis) => {
103                match self
104                    .remote_certificate_retriever
105                    .get_genesis_certificate_details()
106                    .await?
107                {
108                    Some(remote_genesis) if (local_genesis == remote_genesis) => {
109                        Ok(SyncStatus::RemoteGenesisMatchesLocalGenesis)
110                    }
111                    Some(_) => Ok(SyncStatus::RemoteGenesisDoesntMatchLocalGenesis),
112                    // The remote aggregator doesn't have a chain yet, we can't sync
113                    None => Err(anyhow!("Remote aggregator doesn't have a chain yet")),
114                }
115            }
116            None => Ok(SyncStatus::NoLocalGenesis),
117        }
118    }
119
120    async fn retrieve_and_validate_remote_certificate_chain(
121        &self,
122        starting_point: Certificate,
123    ) -> StdResult<Vec<Certificate>> {
124        // IMPORTANT: Order matters, returned certificates must be ordered from genesis to latest
125        // (fetched database data is returned from last inserted to oldest)
126        let mut validated_certificates = VecDeque::new();
127        let mut certificate = starting_point;
128
129        loop {
130            let parent_certificate = self
131                .certificate_verifier
132                .verify_certificate(&certificate, &self.genesis_verifier.to_verification_key())
133                .await
134                .with_context(
135                    || format!("Failed to verify certificate: `{}`", certificate.hash,),
136                )?;
137
138            match parent_certificate {
139                None => {
140                    validated_certificates.push_front(certificate);
141                    break;
142                }
143                Some(parent) => {
144                    // At the start of the retrieval the first certificate may not be the first of
145                    // its epoch, filter them out since we only need one certificate per epoch
146                    if !validated_certificates.is_empty() || parent.epoch != certificate.epoch {
147                        validated_certificates.push_front(certificate);
148                    }
149
150                    certificate = parent;
151                }
152            }
153        }
154
155        Ok(validated_certificates.into())
156    }
157
158    async fn store_certificate_chain(&self, certificate_chain: Vec<Certificate>) -> StdResult<()> {
159        self.certificate_storer
160            .insert_or_replace_many(certificate_chain)
161            .await?;
162        Ok(())
163    }
164}
165
166#[async_trait]
167impl CertificateChainSynchronizer for MithrilCertificateChainSynchronizer {
168    async fn synchronize_certificate_chain(&self, force: bool) -> StdResult<()> {
169        debug!(self.logger, ">> synchronize_certificate_chain"; "force" => force);
170
171        let sync_state = self.check_sync_state(force).await.with_context(|| {
172            format!("Failed to check if certificate chain should be sync (force: `{force}`)")
173        })?;
174        if sync_state.should_sync() {
175            info!(self.logger, "Start synchronizing certificate chain"; "sync_state" => ?sync_state);
176        } else {
177            info!(self.logger, "No need to synchronize certificate chain"; "sync_state" => ?sync_state);
178            return Ok(());
179        }
180
181        let starting_point = self
182            .remote_certificate_retriever
183            .get_latest_certificate_details()
184            .await?
185            .with_context(|| "Failed to retrieve latest remote certificate details")
186            .with_context(|| "Remote aggregator doesn't have a chain yet")?;
187        let remote_certificate_chain = self
188            .retrieve_and_validate_remote_certificate_chain(starting_point)
189            .await
190            .with_context(|| "Failed to retrieve and validate remote certificate chain")?;
191        let latest_certificate = remote_certificate_chain
192            .last()
193            .with_context(|| "Retrieved certificate chain is empty")?
194            .clone();
195
196        self.store_certificate_chain(remote_certificate_chain)
197            .await
198            .with_context(|| "Failed to store remote retrieved certificate chain")?;
199
200        // In case the synchronized certificate chain is incomplete, the open message creation
201        // may be skipped to avoid foreign key constraint violations which could occur with the absence of
202        // epoch settings (as we they are pruned regularly) for the latest synchronized certificate's epoch.
203        // This situation may arise when synchronizing a gapped certificate chain.
204        let epoch_settings_exist = self
205            .epoch_settings_storer
206            .get_epoch_settings(latest_certificate.epoch)
207            .await?
208            .is_some();
209        if epoch_settings_exist {
210            let open_message = prepare_open_message_to_store(&latest_certificate);
211            self.open_message_storer
212                .insert_or_replace_open_message(open_message)
213                .await
214                .with_context(
215                    || "Failed to store open message when synchronizing certificate chain",
216                )?;
217        } else {
218            warn!(
219                self.logger,
220                "Skipping open message creation: no epoch settings for epoch {}",
221                latest_certificate.epoch
222            );
223        }
224
225        info!(
226            self.logger,
227            "Certificate chain synchronized with remote source"
228        );
229        Ok(())
230    }
231}
232
233fn prepare_open_message_to_store(latest_certificate: &Certificate) -> OpenMessage {
234    OpenMessage {
235        epoch: latest_certificate.epoch,
236        signed_entity_type: SignedEntityType::MithrilStakeDistribution(latest_certificate.epoch),
237        protocol_message: latest_certificate.protocol_message.clone(),
238        is_certified: true,
239        is_expired: false,
240        single_signatures: Vec::new(),
241        created_at: Utc::now(),
242        expires_at: None,
243    }
244}
245
246#[cfg(test)]
247mod tests {
248    use anyhow::anyhow;
249    use std::sync::RwLock;
250
251    use mithril_common::certificate_chain::MithrilCertificateVerifier;
252    use mithril_common::entities::Epoch;
253    use mithril_common::test::{
254        builder::{CertificateChainBuilder, CertificateChainFixture},
255        double::{Dummy, FakeCertificaterRetriever, fake_data, fake_keys},
256        mock_extensions::MockBuilder,
257    };
258
259    use crate::entities::AggregatorEpochSettings;
260    use crate::services::{
261        MockOpenMessageStorer, MockRemoteCertificateRetriever, MockSynchronizedCertificateStorer,
262    };
263    use crate::store::FakeEpochSettingsStorer;
264    use crate::test::TestLogger;
265    use crate::test::double::mocks::MockCertificateVerifier;
266
267    use super::*;
268
269    impl MithrilCertificateChainSynchronizer {
270        fn default_for_test() -> Self {
271            Self::default_for_test_with_epoch_settings(vec![])
272        }
273
274        fn default_for_test_with_epoch_settings(
275            epoch_settings: Vec<(Epoch, AggregatorEpochSettings)>,
276        ) -> Self {
277            let genesis_verification_key =
278                fake_keys::genesis_verification_key()[0].try_into().unwrap();
279            Self::new(
280                Arc::new(MockRemoteCertificateRetriever::new()),
281                Arc::new(MockSynchronizedCertificateStorer::new()),
282                Arc::new(MockCertificateVerifier::new()),
283                Arc::new(ProtocolGenesisVerifier::from_verification_key(
284                    genesis_verification_key,
285                )),
286                Arc::new(MockOpenMessageStorer::new()),
287                Arc::new(FakeEpochSettingsStorer::new(epoch_settings)),
288                TestLogger::stdout(),
289            )
290        }
291    }
292
293    macro_rules! mocked_synchronizer {
294        (with_remote_genesis: $remote_genesis_result:expr) => {
295            MithrilCertificateChainSynchronizer {
296                remote_certificate_retriever:
297                    MockBuilder::<MockRemoteCertificateRetriever>::configure(|retriever| {
298                        retriever
299                            .expect_get_genesis_certificate_details()
300                            .return_once(move || $remote_genesis_result);
301                    }),
302                ..MithrilCertificateChainSynchronizer::default_for_test()
303            }
304        };
305        (with_local_genesis: $local_genesis_result:expr) => {
306            MithrilCertificateChainSynchronizer {
307                certificate_storer: MockBuilder::<MockSynchronizedCertificateStorer>::configure(
308                    |storer| {
309                        storer
310                            .expect_get_latest_genesis()
311                            .return_once(move || $local_genesis_result);
312                    },
313                ),
314                ..MithrilCertificateChainSynchronizer::default_for_test()
315            }
316        };
317        (with_remote_genesis: $remote_genesis_result:expr, with_local_genesis: $local_genesis_result:expr) => {
318            MithrilCertificateChainSynchronizer {
319                remote_certificate_retriever:
320                    MockBuilder::<MockRemoteCertificateRetriever>::configure(|retriever| {
321                        retriever
322                            .expect_get_genesis_certificate_details()
323                            .return_once(move || $remote_genesis_result);
324                    }),
325                certificate_storer: MockBuilder::<MockSynchronizedCertificateStorer>::configure(
326                    |storer| {
327                        storer
328                            .expect_get_latest_genesis()
329                            .return_once(move || $local_genesis_result);
330                    },
331                ),
332                ..MithrilCertificateChainSynchronizer::default_for_test()
333            }
334        };
335        (with_verify_certificate_result: $verify_certificate_result:expr) => {
336            MithrilCertificateChainSynchronizer {
337                certificate_verifier: MockBuilder::<MockCertificateVerifier>::configure(
338                    |verifier| {
339                        verifier
340                            .expect_verify_certificate()
341                            .return_once(move |_, _| $verify_certificate_result);
342                    },
343                ),
344                ..MithrilCertificateChainSynchronizer::default_for_test()
345            }
346        };
347    }
348
349    fn fake_verifier(remote_certificate_chain: &[Certificate]) -> Arc<dyn CertificateVerifier> {
350        let verifier = MithrilCertificateVerifier::new(
351            TestLogger::stdout(),
352            Arc::new(FakeCertificaterRetriever::from_certificates(
353                remote_certificate_chain,
354            )),
355        );
356        Arc::new(verifier)
357    }
358
359    #[derive(Default)]
360    struct DumbCertificateStorer {
361        certificates: RwLock<Vec<Certificate>>,
362        genesis_certificate: Option<Certificate>,
363    }
364
365    impl DumbCertificateStorer {
366        fn new(genesis: Certificate, already_stored: Vec<Certificate>) -> Self {
367            Self {
368                certificates: RwLock::new(already_stored),
369                genesis_certificate: Some(genesis),
370            }
371        }
372
373        fn stored_certificates(&self) -> Vec<Certificate> {
374            self.certificates.read().unwrap().clone()
375        }
376    }
377
378    #[async_trait]
379    impl SynchronizedCertificateStorer for DumbCertificateStorer {
380        async fn insert_or_replace_many(
381            &self,
382            certificates_chain: Vec<Certificate>,
383        ) -> StdResult<()> {
384            let mut certificates = self.certificates.write().unwrap();
385            *certificates = certificates_chain;
386            Ok(())
387        }
388
389        async fn get_latest_genesis(&self) -> StdResult<Option<Certificate>> {
390            Ok(self.genesis_certificate.clone())
391        }
392    }
393
394    mod check_sync_state {
395        use super::*;
396
397        #[test]
398        fn sync_state_should_sync() {
399            assert!(SyncStatus::Forced.should_sync());
400            assert!(!SyncStatus::RemoteGenesisMatchesLocalGenesis.should_sync());
401            assert!(SyncStatus::RemoteGenesisDoesntMatchLocalGenesis.should_sync());
402            assert!(SyncStatus::NoLocalGenesis.should_sync());
403        }
404
405        #[tokio::test]
406        async fn state_when_force_true() {
407            let synchronizer = MithrilCertificateChainSynchronizer::default_for_test();
408
409            let sync_state = synchronizer.check_sync_state(true).await.unwrap();
410            assert_eq!(SyncStatus::Forced, sync_state);
411        }
412
413        #[tokio::test]
414        async fn state_when_force_false_and_no_local_genesis_certificate_found() {
415            let synchronizer = mocked_synchronizer!(with_local_genesis: Ok(None));
416
417            let sync_state = synchronizer.check_sync_state(false).await.unwrap();
418            assert_eq!(SyncStatus::NoLocalGenesis, sync_state);
419        }
420
421        #[tokio::test]
422        async fn state_when_force_false_and_remote_genesis_dont_matches_local_genesis() {
423            let synchronizer = mocked_synchronizer!(
424                with_remote_genesis: Ok(Some(fake_data::genesis_certificate("remote_genesis"))),
425                with_local_genesis: Ok(Some(fake_data::genesis_certificate("local_genesis")))
426            );
427
428            let sync_state = synchronizer.check_sync_state(false).await.unwrap();
429            assert_eq!(SyncStatus::RemoteGenesisDoesntMatchLocalGenesis, sync_state);
430        }
431
432        #[tokio::test]
433        async fn state_when_force_false_and_remote_genesis_matches_local_genesis() {
434            let remote_genesis = fake_data::genesis_certificate("genesis");
435            let local_genesis = remote_genesis.clone();
436            let synchronizer = mocked_synchronizer!(
437                with_remote_genesis: Ok(Some(remote_genesis)),
438                with_local_genesis: Ok(Some(local_genesis))
439            );
440
441            let sync_state = synchronizer.check_sync_state(false).await.unwrap();
442            assert_eq!(SyncStatus::RemoteGenesisMatchesLocalGenesis, sync_state);
443        }
444
445        #[tokio::test]
446        async fn if_force_true_it_should_not_fetch_remote_genesis_certificate() {
447            let synchronizer = mocked_synchronizer!(with_remote_genesis: Err(anyhow!(
448                "should not fetch genesis"
449            )));
450
451            synchronizer.check_sync_state(true).await.unwrap();
452        }
453
454        #[tokio::test]
455        async fn should_abort_with_error_if_force_false_and_fails_to_retrieve_local_genesis() {
456            let synchronizer = mocked_synchronizer!(with_local_genesis: Err(anyhow!("failure")));
457            synchronizer
458                .check_sync_state(false)
459                .await
460                .expect_err("Expected an error but was:");
461        }
462
463        #[tokio::test]
464        async fn should_abort_with_error_if_force_false_and_fails_to_retrieve_remote_genesis() {
465            let synchronizer = mocked_synchronizer!(
466                with_remote_genesis: Err(anyhow!("failure")),
467                with_local_genesis: Ok(Some(fake_data::genesis_certificate("local_genesis")))
468            );
469            synchronizer
470                .check_sync_state(false)
471                .await
472                .expect_err("Expected an error but was:");
473        }
474
475        #[tokio::test]
476        async fn should_abort_with_error_if_force_false_and_remote_genesis_is_none() {
477            let synchronizer = mocked_synchronizer!(
478                with_remote_genesis: Ok(None),
479                with_local_genesis: Ok(Some(fake_data::genesis_certificate("local_genesis")))
480            );
481            let error = synchronizer
482                .check_sync_state(false)
483                .await
484                .expect_err("Expected an error but was:");
485
486            assert!(
487                error
488                    .to_string()
489                    .contains("Remote aggregator doesn't have a chain yet"),
490                "Unexpected error:\n{error:?}"
491            );
492        }
493    }
494
495    mod retrieve_validate_remote_certificate_chain {
496        use mockall::predicate::{always, eq};
497
498        use mithril_common::entities::Epoch;
499
500        use super::*;
501
502        #[tokio::test]
503        async fn succeed_if_the_remote_chain_only_contains_a_genesis_certificate() {
504            let chain = CertificateChainBuilder::new().with_total_certificates(1).build();
505            let synchronizer = MithrilCertificateChainSynchronizer {
506                certificate_verifier: fake_verifier(&chain),
507                genesis_verifier: Arc::new(chain.genesis_verifier.clone()),
508                ..MithrilCertificateChainSynchronizer::default_for_test()
509            };
510
511            let starting_point = chain[0].clone();
512            let remote_certificate_chain = synchronizer
513                .retrieve_and_validate_remote_certificate_chain(starting_point)
514                .await
515                .unwrap();
516
517            assert_eq!(remote_certificate_chain, chain.certificates_chained);
518        }
519
520        #[tokio::test]
521        async fn abort_with_error_if_a_certificate_is_invalid() {
522            let synchronizer = mocked_synchronizer!(with_verify_certificate_result: Err(anyhow!("invalid certificate")));
523
524            let starting_point = fake_data::certificate("certificate");
525            synchronizer
526                .retrieve_and_validate_remote_certificate_chain(starting_point)
527                .await
528                .expect_err("Expected an error but was:");
529        }
530
531        #[tokio::test]
532        async fn succeed_with_a_valid_certificate_chain_and_only_get_first_certificate_of_each_epoch_plus_genesis()
533         {
534            // Note: the `CertificateChainBuilder` use one epoch for the genesis only, so in order
535            // for the last epoch to have two certificates when `certificates_per_epoch` is an *even*
536            // number, we need to set `total_certificates` to an *odd* number
537            let chain = CertificateChainBuilder::new()
538                .with_total_certificates(9)
539                .with_certificates_per_epoch(2)
540                .build();
541            let synchronizer = MithrilCertificateChainSynchronizer {
542                certificate_verifier: fake_verifier(&chain),
543                genesis_verifier: Arc::new(chain.genesis_verifier.clone()),
544                ..MithrilCertificateChainSynchronizer::default_for_test()
545            };
546
547            let starting_point = chain[0].clone();
548            let remote_certificate_chain = synchronizer
549                .retrieve_and_validate_remote_certificate_chain(starting_point.clone())
550                .await
551                .unwrap();
552
553            let mut expected = chain.certificate_path_to_genesis(&starting_point.hash);
554            // Remote certificate chain is returned ordered from genesis to latest
555            expected.reverse();
556            // Remove the latest certificate has it's not the first of its epoch
557            expected.pop();
558            assert_eq!(remote_certificate_chain, expected);
559        }
560
561        #[tokio::test]
562        async fn return_chain_ordered_from_genesis_to_latest() {
563            let base_certificate = fake_data::certificate("whatever");
564            let chain = [
565                Certificate {
566                    epoch: Epoch(2),
567                    ..fake_data::genesis_certificate("genesis")
568                },
569                Certificate {
570                    epoch: Epoch(3),
571                    hash: "hash1".to_string(),
572                    previous_hash: "genesis".to_string(),
573                    ..base_certificate.clone()
574                },
575                Certificate {
576                    epoch: Epoch(4),
577                    hash: "hash2".to_string(),
578                    previous_hash: "hash1".to_string(),
579                    ..base_certificate
580                },
581            ];
582            let synchronizer = MithrilCertificateChainSynchronizer {
583                certificate_verifier: MockBuilder::<MockCertificateVerifier>::configure(|mock| {
584                    let cert_1 = chain[1].clone();
585                    mock.expect_verify_certificate()
586                        .with(eq(chain[2].clone()), always())
587                        .return_once(move |_, _| Ok(Some(cert_1)));
588                    let genesis = chain[0].clone();
589                    mock.expect_verify_certificate()
590                        .with(eq(chain[1].clone()), always())
591                        .return_once(move |_, _| Ok(Some(genesis)));
592                    mock.expect_verify_certificate()
593                        .with(eq(chain[0].clone()), always())
594                        .return_once(move |_, _| Ok(None));
595                }),
596                ..MithrilCertificateChainSynchronizer::default_for_test()
597            };
598
599            let starting_point = chain[2].clone();
600            let remote_certificate_chain = synchronizer
601                .retrieve_and_validate_remote_certificate_chain(starting_point.clone())
602                .await
603                .unwrap();
604
605            assert_eq!(
606                remote_certificate_chain
607                    .into_iter()
608                    .map(|c| c.hash)
609                    .collect::<Vec<_>>(),
610                vec!["genesis".to_string(), "hash1".to_string(), "hash2".to_string()]
611            );
612        }
613    }
614
615    mod store_remote_certificate_chain {
616        use super::*;
617
618        #[tokio::test]
619        async fn do_store_given_certificates() {
620            let certificates_chain = vec![
621                fake_data::genesis_certificate("genesis"),
622                fake_data::certificate("certificate1"),
623                fake_data::certificate("certificate2"),
624            ];
625            let storer = Arc::new(DumbCertificateStorer::default());
626            let synchronizer = MithrilCertificateChainSynchronizer {
627                certificate_storer: storer.clone(),
628                ..MithrilCertificateChainSynchronizer::default_for_test()
629            };
630
631            assert_eq!(Vec::<Certificate>::new(), storer.stored_certificates());
632
633            synchronizer
634                .store_certificate_chain(certificates_chain.clone())
635                .await
636                .unwrap();
637
638            assert_eq!(certificates_chain, storer.stored_certificates());
639        }
640
641        #[tokio::test]
642        async fn fail_on_storer_error() {
643            let synchronizer = MithrilCertificateChainSynchronizer {
644                certificate_storer: MockBuilder::<MockSynchronizedCertificateStorer>::configure(
645                    |mock| {
646                        mock.expect_insert_or_replace_many()
647                            .return_once(move |_| Err(anyhow!("failure")));
648                    },
649                ),
650                ..MithrilCertificateChainSynchronizer::default_for_test()
651            };
652
653            synchronizer
654                .store_certificate_chain(vec![fake_data::certificate("certificate")])
655                .await
656                .unwrap_err();
657        }
658    }
659
660    mod synchronize_certificate_chain {
661        use mockall::predicate::function;
662
663        use super::*;
664
665        fn build_synchronizer(
666            remote_chain: &CertificateChainFixture,
667            storer: Arc<dyn SynchronizedCertificateStorer>,
668        ) -> MithrilCertificateChainSynchronizer {
669            build_synchronizer_with_epoch_settings(remote_chain, storer, true)
670        }
671
672        fn build_synchronizer_with_epoch_settings(
673            remote_chain: &CertificateChainFixture,
674            storer: Arc<dyn SynchronizedCertificateStorer>,
675            expect_open_message_creation: bool,
676        ) -> MithrilCertificateChainSynchronizer {
677            let latest_epoch = remote_chain.latest_certificate().epoch;
678
679            let epoch_settings = if expect_open_message_creation {
680                vec![(latest_epoch, AggregatorEpochSettings::dummy())]
681            } else {
682                vec![]
683            };
684
685            MithrilCertificateChainSynchronizer {
686                certificate_storer: storer.clone(),
687                remote_certificate_retriever:
688                    MockBuilder::<MockRemoteCertificateRetriever>::configure(|mock| {
689                        let genesis = remote_chain.genesis_certificate().clone();
690                        mock.expect_get_genesis_certificate_details()
691                            .return_once(move || Ok(Some(genesis)));
692                        let latest = remote_chain.latest_certificate().clone();
693                        mock.expect_get_latest_certificate_details()
694                            .return_once(move || Ok(Some(latest)));
695                    }),
696                certificate_verifier: fake_verifier(remote_chain),
697                open_message_storer: MockBuilder::<MockOpenMessageStorer>::configure(|mock| {
698                    if expect_open_message_creation {
699                        let expected_msd_epoch = latest_epoch;
700                        mock.expect_insert_or_replace_open_message()
701                            .with(function(move |open_message: &OpenMessage| {
702                                open_message.signed_entity_type
703                                    == SignedEntityType::MithrilStakeDistribution(
704                                        expected_msd_epoch,
705                                    )
706                            }))
707                            .times(1..)
708                            .returning(|_| Ok(()));
709                    } else {
710                        mock.expect_insert_or_replace_open_message().never();
711                    }
712                }),
713                ..MithrilCertificateChainSynchronizer::default_for_test_with_epoch_settings(
714                    epoch_settings,
715                )
716            }
717        }
718
719        #[tokio::test]
720        async fn store_all() {
721            let remote_chain = CertificateChainBuilder::default()
722                .with_certificates_per_epoch(3)
723                .with_total_certificates(8)
724                .build();
725            let storer = Arc::new(DumbCertificateStorer::default());
726            let synchronizer = build_synchronizer(&remote_chain, storer.clone());
727
728            // Will sync even if force is false
729            synchronizer.synchronize_certificate_chain(false).await.unwrap();
730
731            let mut expected =
732                remote_chain.certificate_path_to_genesis(&remote_chain.latest_certificate().hash);
733            expected.reverse();
734            assert_eq!(expected, storer.stored_certificates());
735        }
736
737        #[tokio::test]
738        async fn store_partial() {
739            let remote_chain = CertificateChainBuilder::default()
740                .with_certificates_per_epoch(1)
741                .with_total_certificates(8)
742                .build();
743            let existing_certificates =
744                remote_chain.certificate_path_to_genesis(&remote_chain[5].hash);
745            let storer = Arc::new(DumbCertificateStorer::new(
746                remote_chain.genesis_certificate().clone(),
747                existing_certificates.clone(),
748            ));
749            let synchronizer = build_synchronizer(&remote_chain, storer.clone());
750
751            // Force false - won't sync
752            synchronizer.synchronize_certificate_chain(false).await.unwrap();
753
754            assert_eq!(&existing_certificates, &storer.stored_certificates());
755
756            // Force true - will sync
757            synchronizer.synchronize_certificate_chain(true).await.unwrap();
758
759            let mut expected =
760                remote_chain.certificate_path_to_genesis(&remote_chain.latest_certificate().hash);
761            expected.reverse();
762            assert_eq!(expected, storer.stored_certificates());
763        }
764
765        #[tokio::test]
766        async fn skips_open_message_creation_when_epoch_settings_do_not_exist() {
767            let remote_chain = CertificateChainBuilder::default()
768                .with_certificates_per_epoch(1)
769                .with_total_certificates(5)
770                .build();
771            let storer = Arc::new(DumbCertificateStorer::default());
772            let synchronizer =
773                build_synchronizer_with_epoch_settings(&remote_chain, storer.clone(), false);
774
775            synchronizer.synchronize_certificate_chain(false).await.unwrap();
776
777            let mut expected =
778                remote_chain.certificate_path_to_genesis(&remote_chain.latest_certificate().hash);
779            expected.reverse();
780            assert_eq!(expected, storer.stored_certificates());
781        }
782    }
783}