mithril_client/certificate_client/
verify.rs

1use anyhow::{anyhow, Context};
2use async_trait::async_trait;
3use slog::{trace, Logger};
4use std::sync::Arc;
5
6use mithril_common::{
7    certificate_chain::{
8        CertificateRetriever, CertificateVerifier as CommonCertificateVerifier,
9        MithrilCertificateVerifier as CommonMithrilCertificateVerifier,
10    },
11    crypto_helper::ProtocolGenesisVerificationKey,
12    entities::Certificate,
13    logging::LoggerExtensions,
14};
15
16use crate::aggregator_client::AggregatorClient;
17use crate::certificate_client::fetch::InternalCertificateRetriever;
18#[cfg(feature = "unstable")]
19use crate::certificate_client::CertificateVerifierCache;
20use crate::certificate_client::{CertificateClient, CertificateVerifier};
21use crate::feedback::{FeedbackSender, MithrilEvent};
22use crate::{MithrilCertificate, MithrilResult};
23
24#[inline]
25pub(super) async fn verify_chain(
26    client: &CertificateClient,
27    certificate_hash: &str,
28) -> MithrilResult<MithrilCertificate> {
29    let certificate = client
30        .retriever
31        .get(certificate_hash)
32        .await?
33        .ok_or(anyhow!(
34            "No certificate exist for hash '{certificate_hash}'"
35        ))?;
36
37    client
38        .verifier
39        .verify_chain(&certificate)
40        .await
41        .with_context(|| {
42            format!("Certificate chain of certificate '{certificate_hash}' is invalid")
43        })?;
44
45    Ok(certificate)
46}
47
48/// Implementation of a [CertificateVerifier] that can send feedbacks using
49/// the [feedback][crate::feedback] mechanism.
50pub struct MithrilCertificateVerifier {
51    retriever: Arc<InternalCertificateRetriever>,
52    internal_verifier: Arc<dyn CommonCertificateVerifier>,
53    genesis_verification_key: ProtocolGenesisVerificationKey,
54    feedback_sender: FeedbackSender,
55    #[cfg(feature = "unstable")]
56    verifier_cache: Option<Arc<dyn CertificateVerifierCache>>,
57    logger: Logger,
58}
59
60impl MithrilCertificateVerifier {
61    /// Constructs a new `MithrilCertificateVerifier`.
62    pub fn new(
63        aggregator_client: Arc<dyn AggregatorClient>,
64        genesis_verification_key: &str,
65        feedback_sender: FeedbackSender,
66        #[cfg(feature = "unstable")] verifier_cache: Option<Arc<dyn CertificateVerifierCache>>,
67        logger: Logger,
68    ) -> MithrilResult<MithrilCertificateVerifier> {
69        let logger = logger.new_with_component_name::<Self>();
70        let retriever = Arc::new(InternalCertificateRetriever::new(
71            aggregator_client,
72            logger.clone(),
73        ));
74        let internal_verifier = Arc::new(CommonMithrilCertificateVerifier::new(
75            logger.clone(),
76            retriever.clone(),
77        ));
78        let genesis_verification_key =
79            ProtocolGenesisVerificationKey::try_from(genesis_verification_key)
80                .with_context(|| "Invalid genesis verification key")?;
81
82        Ok(Self {
83            retriever,
84            internal_verifier,
85            genesis_verification_key,
86            feedback_sender,
87            #[cfg(feature = "unstable")]
88            verifier_cache,
89            logger,
90        })
91    }
92
93    #[cfg(feature = "unstable")]
94    async fn fetch_cached_previous_hash(&self, hash: &str) -> MithrilResult<Option<String>> {
95        if let Some(cache) = self.verifier_cache.as_ref() {
96            Ok(cache.get_previous_hash(hash).await?)
97        } else {
98            Ok(None)
99        }
100    }
101
102    #[cfg(not(feature = "unstable"))]
103    async fn fetch_cached_previous_hash(&self, _hash: &str) -> MithrilResult<Option<String>> {
104        Ok(None)
105    }
106
107    async fn verify_with_cache_enabled(
108        &self,
109        certificate_chain_validation_id: &str,
110        certificate: CertificateToVerify,
111    ) -> MithrilResult<Option<CertificateToVerify>> {
112        trace!(self.logger, "Validating certificate"; "hash" => certificate.hash(), "previous_hash" => certificate.hash());
113        if let Some(previous_hash) = self.fetch_cached_previous_hash(certificate.hash()).await? {
114            trace!(self.logger, "Certificate fetched from cache"; "hash" => certificate.hash(), "previous_hash" => &previous_hash);
115            self.feedback_sender
116                .send_event(MithrilEvent::CertificateFetchedFromCache {
117                    certificate_hash: certificate.hash().to_owned(),
118                    certificate_chain_validation_id: certificate_chain_validation_id.to_string(),
119                })
120                .await;
121
122            Ok(Some(CertificateToVerify::ToDownload {
123                hash: previous_hash,
124            }))
125        } else {
126            let certificate = match certificate {
127                CertificateToVerify::Downloaded { certificate } => *certificate,
128                CertificateToVerify::ToDownload { hash } => {
129                    self.retriever.get_certificate_details(&hash).await?
130                }
131            };
132
133            let previous_certificate = self
134                .verify_without_cache(certificate_chain_validation_id, certificate)
135                .await?;
136            Ok(previous_certificate.map(Into::into))
137        }
138    }
139
140    async fn verify_without_cache(
141        &self,
142        certificate_chain_validation_id: &str,
143        certificate: Certificate,
144    ) -> MithrilResult<Option<Certificate>> {
145        let previous_certificate = self
146            .internal_verifier
147            .verify_certificate(&certificate, &self.genesis_verification_key)
148            .await?;
149
150        #[cfg(feature = "unstable")]
151        if let Some(cache) = self.verifier_cache.as_ref() {
152            if !certificate.is_genesis() {
153                cache
154                    .store_validated_certificate(&certificate.hash, &certificate.previous_hash)
155                    .await?;
156            }
157        }
158
159        trace!(self.logger, "Certificate validated"; "hash" => &certificate.hash, "previous_hash" => &certificate.previous_hash);
160        self.feedback_sender
161            .send_event(MithrilEvent::CertificateValidated {
162                certificate_hash: certificate.hash,
163                certificate_chain_validation_id: certificate_chain_validation_id.to_string(),
164            })
165            .await;
166
167        Ok(previous_certificate)
168    }
169}
170
171enum CertificateToVerify {
172    /// The certificate is already downloaded.
173    Downloaded { certificate: Box<Certificate> },
174    /// The certificate is not downloaded yet (since its parent was cached).
175    ToDownload { hash: String },
176}
177
178impl CertificateToVerify {
179    fn hash(&self) -> &str {
180        match self {
181            CertificateToVerify::Downloaded { certificate } => &certificate.hash,
182            CertificateToVerify::ToDownload { hash } => hash,
183        }
184    }
185}
186
187impl From<Certificate> for CertificateToVerify {
188    fn from(value: Certificate) -> Self {
189        Self::Downloaded {
190            certificate: Box::new(value),
191        }
192    }
193}
194
195#[cfg_attr(target_family = "wasm", async_trait(?Send))]
196#[cfg_attr(not(target_family = "wasm"), async_trait)]
197impl CertificateVerifier for MithrilCertificateVerifier {
198    async fn verify_chain(&self, certificate: &MithrilCertificate) -> MithrilResult<()> {
199        // Todo: move most of this code in the `mithril_common` verifier by defining
200        // a new `verify_chain` method that take a callback called when a certificate is
201        // validated.
202        let certificate_chain_validation_id = MithrilEvent::new_certificate_chain_validation_id();
203        self.feedback_sender
204            .send_event(MithrilEvent::CertificateChainValidationStarted {
205                certificate_chain_validation_id: certificate_chain_validation_id.clone(),
206            })
207            .await;
208
209        // Validate certificates without cache until we cross an epoch boundary
210        // This is necessary to ensure that the AVK chaining is correct
211        let start_epoch = certificate.epoch;
212        let mut current_certificate: Option<Certificate> = Some(certificate.clone().try_into()?);
213        loop {
214            match current_certificate {
215                None => break,
216                Some(next) => {
217                    current_certificate = self
218                        .verify_without_cache(&certificate_chain_validation_id, next)
219                        .await?;
220
221                    let has_crossed_epoch_boundary = current_certificate
222                        .as_ref()
223                        .is_some_and(|c| c.epoch != start_epoch);
224                    if has_crossed_epoch_boundary {
225                        break;
226                    }
227                }
228            }
229        }
230
231        let mut current_certificate: Option<CertificateToVerify> =
232            current_certificate.map(Into::into);
233        loop {
234            match current_certificate {
235                None => break,
236                Some(next) => {
237                    current_certificate = self
238                        .verify_with_cache_enabled(&certificate_chain_validation_id, next)
239                        .await?
240                }
241            }
242        }
243
244        self.feedback_sender
245            .send_event(MithrilEvent::CertificateChainValidated {
246                certificate_chain_validation_id,
247            })
248            .await;
249
250        Ok(())
251    }
252}
253
254#[cfg(test)]
255mod tests {
256    use mithril_common::test_utils::CertificateChainBuilder;
257
258    use crate::certificate_client::tests_utils::CertificateClientTestBuilder;
259    use crate::feedback::StackFeedbackReceiver;
260
261    use super::*;
262
263    #[tokio::test]
264    async fn validating_chain_send_feedbacks() {
265        let chain = CertificateChainBuilder::new()
266            .with_total_certificates(3)
267            .with_certificates_per_epoch(1)
268            .build();
269        let last_certificate_hash = chain.first().unwrap().hash.clone();
270
271        let feedback_receiver = Arc::new(StackFeedbackReceiver::new());
272        let certificate_client = CertificateClientTestBuilder::default()
273            .config_aggregator_client_mock(|mock| {
274                mock.expect_certificate_chain(chain.certificates_chained.clone())
275            })
276            .with_genesis_verification_key(chain.genesis_verifier.to_verification_key())
277            .add_feedback_receiver(feedback_receiver.clone())
278            .build();
279
280        certificate_client
281            .verify_chain(&last_certificate_hash)
282            .await
283            .expect("Chain validation should succeed");
284
285        let actual = feedback_receiver.stacked_events();
286        let id = actual[0].event_id();
287
288        let expected = {
289            let mut vec = vec![MithrilEvent::CertificateChainValidationStarted {
290                certificate_chain_validation_id: id.to_string(),
291            }];
292            vec.extend(chain.certificates_chained.into_iter().map(|c| {
293                MithrilEvent::CertificateValidated {
294                    certificate_chain_validation_id: id.to_string(),
295                    certificate_hash: c.hash,
296                }
297            }));
298            vec.push(MithrilEvent::CertificateChainValidated {
299                certificate_chain_validation_id: id.to_string(),
300            });
301            vec
302        };
303
304        assert_eq!(actual, expected);
305    }
306
307    #[tokio::test]
308    async fn verify_chain_return_certificate_with_given_hash() {
309        let chain = CertificateChainBuilder::new()
310            .with_total_certificates(3)
311            .with_certificates_per_epoch(1)
312            .build();
313        let last_certificate_hash = chain.first().unwrap().hash.clone();
314
315        let certificate_client = CertificateClientTestBuilder::default()
316            .config_aggregator_client_mock(|mock| {
317                mock.expect_certificate_chain(chain.certificates_chained.clone())
318            })
319            .with_genesis_verification_key(chain.genesis_verifier.to_verification_key())
320            .build();
321
322        let certificate = certificate_client
323            .verify_chain(&last_certificate_hash)
324            .await
325            .expect("Chain validation should succeed");
326
327        assert_eq!(certificate.hash, last_certificate_hash);
328    }
329
330    #[cfg(feature = "unstable")]
331    mod cache {
332        use chrono::TimeDelta;
333        use mithril_common::test_utils::CertificateChainingMethod;
334        use mockall::predicate::eq;
335
336        use crate::aggregator_client::MockAggregatorClient;
337        use crate::certificate_client::verify_cache::MemoryCertificateVerifierCache;
338        use crate::certificate_client::MockCertificateVerifierCache;
339        use crate::test_utils::TestLogger;
340
341        use super::*;
342
343        fn build_verifier_with_cache(
344            aggregator_client_mock_config: impl FnOnce(&mut MockAggregatorClient),
345            genesis_verification_key: ProtocolGenesisVerificationKey,
346            cache: Arc<dyn CertificateVerifierCache>,
347        ) -> MithrilCertificateVerifier {
348            let mut aggregator_client = MockAggregatorClient::new();
349            aggregator_client_mock_config(&mut aggregator_client);
350            let genesis_verification_key: String = genesis_verification_key.try_into().unwrap();
351
352            MithrilCertificateVerifier::new(
353                Arc::new(aggregator_client),
354                &genesis_verification_key,
355                FeedbackSender::new(&[]),
356                Some(cache),
357                TestLogger::stdout(),
358            )
359            .unwrap()
360        }
361
362        #[tokio::test]
363        async fn genesis_certificates_verification_result_is_not_cached() {
364            let chain = CertificateChainBuilder::new()
365                .with_total_certificates(1)
366                .with_certificates_per_epoch(1)
367                .build();
368            let genesis_certificate = chain.last().unwrap();
369            assert!(genesis_certificate.is_genesis());
370
371            let cache = Arc::new(MemoryCertificateVerifierCache::new(TimeDelta::hours(1)));
372            let verifier = build_verifier_with_cache(
373                |_mock| {},
374                chain.genesis_verifier.to_verification_key(),
375                cache.clone(),
376            );
377
378            verifier
379                .verify_with_cache_enabled(
380                    "certificate_chain_validation_id",
381                    CertificateToVerify::Downloaded {
382                        certificate: Box::new(genesis_certificate.clone()),
383                    },
384                )
385                .await
386                .unwrap();
387
388            assert_eq!(
389                cache
390                    .get_previous_hash(&genesis_certificate.hash)
391                    .await
392                    .unwrap(),
393                None
394            );
395        }
396
397        #[tokio::test]
398        async fn non_genesis_certificates_verification_result_is_cached() {
399            let chain = CertificateChainBuilder::new()
400                .with_total_certificates(2)
401                .with_certificates_per_epoch(1)
402                .build();
403            let certificate = chain.first().unwrap();
404            let genesis_certificate = chain.last().unwrap();
405            assert!(!certificate.is_genesis());
406
407            let cache = Arc::new(MemoryCertificateVerifierCache::new(TimeDelta::hours(1)));
408            let verifier = build_verifier_with_cache(
409                |mock| mock.expect_certificate_chain(vec![genesis_certificate.clone()]),
410                chain.genesis_verifier.to_verification_key(),
411                cache.clone(),
412            );
413
414            verifier
415                .verify_with_cache_enabled(
416                    "certificate_chain_validation_id",
417                    CertificateToVerify::Downloaded {
418                        certificate: Box::new(certificate.clone()),
419                    },
420                )
421                .await
422                .unwrap();
423
424            assert_eq!(
425                cache.get_previous_hash(&certificate.hash).await.unwrap(),
426                Some(certificate.previous_hash.clone())
427            );
428        }
429
430        #[tokio::test]
431        async fn verification_of_first_certificate_of_a_chain_should_always_fetch_it_from_network()
432        {
433            let chain = CertificateChainBuilder::new()
434                .with_total_certificates(2)
435                .with_certificates_per_epoch(1)
436                .build();
437            let first_certificate = chain.first().unwrap();
438
439            let cache = Arc::new(
440                MemoryCertificateVerifierCache::new(TimeDelta::hours(3))
441                    .with_items_from_chain(&vec![first_certificate.clone()]),
442            );
443            let certificate_client = CertificateClientTestBuilder::default()
444                .config_aggregator_client_mock(|mock| {
445                    // Expect to fetch the first certificate from the network
446                    mock.expect_certificate_chain(chain.certificates_chained.clone());
447                })
448                .with_genesis_verification_key(chain.genesis_verifier.to_verification_key())
449                .with_verifier_cache(cache.clone())
450                .build();
451
452            certificate_client
453                .verify_chain(&first_certificate.hash)
454                .await
455                .unwrap();
456        }
457
458        #[tokio::test]
459        async fn verification_of_certificates_should_not_use_cache_until_crossing_an_epoch_boundary(
460        ) {
461            // Scenario:
462            // | Certificate | epoch |         Parent | Can use cache to | Should be fully |
463            // |             |       |                | get parent hash  | Verified        |
464            // |------------:|------:|---------------:|------------------|-----------------|
465            // |         n°6 |     3 |            n°5 | No               | Yes             |
466            // |         n°5 |     3 |            n°4 | No               | Yes             |
467            // |         n°4 |     2 |            n°3 | Yes              | Yes             |
468            // |         n°3 |     2 |            n°2 | Yes              | No              |
469            // |         n°2 |     2 |            n°1 | Yes              | No              |
470            // |         n°1 |     1 | None (genesis) | Yes              | Yes             |
471            let chain = CertificateChainBuilder::new()
472                .with_total_certificates(6)
473                .with_certificates_per_epoch(3)
474                .with_certificate_chaining_method(CertificateChainingMethod::Sequential)
475                .build();
476
477            let first_certificate = chain.first().unwrap();
478            let genesis_certificate = chain.last().unwrap();
479            assert!(genesis_certificate.is_genesis());
480
481            let certificates_that_must_be_fully_verified =
482                [chain[..3].to_vec(), vec![genesis_certificate.clone()]].concat();
483            let certificates_which_parents_can_be_fetched_from_cache = chain[2..5].to_vec();
484
485            let cache = {
486                let mut mock = MockCertificateVerifierCache::new();
487
488                for certificate in certificates_which_parents_can_be_fetched_from_cache {
489                    let previous_hash = certificate.previous_hash.clone();
490                    mock.expect_get_previous_hash()
491                        .with(eq(certificate.hash.clone()))
492                        .return_once(|_| Ok(Some(previous_hash)))
493                        .once();
494                }
495                mock.expect_get_previous_hash()
496                    .with(eq(genesis_certificate.hash.clone()))
497                    .returning(|_| Ok(None));
498                mock.expect_store_validated_certificate()
499                    .returning(|_, _| Ok(()));
500
501                Arc::new(mock)
502            };
503
504            let certificate_client = CertificateClientTestBuilder::default()
505                .config_aggregator_client_mock(|mock| {
506                    mock.expect_certificate_chain(certificates_that_must_be_fully_verified);
507                })
508                .with_genesis_verification_key(chain.genesis_verifier.to_verification_key())
509                .with_verifier_cache(cache)
510                .build();
511
512            certificate_client
513                .verify_chain(&first_certificate.hash)
514                .await
515                .unwrap();
516        }
517
518        #[tokio::test]
519        async fn verify_chain_return_certificate_with_cache() {
520            let chain = CertificateChainBuilder::new()
521                .with_total_certificates(5)
522                .with_certificates_per_epoch(1)
523                .build();
524            let last_certificate_hash = chain.first().unwrap().hash.clone();
525
526            // All certificates are cached except the last two (to cross an epoch boundary) and the genesis
527            let cache = MemoryCertificateVerifierCache::new(TimeDelta::hours(3))
528                .with_items_from_chain(&chain[2..4]);
529
530            let certificate_client = CertificateClientTestBuilder::default()
531                .config_aggregator_client_mock(|mock| {
532                    mock.expect_certificate_chain(
533                        [chain[0..3].to_vec(), vec![chain.last().unwrap().clone()]].concat(),
534                    )
535                })
536                .with_genesis_verification_key(chain.genesis_verifier.to_verification_key())
537                .with_verifier_cache(Arc::new(cache))
538                .build();
539
540            let certificate = certificate_client
541                .verify_chain(&last_certificate_hash)
542                .await
543                .unwrap();
544
545            assert_eq!(certificate.hash, last_certificate_hash);
546        }
547    }
548}