@@ -104,7 +104,7 @@ impl MithrilCertificateVerifier {
104104 Ok ( None )
105105 }
106106
107- async fn verify_one (
107+ async fn verify_with_cache (
108108 & self ,
109109 certificate_chain_validation_id : & str ,
110110 certificate : CertificateToVerify ,
@@ -123,23 +123,25 @@ impl MithrilCertificateVerifier {
123123 hash : previous_hash,
124124 } ) )
125125 } else {
126- self . verify_not_cached_certificate ( certificate_chain_validation_id, certificate)
127- . await
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) )
128137 }
129138 }
130139
131- async fn verify_not_cached_certificate (
140+ async fn verify_without_cache (
132141 & self ,
133142 certificate_chain_validation_id : & str ,
134- certificate : CertificateToVerify ,
135- ) -> MithrilResult < Option < CertificateToVerify > > {
136- let certificate = match certificate {
137- CertificateToVerify :: Downloaded { certificate } => certificate,
138- CertificateToVerify :: ToDownload { hash } => {
139- self . retriever . get_certificate_details ( & hash) . await ?
140- }
141- } ;
142-
143+ certificate : Certificate ,
144+ ) -> MithrilResult < Option < Certificate > > {
143145 let previous_certificate = self
144146 . internal_verifier
145147 . verify_certificate ( & certificate, & self . genesis_verification_key )
@@ -162,7 +164,7 @@ impl MithrilCertificateVerifier {
162164 } )
163165 . await ;
164166
165- Ok ( previous_certificate. map ( |cert| CertificateToVerify :: Downloaded { certificate : cert } ) )
167+ Ok ( previous_certificate)
166168 }
167169}
168170
@@ -182,6 +184,12 @@ impl CertificateToVerify {
182184 }
183185}
184186
187+ impl From < Certificate > for CertificateToVerify {
188+ fn from ( value : Certificate ) -> Self {
189+ Self :: Downloaded { certificate : value }
190+ }
191+ }
192+
185193#[ cfg_attr( target_family = "wasm" , async_trait( ?Send ) ) ]
186194#[ cfg_attr( not( target_family = "wasm" ) , async_trait) ]
187195impl CertificateVerifier for MithrilCertificateVerifier {
@@ -196,22 +204,36 @@ impl CertificateVerifier for MithrilCertificateVerifier {
196204 } )
197205 . await ;
198206
199- // always validate the given certificate even if it was cached
200- let mut current_certificate = self
201- . verify_not_cached_certificate (
202- & certificate_chain_validation_id,
203- CertificateToVerify :: Downloaded {
204- certificate : certificate. clone ( ) . try_into ( ) ?,
205- } ,
206- )
207- . await ?;
207+ // Validate certificates without cache until we cross an epoch boundary
208+ // This is necessary to ensure that the AVK of the current epoch was included
209+ // in a certificate in the previous epoch.
210+ let start_epoch = certificate. epoch ;
211+ let mut current_certificate: Option < Certificate > = Some ( certificate. clone ( ) . try_into ( ) ?) ;
212+ loop {
213+ match current_certificate {
214+ None => break ,
215+ Some ( next) => {
216+ current_certificate = self
217+ . verify_without_cache ( & certificate_chain_validation_id, next)
218+ . await ?;
219+
220+ let has_crossed_epoch_boundary = current_certificate
221+ . as_ref ( )
222+ . is_some_and ( |c| c. epoch != start_epoch) ;
223+ if has_crossed_epoch_boundary {
224+ break ;
225+ }
226+ }
227+ }
228+ }
208229
230+ let mut current_certificate = current_certificate. map ( Into :: into) ;
209231 loop {
210232 match current_certificate {
211233 None => break ,
212234 Some ( next) => {
213235 current_certificate = self
214- . verify_one ( & certificate_chain_validation_id, next)
236+ . verify_with_cache ( & certificate_chain_validation_id, next)
215237 . await ?
216238 }
217239 }
@@ -304,6 +326,7 @@ mod tests {
304326 #[ cfg( feature = "unstable" ) ]
305327 mod cache {
306328 use chrono:: TimeDelta ;
329+ use mithril_common:: test_utils:: CertificateChainingMethod ;
307330 use mockall:: predicate:: eq;
308331
309332 use crate :: aggregator_client:: MockAggregatorHTTPClient ;
@@ -349,7 +372,7 @@ mod tests {
349372 ) ;
350373
351374 verifier
352- . verify_one (
375+ . verify_with_cache (
353376 "certificate_chain_validation_id" ,
354377 CertificateToVerify :: Downloaded {
355378 certificate : genesis_certificate. clone ( ) ,
@@ -385,7 +408,7 @@ mod tests {
385408 ) ;
386409
387410 verifier
388- . verify_one (
411+ . verify_with_cache (
389412 "certificate_chain_validation_id" ,
390413 CertificateToVerify :: Downloaded {
391414 certificate : certificate. clone ( ) ,
@@ -429,28 +452,64 @@ mod tests {
429452 }
430453
431454 #[ tokio:: test]
432- async fn verification_of_first_certificate_of_a_chain_should_not_use_cache ( ) {
455+ async fn verification_of_certificates_should_not_use_cache_until_crossing_an_epoch_boundary (
456+ ) {
457+ // Chain produced:
458+ // Cert epoch 3 n°6 - parent cert n°5
459+ // Cert epoch 3 n°5 - parent cert n°4
460+ // Cert epoch 2 n°4 - parent cert n°3
461+ // Cert epoch 2 n°3 - parent cert n°2
462+ // Cert epoch 2 n°2 - parent cert n°1
463+ // Cert epoch 1 n°1 - genesis
433464 let ( chain, verifier) = CertificateChainBuilder :: new ( )
434- . with_total_certificates ( 2 )
435- . with_certificates_per_epoch ( 1 )
465+ . with_total_certificates ( 6 )
466+ . with_certificates_per_epoch ( 3 )
467+ . with_certificate_chaining_method ( CertificateChainingMethod :: ToDirectPredecessor )
436468 . build ( ) ;
469+
437470 let first_certificate = chain. first ( ) . unwrap ( ) ;
438471 let genesis_certificate = chain. last ( ) . unwrap ( ) ;
439472 assert ! ( genesis_certificate. is_genesis( ) ) ;
440473
474+ // The two certificates on the last epoch plus the last of the second epoch must be
475+ // fetched from the network as we need to cross an epoch boundary
476+ let certificates_that_must_not_be_fetched_from_cache =
477+ chain[ ..3 ] . iter ( ) . cloned ( ) . collect :: < Vec < _ > > ( ) ;
478+ let certificate_that_can_be_fetched_from_cache = chain[ 2 ..5 ] . to_vec ( ) ;
479+
441480 let mut cache = MockCertificateVerifierCache :: new ( ) ;
442- cache. expect_get_previous_hash ( ) . returning ( |_| Ok ( None ) ) ;
481+
482+ for certificate in & certificates_that_must_not_be_fetched_from_cache {
483+ cache
484+ . expect_get_previous_hash ( )
485+ . with ( eq ( certificate. hash . clone ( ) ) )
486+ . never ( ) ;
487+ }
488+ for certificate in certificate_that_can_be_fetched_from_cache {
489+ let previous_hash = certificate. previous_hash . clone ( ) ;
490+ cache
491+ . expect_get_previous_hash ( )
492+ . with ( eq ( certificate. hash . clone ( ) ) )
493+ . return_once ( |_| Ok ( Some ( previous_hash) ) ) ;
494+ }
443495 cache
444496 . expect_get_previous_hash ( )
445- . with ( eq ( first_certificate. hash . clone ( ) ) )
446- . never ( ) ;
497+ . with ( eq ( genesis_certificate. hash . clone ( ) ) )
498+ . returning ( |_| Ok ( None ) ) ;
499+
447500 cache
448501 . expect_store_validated_certificate ( )
449502 . returning ( |_, _| Ok ( ( ) ) ) ;
450503
451504 let certificate_client = CertificateClientTestBuilder :: default ( )
452505 . config_aggregator_client_mock ( |mock| {
453- mock. expect_certificate_chain ( chain. clone ( ) ) ;
506+ mock. expect_certificate_chain (
507+ [
508+ certificates_that_must_not_be_fetched_from_cache,
509+ vec ! [ genesis_certificate. clone( ) ] ,
510+ ]
511+ . concat ( ) ,
512+ ) ;
454513 } )
455514 . with_genesis_verification_key ( verifier. to_verification_key ( ) )
456515 . with_verifier_cache ( Arc :: new ( cache) )
@@ -470,14 +529,14 @@ mod tests {
470529 . build ( ) ;
471530 let last_certificate_hash = chain. first ( ) . unwrap ( ) . hash . clone ( ) ;
472531
473- // All certificates are cached except the last and the genesis (we always fetch the both)
532+ // All certificates are cached except the last two (to cross an epoch boundary) and the genesis (we always fetch the both)
474533 let cache = MemoryCertificateVerifierCache :: new ( TimeDelta :: hours ( 3 ) )
475- . with_items_from_chain ( & chain[ 1 ..4 ] ) ;
534+ . with_items_from_chain ( & chain[ 2 ..4 ] ) ;
476535
477536 let certificate_client = CertificateClientTestBuilder :: default ( )
478537 . config_aggregator_client_mock ( |mock| {
479538 mock. expect_certificate_chain (
480- [ chain[ 0 ..2 ] . to_vec ( ) , vec ! [ chain. last( ) . unwrap( ) . clone( ) ] ] . concat ( ) ,
539+ [ chain[ 0 ..3 ] . to_vec ( ) , vec ! [ chain. last( ) . unwrap( ) . clone( ) ] ] . concat ( ) ,
481540 )
482541 } )
483542 . with_genesis_verification_key ( verifier. to_verification_key ( ) )
0 commit comments