@@ -39,6 +39,7 @@ use sp_runtime::{
3939 ConsensusEngineId , KeyTypeId , Permill ,
4040} ;
4141use sp_session:: { GetSessionNumber , GetValidatorCount } ;
42+ use sp_staking:: SessionIndex ;
4243use sp_std:: prelude:: * ;
4344
4445use sp_consensus_babe:: {
@@ -102,7 +103,7 @@ impl EpochChangeTrigger for SameAuthoritiesForever {
102103 let authorities = <Pallet < T > >:: authorities ( ) ;
103104 let next_authorities = authorities. clone ( ) ;
104105
105- <Pallet < T > >:: enact_epoch_change ( authorities, next_authorities) ;
106+ <Pallet < T > >:: enact_epoch_change ( authorities, next_authorities, None ) ;
106107 }
107108 }
108109}
@@ -316,6 +317,19 @@ pub mod pallet {
316317 #[ pallet:: storage]
317318 pub ( super ) type NextEpochConfig < T > = StorageValue < _ , BabeEpochConfiguration > ;
318319
320+ /// A list of the last 100 skipped epochs and the corresponding session index
321+ /// when the epoch was skipped.
322+ ///
323+ /// This is only used for validating equivocation proofs. An equivocation proof
324+ /// must contains a key-ownership proof for a given session, therefore we need a
325+ /// way to tie together sessions and epoch indices, i.e. we need to validate that
326+ /// a validator was the owner of a given key on a given session, and what the
327+ /// active epoch index was during that session.
328+ #[ pallet:: storage]
329+ #[ pallet:: getter( fn skipped_epochs) ]
330+ pub ( super ) type SkippedEpochs < T > =
331+ StorageValue < _ , BoundedVec < ( u64 , SessionIndex ) , ConstU32 < 100 > > , ValueQuery > ;
332+
319333 #[ cfg_attr( feature = "std" , derive( Default ) ) ]
320334 #[ pallet:: genesis_config]
321335 pub struct GenesisConfig {
@@ -577,6 +591,7 @@ impl<T: Config> Pallet<T> {
577591 pub fn enact_epoch_change (
578592 authorities : WeakBoundedVec < ( AuthorityId , BabeAuthorityWeight ) , T :: MaxAuthorities > ,
579593 next_authorities : WeakBoundedVec < ( AuthorityId , BabeAuthorityWeight ) , T :: MaxAuthorities > ,
594+ session_index : Option < SessionIndex > ,
580595 ) {
581596 // PRECONDITION: caller has done initialization and is guaranteed
582597 // by the session module to be called before this.
@@ -602,6 +617,35 @@ impl<T: Config> Pallet<T> {
602617 T :: EpochDuration :: get ( ) ,
603618 ) ;
604619
620+ let current_epoch_index = EpochIndex :: < T > :: get ( ) ;
621+ if current_epoch_index. saturating_add ( 1 ) != epoch_index {
622+ // we are skipping epochs therefore we need to update the mapping
623+ // of epochs to session
624+ if let Some ( session_index) = session_index {
625+ SkippedEpochs :: < T > :: mutate ( |skipped_epochs| {
626+ if epoch_index < session_index as u64 {
627+ log:: warn!(
628+ target: LOG_TARGET ,
629+ "Current epoch index {} is lower than session index {}." ,
630+ epoch_index,
631+ session_index,
632+ ) ;
633+
634+ return
635+ }
636+
637+ if skipped_epochs. is_full ( ) {
638+ // NOTE: this is O(n) but we currently don't have a bounded `VecDeque`.
639+ // this vector is bounded to a small number of elements so performance
640+ // shouldn't be an issue.
641+ skipped_epochs. remove ( 0 ) ;
642+ }
643+
644+ skipped_epochs. force_push ( ( epoch_index, session_index) ) ;
645+ } )
646+ }
647+ }
648+
605649 EpochIndex :: < T > :: put ( epoch_index) ;
606650 Authorities :: < T > :: put ( authorities) ;
607651
@@ -816,6 +860,36 @@ impl<T: Config> Pallet<T> {
816860 this_randomness
817861 }
818862
863+ /// Returns the session index that was live when the given epoch happened,
864+ /// taking into account any skipped epochs.
865+ ///
866+ /// This function is only well defined for epochs that actually existed,
867+ /// e.g. if we skipped from epoch 10 to 20 then a call for epoch 15 (which
868+ /// didn't exist) will return an incorrect session index.
869+ fn session_index_for_epoch ( epoch_index : u64 ) -> SessionIndex {
870+ let skipped_epochs = SkippedEpochs :: < T > :: get ( ) ;
871+ match skipped_epochs. binary_search_by_key ( & epoch_index, |( epoch_index, _) | * epoch_index) {
872+ // we have an exact match so we just return the given session index
873+ Ok ( index) => skipped_epochs[ index] . 1 ,
874+ // we haven't found any skipped epoch before the given epoch,
875+ // so the epoch index and session index should match
876+ Err ( 0 ) => epoch_index. saturated_into :: < u32 > ( ) ,
877+ // we have found a skipped epoch before the given epoch
878+ Err ( index) => {
879+ // the element before the given index should give us the skipped epoch
880+ // that's closest to the one we're trying to find the session index for
881+ let closest_skipped_epoch = skipped_epochs[ index - 1 ] ;
882+
883+ // calculate the number of skipped epochs at this point by checking the difference
884+ // between the epoch and session indices. epoch index should always be greater or
885+ // equal to session index, this is because epochs can be skipped whereas sessions
886+ // can't (this is enforced when pushing into `SkippedEpochs`)
887+ let skipped_epochs = closest_skipped_epoch. 0 - closest_skipped_epoch. 1 as u64 ;
888+ epoch_index. saturating_sub ( skipped_epochs) . saturated_into :: < u32 > ( )
889+ } ,
890+ }
891+ }
892+
819893 fn do_report_equivocation (
820894 reporter : Option < T :: AccountId > ,
821895 equivocation_proof : EquivocationProof < T :: Header > ,
@@ -832,12 +906,11 @@ impl<T: Config> Pallet<T> {
832906 let validator_set_count = key_owner_proof. validator_count ( ) ;
833907 let session_index = key_owner_proof. session ( ) ;
834908
835- let epoch_index = ( * slot. saturating_sub ( GenesisSlot :: < T > :: get ( ) ) / T :: EpochDuration :: get ( ) )
836- . saturated_into :: < u32 > ( ) ;
909+ let epoch_index = * slot. saturating_sub ( GenesisSlot :: < T > :: get ( ) ) / T :: EpochDuration :: get ( ) ;
837910
838911 // check that the slot number is consistent with the session index
839912 // in the key ownership proof (i.e. slot is for that epoch)
840- if epoch_index != session_index {
913+ if Self :: session_index_for_epoch ( epoch_index) != session_index {
841914 return Err ( Error :: < T > :: InvalidKeyOwnershipProof . into ( ) )
842915 }
843916
@@ -926,7 +999,10 @@ impl<T: Config> sp_runtime::BoundToRuntimeAppPublic for Pallet<T> {
926999 type Public = AuthorityId ;
9271000}
9281001
929- impl < T : Config > OneSessionHandler < T :: AccountId > for Pallet < T > {
1002+ impl < T : Config > OneSessionHandler < T :: AccountId > for Pallet < T >
1003+ where
1004+ T : pallet_session:: Config ,
1005+ {
9301006 type Key = AuthorityId ;
9311007
9321008 fn on_genesis_session < ' a , I : ' a > ( validators : I )
@@ -959,7 +1035,9 @@ impl<T: Config> OneSessionHandler<T::AccountId> for Pallet<T> {
9591035 ) ,
9601036 ) ;
9611037
962- Self :: enact_epoch_change ( bounded_authorities, next_bounded_authorities)
1038+ let session_index = <pallet_session:: Pallet < T > >:: current_index ( ) ;
1039+
1040+ Self :: enact_epoch_change ( bounded_authorities, next_bounded_authorities, Some ( session_index) )
9631041 }
9641042
9651043 fn on_disabled ( i : u32 ) {
0 commit comments