mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-16 11:22:56 +00:00
Single-pass epoch processing and optimised block processing (#5279)
* Single-pass epoch processing (#4483, #4573) Co-authored-by: Michael Sproul <michael@sigmaprime.io> * Delete unused epoch processing code (#5170) * Delete unused epoch processing code * Compare total deltas * Remove unnecessary apply_pending * cargo fmt * Remove newline * Use epoch cache in block packing (#5223) * Remove progressive balances mode (#5224) * inline inactivity_penalty_quotient_for_state * drop previous_epoch_total_active_balance * fc lint * spec compliant process_sync_aggregate (#15) * spec compliant process_sync_aggregate * Update consensus/state_processing/src/per_block_processing/altair/sync_committee.rs Co-authored-by: Michael Sproul <micsproul@gmail.com> --------- Co-authored-by: Michael Sproul <micsproul@gmail.com> * Delete the participation cache (#16) * update help * Fix op_pool tests * Fix fork choice tests * Merge remote-tracking branch 'sigp/unstable' into epoch-single-pass * Simplify exit cache (#5280) * Fix clippy on exit cache * Clean up single-pass a bit (#5282) * Address Mark's review of single-pass (#5386) * Merge remote-tracking branch 'origin/unstable' into epoch-single-pass * Address Sean's review comments (#5414) * Address most of Sean's review comments * Simplify total balance cache building * Clean up unused junk * Merge remote-tracking branch 'origin/unstable' into epoch-single-pass * More self-review * Merge remote-tracking branch 'origin/unstable' into epoch-single-pass * Merge branch 'unstable' into epoch-single-pass * Fix imports for beta compiler * Fix tests, probably
This commit is contained in:
@@ -1,23 +1,23 @@
|
||||
use super::{process_registry_updates, process_slashings, EpochProcessingSummary, Error};
|
||||
use super::{EpochProcessingSummary, Error};
|
||||
use crate::common::update_progressive_balances_cache::{
|
||||
initialize_progressive_balances_cache, update_progressive_balances_on_epoch_transition,
|
||||
};
|
||||
use crate::epoch_cache::initialize_epoch_cache;
|
||||
use crate::per_epoch_processing::single_pass::{process_epoch_single_pass, SinglePassConfig};
|
||||
use crate::per_epoch_processing::{
|
||||
effective_balance_updates::process_effective_balance_updates,
|
||||
capella::process_historical_summaries_update,
|
||||
historical_roots_update::process_historical_roots_update,
|
||||
resets::{process_eth1_data_reset, process_randao_mixes_reset, process_slashings_reset},
|
||||
};
|
||||
pub use inactivity_updates::process_inactivity_updates;
|
||||
pub use inactivity_updates::process_inactivity_updates_slow;
|
||||
pub use justification_and_finalization::process_justification_and_finalization;
|
||||
pub use participation_cache::ParticipationCache;
|
||||
pub use participation_flag_updates::process_participation_flag_updates;
|
||||
pub use rewards_and_penalties::process_rewards_and_penalties;
|
||||
pub use rewards_and_penalties::process_rewards_and_penalties_slow;
|
||||
pub use sync_committee_updates::process_sync_committee_updates;
|
||||
use types::{BeaconState, ChainSpec, EthSpec, RelativeEpoch};
|
||||
|
||||
pub mod inactivity_updates;
|
||||
pub mod justification_and_finalization;
|
||||
pub mod participation_cache;
|
||||
pub mod participation_flag_updates;
|
||||
pub mod rewards_and_penalties;
|
||||
pub mod sync_committee_updates;
|
||||
@@ -26,50 +26,51 @@ pub fn process_epoch<E: EthSpec>(
|
||||
state: &mut BeaconState<E>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<EpochProcessingSummary<E>, Error> {
|
||||
// Ensure the committee caches are built.
|
||||
// Ensure the required caches are built.
|
||||
state.build_committee_cache(RelativeEpoch::Previous, spec)?;
|
||||
state.build_committee_cache(RelativeEpoch::Current, spec)?;
|
||||
state.build_committee_cache(RelativeEpoch::Next, spec)?;
|
||||
state.build_total_active_balance_cache(spec)?;
|
||||
initialize_epoch_cache(state, spec)?;
|
||||
initialize_progressive_balances_cache::<E>(state, spec)?;
|
||||
|
||||
// Pre-compute participating indices and total balances.
|
||||
let participation_cache = ParticipationCache::new(state, spec)?;
|
||||
let sync_committee = state.current_sync_committee()?.clone();
|
||||
initialize_progressive_balances_cache::<E>(state, Some(&participation_cache), spec)?;
|
||||
|
||||
// Justification and finalization.
|
||||
let justification_and_finalization_state =
|
||||
process_justification_and_finalization(state, &participation_cache)?;
|
||||
let justification_and_finalization_state = process_justification_and_finalization(state)?;
|
||||
justification_and_finalization_state.apply_changes_to_state(state);
|
||||
|
||||
process_inactivity_updates(state, &participation_cache, spec)?;
|
||||
|
||||
// Rewards and Penalties.
|
||||
process_rewards_and_penalties(state, &participation_cache, spec)?;
|
||||
|
||||
// Registry Updates.
|
||||
process_registry_updates(state, spec)?;
|
||||
|
||||
// Slashings.
|
||||
process_slashings(
|
||||
state,
|
||||
participation_cache.current_epoch_total_active_balance(),
|
||||
spec,
|
||||
)?;
|
||||
// In a single pass:
|
||||
// - Inactivity updates
|
||||
// - Rewards and penalties
|
||||
// - Registry updates
|
||||
// - Slashings
|
||||
// - Effective balance updates
|
||||
//
|
||||
// The `process_eth1_data_reset` is not covered in the single pass, but happens afterwards
|
||||
// without loss of correctness.
|
||||
let current_epoch_progressive_balances = state.progressive_balances_cache().clone();
|
||||
let current_epoch_total_active_balance = state.get_total_active_balance()?;
|
||||
let participation_summary =
|
||||
process_epoch_single_pass(state, spec, SinglePassConfig::default())?;
|
||||
|
||||
// Reset eth1 data votes.
|
||||
process_eth1_data_reset(state)?;
|
||||
|
||||
// Update effective balances with hysteresis (lag).
|
||||
process_effective_balance_updates(state, Some(&participation_cache), spec)?;
|
||||
|
||||
// Reset slashings
|
||||
process_slashings_reset(state)?;
|
||||
|
||||
// Set randao mix
|
||||
process_randao_mixes_reset(state)?;
|
||||
|
||||
// Set historical root accumulator
|
||||
process_historical_roots_update(state)?;
|
||||
// Set historical summaries accumulator
|
||||
if state.historical_summaries().is_ok() {
|
||||
// Post-Capella.
|
||||
process_historical_summaries_update(state)?;
|
||||
} else {
|
||||
// Pre-Capella
|
||||
process_historical_roots_update(state)?;
|
||||
}
|
||||
|
||||
// Rotate current/previous epoch participation
|
||||
process_participation_flag_updates(state)?;
|
||||
@@ -77,12 +78,13 @@ pub fn process_epoch<E: EthSpec>(
|
||||
process_sync_committee_updates(state, spec)?;
|
||||
|
||||
// Rotate the epoch caches to suit the epoch transition.
|
||||
state.advance_caches(spec)?;
|
||||
|
||||
state.advance_caches()?;
|
||||
update_progressive_balances_on_epoch_transition(state, spec)?;
|
||||
|
||||
Ok(EpochProcessingSummary::Altair {
|
||||
participation_cache,
|
||||
progressive_balances: current_epoch_progressive_balances,
|
||||
current_epoch_total_active_balance,
|
||||
participation: participation_summary,
|
||||
sync_committee,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,42 +1,23 @@
|
||||
use super::ParticipationCache;
|
||||
use crate::per_epoch_processing::single_pass::{process_epoch_single_pass, SinglePassConfig};
|
||||
use crate::EpochProcessingError;
|
||||
use safe_arith::SafeArith;
|
||||
use std::cmp::min;
|
||||
use types::beacon_state::BeaconState;
|
||||
use types::chain_spec::ChainSpec;
|
||||
use types::consts::altair::TIMELY_TARGET_FLAG_INDEX;
|
||||
use types::eth_spec::EthSpec;
|
||||
|
||||
pub fn process_inactivity_updates<E: EthSpec>(
|
||||
/// Slow version of `process_inactivity_updates` that runs a subset of single-pass processing.
|
||||
///
|
||||
/// Should not be used for block processing, but is useful for testing & analytics.
|
||||
pub fn process_inactivity_updates_slow<E: EthSpec>(
|
||||
state: &mut BeaconState<E>,
|
||||
participation_cache: &ParticipationCache,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), EpochProcessingError> {
|
||||
let previous_epoch = state.previous_epoch();
|
||||
// Score updates based on previous epoch participation, skip genesis epoch
|
||||
if state.current_epoch() == E::genesis_epoch() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let unslashed_indices = participation_cache
|
||||
.get_unslashed_participating_indices(TIMELY_TARGET_FLAG_INDEX, state.previous_epoch())?;
|
||||
|
||||
for &index in participation_cache.eligible_validator_indices() {
|
||||
// Increase inactivity score of inactive validators
|
||||
if unslashed_indices.contains(index)? {
|
||||
let inactivity_score = state.get_inactivity_score_mut(index)?;
|
||||
inactivity_score.safe_sub_assign(min(1, *inactivity_score))?;
|
||||
} else {
|
||||
state
|
||||
.get_inactivity_score_mut(index)?
|
||||
.safe_add_assign(spec.inactivity_score_bias)?;
|
||||
}
|
||||
// Decrease the score of all validators for forgiveness when not during a leak
|
||||
if !state.is_in_inactivity_leak(previous_epoch, spec)? {
|
||||
let inactivity_score = state.get_inactivity_score_mut(index)?;
|
||||
inactivity_score
|
||||
.safe_sub_assign(min(spec.inactivity_score_recovery_rate, *inactivity_score))?;
|
||||
}
|
||||
}
|
||||
process_epoch_single_pass(
|
||||
state,
|
||||
spec,
|
||||
SinglePassConfig {
|
||||
inactivity_updates: true,
|
||||
..SinglePassConfig::disable_all()
|
||||
},
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,32 +1,27 @@
|
||||
use super::ParticipationCache;
|
||||
use crate::per_epoch_processing::Error;
|
||||
use crate::per_epoch_processing::{
|
||||
weigh_justification_and_finalization, JustificationAndFinalizationState,
|
||||
};
|
||||
use safe_arith::SafeArith;
|
||||
use types::consts::altair::TIMELY_TARGET_FLAG_INDEX;
|
||||
use types::{BeaconState, EthSpec};
|
||||
|
||||
/// Update the justified and finalized checkpoints for matching target attestations.
|
||||
/// Process justification and finalization using the progressive balances cache.
|
||||
pub fn process_justification_and_finalization<E: EthSpec>(
|
||||
state: &BeaconState<E>,
|
||||
participation_cache: &ParticipationCache,
|
||||
) -> Result<JustificationAndFinalizationState<E>, Error> {
|
||||
let justification_and_finalization_state = JustificationAndFinalizationState::new(state);
|
||||
|
||||
if state.current_epoch() <= E::genesis_epoch().safe_add(1)? {
|
||||
return Ok(justification_and_finalization_state);
|
||||
}
|
||||
|
||||
let previous_epoch = state.previous_epoch();
|
||||
let current_epoch = state.current_epoch();
|
||||
let previous_indices = participation_cache
|
||||
.get_unslashed_participating_indices(TIMELY_TARGET_FLAG_INDEX, previous_epoch)?;
|
||||
let current_indices = participation_cache
|
||||
.get_unslashed_participating_indices(TIMELY_TARGET_FLAG_INDEX, current_epoch)?;
|
||||
let total_active_balance = participation_cache.current_epoch_total_active_balance();
|
||||
let previous_target_balance = previous_indices.total_balance()?;
|
||||
let current_target_balance = current_indices.total_balance()?;
|
||||
// Load cached balances
|
||||
let progressive_balances_cache = state.progressive_balances_cache();
|
||||
let previous_target_balance =
|
||||
progressive_balances_cache.previous_epoch_target_attesting_balance()?;
|
||||
let current_target_balance =
|
||||
progressive_balances_cache.current_epoch_target_attesting_balance()?;
|
||||
let total_active_balance = state.get_total_active_balance()?;
|
||||
|
||||
weigh_justification_and_finalization(
|
||||
justification_and_finalization_state,
|
||||
total_active_balance,
|
||||
|
||||
@@ -1,98 +1,25 @@
|
||||
use super::ParticipationCache;
|
||||
use safe_arith::SafeArith;
|
||||
use types::consts::altair::{
|
||||
PARTICIPATION_FLAG_WEIGHTS, TIMELY_HEAD_FLAG_INDEX, TIMELY_TARGET_FLAG_INDEX,
|
||||
WEIGHT_DENOMINATOR,
|
||||
use crate::per_epoch_processing::{
|
||||
single_pass::{process_epoch_single_pass, SinglePassConfig},
|
||||
Error,
|
||||
};
|
||||
use types::consts::altair::PARTICIPATION_FLAG_WEIGHTS;
|
||||
use types::{BeaconState, ChainSpec, EthSpec};
|
||||
|
||||
use crate::common::{
|
||||
altair::{get_base_reward, BaseRewardPerIncrement},
|
||||
decrease_balance, increase_balance,
|
||||
};
|
||||
use crate::per_epoch_processing::{Delta, Error};
|
||||
|
||||
/// Apply attester and proposer rewards.
|
||||
///
|
||||
/// Spec v1.1.0
|
||||
pub fn process_rewards_and_penalties<E: EthSpec>(
|
||||
/// This function should only be used for testing.
|
||||
pub fn process_rewards_and_penalties_slow<E: EthSpec>(
|
||||
state: &mut BeaconState<E>,
|
||||
participation_cache: &ParticipationCache,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
if state.current_epoch() == E::genesis_epoch() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut deltas = vec![Delta::default(); state.validators().len()];
|
||||
|
||||
let total_active_balance = participation_cache.current_epoch_total_active_balance();
|
||||
|
||||
for flag_index in 0..PARTICIPATION_FLAG_WEIGHTS.len() {
|
||||
get_flag_index_deltas(
|
||||
&mut deltas,
|
||||
state,
|
||||
flag_index,
|
||||
total_active_balance,
|
||||
participation_cache,
|
||||
spec,
|
||||
)?;
|
||||
}
|
||||
|
||||
get_inactivity_penalty_deltas(&mut deltas, state, participation_cache, spec)?;
|
||||
|
||||
// Apply the deltas, erroring on overflow above but not on overflow below (saturating at 0
|
||||
// instead).
|
||||
for (i, delta) in deltas.into_iter().enumerate() {
|
||||
increase_balance(state, i, delta.rewards)?;
|
||||
decrease_balance(state, i, delta.penalties)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Return the deltas for a given flag index by scanning through the participation flags.
|
||||
///
|
||||
/// Spec v1.1.0
|
||||
pub fn get_flag_index_deltas<E: EthSpec>(
|
||||
deltas: &mut [Delta],
|
||||
state: &BeaconState<E>,
|
||||
flag_index: usize,
|
||||
total_active_balance: u64,
|
||||
participation_cache: &ParticipationCache,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
let previous_epoch = state.previous_epoch();
|
||||
let unslashed_participating_indices =
|
||||
participation_cache.get_unslashed_participating_indices(flag_index, previous_epoch)?;
|
||||
let weight = get_flag_weight(flag_index)?;
|
||||
let unslashed_participating_balance = unslashed_participating_indices.total_balance()?;
|
||||
let unslashed_participating_increments =
|
||||
unslashed_participating_balance.safe_div(spec.effective_balance_increment)?;
|
||||
let active_increments = total_active_balance.safe_div(spec.effective_balance_increment)?;
|
||||
let base_reward_per_increment = BaseRewardPerIncrement::new(total_active_balance, spec)?;
|
||||
|
||||
for &index in participation_cache.eligible_validator_indices() {
|
||||
let base_reward = get_base_reward(state, index, base_reward_per_increment, spec)?;
|
||||
let mut delta = Delta::default();
|
||||
|
||||
if unslashed_participating_indices.contains(index)? {
|
||||
if !state.is_in_inactivity_leak(previous_epoch, spec)? {
|
||||
let reward_numerator = base_reward
|
||||
.safe_mul(weight)?
|
||||
.safe_mul(unslashed_participating_increments)?;
|
||||
delta.reward(
|
||||
reward_numerator.safe_div(active_increments.safe_mul(WEIGHT_DENOMINATOR)?)?,
|
||||
)?;
|
||||
}
|
||||
} else if flag_index != TIMELY_HEAD_FLAG_INDEX {
|
||||
delta.penalize(base_reward.safe_mul(weight)?.safe_div(WEIGHT_DENOMINATOR)?)?;
|
||||
}
|
||||
deltas
|
||||
.get_mut(index)
|
||||
.ok_or(Error::DeltaOutOfBounds(index))?
|
||||
.combine(delta)?;
|
||||
}
|
||||
process_epoch_single_pass(
|
||||
state,
|
||||
spec,
|
||||
SinglePassConfig {
|
||||
rewards_and_penalties: true,
|
||||
..SinglePassConfig::disable_all()
|
||||
},
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -103,33 +30,3 @@ pub fn get_flag_weight(flag_index: usize) -> Result<u64, Error> {
|
||||
.copied()
|
||||
.ok_or(Error::InvalidFlagIndex(flag_index))
|
||||
}
|
||||
|
||||
pub fn get_inactivity_penalty_deltas<E: EthSpec>(
|
||||
deltas: &mut [Delta],
|
||||
state: &BeaconState<E>,
|
||||
participation_cache: &ParticipationCache,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
let previous_epoch = state.previous_epoch();
|
||||
let matching_target_indices = participation_cache
|
||||
.get_unslashed_participating_indices(TIMELY_TARGET_FLAG_INDEX, previous_epoch)?;
|
||||
for &index in participation_cache.eligible_validator_indices() {
|
||||
let mut delta = Delta::default();
|
||||
|
||||
if !matching_target_indices.contains(index)? {
|
||||
let penalty_numerator = state
|
||||
.get_validator(index)?
|
||||
.effective_balance
|
||||
.safe_mul(state.get_inactivity_score(index)?)?;
|
||||
let penalty_denominator = spec
|
||||
.inactivity_score_bias
|
||||
.safe_mul(spec.inactivity_penalty_quotient_for_state(state))?;
|
||||
delta.penalize(penalty_numerator.safe_div(penalty_denominator)?)?;
|
||||
}
|
||||
deltas
|
||||
.get_mut(index)
|
||||
.ok_or(Error::DeltaOutOfBounds(index))?
|
||||
.combine(delta)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use super::{process_registry_updates, process_slashings, EpochProcessingSummary, Error};
|
||||
use crate::epoch_cache::initialize_epoch_cache;
|
||||
use crate::per_epoch_processing::{
|
||||
effective_balance_updates::process_effective_balance_updates,
|
||||
historical_roots_update::process_historical_roots_update,
|
||||
@@ -23,6 +24,8 @@ pub fn process_epoch<E: EthSpec>(
|
||||
state.build_committee_cache(RelativeEpoch::Previous, spec)?;
|
||||
state.build_committee_cache(RelativeEpoch::Current, spec)?;
|
||||
state.build_committee_cache(RelativeEpoch::Next, spec)?;
|
||||
state.build_total_active_balance_cache(spec)?;
|
||||
initialize_epoch_cache(state, spec)?;
|
||||
|
||||
// Load the struct we use to assign validators into sets based on their participation.
|
||||
//
|
||||
@@ -52,7 +55,7 @@ pub fn process_epoch<E: EthSpec>(
|
||||
process_eth1_data_reset(state)?;
|
||||
|
||||
// Update effective balances with hysteresis (lag).
|
||||
process_effective_balance_updates(state, None, spec)?;
|
||||
process_effective_balance_updates(state, spec)?;
|
||||
|
||||
// Reset slashings
|
||||
process_slashings_reset(state)?;
|
||||
@@ -67,7 +70,7 @@ pub fn process_epoch<E: EthSpec>(
|
||||
process_participation_record_updates(state)?;
|
||||
|
||||
// Rotate the epoch caches to suit the epoch transition.
|
||||
state.advance_caches(spec)?;
|
||||
state.advance_caches()?;
|
||||
|
||||
Ok(EpochProcessingSummary::Base {
|
||||
total_balances: validator_statuses.total_balances,
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use crate::common::{base::get_base_reward, decrease_balance, increase_balance};
|
||||
use crate::common::{
|
||||
base::{get_base_reward, SqrtTotalActiveBalance},
|
||||
decrease_balance, increase_balance,
|
||||
};
|
||||
use crate::per_epoch_processing::{
|
||||
base::{TotalBalances, ValidatorStatus, ValidatorStatuses},
|
||||
Delta, Error,
|
||||
@@ -109,7 +112,6 @@ fn get_attestation_deltas<E: EthSpec>(
|
||||
maybe_validators_subset: Option<&Vec<usize>>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<Vec<AttestationDelta>, Error> {
|
||||
let previous_epoch = state.previous_epoch();
|
||||
let finality_delay = state
|
||||
.previous_epoch()
|
||||
.safe_sub(state.finalized_checkpoint().epoch)?
|
||||
@@ -118,6 +120,7 @@ fn get_attestation_deltas<E: EthSpec>(
|
||||
let mut deltas = vec![AttestationDelta::default(); state.validators().len()];
|
||||
|
||||
let total_balances = &validator_statuses.total_balances;
|
||||
let sqrt_total_active_balance = SqrtTotalActiveBalance::new(total_balances.current_epoch());
|
||||
|
||||
// Ignore validator if a subset is specified and validator is not in the subset
|
||||
let include_validator_delta = |idx| match maybe_validators_subset.as_ref() {
|
||||
@@ -131,11 +134,15 @@ fn get_attestation_deltas<E: EthSpec>(
|
||||
// `get_inclusion_delay_deltas`. It's safe to do so here because any validator that is in
|
||||
// the unslashed indices of the matching source attestations is active, and therefore
|
||||
// eligible.
|
||||
if !state.is_eligible_validator(previous_epoch, index)? {
|
||||
if !validator.is_eligible {
|
||||
continue;
|
||||
}
|
||||
|
||||
let base_reward = get_base_reward(state, index, total_balances.current_epoch(), spec)?;
|
||||
let base_reward = get_base_reward(
|
||||
validator.current_epoch_effective_balance,
|
||||
sqrt_total_active_balance,
|
||||
spec,
|
||||
)?;
|
||||
|
||||
let (inclusion_delay_delta, proposer_delta) =
|
||||
get_inclusion_delay_delta(validator, base_reward, spec)?;
|
||||
|
||||
@@ -53,6 +53,8 @@ impl InclusionInfo {
|
||||
pub struct ValidatorStatus {
|
||||
/// True if the validator has been slashed, ever.
|
||||
pub is_slashed: bool,
|
||||
/// True if the validator is eligible.
|
||||
pub is_eligible: bool,
|
||||
/// True if the validator can withdraw in the current epoch.
|
||||
pub is_withdrawable_in_current_epoch: bool,
|
||||
/// True if the validator was active in the state's _current_ epoch.
|
||||
@@ -92,6 +94,7 @@ impl ValidatorStatus {
|
||||
// Update all the bool fields, only updating `self` if `other` is true (never setting
|
||||
// `self` to false).
|
||||
set_self_if_other_is_true!(self, other, is_slashed);
|
||||
set_self_if_other_is_true!(self, other, is_eligible);
|
||||
set_self_if_other_is_true!(self, other, is_withdrawable_in_current_epoch);
|
||||
set_self_if_other_is_true!(self, other, is_active_in_current_epoch);
|
||||
set_self_if_other_is_true!(self, other, is_active_in_previous_epoch);
|
||||
@@ -195,24 +198,27 @@ impl ValidatorStatuses {
|
||||
let mut statuses = Vec::with_capacity(state.validators().len());
|
||||
let mut total_balances = TotalBalances::new(spec);
|
||||
|
||||
for (i, validator) in state.validators().iter().enumerate() {
|
||||
let effective_balance = state.get_effective_balance(i)?;
|
||||
let current_epoch = state.current_epoch();
|
||||
let previous_epoch = state.previous_epoch();
|
||||
|
||||
for validator in state.validators().iter() {
|
||||
let effective_balance = validator.effective_balance;
|
||||
let mut status = ValidatorStatus {
|
||||
is_slashed: validator.slashed,
|
||||
is_withdrawable_in_current_epoch: validator
|
||||
.is_withdrawable_at(state.current_epoch()),
|
||||
is_eligible: state.is_eligible_validator(previous_epoch, validator)?,
|
||||
is_withdrawable_in_current_epoch: validator.is_withdrawable_at(current_epoch),
|
||||
current_epoch_effective_balance: effective_balance,
|
||||
..ValidatorStatus::default()
|
||||
};
|
||||
|
||||
if validator.is_active_at(state.current_epoch()) {
|
||||
if validator.is_active_at(current_epoch) {
|
||||
status.is_active_in_current_epoch = true;
|
||||
total_balances
|
||||
.current_epoch
|
||||
.safe_add_assign(effective_balance)?;
|
||||
}
|
||||
|
||||
if validator.is_active_at(state.previous_epoch()) {
|
||||
if validator.is_active_at(previous_epoch) {
|
||||
status.is_active_in_previous_epoch = true;
|
||||
total_balances
|
||||
.previous_epoch
|
||||
@@ -285,10 +291,10 @@ impl ValidatorStatuses {
|
||||
}
|
||||
|
||||
// Compute the total balances
|
||||
for (index, v) in self.statuses.iter().enumerate() {
|
||||
for v in self.statuses.iter() {
|
||||
// According to the spec, we only count unslashed validators towards the totals.
|
||||
if !v.is_slashed {
|
||||
let validator_balance = state.get_effective_balance(index)?;
|
||||
let validator_balance = v.current_epoch_effective_balance;
|
||||
|
||||
if v.is_current_epoch_attester {
|
||||
self.total_balances
|
||||
|
||||
@@ -1,84 +1,3 @@
|
||||
use super::altair::inactivity_updates::process_inactivity_updates;
|
||||
use super::altair::justification_and_finalization::process_justification_and_finalization;
|
||||
use super::altair::participation_cache::ParticipationCache;
|
||||
use super::altair::participation_flag_updates::process_participation_flag_updates;
|
||||
use super::altair::rewards_and_penalties::process_rewards_and_penalties;
|
||||
use super::altair::sync_committee_updates::process_sync_committee_updates;
|
||||
use super::{process_registry_updates, process_slashings, EpochProcessingSummary, Error};
|
||||
use crate::per_epoch_processing::{
|
||||
effective_balance_updates::process_effective_balance_updates,
|
||||
resets::{process_eth1_data_reset, process_randao_mixes_reset, process_slashings_reset},
|
||||
};
|
||||
use types::{BeaconState, ChainSpec, EthSpec, RelativeEpoch};
|
||||
|
||||
use crate::common::update_progressive_balances_cache::{
|
||||
initialize_progressive_balances_cache, update_progressive_balances_on_epoch_transition,
|
||||
};
|
||||
pub use historical_summaries_update::process_historical_summaries_update;
|
||||
|
||||
mod historical_summaries_update;
|
||||
|
||||
pub fn process_epoch<E: EthSpec>(
|
||||
state: &mut BeaconState<E>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<EpochProcessingSummary<E>, Error> {
|
||||
// Ensure the committee caches are built.
|
||||
state.build_committee_cache(RelativeEpoch::Previous, spec)?;
|
||||
state.build_committee_cache(RelativeEpoch::Current, spec)?;
|
||||
state.build_committee_cache(RelativeEpoch::Next, spec)?;
|
||||
|
||||
// Pre-compute participating indices and total balances.
|
||||
let participation_cache = ParticipationCache::new(state, spec)?;
|
||||
let sync_committee = state.current_sync_committee()?.clone();
|
||||
initialize_progressive_balances_cache(state, Some(&participation_cache), spec)?;
|
||||
|
||||
// Justification and finalization.
|
||||
let justification_and_finalization_state =
|
||||
process_justification_and_finalization(state, &participation_cache)?;
|
||||
justification_and_finalization_state.apply_changes_to_state(state);
|
||||
|
||||
process_inactivity_updates(state, &participation_cache, spec)?;
|
||||
|
||||
// Rewards and Penalties.
|
||||
process_rewards_and_penalties(state, &participation_cache, spec)?;
|
||||
|
||||
// Registry Updates.
|
||||
process_registry_updates(state, spec)?;
|
||||
|
||||
// Slashings.
|
||||
process_slashings(
|
||||
state,
|
||||
participation_cache.current_epoch_total_active_balance(),
|
||||
spec,
|
||||
)?;
|
||||
|
||||
// Reset eth1 data votes.
|
||||
process_eth1_data_reset(state)?;
|
||||
|
||||
// Update effective balances with hysteresis (lag).
|
||||
process_effective_balance_updates(state, Some(&participation_cache), spec)?;
|
||||
|
||||
// Reset slashings
|
||||
process_slashings_reset(state)?;
|
||||
|
||||
// Set randao mix
|
||||
process_randao_mixes_reset(state)?;
|
||||
|
||||
// Set historical summaries accumulator
|
||||
process_historical_summaries_update(state)?;
|
||||
|
||||
// Rotate current/previous epoch participation
|
||||
process_participation_flag_updates(state)?;
|
||||
|
||||
process_sync_committee_updates(state, spec)?;
|
||||
|
||||
// Rotate the epoch caches to suit the epoch transition.
|
||||
state.advance_caches(spec)?;
|
||||
|
||||
update_progressive_balances_on_epoch_transition(state, spec)?;
|
||||
|
||||
Ok(EpochProcessingSummary::Altair {
|
||||
participation_cache,
|
||||
sync_committee,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,68 +1,70 @@
|
||||
use super::errors::EpochProcessingError;
|
||||
use crate::per_epoch_processing::altair::ParticipationCache;
|
||||
use crate::per_epoch_processing::single_pass::{process_epoch_single_pass, SinglePassConfig};
|
||||
use safe_arith::SafeArith;
|
||||
use types::beacon_state::BeaconState;
|
||||
use types::chain_spec::ChainSpec;
|
||||
use types::{BeaconStateError, EthSpec, ProgressiveBalancesCache};
|
||||
use types::{BeaconStateError, EthSpec};
|
||||
|
||||
/// This implementation is now only used in phase0. Later hard forks use single-pass.
|
||||
pub fn process_effective_balance_updates<E: EthSpec>(
|
||||
state: &mut BeaconState<E>,
|
||||
maybe_participation_cache: Option<&ParticipationCache>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), EpochProcessingError> {
|
||||
// Compute new total active balance for the next epoch as a side-effect of iterating the
|
||||
// effective balances.
|
||||
let next_epoch = state.next_epoch()?;
|
||||
let mut new_total_active_balance = 0;
|
||||
|
||||
let hysteresis_increment = spec
|
||||
.effective_balance_increment
|
||||
.safe_div(spec.hysteresis_quotient)?;
|
||||
let downward_threshold = hysteresis_increment.safe_mul(spec.hysteresis_downward_multiplier)?;
|
||||
let upward_threshold = hysteresis_increment.safe_mul(spec.hysteresis_upward_multiplier)?;
|
||||
let (validators, balances, progressive_balances_cache) =
|
||||
state.validators_and_balances_and_progressive_balances_mut();
|
||||
let (validators, balances, _) = state.validators_and_balances_and_progressive_balances_mut();
|
||||
for (index, validator) in validators.iter_mut().enumerate() {
|
||||
let balance = balances
|
||||
.get(index)
|
||||
.copied()
|
||||
.ok_or(BeaconStateError::BalancesOutOfBounds(index))?;
|
||||
|
||||
if balance.safe_add(downward_threshold)? < validator.effective_balance
|
||||
let new_effective_balance = if balance.safe_add(downward_threshold)?
|
||||
< validator.effective_balance
|
||||
|| validator.effective_balance.safe_add(upward_threshold)? < balance
|
||||
{
|
||||
let old_effective_balance = validator.effective_balance;
|
||||
let new_effective_balance = std::cmp::min(
|
||||
std::cmp::min(
|
||||
balance.safe_sub(balance.safe_rem(spec.effective_balance_increment)?)?,
|
||||
spec.max_effective_balance,
|
||||
);
|
||||
)
|
||||
} else {
|
||||
validator.effective_balance
|
||||
};
|
||||
|
||||
if let Some(participation_cache) = maybe_participation_cache {
|
||||
update_progressive_balances(
|
||||
participation_cache,
|
||||
progressive_balances_cache,
|
||||
index,
|
||||
old_effective_balance,
|
||||
new_effective_balance,
|
||||
)?;
|
||||
}
|
||||
if validator.is_active_at(next_epoch) {
|
||||
new_total_active_balance.safe_add_assign(new_effective_balance)?;
|
||||
}
|
||||
|
||||
if new_effective_balance != validator.effective_balance {
|
||||
validator.effective_balance = new_effective_balance;
|
||||
}
|
||||
}
|
||||
|
||||
state.set_total_active_balance(next_epoch, new_total_active_balance, spec);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_progressive_balances(
|
||||
participation_cache: &ParticipationCache,
|
||||
progressive_balances_cache: &mut ProgressiveBalancesCache,
|
||||
index: usize,
|
||||
old_effective_balance: u64,
|
||||
new_effective_balance: u64,
|
||||
/// Only used to test the effective balance part of single-pass in isolation.
|
||||
pub fn process_effective_balance_updates_slow<E: EthSpec>(
|
||||
state: &mut BeaconState<E>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), EpochProcessingError> {
|
||||
if old_effective_balance != new_effective_balance {
|
||||
let is_current_epoch_target_attester =
|
||||
participation_cache.is_current_epoch_timely_target_attester(index)?;
|
||||
progressive_balances_cache.on_effective_balance_change(
|
||||
is_current_epoch_target_attester,
|
||||
old_effective_balance,
|
||||
new_effective_balance,
|
||||
)?;
|
||||
}
|
||||
process_epoch_single_pass(
|
||||
state,
|
||||
spec,
|
||||
SinglePassConfig {
|
||||
effective_balance_updates: true,
|
||||
..SinglePassConfig::disable_all()
|
||||
},
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use super::{
|
||||
altair::{participation_cache::Error as ParticipationCacheError, ParticipationCache},
|
||||
base::{validator_statuses::InclusionInfo, TotalBalances, ValidatorStatus},
|
||||
};
|
||||
use super::base::{validator_statuses::InclusionInfo, TotalBalances, ValidatorStatus};
|
||||
use crate::metrics;
|
||||
use std::sync::Arc;
|
||||
use types::{EthSpec, SyncCommittee};
|
||||
use types::{
|
||||
consts::altair::{TIMELY_HEAD_FLAG_INDEX, TIMELY_SOURCE_FLAG_INDEX, TIMELY_TARGET_FLAG_INDEX},
|
||||
BeaconStateError, Epoch, EthSpec, ParticipationFlags, ProgressiveBalancesCache, SyncCommittee,
|
||||
Validator, VariableList,
|
||||
};
|
||||
|
||||
/// Provides a summary of validator participation during the epoch.
|
||||
#[derive(PartialEq, Debug)]
|
||||
@@ -14,14 +15,79 @@ pub enum EpochProcessingSummary<E: EthSpec> {
|
||||
statuses: Vec<ValidatorStatus>,
|
||||
},
|
||||
Altair {
|
||||
participation_cache: ParticipationCache,
|
||||
progressive_balances: ProgressiveBalancesCache,
|
||||
current_epoch_total_active_balance: u64,
|
||||
participation: ParticipationEpochSummary<E>,
|
||||
sync_committee: Arc<SyncCommittee<E>>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub struct ParticipationEpochSummary<E: EthSpec> {
|
||||
/// Copy of the validator registry prior to mutation.
|
||||
validators: VariableList<Validator, E::ValidatorRegistryLimit>,
|
||||
/// Copy of the participation flags for the previous epoch.
|
||||
previous_epoch_participation: VariableList<ParticipationFlags, E::ValidatorRegistryLimit>,
|
||||
/// Copy of the participation flags for the current epoch.
|
||||
current_epoch_participation: VariableList<ParticipationFlags, E::ValidatorRegistryLimit>,
|
||||
previous_epoch: Epoch,
|
||||
current_epoch: Epoch,
|
||||
}
|
||||
|
||||
impl<E: EthSpec> ParticipationEpochSummary<E> {
|
||||
pub fn new(
|
||||
validators: VariableList<Validator, E::ValidatorRegistryLimit>,
|
||||
previous_epoch_participation: VariableList<ParticipationFlags, E::ValidatorRegistryLimit>,
|
||||
current_epoch_participation: VariableList<ParticipationFlags, E::ValidatorRegistryLimit>,
|
||||
previous_epoch: Epoch,
|
||||
current_epoch: Epoch,
|
||||
) -> Self {
|
||||
Self {
|
||||
validators,
|
||||
previous_epoch_participation,
|
||||
current_epoch_participation,
|
||||
previous_epoch,
|
||||
current_epoch,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_active_and_unslashed(&self, val_index: usize, epoch: Epoch) -> bool {
|
||||
self.validators
|
||||
.get(val_index)
|
||||
.map(|validator| !validator.slashed && validator.is_active_at(epoch))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn is_previous_epoch_unslashed_participating_index(
|
||||
&self,
|
||||
val_index: usize,
|
||||
flag_index: usize,
|
||||
) -> Result<bool, BeaconStateError> {
|
||||
Ok(self.is_active_and_unslashed(val_index, self.previous_epoch)
|
||||
&& self
|
||||
.previous_epoch_participation
|
||||
.get(val_index)
|
||||
.ok_or(BeaconStateError::UnknownValidator(val_index))?
|
||||
.has_flag(flag_index)?)
|
||||
}
|
||||
|
||||
pub fn is_current_epoch_unslashed_participating_index(
|
||||
&self,
|
||||
val_index: usize,
|
||||
flag_index: usize,
|
||||
) -> Result<bool, BeaconStateError> {
|
||||
Ok(self.is_active_and_unslashed(val_index, self.current_epoch)
|
||||
&& self
|
||||
.current_epoch_participation
|
||||
.get(val_index)
|
||||
.ok_or(BeaconStateError::UnknownValidator(val_index))?
|
||||
.has_flag(flag_index)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: EthSpec> EpochProcessingSummary<E> {
|
||||
/// Updates some Prometheus metrics with some values in `self`.
|
||||
pub fn observe_metrics(&self) -> Result<(), ParticipationCacheError> {
|
||||
pub fn observe_metrics(&self) -> Result<(), BeaconStateError> {
|
||||
metrics::set_gauge(
|
||||
&metrics::PARTICIPATION_PREV_EPOCH_HEAD_ATTESTING_GWEI_TOTAL,
|
||||
self.previous_epoch_head_attesting_balance()? as i64,
|
||||
@@ -34,10 +100,6 @@ impl<E: EthSpec> EpochProcessingSummary<E> {
|
||||
&metrics::PARTICIPATION_PREV_EPOCH_SOURCE_ATTESTING_GWEI_TOTAL,
|
||||
self.previous_epoch_source_attesting_balance()? as i64,
|
||||
);
|
||||
metrics::set_gauge(
|
||||
&metrics::PARTICIPATION_PREV_EPOCH_ACTIVE_GWEI_TOTAL,
|
||||
self.previous_epoch_total_active_balance() as i64,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -55,34 +117,23 @@ impl<E: EthSpec> EpochProcessingSummary<E> {
|
||||
match self {
|
||||
EpochProcessingSummary::Base { total_balances, .. } => total_balances.current_epoch(),
|
||||
EpochProcessingSummary::Altair {
|
||||
participation_cache,
|
||||
current_epoch_total_active_balance,
|
||||
..
|
||||
} => participation_cache.current_epoch_total_active_balance(),
|
||||
} => *current_epoch_total_active_balance,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the sum of the effective balance of all validators in the current epoch who
|
||||
/// included an attestation that matched the target.
|
||||
pub fn current_epoch_target_attesting_balance(&self) -> Result<u64, ParticipationCacheError> {
|
||||
pub fn current_epoch_target_attesting_balance(&self) -> Result<u64, BeaconStateError> {
|
||||
match self {
|
||||
EpochProcessingSummary::Base { total_balances, .. } => {
|
||||
Ok(total_balances.current_epoch_target_attesters())
|
||||
}
|
||||
EpochProcessingSummary::Altair {
|
||||
participation_cache,
|
||||
progressive_balances,
|
||||
..
|
||||
} => participation_cache.current_epoch_target_attesting_balance(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the sum of the effective balance of all validators in the previous epoch.
|
||||
pub fn previous_epoch_total_active_balance(&self) -> u64 {
|
||||
match self {
|
||||
EpochProcessingSummary::Base { total_balances, .. } => total_balances.previous_epoch(),
|
||||
EpochProcessingSummary::Altair {
|
||||
participation_cache,
|
||||
..
|
||||
} => participation_cache.previous_epoch_total_active_balance(),
|
||||
} => progressive_balances.current_epoch_target_attesting_balance(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,12 +148,9 @@ impl<E: EthSpec> EpochProcessingSummary<E> {
|
||||
EpochProcessingSummary::Base { statuses, .. } => statuses
|
||||
.get(val_index)
|
||||
.map_or(false, |s| s.is_active_in_current_epoch && !s.is_slashed),
|
||||
EpochProcessingSummary::Altair {
|
||||
participation_cache,
|
||||
..
|
||||
} => participation_cache
|
||||
.is_active_unslashed_in_current_epoch(val_index)
|
||||
.unwrap_or(false),
|
||||
EpochProcessingSummary::Altair { participation, .. } => {
|
||||
participation.is_active_and_unslashed(val_index, participation.current_epoch)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,34 +168,30 @@ impl<E: EthSpec> EpochProcessingSummary<E> {
|
||||
pub fn is_current_epoch_target_attester(
|
||||
&self,
|
||||
val_index: usize,
|
||||
) -> Result<bool, ParticipationCacheError> {
|
||||
) -> Result<bool, BeaconStateError> {
|
||||
match self {
|
||||
EpochProcessingSummary::Base { statuses, .. } => Ok(statuses
|
||||
.get(val_index)
|
||||
.map_or(false, |s| s.is_current_epoch_target_attester)),
|
||||
EpochProcessingSummary::Altair {
|
||||
participation_cache,
|
||||
..
|
||||
} => participation_cache
|
||||
.is_current_epoch_timely_target_attester(val_index)
|
||||
.or_else(|e| match e {
|
||||
ParticipationCacheError::InvalidValidatorIndex(_) => Ok(false),
|
||||
e => Err(e),
|
||||
}),
|
||||
EpochProcessingSummary::Altair { participation, .. } => participation
|
||||
.is_current_epoch_unslashed_participating_index(
|
||||
val_index,
|
||||
TIMELY_TARGET_FLAG_INDEX,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the sum of the effective balance of all validators in the previous epoch who
|
||||
/// included an attestation that matched the target.
|
||||
pub fn previous_epoch_target_attesting_balance(&self) -> Result<u64, ParticipationCacheError> {
|
||||
pub fn previous_epoch_target_attesting_balance(&self) -> Result<u64, BeaconStateError> {
|
||||
match self {
|
||||
EpochProcessingSummary::Base { total_balances, .. } => {
|
||||
Ok(total_balances.previous_epoch_target_attesters())
|
||||
}
|
||||
EpochProcessingSummary::Altair {
|
||||
participation_cache,
|
||||
progressive_balances,
|
||||
..
|
||||
} => participation_cache.previous_epoch_target_attesting_balance(),
|
||||
} => progressive_balances.previous_epoch_target_attesting_balance(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,15 +202,15 @@ impl<E: EthSpec> EpochProcessingSummary<E> {
|
||||
///
|
||||
/// - Base: any attestation can match the head.
|
||||
/// - Altair: only "timely" attestations can match the head.
|
||||
pub fn previous_epoch_head_attesting_balance(&self) -> Result<u64, ParticipationCacheError> {
|
||||
pub fn previous_epoch_head_attesting_balance(&self) -> Result<u64, BeaconStateError> {
|
||||
match self {
|
||||
EpochProcessingSummary::Base { total_balances, .. } => {
|
||||
Ok(total_balances.previous_epoch_head_attesters())
|
||||
}
|
||||
EpochProcessingSummary::Altair {
|
||||
participation_cache,
|
||||
progressive_balances,
|
||||
..
|
||||
} => participation_cache.previous_epoch_head_attesting_balance(),
|
||||
} => progressive_balances.previous_epoch_head_attesting_balance(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,15 +221,15 @@ impl<E: EthSpec> EpochProcessingSummary<E> {
|
||||
///
|
||||
/// - Base: any attestation can match the source.
|
||||
/// - Altair: only "timely" attestations can match the source.
|
||||
pub fn previous_epoch_source_attesting_balance(&self) -> Result<u64, ParticipationCacheError> {
|
||||
pub fn previous_epoch_source_attesting_balance(&self) -> Result<u64, BeaconStateError> {
|
||||
match self {
|
||||
EpochProcessingSummary::Base { total_balances, .. } => {
|
||||
Ok(total_balances.previous_epoch_attesters())
|
||||
}
|
||||
EpochProcessingSummary::Altair {
|
||||
participation_cache,
|
||||
progressive_balances,
|
||||
..
|
||||
} => participation_cache.previous_epoch_source_attesting_balance(),
|
||||
} => progressive_balances.previous_epoch_source_attesting_balance(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,12 +244,9 @@ impl<E: EthSpec> EpochProcessingSummary<E> {
|
||||
EpochProcessingSummary::Base { statuses, .. } => statuses
|
||||
.get(val_index)
|
||||
.map_or(false, |s| s.is_active_in_previous_epoch && !s.is_slashed),
|
||||
EpochProcessingSummary::Altair {
|
||||
participation_cache,
|
||||
..
|
||||
} => participation_cache
|
||||
.is_active_unslashed_in_previous_epoch(val_index)
|
||||
.unwrap_or(false),
|
||||
EpochProcessingSummary::Altair { participation, .. } => {
|
||||
participation.is_active_and_unslashed(val_index, participation.previous_epoch)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,20 +259,16 @@ impl<E: EthSpec> EpochProcessingSummary<E> {
|
||||
pub fn is_previous_epoch_target_attester(
|
||||
&self,
|
||||
val_index: usize,
|
||||
) -> Result<bool, ParticipationCacheError> {
|
||||
) -> Result<bool, BeaconStateError> {
|
||||
match self {
|
||||
EpochProcessingSummary::Base { statuses, .. } => Ok(statuses
|
||||
.get(val_index)
|
||||
.map_or(false, |s| s.is_previous_epoch_target_attester)),
|
||||
EpochProcessingSummary::Altair {
|
||||
participation_cache,
|
||||
..
|
||||
} => participation_cache
|
||||
.is_previous_epoch_timely_target_attester(val_index)
|
||||
.or_else(|e| match e {
|
||||
ParticipationCacheError::InvalidValidatorIndex(_) => Ok(false),
|
||||
e => Err(e),
|
||||
}),
|
||||
EpochProcessingSummary::Altair { participation, .. } => participation
|
||||
.is_previous_epoch_unslashed_participating_index(
|
||||
val_index,
|
||||
TIMELY_TARGET_FLAG_INDEX,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,20 +286,13 @@ impl<E: EthSpec> EpochProcessingSummary<E> {
|
||||
pub fn is_previous_epoch_head_attester(
|
||||
&self,
|
||||
val_index: usize,
|
||||
) -> Result<bool, ParticipationCacheError> {
|
||||
) -> Result<bool, BeaconStateError> {
|
||||
match self {
|
||||
EpochProcessingSummary::Base { statuses, .. } => Ok(statuses
|
||||
.get(val_index)
|
||||
.map_or(false, |s| s.is_previous_epoch_head_attester)),
|
||||
EpochProcessingSummary::Altair {
|
||||
participation_cache,
|
||||
..
|
||||
} => participation_cache
|
||||
.is_previous_epoch_timely_head_attester(val_index)
|
||||
.or_else(|e| match e {
|
||||
ParticipationCacheError::InvalidValidatorIndex(_) => Ok(false),
|
||||
e => Err(e),
|
||||
}),
|
||||
EpochProcessingSummary::Altair { participation, .. } => participation
|
||||
.is_previous_epoch_unslashed_participating_index(val_index, TIMELY_HEAD_FLAG_INDEX),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,20 +310,16 @@ impl<E: EthSpec> EpochProcessingSummary<E> {
|
||||
pub fn is_previous_epoch_source_attester(
|
||||
&self,
|
||||
val_index: usize,
|
||||
) -> Result<bool, ParticipationCacheError> {
|
||||
) -> Result<bool, BeaconStateError> {
|
||||
match self {
|
||||
EpochProcessingSummary::Base { statuses, .. } => Ok(statuses
|
||||
.get(val_index)
|
||||
.map_or(false, |s| s.is_previous_epoch_attester)),
|
||||
EpochProcessingSummary::Altair {
|
||||
participation_cache,
|
||||
..
|
||||
} => participation_cache
|
||||
.is_previous_epoch_timely_source_attester(val_index)
|
||||
.or_else(|e| match e {
|
||||
ParticipationCacheError::InvalidValidatorIndex(_) => Ok(false),
|
||||
e => Err(e),
|
||||
}),
|
||||
EpochProcessingSummary::Altair { participation, .. } => participation
|
||||
.is_previous_epoch_unslashed_participating_index(
|
||||
val_index,
|
||||
TIMELY_SOURCE_FLAG_INDEX,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use crate::per_epoch_processing::altair::participation_cache::Error as ParticipationCacheError;
|
||||
use types::{BeaconStateError, InconsistentFork};
|
||||
use types::{BeaconStateError, EpochCacheError, InconsistentFork};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum EpochProcessingError {
|
||||
@@ -24,7 +23,7 @@ pub enum EpochProcessingError {
|
||||
InconsistentStateFork(InconsistentFork),
|
||||
InvalidJustificationBit(ssz_types::Error),
|
||||
InvalidFlagIndex(usize),
|
||||
ParticipationCache(ParticipationCacheError),
|
||||
EpochCache(EpochCacheError),
|
||||
}
|
||||
|
||||
impl From<InclusionError> for EpochProcessingError {
|
||||
@@ -51,9 +50,9 @@ impl From<safe_arith::ArithError> for EpochProcessingError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParticipationCacheError> for EpochProcessingError {
|
||||
fn from(e: ParticipationCacheError) -> EpochProcessingError {
|
||||
EpochProcessingError::ParticipationCache(e)
|
||||
impl From<EpochCacheError> for EpochProcessingError {
|
||||
fn from(e: EpochCacheError) -> Self {
|
||||
EpochProcessingError::EpochCache(e)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::per_epoch_processing::single_pass::{process_epoch_single_pass, SinglePassConfig};
|
||||
use crate::{common::initiate_validator_exit, per_epoch_processing::Error};
|
||||
use itertools::Itertools;
|
||||
use safe_arith::SafeArith;
|
||||
use types::{BeaconState, ChainSpec, EthSpec, Validator};
|
||||
|
||||
@@ -40,21 +40,33 @@ pub fn process_registry_updates<E: EthSpec>(
|
||||
}
|
||||
|
||||
// Queue validators eligible for activation and not dequeued for activation prior to finalized epoch
|
||||
let activation_queue = state
|
||||
.validators()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, validator)| validator.is_eligible_for_activation(state, spec))
|
||||
.sorted_by_key(|(index, validator)| (validator.activation_eligibility_epoch, *index))
|
||||
.map(|(index, _)| index)
|
||||
.collect_vec();
|
||||
|
||||
// Dequeue validators for activation up to churn limit
|
||||
let activation_churn_limit = state.get_activation_churn_limit(spec)? as usize;
|
||||
let churn_limit = state.get_activation_churn_limit(spec)? as usize;
|
||||
|
||||
let epoch_cache = state.epoch_cache();
|
||||
let activation_queue = epoch_cache
|
||||
.activation_queue()?
|
||||
.get_validators_eligible_for_activation(state.finalized_checkpoint().epoch, churn_limit);
|
||||
|
||||
let delayed_activation_epoch = state.compute_activation_exit_epoch(current_epoch, spec)?;
|
||||
for index in activation_queue.into_iter().take(activation_churn_limit) {
|
||||
for index in activation_queue {
|
||||
state.get_validator_mut(index)?.activation_epoch = delayed_activation_epoch;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn process_registry_updates_slow<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
process_epoch_single_pass(
|
||||
state,
|
||||
spec,
|
||||
SinglePassConfig {
|
||||
registry_updates: true,
|
||||
..SinglePassConfig::disable_all()
|
||||
},
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,630 @@
|
||||
use crate::{
|
||||
common::update_progressive_balances_cache::initialize_progressive_balances_cache,
|
||||
epoch_cache::{initialize_epoch_cache, PreEpochCache},
|
||||
per_epoch_processing::{Delta, Error, ParticipationEpochSummary},
|
||||
};
|
||||
use itertools::izip;
|
||||
use safe_arith::{SafeArith, SafeArithIter};
|
||||
use std::cmp::{max, min};
|
||||
use std::collections::BTreeSet;
|
||||
use types::{
|
||||
consts::altair::{
|
||||
NUM_FLAG_INDICES, PARTICIPATION_FLAG_WEIGHTS, TIMELY_HEAD_FLAG_INDEX,
|
||||
TIMELY_TARGET_FLAG_INDEX, WEIGHT_DENOMINATOR,
|
||||
},
|
||||
ActivationQueue, BeaconState, BeaconStateError, ChainSpec, Epoch, EthSpec, ExitCache, ForkName,
|
||||
ParticipationFlags, ProgressiveBalancesCache, RelativeEpoch, Unsigned, Validator,
|
||||
};
|
||||
|
||||
pub struct SinglePassConfig {
|
||||
pub inactivity_updates: bool,
|
||||
pub rewards_and_penalties: bool,
|
||||
pub registry_updates: bool,
|
||||
pub slashings: bool,
|
||||
pub effective_balance_updates: bool,
|
||||
}
|
||||
|
||||
impl Default for SinglePassConfig {
|
||||
fn default() -> SinglePassConfig {
|
||||
Self::enable_all()
|
||||
}
|
||||
}
|
||||
|
||||
impl SinglePassConfig {
|
||||
pub fn enable_all() -> SinglePassConfig {
|
||||
Self {
|
||||
inactivity_updates: true,
|
||||
rewards_and_penalties: true,
|
||||
registry_updates: true,
|
||||
slashings: true,
|
||||
effective_balance_updates: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn disable_all() -> SinglePassConfig {
|
||||
SinglePassConfig {
|
||||
inactivity_updates: false,
|
||||
rewards_and_penalties: false,
|
||||
registry_updates: false,
|
||||
slashings: false,
|
||||
effective_balance_updates: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Values from the state that are immutable throughout epoch processing.
|
||||
struct StateContext {
|
||||
current_epoch: Epoch,
|
||||
next_epoch: Epoch,
|
||||
is_in_inactivity_leak: bool,
|
||||
total_active_balance: u64,
|
||||
churn_limit: u64,
|
||||
fork_name: ForkName,
|
||||
}
|
||||
|
||||
struct RewardsAndPenaltiesContext {
|
||||
unslashed_participating_increments_array: [u64; NUM_FLAG_INDICES],
|
||||
active_increments: u64,
|
||||
}
|
||||
|
||||
struct SlashingsContext {
|
||||
adjusted_total_slashing_balance: u64,
|
||||
target_withdrawable_epoch: Epoch,
|
||||
}
|
||||
|
||||
struct EffectiveBalancesContext {
|
||||
downward_threshold: u64,
|
||||
upward_threshold: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct ValidatorInfo {
|
||||
pub index: usize,
|
||||
pub effective_balance: u64,
|
||||
pub base_reward: u64,
|
||||
pub is_eligible: bool,
|
||||
pub is_slashed: bool,
|
||||
pub is_active_current_epoch: bool,
|
||||
pub is_active_previous_epoch: bool,
|
||||
// Used for determining rewards.
|
||||
pub previous_epoch_participation: ParticipationFlags,
|
||||
// Used for updating the progressive balances cache for next epoch.
|
||||
pub current_epoch_participation: ParticipationFlags,
|
||||
}
|
||||
|
||||
impl ValidatorInfo {
|
||||
#[inline]
|
||||
pub fn is_unslashed_participating_index(&self, flag_index: usize) -> Result<bool, Error> {
|
||||
Ok(self.is_active_previous_epoch
|
||||
&& !self.is_slashed
|
||||
&& self
|
||||
.previous_epoch_participation
|
||||
.has_flag(flag_index)
|
||||
.map_err(|_| Error::InvalidFlagIndex(flag_index))?)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_epoch_single_pass<E: EthSpec>(
|
||||
state: &mut BeaconState<E>,
|
||||
spec: &ChainSpec,
|
||||
conf: SinglePassConfig,
|
||||
) -> Result<ParticipationEpochSummary<E>, Error> {
|
||||
initialize_epoch_cache(state, spec)?;
|
||||
initialize_progressive_balances_cache(state, spec)?;
|
||||
state.build_exit_cache(spec)?;
|
||||
state.build_committee_cache(RelativeEpoch::Previous, spec)?;
|
||||
state.build_committee_cache(RelativeEpoch::Current, spec)?;
|
||||
|
||||
let previous_epoch = state.previous_epoch();
|
||||
let current_epoch = state.current_epoch();
|
||||
let next_epoch = state.next_epoch()?;
|
||||
let is_in_inactivity_leak = state.is_in_inactivity_leak(previous_epoch, spec)?;
|
||||
let total_active_balance = state.get_total_active_balance()?;
|
||||
let churn_limit = state.get_churn_limit(spec)?;
|
||||
let activation_churn_limit = state.get_activation_churn_limit(spec)?;
|
||||
let finalized_checkpoint = state.finalized_checkpoint();
|
||||
let fork_name = state.fork_name_unchecked();
|
||||
|
||||
let state_ctxt = &StateContext {
|
||||
current_epoch,
|
||||
next_epoch,
|
||||
is_in_inactivity_leak,
|
||||
total_active_balance,
|
||||
churn_limit,
|
||||
fork_name,
|
||||
};
|
||||
|
||||
// Contexts that require immutable access to `state`.
|
||||
let slashings_ctxt = &SlashingsContext::new(state, state_ctxt, spec)?;
|
||||
let mut next_epoch_cache = PreEpochCache::new_for_next_epoch(state)?;
|
||||
|
||||
// Split the state into several disjoint mutable borrows.
|
||||
let (
|
||||
validators,
|
||||
balances,
|
||||
previous_epoch_participation,
|
||||
current_epoch_participation,
|
||||
inactivity_scores,
|
||||
progressive_balances,
|
||||
exit_cache,
|
||||
epoch_cache,
|
||||
) = state.mutable_validator_fields()?;
|
||||
|
||||
let num_validators = validators.len();
|
||||
|
||||
// Take a snapshot of the validators and participation before mutating. This is used for
|
||||
// informational purposes (e.g. by the validator monitor).
|
||||
let summary = ParticipationEpochSummary::new(
|
||||
validators.clone(),
|
||||
previous_epoch_participation.clone(),
|
||||
current_epoch_participation.clone(),
|
||||
previous_epoch,
|
||||
current_epoch,
|
||||
);
|
||||
|
||||
// Compute shared values required for different parts of epoch processing.
|
||||
let rewards_ctxt = &RewardsAndPenaltiesContext::new(progressive_balances, state_ctxt, spec)?;
|
||||
let activation_queue = &epoch_cache
|
||||
.activation_queue()?
|
||||
.get_validators_eligible_for_activation(
|
||||
finalized_checkpoint.epoch,
|
||||
activation_churn_limit as usize,
|
||||
);
|
||||
let effective_balances_ctxt = &EffectiveBalancesContext::new(spec)?;
|
||||
|
||||
// Iterate over the validators and related fields in one pass.
|
||||
let mut validators_iter = validators.iter_mut();
|
||||
let mut balances_iter = balances.iter_mut();
|
||||
let mut inactivity_scores_iter = inactivity_scores.iter_mut();
|
||||
|
||||
// Values computed for the next epoch transition.
|
||||
let mut next_epoch_total_active_balance = 0;
|
||||
let mut next_epoch_activation_queue = ActivationQueue::default();
|
||||
|
||||
for (index, &previous_epoch_participation, ¤t_epoch_participation) in izip!(
|
||||
0..num_validators,
|
||||
previous_epoch_participation.iter(),
|
||||
current_epoch_participation.iter(),
|
||||
) {
|
||||
let validator = validators_iter
|
||||
.next()
|
||||
.ok_or(BeaconStateError::UnknownValidator(index))?;
|
||||
let balance = balances_iter
|
||||
.next()
|
||||
.ok_or(BeaconStateError::UnknownValidator(index))?;
|
||||
let inactivity_score = inactivity_scores_iter
|
||||
.next()
|
||||
.ok_or(BeaconStateError::UnknownValidator(index))?;
|
||||
|
||||
let is_active_current_epoch = validator.is_active_at(current_epoch);
|
||||
let is_active_previous_epoch = validator.is_active_at(previous_epoch);
|
||||
let is_eligible = is_active_previous_epoch
|
||||
|| (validator.slashed && previous_epoch.safe_add(1)? < validator.withdrawable_epoch);
|
||||
|
||||
let base_reward = if is_eligible {
|
||||
epoch_cache.get_base_reward(index)?
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
let validator_info = &ValidatorInfo {
|
||||
index,
|
||||
effective_balance: validator.effective_balance,
|
||||
base_reward,
|
||||
is_eligible,
|
||||
is_slashed: validator.slashed,
|
||||
is_active_current_epoch,
|
||||
is_active_previous_epoch,
|
||||
previous_epoch_participation,
|
||||
current_epoch_participation,
|
||||
};
|
||||
|
||||
if current_epoch != E::genesis_epoch() {
|
||||
// `process_inactivity_updates`
|
||||
if conf.inactivity_updates {
|
||||
process_single_inactivity_update(
|
||||
inactivity_score,
|
||||
validator_info,
|
||||
state_ctxt,
|
||||
spec,
|
||||
)?;
|
||||
}
|
||||
|
||||
// `process_rewards_and_penalties`
|
||||
if conf.rewards_and_penalties {
|
||||
process_single_reward_and_penalty(
|
||||
balance,
|
||||
inactivity_score,
|
||||
validator_info,
|
||||
rewards_ctxt,
|
||||
state_ctxt,
|
||||
spec,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
// `process_registry_updates`
|
||||
if conf.registry_updates {
|
||||
process_single_registry_update(
|
||||
validator,
|
||||
validator_info,
|
||||
exit_cache,
|
||||
activation_queue,
|
||||
&mut next_epoch_activation_queue,
|
||||
state_ctxt,
|
||||
spec,
|
||||
)?;
|
||||
}
|
||||
|
||||
// `process_slashings`
|
||||
if conf.slashings {
|
||||
process_single_slashing(balance, validator, slashings_ctxt, state_ctxt, spec)?;
|
||||
}
|
||||
|
||||
// `process_effective_balance_updates`
|
||||
if conf.effective_balance_updates {
|
||||
process_single_effective_balance_update(
|
||||
*balance,
|
||||
validator,
|
||||
validator_info,
|
||||
&mut next_epoch_total_active_balance,
|
||||
&mut next_epoch_cache,
|
||||
progressive_balances,
|
||||
effective_balances_ctxt,
|
||||
state_ctxt,
|
||||
spec,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
if conf.effective_balance_updates {
|
||||
state.set_total_active_balance(next_epoch, next_epoch_total_active_balance, spec);
|
||||
*state.epoch_cache_mut() = next_epoch_cache.into_epoch_cache(
|
||||
next_epoch_total_active_balance,
|
||||
next_epoch_activation_queue,
|
||||
spec,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(summary)
|
||||
}
|
||||
|
||||
fn process_single_inactivity_update(
|
||||
inactivity_score: &mut u64,
|
||||
validator_info: &ValidatorInfo,
|
||||
state_ctxt: &StateContext,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
if !validator_info.is_eligible {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Increase inactivity score of inactive validators
|
||||
if validator_info.is_unslashed_participating_index(TIMELY_TARGET_FLAG_INDEX)? {
|
||||
// Avoid mutating when the inactivity score is 0 and can't go any lower -- the common
|
||||
// case.
|
||||
if *inactivity_score == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
inactivity_score.safe_sub_assign(1)?;
|
||||
} else {
|
||||
inactivity_score.safe_add_assign(spec.inactivity_score_bias)?;
|
||||
}
|
||||
|
||||
// Decrease the score of all validators for forgiveness when not during a leak
|
||||
if !state_ctxt.is_in_inactivity_leak {
|
||||
let deduction = min(spec.inactivity_score_recovery_rate, *inactivity_score);
|
||||
inactivity_score.safe_sub_assign(deduction)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_single_reward_and_penalty(
|
||||
balance: &mut u64,
|
||||
inactivity_score: &u64,
|
||||
validator_info: &ValidatorInfo,
|
||||
rewards_ctxt: &RewardsAndPenaltiesContext,
|
||||
state_ctxt: &StateContext,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
if !validator_info.is_eligible {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut delta = Delta::default();
|
||||
for flag_index in 0..NUM_FLAG_INDICES {
|
||||
get_flag_index_delta(
|
||||
&mut delta,
|
||||
validator_info,
|
||||
flag_index,
|
||||
rewards_ctxt,
|
||||
state_ctxt,
|
||||
)?;
|
||||
}
|
||||
get_inactivity_penalty_delta(
|
||||
&mut delta,
|
||||
validator_info,
|
||||
inactivity_score,
|
||||
state_ctxt,
|
||||
spec,
|
||||
)?;
|
||||
|
||||
if delta.rewards != 0 || delta.penalties != 0 {
|
||||
balance.safe_add_assign(delta.rewards)?;
|
||||
*balance = balance.saturating_sub(delta.penalties);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_flag_index_delta(
|
||||
delta: &mut Delta,
|
||||
validator_info: &ValidatorInfo,
|
||||
flag_index: usize,
|
||||
rewards_ctxt: &RewardsAndPenaltiesContext,
|
||||
state_ctxt: &StateContext,
|
||||
) -> Result<(), Error> {
|
||||
let base_reward = validator_info.base_reward;
|
||||
let weight = get_flag_weight(flag_index)?;
|
||||
let unslashed_participating_increments =
|
||||
rewards_ctxt.get_unslashed_participating_increments(flag_index)?;
|
||||
|
||||
if validator_info.is_unslashed_participating_index(flag_index)? {
|
||||
if !state_ctxt.is_in_inactivity_leak {
|
||||
let reward_numerator = base_reward
|
||||
.safe_mul(weight)?
|
||||
.safe_mul(unslashed_participating_increments)?;
|
||||
delta.reward(
|
||||
reward_numerator.safe_div(
|
||||
rewards_ctxt
|
||||
.active_increments
|
||||
.safe_mul(WEIGHT_DENOMINATOR)?,
|
||||
)?,
|
||||
)?;
|
||||
}
|
||||
} else if flag_index != TIMELY_HEAD_FLAG_INDEX {
|
||||
delta.penalize(base_reward.safe_mul(weight)?.safe_div(WEIGHT_DENOMINATOR)?)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the weight for a `flag_index` from the constant list of all weights.
|
||||
fn get_flag_weight(flag_index: usize) -> Result<u64, Error> {
|
||||
PARTICIPATION_FLAG_WEIGHTS
|
||||
.get(flag_index)
|
||||
.copied()
|
||||
.ok_or(Error::InvalidFlagIndex(flag_index))
|
||||
}
|
||||
|
||||
fn get_inactivity_penalty_delta(
|
||||
delta: &mut Delta,
|
||||
validator_info: &ValidatorInfo,
|
||||
inactivity_score: &u64,
|
||||
state_ctxt: &StateContext,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
if !validator_info.is_unslashed_participating_index(TIMELY_TARGET_FLAG_INDEX)? {
|
||||
let penalty_numerator = validator_info
|
||||
.effective_balance
|
||||
.safe_mul(*inactivity_score)?;
|
||||
let penalty_denominator = spec
|
||||
.inactivity_score_bias
|
||||
.safe_mul(spec.inactivity_penalty_quotient_for_fork(state_ctxt.fork_name))?;
|
||||
delta.penalize(penalty_numerator.safe_div(penalty_denominator)?)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl RewardsAndPenaltiesContext {
|
||||
fn new(
|
||||
progressive_balances: &ProgressiveBalancesCache,
|
||||
state_ctxt: &StateContext,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<Self, Error> {
|
||||
let mut unslashed_participating_increments_array = [0; NUM_FLAG_INDICES];
|
||||
for flag_index in 0..NUM_FLAG_INDICES {
|
||||
let unslashed_participating_balance =
|
||||
progressive_balances.previous_epoch_flag_attesting_balance(flag_index)?;
|
||||
let unslashed_participating_increments =
|
||||
unslashed_participating_balance.safe_div(spec.effective_balance_increment)?;
|
||||
|
||||
*unslashed_participating_increments_array
|
||||
.get_mut(flag_index)
|
||||
.ok_or(Error::InvalidFlagIndex(flag_index))? = unslashed_participating_increments;
|
||||
}
|
||||
let active_increments = state_ctxt
|
||||
.total_active_balance
|
||||
.safe_div(spec.effective_balance_increment)?;
|
||||
|
||||
Ok(Self {
|
||||
unslashed_participating_increments_array,
|
||||
active_increments,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_unslashed_participating_increments(&self, flag_index: usize) -> Result<u64, Error> {
|
||||
self.unslashed_participating_increments_array
|
||||
.get(flag_index)
|
||||
.copied()
|
||||
.ok_or(Error::InvalidFlagIndex(flag_index))
|
||||
}
|
||||
}
|
||||
|
||||
fn process_single_registry_update(
|
||||
validator: &mut Validator,
|
||||
validator_info: &ValidatorInfo,
|
||||
exit_cache: &mut ExitCache,
|
||||
activation_queue: &BTreeSet<usize>,
|
||||
next_epoch_activation_queue: &mut ActivationQueue,
|
||||
state_ctxt: &StateContext,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
let current_epoch = state_ctxt.current_epoch;
|
||||
|
||||
if validator.is_eligible_for_activation_queue(spec) {
|
||||
validator.activation_eligibility_epoch = current_epoch.safe_add(1)?;
|
||||
}
|
||||
|
||||
if validator.is_active_at(current_epoch) && validator.effective_balance <= spec.ejection_balance
|
||||
{
|
||||
initiate_validator_exit(validator, exit_cache, state_ctxt, spec)?;
|
||||
}
|
||||
|
||||
if activation_queue.contains(&validator_info.index) {
|
||||
validator.activation_epoch = spec.compute_activation_exit_epoch(current_epoch)?;
|
||||
}
|
||||
|
||||
// Caching: add to speculative activation queue for next epoch.
|
||||
next_epoch_activation_queue.add_if_could_be_eligible_for_activation(
|
||||
validator_info.index,
|
||||
validator,
|
||||
state_ctxt.next_epoch,
|
||||
spec,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn initiate_validator_exit(
|
||||
validator: &mut Validator,
|
||||
exit_cache: &mut ExitCache,
|
||||
state_ctxt: &StateContext,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
// Return if the validator already initiated exit
|
||||
if validator.exit_epoch != spec.far_future_epoch {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Compute exit queue epoch
|
||||
let delayed_epoch = spec.compute_activation_exit_epoch(state_ctxt.current_epoch)?;
|
||||
let mut exit_queue_epoch = exit_cache
|
||||
.max_epoch()?
|
||||
.map_or(delayed_epoch, |epoch| max(epoch, delayed_epoch));
|
||||
let exit_queue_churn = exit_cache.get_churn_at(exit_queue_epoch)?;
|
||||
|
||||
if exit_queue_churn >= state_ctxt.churn_limit {
|
||||
exit_queue_epoch.safe_add_assign(1)?;
|
||||
}
|
||||
|
||||
validator.exit_epoch = exit_queue_epoch;
|
||||
validator.withdrawable_epoch =
|
||||
exit_queue_epoch.safe_add(spec.min_validator_withdrawability_delay)?;
|
||||
|
||||
exit_cache.record_validator_exit(exit_queue_epoch)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl SlashingsContext {
|
||||
fn new<E: EthSpec>(
|
||||
state: &BeaconState<E>,
|
||||
state_ctxt: &StateContext,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<Self, Error> {
|
||||
let sum_slashings = state.get_all_slashings().iter().copied().safe_sum()?;
|
||||
let adjusted_total_slashing_balance = min(
|
||||
sum_slashings.safe_mul(spec.proportional_slashing_multiplier_for_state(state))?,
|
||||
state_ctxt.total_active_balance,
|
||||
);
|
||||
|
||||
let target_withdrawable_epoch = state_ctxt
|
||||
.current_epoch
|
||||
.safe_add(E::EpochsPerSlashingsVector::to_u64().safe_div(2)?)?;
|
||||
|
||||
Ok(Self {
|
||||
adjusted_total_slashing_balance,
|
||||
target_withdrawable_epoch,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn process_single_slashing(
|
||||
balance: &mut u64,
|
||||
validator: &Validator,
|
||||
slashings_ctxt: &SlashingsContext,
|
||||
state_ctxt: &StateContext,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
if validator.slashed && slashings_ctxt.target_withdrawable_epoch == validator.withdrawable_epoch
|
||||
{
|
||||
let increment = spec.effective_balance_increment;
|
||||
let penalty_numerator = validator
|
||||
.effective_balance
|
||||
.safe_div(increment)?
|
||||
.safe_mul(slashings_ctxt.adjusted_total_slashing_balance)?;
|
||||
let penalty = penalty_numerator
|
||||
.safe_div(state_ctxt.total_active_balance)?
|
||||
.safe_mul(increment)?;
|
||||
|
||||
*balance = balance.saturating_sub(penalty);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl EffectiveBalancesContext {
|
||||
fn new(spec: &ChainSpec) -> Result<Self, Error> {
|
||||
let hysteresis_increment = spec
|
||||
.effective_balance_increment
|
||||
.safe_div(spec.hysteresis_quotient)?;
|
||||
let downward_threshold =
|
||||
hysteresis_increment.safe_mul(spec.hysteresis_downward_multiplier)?;
|
||||
let upward_threshold = hysteresis_increment.safe_mul(spec.hysteresis_upward_multiplier)?;
|
||||
|
||||
Ok(Self {
|
||||
downward_threshold,
|
||||
upward_threshold,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn process_single_effective_balance_update(
|
||||
balance: u64,
|
||||
validator: &mut Validator,
|
||||
validator_info: &ValidatorInfo,
|
||||
next_epoch_total_active_balance: &mut u64,
|
||||
next_epoch_cache: &mut PreEpochCache,
|
||||
progressive_balances: &mut ProgressiveBalancesCache,
|
||||
eb_ctxt: &EffectiveBalancesContext,
|
||||
state_ctxt: &StateContext,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
let old_effective_balance = validator.effective_balance;
|
||||
let new_effective_balance = if balance.safe_add(eb_ctxt.downward_threshold)?
|
||||
< validator.effective_balance
|
||||
|| validator
|
||||
.effective_balance
|
||||
.safe_add(eb_ctxt.upward_threshold)?
|
||||
< balance
|
||||
{
|
||||
min(
|
||||
balance.safe_sub(balance.safe_rem(spec.effective_balance_increment)?)?,
|
||||
spec.max_effective_balance,
|
||||
)
|
||||
} else {
|
||||
validator.effective_balance
|
||||
};
|
||||
|
||||
if validator.is_active_at(state_ctxt.next_epoch) {
|
||||
next_epoch_total_active_balance.safe_add_assign(new_effective_balance)?;
|
||||
}
|
||||
|
||||
if new_effective_balance != old_effective_balance {
|
||||
validator.effective_balance = new_effective_balance;
|
||||
|
||||
// Update progressive balances cache for the *current* epoch, which will soon become the
|
||||
// previous epoch once the epoch transition completes.
|
||||
progressive_balances.on_effective_balance_change(
|
||||
validator.slashed,
|
||||
validator_info.current_epoch_participation,
|
||||
old_effective_balance,
|
||||
new_effective_balance,
|
||||
)?;
|
||||
}
|
||||
|
||||
// Caching: update next epoch effective balances.
|
||||
next_epoch_cache.push_effective_balance(new_effective_balance);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,6 +1,10 @@
|
||||
use crate::per_epoch_processing::Error;
|
||||
use crate::common::decrease_balance;
|
||||
use crate::per_epoch_processing::{
|
||||
single_pass::{process_epoch_single_pass, SinglePassConfig},
|
||||
Error,
|
||||
};
|
||||
use safe_arith::{SafeArith, SafeArithIter};
|
||||
use types::{BeaconState, BeaconStateError, ChainSpec, EthSpec, Unsigned};
|
||||
use types::{BeaconState, ChainSpec, EthSpec, Unsigned};
|
||||
|
||||
/// Process slashings.
|
||||
pub fn process_slashings<E: EthSpec>(
|
||||
@@ -16,28 +20,44 @@ pub fn process_slashings<E: EthSpec>(
|
||||
total_balance,
|
||||
);
|
||||
|
||||
let (validators, balances, _) = state.validators_and_balances_and_progressive_balances_mut();
|
||||
for (index, validator) in validators.iter().enumerate() {
|
||||
if validator.slashed
|
||||
&& epoch.safe_add(E::EpochsPerSlashingsVector::to_u64().safe_div(2)?)?
|
||||
== validator.withdrawable_epoch
|
||||
{
|
||||
let increment = spec.effective_balance_increment;
|
||||
let penalty_numerator = validator
|
||||
.effective_balance
|
||||
.safe_div(increment)?
|
||||
.safe_mul(adjusted_total_slashing_balance)?;
|
||||
let penalty = penalty_numerator
|
||||
.safe_div(total_balance)?
|
||||
.safe_mul(increment)?;
|
||||
let target_withdrawable_epoch =
|
||||
epoch.safe_add(E::EpochsPerSlashingsVector::to_u64().safe_div(2)?)?;
|
||||
let indices = state
|
||||
.validators()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, validator)| {
|
||||
validator.slashed && target_withdrawable_epoch == validator.withdrawable_epoch
|
||||
})
|
||||
.map(|(index, validator)| (index, validator.effective_balance))
|
||||
.collect::<Vec<(usize, u64)>>();
|
||||
|
||||
// Equivalent to `decrease_balance(state, index, penalty)`, but avoids borrowing `state`.
|
||||
let balance = balances
|
||||
.get_mut(index)
|
||||
.ok_or(BeaconStateError::BalancesOutOfBounds(index))?;
|
||||
*balance = balance.saturating_sub(penalty);
|
||||
}
|
||||
for (index, validator_effective_balance) in indices {
|
||||
let increment = spec.effective_balance_increment;
|
||||
let penalty_numerator = validator_effective_balance
|
||||
.safe_div(increment)?
|
||||
.safe_mul(adjusted_total_slashing_balance)?;
|
||||
let penalty = penalty_numerator
|
||||
.safe_div(total_balance)?
|
||||
.safe_mul(increment)?;
|
||||
|
||||
decrease_balance(state, index, penalty)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn process_slashings_slow<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
process_epoch_single_pass(
|
||||
state,
|
||||
spec,
|
||||
SinglePassConfig {
|
||||
slashings: true,
|
||||
..SinglePassConfig::disable_all()
|
||||
},
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user