@@ -251,6 +251,29 @@ impl<
251251 }
252252 }
253253
254+ /// Syncs all open journal sections.
255+ async fn sync_all_journal ( & mut self ) {
256+ if let Some ( journal) = self . journal . as_mut ( ) {
257+ journal. sync_all ( ) . await . expect ( "unable to sync journal" ) ;
258+ }
259+ }
260+
261+ /// Records a notarization during journal replay without running ancestor inference.
262+ ///
263+ /// Inference is deferred to a post-replay pass (see `run`) so that
264+ /// Artifact::Certification entries already in the journal are applied before any
265+ /// inference fires. Running inference here would certify ancestors as true before
266+ /// their journal entry (which may say false) is replayed, causing a conflict panic.
267+ async fn record_notarization ( & mut self , notarization : Notarization < S , D > ) {
268+ let view = notarization. view ( ) ;
269+ let artifact = Artifact :: Notarization ( notarization. clone ( ) ) ;
270+ let ( added, equivocator) = self . state . add_notarization ( notarization) ;
271+ if added {
272+ self . append_journal ( view, artifact) . await ;
273+ }
274+ self . block_equivocator ( equivocator) . await ;
275+ }
276+
254277 /// Send a vote to every peer.
255278 async fn broadcast_vote < T : Sender > (
256279 & mut self ,
@@ -414,33 +437,57 @@ impl<
414437 }
415438
416439 /// Records a notarization certificate and blocks any equivocating leader.
417- async fn handle_notarization ( & mut self , notarization : Notarization < S , D > ) {
440+ /// Returns the notarizations of any ancestors inferred as certified so callers
441+ /// can forward them to the resolver and reporter.
442+ async fn handle_notarization (
443+ & mut self ,
444+ notarization : Notarization < S , D > ,
445+ ) -> Vec < Notarization < S , D > > {
418446 let view = notarization. view ( ) ;
419447 let artifact = Artifact :: Notarization ( notarization. clone ( ) ) ;
420448 let ( added, equivocator) = self . state . add_notarization ( notarization) ;
449+ let mut inferred = Vec :: new ( ) ;
421450 if added {
422451 self . append_journal ( view, artifact) . await ;
452+ // The f+1 signers of `view` must have certified the parent before voting,
453+ // and by the same logic the grandparent must have been certified before
454+ // the parent was proposed. Walk the ancestor chain until we reach a view
455+ // that is already certified, finalized, or has no notarization.
456+ let mut current = view;
457+ while let Some ( ancestor) = self . state . infer_parent ( current) {
458+ if let Some ( n) = self . handle_certification ( ancestor, true ) . await {
459+ inferred. push ( n) ;
460+ }
461+ current = ancestor;
462+ }
463+ // Sync the notarization section first so it is durable before any ancestor
464+ // certification sections. This ordering ensures crash recovery always sees the
465+ // notarization that triggered the inferred certifications.
466+ self . sync_journal ( view) . await ;
467+ if !inferred. is_empty ( ) {
468+ // Sync all ancestor certification sections concurrently in a single call
469+ // rather than one fsync per ancestor.
470+ self . sync_all_journal ( ) . await ;
471+ }
423472 }
424473 self . block_equivocator ( equivocator) . await ;
474+ inferred
425475 }
426476
427477 /// Handles the certification of a proposal.
428478 ///
429479 /// The certification may succeed, in which case the proposal can be used in future views—
430480 /// or fail, in which case we should nullify the view as fast as possible.
481+ ///
482+ /// Appends to the journal but does not sync; callers are responsible for syncing.
431483 async fn handle_certification (
432484 & mut self ,
433485 view : View ,
434486 success : bool ,
435487 ) -> Option < Notarization < S , D > > {
436- // Get the notarization before advancing state
437488 let notarization = self . state . certified ( view, success) ?;
438-
439- // Persist certification result for recovery
440489 let artifact = Artifact :: Certification ( Rnd :: new ( self . state . epoch ( ) , view) , success) ;
441490 self . append_journal ( view, artifact. clone ( ) ) . await ;
442- self . sync_journal ( view) . await ;
443-
444491 Some ( notarization)
445492 }
446493
@@ -515,9 +562,13 @@ impl<
515562 . await ;
516563 }
517564 // Update our local round with the certificate.
518- self . handle_notarization ( notarization. clone ( ) ) . await ;
519- // Persist the certificate before informing others.
520- self . sync_journal ( view) . await ;
565+ // handle_notarization syncs all written sections before returning.
566+ let inferred = self . handle_notarization ( notarization. clone ( ) ) . await ;
567+ for n in inferred {
568+ resolver. updated ( Certificate :: Notarization ( n. clone ( ) ) ) . await ;
569+ resolver. certified ( n. view ( ) , true ) . await ;
570+ self . reporter . report ( Activity :: Certification ( n) ) . await ;
571+ }
521572 // Broadcast the notarization certificate
522573 debug ! ( proposal=?notarization. proposal, "broadcasting notarization" ) ;
523574 self . broadcast_certificate (
@@ -744,7 +795,7 @@ impl<
744795 self . reporter . report ( Activity :: Notarize ( notarize) ) . await ;
745796 }
746797 Artifact :: Notarization ( notarization) => {
747- self . handle_notarization ( notarization. clone ( ) ) . await ;
798+ self . record_notarization ( notarization. clone ( ) ) . await ;
748799 resolver
749800 . updated ( Certificate :: Notarization ( notarization. clone ( ) ) )
750801 . await ;
@@ -796,6 +847,29 @@ impl<
796847 }
797848 self . journal = Some ( journal) ;
798849
850+ // Run one inference pass now that all journal entries are applied and their
851+ // certification states are authoritative. This recovers certifications that
852+ // were inferred during the previous run but whose Artifact::Certification
853+ // journal entries were not synced before a crash.
854+ let infer_views = self . state . views_with_notarization ( ) ;
855+ let mut any_inferred = false ;
856+ for view in infer_views. into_iter ( ) . rev ( ) {
857+ let mut current = view;
858+ while let Some ( ancestor) = self . state . infer_parent ( current) {
859+ let Some ( n) = self . handle_certification ( ancestor, true ) . await else {
860+ break ;
861+ } ;
862+ resolver. updated ( Certificate :: Notarization ( n. clone ( ) ) ) . await ;
863+ resolver. certified ( n. view ( ) , true ) . await ;
864+ self . reporter . report ( Activity :: Certification ( n) ) . await ;
865+ any_inferred = true ;
866+ current = ancestor;
867+ }
868+ }
869+ if any_inferred {
870+ self . sync_all_journal ( ) . await ;
871+ }
872+
799873 // Log current view after recovery
800874 let end = self . context . current ( ) ;
801875 let elapsed = end. duration_since ( start) . unwrap_or_default ( ) ;
@@ -957,6 +1031,7 @@ impl<
9571031 else {
9581032 continue ;
9591033 } ;
1034+ self . sync_journal( view) . await ;
9601035 // Always forward certification outcomes to resolver.
9611036 // This can happen after a nullification for the same view because
9621037 // certification is asynchronous; finalization is the boundary that
@@ -1005,7 +1080,12 @@ impl<
10051080 match certificate {
10061081 Certificate :: Notarization ( notarization) => {
10071082 trace!( %view, from_resolver, "received notarization" ) ;
1008- self . handle_notarization( notarization) . await ;
1083+ let inferred = self . handle_notarization( notarization) . await ;
1084+ for n in inferred {
1085+ resolver. updated( Certificate :: Notarization ( n. clone( ) ) ) . await ;
1086+ resolver. certified( n. view( ) , true ) . await ;
1087+ self . reporter. report( Activity :: Certification ( n) ) . await ;
1088+ }
10091089 if from_resolver {
10101090 resolved = Resolved :: Notarization ;
10111091 }
0 commit comments