@@ -86,6 +86,11 @@ pub enum Error {
86
86
} ,
87
87
PreviousCommitteeCacheUninitialized ,
88
88
CurrentCommitteeCacheUninitialized ,
89
+ TotalActiveBalanceCacheUninitialized ,
90
+ TotalActiveBalanceCacheInconsistent {
91
+ initialized_epoch : Epoch ,
92
+ current_epoch : Epoch ,
93
+ } ,
89
94
RelativeEpochError ( RelativeEpochError ) ,
90
95
ExitCacheUninitialized ,
91
96
CommitteeCacheUninitialized ( Option < RelativeEpoch > ) ,
@@ -275,6 +280,13 @@ where
275
280
#[ tree_hash( skip_hashing) ]
276
281
#[ test_random( default ) ]
277
282
#[ 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" ) ) ]
278
290
pub committee_caches : [ CommitteeCache ; CACHED_EPOCHS ] ,
279
291
#[ serde( skip_serializing, skip_deserializing) ]
280
292
#[ ssz( skip_serializing) ]
@@ -353,6 +365,7 @@ impl<T: EthSpec> BeaconState<T> {
353
365
finalized_checkpoint : Checkpoint :: default ( ) ,
354
366
355
367
// Caching (not in spec)
368
+ total_active_balance : None ,
356
369
committee_caches : [
357
370
CommitteeCache :: default ( ) ,
358
371
CommitteeCache :: default ( ) ,
@@ -1226,12 +1239,45 @@ impl<T: EthSpec> BeaconState<T> {
1226
1239
}
1227
1240
1228
1241
/// 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 > {
1230
1268
// 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 (
1232
1271
self . get_cached_active_validator_indices ( RelativeEpoch :: Current ) ?,
1233
1272
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 ;
1235
1281
}
1236
1282
1237
1283
/// Get a mutable reference to the epoch participation flags for `epoch`.
@@ -1294,6 +1340,7 @@ impl<T: EthSpec> BeaconState<T> {
1294
1340
1295
1341
/// Drop all caches on the state.
1296
1342
pub fn drop_all_caches ( & mut self ) -> Result < ( ) , Error > {
1343
+ self . drop_total_active_balance_cache ( ) ;
1297
1344
self . drop_committee_cache ( RelativeEpoch :: Previous ) ?;
1298
1345
self . drop_committee_cache ( RelativeEpoch :: Current ) ?;
1299
1346
self . drop_committee_cache ( RelativeEpoch :: Next ) ?;
@@ -1323,11 +1370,14 @@ impl<T: EthSpec> BeaconState<T> {
1323
1370
. committee_cache_at_index ( i) ?
1324
1371
. is_initialized_at ( relative_epoch. into_epoch ( self . current_epoch ( ) ) ) ;
1325
1372
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) ?;
1330
1379
}
1380
+ Ok ( ( ) )
1331
1381
}
1332
1382
1333
1383
/// Always builds the previous epoch cache, even if it is already initialized.
@@ -1359,10 +1409,36 @@ impl<T: EthSpec> BeaconState<T> {
1359
1409
///
1360
1410
/// This should be used if the `slot` of this state is advanced beyond an epoch boundary.
1361
1411
///
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 > {
1364
1415
self . committee_caches_mut ( ) . rotate_left ( 1 ) ;
1365
1416
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
+
1366
1442
let next = Self :: committee_cache_index ( RelativeEpoch :: Next ) ;
1367
1443
* self . committee_cache_at_index_mut ( next) ? = CommitteeCache :: default ( ) ;
1368
1444
Ok ( ( ) )
@@ -1504,6 +1580,7 @@ impl<T: EthSpec> BeaconState<T> {
1504
1580
} ;
1505
1581
if config. committee_caches {
1506
1582
* res. committee_caches_mut ( ) = self . committee_caches ( ) . clone ( ) ;
1583
+ * res. total_active_balance_mut ( ) = * self . total_active_balance ( ) ;
1507
1584
}
1508
1585
if config. pubkey_cache {
1509
1586
* res. pubkey_cache_mut ( ) = self . pubkey_cache ( ) . clone ( ) ;
0 commit comments