@@ -86,6 +86,11 @@ pub enum Error {
8686 } ,
8787 PreviousCommitteeCacheUninitialized ,
8888 CurrentCommitteeCacheUninitialized ,
89+ TotalActiveBalanceCacheUninitialized ,
90+ TotalActiveBalanceCacheInconsistent {
91+ initialized_epoch : Epoch ,
92+ current_epoch : Epoch ,
93+ } ,
8994 RelativeEpochError ( RelativeEpochError ) ,
9095 ExitCacheUninitialized ,
9196 CommitteeCacheUninitialized ( Option < RelativeEpoch > ) ,
@@ -275,6 +280,13 @@ where
275280 #[ tree_hash( skip_hashing) ]
276281 #[ test_random( default ) ]
277282 #[ derivative( Clone ( clone_with = "clone_default" ) ) ]
283+ pub total_active_balance : Option < ( Epoch , u64 ) > ,
284+ #[ serde( skip_serializing, skip_deserializing) ]
285+ #[ ssz( skip_serializing) ]
286+ #[ ssz( skip_deserializing) ]
287+ #[ tree_hash( skip_hashing) ]
288+ #[ test_random( default ) ]
289+ #[ derivative( Clone ( clone_with = "clone_default" ) ) ]
278290 pub committee_caches : [ CommitteeCache ; CACHED_EPOCHS ] ,
279291 #[ serde( skip_serializing, skip_deserializing) ]
280292 #[ ssz( skip_serializing) ]
@@ -353,6 +365,7 @@ impl<T: EthSpec> BeaconState<T> {
353365 finalized_checkpoint : Checkpoint :: default ( ) ,
354366
355367 // Caching (not in spec)
368+ total_active_balance : None ,
356369 committee_caches : [
357370 CommitteeCache :: default ( ) ,
358371 CommitteeCache :: default ( ) ,
@@ -1226,12 +1239,45 @@ impl<T: EthSpec> BeaconState<T> {
12261239 }
12271240
12281241 /// Implementation of `get_total_active_balance`, matching the spec.
1229- pub fn get_total_active_balance ( & self , spec : & ChainSpec ) -> Result < u64 , Error > {
1242+ ///
1243+ /// Requires the total active balance cache to be initialised, which is initialised whenever
1244+ /// the current committee cache is.
1245+ ///
1246+ /// Returns minimum `EFFECTIVE_BALANCE_INCREMENT`, to avoid div by 0.
1247+ pub fn get_total_active_balance ( & self ) -> Result < u64 , Error > {
1248+ let ( initialized_epoch, balance) = self
1249+ . total_active_balance ( )
1250+ . ok_or ( Error :: TotalActiveBalanceCacheUninitialized ) ?;
1251+
1252+ let current_epoch = self . current_epoch ( ) ;
1253+ if initialized_epoch == current_epoch {
1254+ Ok ( balance)
1255+ } else {
1256+ Err ( Error :: TotalActiveBalanceCacheInconsistent {
1257+ initialized_epoch,
1258+ current_epoch,
1259+ } )
1260+ }
1261+ }
1262+
1263+ /// Build the total active balance cache.
1264+ ///
1265+ /// This function requires the current committee cache to be already built. It is called
1266+ /// automatically when `build_committee_cache` is called for the current epoch.
1267+ fn build_total_active_balance_cache ( & mut self , spec : & ChainSpec ) -> Result < ( ) , Error > {
12301268 // Order is irrelevant, so use the cached indices.
1231- self . get_total_balance (
1269+ let current_epoch = self . current_epoch ( ) ;
1270+ let total_active_balance = self . get_total_balance (
12321271 self . get_cached_active_validator_indices ( RelativeEpoch :: Current ) ?,
12331272 spec,
1234- )
1273+ ) ?;
1274+ * self . total_active_balance_mut ( ) = Some ( ( current_epoch, total_active_balance) ) ;
1275+ Ok ( ( ) )
1276+ }
1277+
1278+ /// Set the cached total active balance to `None`, representing no known value.
1279+ pub fn drop_total_active_balance_cache ( & mut self ) {
1280+ * self . total_active_balance_mut ( ) = None ;
12351281 }
12361282
12371283 /// Get a mutable reference to the epoch participation flags for `epoch`.
@@ -1294,6 +1340,7 @@ impl<T: EthSpec> BeaconState<T> {
12941340
12951341 /// Drop all caches on the state.
12961342 pub fn drop_all_caches ( & mut self ) -> Result < ( ) , Error > {
1343+ self . drop_total_active_balance_cache ( ) ;
12971344 self . drop_committee_cache ( RelativeEpoch :: Previous ) ?;
12981345 self . drop_committee_cache ( RelativeEpoch :: Current ) ?;
12991346 self . drop_committee_cache ( RelativeEpoch :: Next ) ?;
@@ -1323,11 +1370,14 @@ impl<T: EthSpec> BeaconState<T> {
13231370 . committee_cache_at_index ( i) ?
13241371 . is_initialized_at ( relative_epoch. into_epoch ( self . current_epoch ( ) ) ) ;
13251372
1326- if is_initialized {
1327- Ok ( ( ) )
1328- } else {
1329- self . force_build_committee_cache ( relative_epoch, spec)
1373+ if !is_initialized {
1374+ self . force_build_committee_cache ( relative_epoch, spec) ?;
1375+ }
1376+
1377+ if self . total_active_balance ( ) . is_none ( ) && relative_epoch == RelativeEpoch :: Current {
1378+ self . build_total_active_balance_cache ( spec) ?;
13301379 }
1380+ Ok ( ( ) )
13311381 }
13321382
13331383 /// Always builds the previous epoch cache, even if it is already initialized.
@@ -1359,10 +1409,36 @@ impl<T: EthSpec> BeaconState<T> {
13591409 ///
13601410 /// This should be used if the `slot` of this state is advanced beyond an epoch boundary.
13611411 ///
1362- /// Note: whilst this function will preserve already-built caches, it will not build any.
1363- pub fn advance_caches ( & mut self ) -> Result < ( ) , Error > {
1412+ /// Note: this function will not build any new committee caches, but will build the total
1413+ /// balance cache if the (new) current epoch cache is initialized.
1414+ pub fn advance_caches ( & mut self , spec : & ChainSpec ) -> Result < ( ) , Error > {
13641415 self . committee_caches_mut ( ) . rotate_left ( 1 ) ;
13651416
1417+ // Re-compute total active balance for current epoch.
1418+ //
1419+ // This can only be computed once the state's effective balances have been updated
1420+ // for the current epoch. I.e. it is not possible to know this value with the same
1421+ // lookahead as the committee shuffling.
1422+ let curr = Self :: committee_cache_index ( RelativeEpoch :: Current ) ;
1423+ let curr_cache = mem:: take ( self . committee_cache_at_index_mut ( curr) ?) ;
1424+
1425+ // If current epoch cache is initialized, compute the total active balance from its
1426+ // indices. We check that the cache is initialized at the _next_ epoch because the slot has
1427+ // not yet been advanced.
1428+ let new_current_epoch = self . next_epoch ( ) ?;
1429+ if curr_cache. is_initialized_at ( new_current_epoch) {
1430+ * self . total_active_balance_mut ( ) = Some ( (
1431+ new_current_epoch,
1432+ self . get_total_balance ( curr_cache. active_validator_indices ( ) , spec) ?,
1433+ ) ) ;
1434+ }
1435+ // If the cache is not initialized, then the previous cached value for the total balance is
1436+ // wrong, so delete it.
1437+ else {
1438+ self . drop_total_active_balance_cache ( ) ;
1439+ }
1440+ * self . committee_cache_at_index_mut ( curr) ? = curr_cache;
1441+
13661442 let next = Self :: committee_cache_index ( RelativeEpoch :: Next ) ;
13671443 * self . committee_cache_at_index_mut ( next) ? = CommitteeCache :: default ( ) ;
13681444 Ok ( ( ) )
@@ -1504,6 +1580,7 @@ impl<T: EthSpec> BeaconState<T> {
15041580 } ;
15051581 if config. committee_caches {
15061582 * res. committee_caches_mut ( ) = self . committee_caches ( ) . clone ( ) ;
1583+ * res. total_active_balance_mut ( ) = * self . total_active_balance ( ) ;
15071584 }
15081585 if config. pubkey_cache {
15091586 * res. pubkey_cache_mut ( ) = self . pubkey_cache ( ) . clone ( ) ;
0 commit comments