@@ -175,6 +175,7 @@ pub fn process_epoch_single_pass<E: EthSpec>(
175175
176176 let mut earliest_exit_epoch = state. earliest_exit_epoch ( ) . ok ( ) ;
177177 let mut exit_balance_to_consume = state. exit_balance_to_consume ( ) . ok ( ) ;
178+ let validators_in_consolidations = get_validators_in_consolidations ( state) ;
178179
179180 // Split the state into several disjoint mutable borrows.
180181 let (
@@ -317,17 +318,26 @@ pub fn process_epoch_single_pass<E: EthSpec>(
317318
318319 // `process_effective_balance_updates`
319320 if conf. effective_balance_updates {
320- process_single_effective_balance_update (
321- validator_info. index ,
322- * balance,
323- & mut validator,
324- validator_info. current_epoch_participation ,
325- & mut next_epoch_cache,
326- progressive_balances,
327- effective_balances_ctxt,
328- state_ctxt,
329- spec,
330- ) ?;
321+ if validators_in_consolidations. contains ( & validator_info. index ) {
322+ process_single_dummy_effective_balance_update (
323+ validator_info. index ,
324+ & validator,
325+ & mut next_epoch_cache,
326+ state_ctxt,
327+ ) ?;
328+ } else {
329+ process_single_effective_balance_update (
330+ validator_info. index ,
331+ * balance,
332+ & mut validator,
333+ validator_info. current_epoch_participation ,
334+ & mut next_epoch_cache,
335+ progressive_balances,
336+ effective_balances_ctxt,
337+ state_ctxt,
338+ spec,
339+ ) ?;
340+ }
331341 }
332342 }
333343
@@ -430,6 +440,7 @@ pub fn process_epoch_single_pass<E: EthSpec>(
430440 if fork_name. electra_enabled ( ) && conf. pending_consolidations {
431441 process_pending_consolidations (
432442 state,
443+ & validators_in_consolidations,
433444 & mut next_epoch_cache,
434445 effective_balances_ctxt,
435446 conf. effective_balance_updates ,
@@ -1026,12 +1037,38 @@ fn process_pending_deposits_for_validator(
10261037 Ok ( ( ) )
10271038}
10281039
1040+ /// Return the set of validators referenced by consolidations, either as source or target.
1041+ ///
1042+ /// This function is blind to whether the consolidations are valid and capable of being processed,
1043+ /// it just returns the set of all indices present in consolidations. This is *sufficient* to
1044+ /// make consolidations play nicely with effective balance updates. The algorithm used is:
1045+ ///
1046+ /// - In the single pass: apply effective balance updates for all validators *not* referenced by
1047+ /// consolidations.
1048+ /// - Apply consolidations.
1049+ /// - Apply effective balance updates for all validators previously skipped.
1050+ ///
1051+ /// Prior to Electra, the empty set is returned.
1052+ fn get_validators_in_consolidations < E : EthSpec > ( state : & BeaconState < E > ) -> BTreeSet < usize > {
1053+ let mut referenced_validators = BTreeSet :: new ( ) ;
1054+
1055+ if let Ok ( pending_consolidations) = state. pending_consolidations ( ) {
1056+ for pending_consolidation in pending_consolidations {
1057+ referenced_validators. insert ( pending_consolidation. source_index as usize ) ;
1058+ referenced_validators. insert ( pending_consolidation. target_index as usize ) ;
1059+ }
1060+ }
1061+
1062+ referenced_validators
1063+ }
1064+
10291065/// We process pending consolidations after all of single-pass epoch processing, and then patch up
10301066/// the effective balances for affected validators.
10311067///
10321068/// This is safe because processing consolidations does not depend on the `effective_balance`.
10331069fn process_pending_consolidations < E : EthSpec > (
10341070 state : & mut BeaconState < E > ,
1071+ validators_in_consolidations : & BTreeSet < usize > ,
10351072 next_epoch_cache : & mut PreEpochCache ,
10361073 effective_balances_ctxt : & EffectiveBalancesContext ,
10371074 perform_effective_balance_updates : bool ,
@@ -1042,8 +1079,6 @@ fn process_pending_consolidations<E: EthSpec>(
10421079 let next_epoch = state. next_epoch ( ) ?;
10431080 let pending_consolidations = state. pending_consolidations ( ) ?. clone ( ) ;
10441081
1045- let mut affected_validators = BTreeSet :: new ( ) ;
1046-
10471082 for pending_consolidation in & pending_consolidations {
10481083 let source_index = pending_consolidation. source_index as usize ;
10491084 let target_index = pending_consolidation. target_index as usize ;
@@ -1069,9 +1104,6 @@ fn process_pending_consolidations<E: EthSpec>(
10691104 decrease_balance ( state, source_index, source_effective_balance) ?;
10701105 increase_balance ( state, target_index, source_effective_balance) ?;
10711106
1072- affected_validators. insert ( source_index) ;
1073- affected_validators. insert ( target_index) ;
1074-
10751107 next_pending_consolidation. safe_add_assign ( 1 ) ?;
10761108 }
10771109
@@ -1087,7 +1119,7 @@ fn process_pending_consolidations<E: EthSpec>(
10871119 // Re-process effective balance updates for validators affected by consolidations.
10881120 let ( validators, balances, _, current_epoch_participation, _, progressive_balances, _, _) =
10891121 state. mutable_validator_fields ( ) ?;
1090- for validator_index in affected_validators {
1122+ for & validator_index in validators_in_consolidations {
10911123 let balance = * balances
10921124 . get ( validator_index)
10931125 . ok_or ( BeaconStateError :: UnknownValidator ( validator_index) ) ?;
@@ -1129,6 +1161,28 @@ impl EffectiveBalancesContext {
11291161 }
11301162}
11311163
1164+ /// This function is called for validators that do not have their effective balance updated as
1165+ /// part of the single-pass loop. For these validators we compute their true effective balance
1166+ /// update after processing consolidations. However, to maintain the invariants of the
1167+ /// `PreEpochCache` we must register _some_ effective balance for them immediately.
1168+ fn process_single_dummy_effective_balance_update (
1169+ validator_index : usize ,
1170+ validator : & Cow < Validator > ,
1171+ next_epoch_cache : & mut PreEpochCache ,
1172+ state_ctxt : & StateContext ,
1173+ ) -> Result < ( ) , Error > {
1174+ // Populate the effective balance cache with the current effective balance. This will be
1175+ // overriden when `process_single_effective_balance_update` is called.
1176+ let is_active_next_epoch = validator. is_active_at ( state_ctxt. next_epoch ) ;
1177+ let temporary_effective_balance = validator. effective_balance ;
1178+ next_epoch_cache. update_effective_balance (
1179+ validator_index,
1180+ temporary_effective_balance,
1181+ is_active_next_epoch,
1182+ ) ?;
1183+ Ok ( ( ) )
1184+ }
1185+
11321186/// This function abstracts over phase0 and Electra effective balance processing.
11331187#[ allow( clippy:: too_many_arguments) ]
11341188fn process_single_effective_balance_update (
0 commit comments