Skip to content

Commit 0875326

Browse files
Prevent duplicate effective balance processing (#7209)
1 parent 0486802 commit 0875326

File tree

1 file changed

+71
-17
lines changed

1 file changed

+71
-17
lines changed

consensus/state_processing/src/per_epoch_processing/single_pass.rs

Lines changed: 71 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -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`.
10331069
fn 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)]
11341188
fn process_single_effective_balance_update(

0 commit comments

Comments
 (0)