mithril_client/certificate_client/
verify.rs1use 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
48pub 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 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 Downloaded { certificate: Box<Certificate> },
174 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 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 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 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 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 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}