@@ -104,7 +104,7 @@ impl MithrilCertificateVerifier {
104104 Ok ( None )
105105 }
106106
107- async fn verify_one (
107+ async fn verify_with_cache_enabled (
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 chaining is correct
209+ let start_epoch = certificate. epoch ;
210+ let mut current_certificate: Option < Certificate > = Some ( certificate. clone ( ) . try_into ( ) ?) ;
211+ loop {
212+ match current_certificate {
213+ None => break ,
214+ Some ( next) => {
215+ current_certificate = self
216+ . verify_without_cache ( & certificate_chain_validation_id, next)
217+ . await ?;
218+
219+ let has_crossed_epoch_boundary = current_certificate
220+ . as_ref ( )
221+ . is_some_and ( |c| c. epoch != start_epoch) ;
222+ if has_crossed_epoch_boundary {
223+ break ;
224+ }
225+ }
226+ }
227+ }
208228
229+ let mut current_certificate: Option < CertificateToVerify > =
230+ 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_enabled ( & 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_enabled (
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_enabled (
389412 "certificate_chain_validation_id" ,
390413 CertificateToVerify :: Downloaded {
391414 certificate : certificate. clone ( ) ,
@@ -429,31 +452,57 @@ 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+ // Scenario:
458+ // | Certificate | epoch | Parent | Can use cache to | Should be fully |
459+ // | | | | get parent hash | Verified |
460+ // |------------:|------:|---------------:|------------------|-----------------|
461+ // | n°6 | 3 | n°5 | No | Yes |
462+ // | n°5 | 3 | n°4 | No | Yes |
463+ // | n°4 | 2 | n°3 | Yes | Yes |
464+ // | n°3 | 2 | n°2 | Yes | No |
465+ // | n°2 | 2 | n°1 | Yes | No |
466+ // | n°1 | 1 | None (genesis) | Yes | Yes |
433467 let ( chain, verifier) = CertificateChainBuilder :: new ( )
434- . with_total_certificates ( 2 )
435- . with_certificates_per_epoch ( 1 )
468+ . with_total_certificates ( 6 )
469+ . with_certificates_per_epoch ( 3 )
470+ . with_certificate_chaining_method ( CertificateChainingMethod :: Sequential )
436471 . build ( ) ;
472+
437473 let first_certificate = chain. first ( ) . unwrap ( ) ;
438474 let genesis_certificate = chain. last ( ) . unwrap ( ) ;
439475 assert ! ( genesis_certificate. is_genesis( ) ) ;
440476
441- let mut cache = MockCertificateVerifierCache :: new ( ) ;
442- cache. expect_get_previous_hash ( ) . returning ( |_| Ok ( None ) ) ;
443- cache
444- . expect_get_previous_hash ( )
445- . with ( eq ( first_certificate. hash . clone ( ) ) )
446- . never ( ) ;
447- cache
448- . expect_store_validated_certificate ( )
449- . returning ( |_, _| Ok ( ( ) ) ) ;
477+ let certificates_that_must_be_fully_verified =
478+ [ chain[ ..3 ] . to_vec ( ) , vec ! [ genesis_certificate. clone( ) ] ] . concat ( ) ;
479+ let certificates_which_parents_can_be_fetched_from_cache = chain[ 2 ..5 ] . to_vec ( ) ;
480+
481+ let cache = {
482+ let mut mock = MockCertificateVerifierCache :: new ( ) ;
483+
484+ for certificate in certificates_which_parents_can_be_fetched_from_cache {
485+ let previous_hash = certificate. previous_hash . clone ( ) ;
486+ mock. expect_get_previous_hash ( )
487+ . with ( eq ( certificate. hash . clone ( ) ) )
488+ . return_once ( |_| Ok ( Some ( previous_hash) ) )
489+ . once ( ) ;
490+ }
491+ mock. expect_get_previous_hash ( )
492+ . with ( eq ( genesis_certificate. hash . clone ( ) ) )
493+ . returning ( |_| Ok ( None ) ) ;
494+ mock. expect_store_validated_certificate ( )
495+ . returning ( |_, _| Ok ( ( ) ) ) ;
496+
497+ Arc :: new ( mock)
498+ } ;
450499
451500 let certificate_client = CertificateClientTestBuilder :: default ( )
452501 . config_aggregator_client_mock ( |mock| {
453- mock. expect_certificate_chain ( chain . clone ( ) ) ;
502+ mock. expect_certificate_chain ( certificates_that_must_be_fully_verified ) ;
454503 } )
455504 . with_genesis_verification_key ( verifier. to_verification_key ( ) )
456- . with_verifier_cache ( Arc :: new ( cache) )
505+ . with_verifier_cache ( cache)
457506 . build ( ) ;
458507
459508 certificate_client
@@ -470,14 +519,14 @@ mod tests {
470519 . build ( ) ;
471520 let last_certificate_hash = chain. first ( ) . unwrap ( ) . hash . clone ( ) ;
472521
473- // All certificates are cached except the last and the genesis (we always fetch the both)
522+ // All certificates are cached except the last two (to cross an epoch boundary) and the genesis
474523 let cache = MemoryCertificateVerifierCache :: new ( TimeDelta :: hours ( 3 ) )
475- . with_items_from_chain ( & chain[ 1 ..4 ] ) ;
524+ . with_items_from_chain ( & chain[ 2 ..4 ] ) ;
476525
477526 let certificate_client = CertificateClientTestBuilder :: default ( )
478527 . config_aggregator_client_mock ( |mock| {
479528 mock. expect_certificate_chain (
480- [ chain[ 0 ..2 ] . to_vec ( ) , vec ! [ chain. last( ) . unwrap( ) . clone( ) ] ] . concat ( ) ,
529+ [ chain[ 0 ..3 ] . to_vec ( ) , vec ! [ chain. last( ) . unwrap( ) . clone( ) ] ] . concat ( ) ,
481530 )
482531 } )
483532 . with_genesis_verification_key ( verifier. to_verification_key ( ) )
0 commit comments