11use super :: { BeaconStateError , ChainSpec , Epoch , Validator } ;
2- use safe_arith:: SafeArith ;
32use serde:: { Deserialize , Serialize } ;
4- use std:: collections :: HashMap ;
3+ use std:: cmp :: Ordering ;
54
65/// Map from exit epoch to the number of validators with that exit epoch.
76#[ derive( Debug , Default , Clone , PartialEq , Serialize , Deserialize ) ]
87pub struct ExitCache {
8+ /// True if the cache has been initialized.
99 initialized : bool ,
10- exit_epoch_counts : HashMap < Epoch , u64 > ,
10+ /// Maximum `exit_epoch` of any validator.
11+ max_exit_epoch : Epoch ,
12+ /// Number of validators known to be exiting at `max_exit_epoch`.
13+ max_exit_epoch_churn : u64 ,
1114}
1215
1316impl ExitCache {
1417 /// Initialize a new cache for the given list of validators.
1518 pub fn new ( validators : & [ Validator ] , spec : & ChainSpec ) -> Result < Self , BeaconStateError > {
1619 let mut exit_cache = ExitCache {
1720 initialized : true ,
18- ..ExitCache :: default ( )
21+ max_exit_epoch : Epoch :: new ( 0 ) ,
22+ max_exit_epoch_churn : 0 ,
1923 } ;
2024 // Add all validators with a non-default exit epoch to the cache.
2125 validators
@@ -37,27 +41,44 @@ impl ExitCache {
3741 /// Record the exit epoch of a validator. Must be called only once per exiting validator.
3842 pub fn record_validator_exit ( & mut self , exit_epoch : Epoch ) -> Result < ( ) , BeaconStateError > {
3943 self . check_initialized ( ) ?;
40- self . exit_epoch_counts
41- . entry ( exit_epoch)
42- . or_insert ( 0 )
43- . safe_add_assign ( 1 ) ?;
44+ match exit_epoch. cmp ( & self . max_exit_epoch ) {
45+ // Update churn for the current maximum epoch.
46+ Ordering :: Equal => {
47+ self . max_exit_epoch_churn += 1 ;
48+ }
49+ // Increase the max exit epoch, reset the churn to 1.
50+ Ordering :: Greater => {
51+ self . max_exit_epoch = exit_epoch;
52+ self . max_exit_epoch_churn = 1 ;
53+ }
54+ // Older exit epochs are not relevant.
55+ Ordering :: Less => ( ) ,
56+ }
4457 Ok ( ( ) )
4558 }
4659
4760 /// Get the largest exit epoch with a non-zero exit epoch count.
4861 pub fn max_epoch ( & self ) -> Result < Option < Epoch > , BeaconStateError > {
4962 self . check_initialized ( ) ?;
50- Ok ( self . exit_epoch_counts . keys ( ) . max ( ) . cloned ( ) )
63+ Ok ( ( self . max_exit_epoch_churn > 0 ) . then_some ( self . max_exit_epoch ) )
5164 }
5265
5366 /// Get number of validators with the given exit epoch. (Return 0 for the default exit epoch.)
5467 pub fn get_churn_at ( & self , exit_epoch : Epoch ) -> Result < u64 , BeaconStateError > {
5568 self . check_initialized ( ) ?;
56- Ok ( self
57- . exit_epoch_counts
58- . get ( & exit_epoch)
59- . cloned ( )
60- . unwrap_or ( 0 ) )
69+ match exit_epoch. cmp ( & self . max_exit_epoch ) {
70+ // Epochs are equal, we know the churn exactly.
71+ Ordering :: Equal => Ok ( self . max_exit_epoch_churn ) ,
72+ // If exiting at an epoch later than the cached epoch then the churn is 0. This is a
73+ // common case which happens when there are no exits for an epoch.
74+ Ordering :: Greater => Ok ( 0 ) ,
75+ // Consensus code should never require the churn at an epoch prior to the cached epoch.
76+ // That's a bug.
77+ Ordering :: Less => Err ( BeaconStateError :: ExitCacheInvalidEpoch {
78+ max_exit_epoch : self . max_exit_epoch ,
79+ request_epoch : exit_epoch,
80+ } ) ,
81+ }
6182 }
6283}
6384
0 commit comments