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, verifier) = 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| mock.expect_certificate_chain(chain.clone()))
274 .with_genesis_verification_key(verifier.to_verification_key())
275 .add_feedback_receiver(feedback_receiver.clone())
276 .build();
277
278 certificate_client
279 .verify_chain(&last_certificate_hash)
280 .await
281 .expect("Chain validation should succeed");
282
283 let actual = feedback_receiver.stacked_events();
284 let id = actual[0].event_id();
285
286 let expected = {
287 let mut vec = vec![MithrilEvent::CertificateChainValidationStarted {
288 certificate_chain_validation_id: id.to_string(),
289 }];
290 vec.extend(
291 chain
292 .into_iter()
293 .map(|c| 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, verifier) = 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| mock.expect_certificate_chain(chain.clone()))
317 .with_genesis_verification_key(verifier.to_verification_key())
318 .build();
319
320 let certificate = certificate_client
321 .verify_chain(&last_certificate_hash)
322 .await
323 .expect("Chain validation should succeed");
324
325 assert_eq!(certificate.hash, last_certificate_hash);
326 }
327
328 #[cfg(feature = "unstable")]
329 mod cache {
330 use chrono::TimeDelta;
331 use mithril_common::test_utils::CertificateChainingMethod;
332 use mockall::predicate::eq;
333
334 use crate::aggregator_client::MockAggregatorClient;
335 use crate::certificate_client::verify_cache::MemoryCertificateVerifierCache;
336 use crate::certificate_client::MockCertificateVerifierCache;
337 use crate::test_utils::TestLogger;
338
339 use super::*;
340
341 fn build_verifier_with_cache(
342 aggregator_client_mock_config: impl FnOnce(&mut MockAggregatorClient),
343 genesis_verification_key: ProtocolGenesisVerificationKey,
344 cache: Arc<dyn CertificateVerifierCache>,
345 ) -> MithrilCertificateVerifier {
346 let mut aggregator_client = MockAggregatorClient::new();
347 aggregator_client_mock_config(&mut aggregator_client);
348 let genesis_verification_key: String = genesis_verification_key.try_into().unwrap();
349
350 MithrilCertificateVerifier::new(
351 Arc::new(aggregator_client),
352 &genesis_verification_key,
353 FeedbackSender::new(&[]),
354 Some(cache),
355 TestLogger::stdout(),
356 )
357 .unwrap()
358 }
359
360 #[tokio::test]
361 async fn genesis_certificates_verification_result_is_not_cached() {
362 let (chain, verifier) = CertificateChainBuilder::new()
363 .with_total_certificates(1)
364 .with_certificates_per_epoch(1)
365 .build();
366 let genesis_certificate = chain.last().unwrap();
367 assert!(genesis_certificate.is_genesis());
368
369 let cache = Arc::new(MemoryCertificateVerifierCache::new(TimeDelta::hours(1)));
370 let verifier = build_verifier_with_cache(
371 |_mock| {},
372 verifier.to_verification_key(),
373 cache.clone(),
374 );
375
376 verifier
377 .verify_with_cache_enabled(
378 "certificate_chain_validation_id",
379 CertificateToVerify::Downloaded {
380 certificate: Box::new(genesis_certificate.clone()),
381 },
382 )
383 .await
384 .unwrap();
385
386 assert_eq!(
387 cache
388 .get_previous_hash(&genesis_certificate.hash)
389 .await
390 .unwrap(),
391 None
392 );
393 }
394
395 #[tokio::test]
396 async fn non_genesis_certificates_verification_result_is_cached() {
397 let (chain, verifier) = CertificateChainBuilder::new()
398 .with_total_certificates(2)
399 .with_certificates_per_epoch(1)
400 .build();
401 let certificate = chain.first().unwrap();
402 let genesis_certificate = chain.last().unwrap();
403 assert!(!certificate.is_genesis());
404
405 let cache = Arc::new(MemoryCertificateVerifierCache::new(TimeDelta::hours(1)));
406 let verifier = build_verifier_with_cache(
407 |mock| mock.expect_certificate_chain(vec![genesis_certificate.clone()]),
408 verifier.to_verification_key(),
409 cache.clone(),
410 );
411
412 verifier
413 .verify_with_cache_enabled(
414 "certificate_chain_validation_id",
415 CertificateToVerify::Downloaded {
416 certificate: Box::new(certificate.clone()),
417 },
418 )
419 .await
420 .unwrap();
421
422 assert_eq!(
423 cache.get_previous_hash(&certificate.hash).await.unwrap(),
424 Some(certificate.previous_hash.clone())
425 );
426 }
427
428 #[tokio::test]
429 async fn verification_of_first_certificate_of_a_chain_should_always_fetch_it_from_network()
430 {
431 let (chain, verifier) = CertificateChainBuilder::new()
432 .with_total_certificates(2)
433 .with_certificates_per_epoch(1)
434 .build();
435 let first_certificate = chain.first().unwrap();
436
437 let cache = Arc::new(
438 MemoryCertificateVerifierCache::new(TimeDelta::hours(3))
439 .with_items_from_chain(&vec![first_certificate.clone()]),
440 );
441 let certificate_client = CertificateClientTestBuilder::default()
442 .config_aggregator_client_mock(|mock| {
443 mock.expect_certificate_chain(chain.clone());
445 })
446 .with_genesis_verification_key(verifier.to_verification_key())
447 .with_verifier_cache(cache.clone())
448 .build();
449
450 certificate_client
451 .verify_chain(&first_certificate.hash)
452 .await
453 .unwrap();
454 }
455
456 #[tokio::test]
457 async fn verification_of_certificates_should_not_use_cache_until_crossing_an_epoch_boundary(
458 ) {
459 let (chain, verifier) = CertificateChainBuilder::new()
470 .with_total_certificates(6)
471 .with_certificates_per_epoch(3)
472 .with_certificate_chaining_method(CertificateChainingMethod::Sequential)
473 .build();
474
475 let first_certificate = chain.first().unwrap();
476 let genesis_certificate = chain.last().unwrap();
477 assert!(genesis_certificate.is_genesis());
478
479 let certificates_that_must_be_fully_verified =
480 [chain[..3].to_vec(), vec![genesis_certificate.clone()]].concat();
481 let certificates_which_parents_can_be_fetched_from_cache = chain[2..5].to_vec();
482
483 let cache = {
484 let mut mock = MockCertificateVerifierCache::new();
485
486 for certificate in certificates_which_parents_can_be_fetched_from_cache {
487 let previous_hash = certificate.previous_hash.clone();
488 mock.expect_get_previous_hash()
489 .with(eq(certificate.hash.clone()))
490 .return_once(|_| Ok(Some(previous_hash)))
491 .once();
492 }
493 mock.expect_get_previous_hash()
494 .with(eq(genesis_certificate.hash.clone()))
495 .returning(|_| Ok(None));
496 mock.expect_store_validated_certificate()
497 .returning(|_, _| Ok(()));
498
499 Arc::new(mock)
500 };
501
502 let certificate_client = CertificateClientTestBuilder::default()
503 .config_aggregator_client_mock(|mock| {
504 mock.expect_certificate_chain(certificates_that_must_be_fully_verified);
505 })
506 .with_genesis_verification_key(verifier.to_verification_key())
507 .with_verifier_cache(cache)
508 .build();
509
510 certificate_client
511 .verify_chain(&first_certificate.hash)
512 .await
513 .unwrap();
514 }
515
516 #[tokio::test]
517 async fn verify_chain_return_certificate_with_cache() {
518 let (chain, verifier) = CertificateChainBuilder::new()
519 .with_total_certificates(5)
520 .with_certificates_per_epoch(1)
521 .build();
522 let last_certificate_hash = chain.first().unwrap().hash.clone();
523
524 let cache = MemoryCertificateVerifierCache::new(TimeDelta::hours(3))
526 .with_items_from_chain(&chain[2..4]);
527
528 let certificate_client = CertificateClientTestBuilder::default()
529 .config_aggregator_client_mock(|mock| {
530 mock.expect_certificate_chain(
531 [chain[0..3].to_vec(), vec![chain.last().unwrap().clone()]].concat(),
532 )
533 })
534 .with_genesis_verification_key(verifier.to_verification_key())
535 .with_verifier_cache(Arc::new(cache))
536 .build();
537
538 let certificate = certificate_client
539 .verify_chain(&last_certificate_hash)
540 .await
541 .unwrap();
542
543 assert_eq!(certificate.hash, last_certificate_hash);
544 }
545 }
546}