Single-pass epoch processing (#4483)

This commit is contained in:
Michael Sproul
2023-07-18 16:59:55 +10:00
committed by GitHub
parent 079cd67df2
commit 5d2063d262
42 changed files with 1558 additions and 730 deletions

View File

@@ -0,0 +1,55 @@
use crate::common::update_progressive_balances_cache::initialize_progressive_balances_cache;
use crate::epoch_cache::initialize_epoch_cache;
use types::{BeaconState, ChainSpec, EpochCacheError, EthSpec, Hash256, RelativeEpoch};
/// Mixin trait for the beacon state that provides operations on *all* caches.
///
/// The reason this trait exists here away from `BeaconState` itself is that some caches are
/// computed by functions in `state_processing`.
pub trait AllCaches {
/// Build all caches.
///
/// Note that this excludes milhouse's intrinsic tree-hash cache. That needs to be managed
/// separately.
fn build_all_caches(&mut self, spec: &ChainSpec) -> Result<(), EpochCacheError>;
/// Return true if all caches are built.
///
/// Note that this excludes milhouse's intrinsic tree-hash cache. That needs to be managed
/// separately.
fn all_caches_built(&self) -> bool;
}
impl<E: EthSpec> AllCaches for BeaconState<E> {
fn build_all_caches(&mut self, spec: &ChainSpec) -> Result<(), EpochCacheError> {
self.build_caches(spec)?;
initialize_epoch_cache(self, spec)?;
initialize_progressive_balances_cache(self, None, spec)?;
Ok(())
}
fn all_caches_built(&self) -> bool {
let current_epoch = self.current_epoch();
let epoch_cache_decision_block_root =
if let Ok(root) = self.proposer_shuffling_decision_root(Hash256::zero()) {
root
} else {
return false;
};
self.get_total_active_balance_at_epoch(current_epoch)
.is_ok()
&& self.committee_cache_is_initialized(RelativeEpoch::Previous)
&& self.committee_cache_is_initialized(RelativeEpoch::Current)
&& self.committee_cache_is_initialized(RelativeEpoch::Next)
&& self
.progressive_balances_cache()
.is_initialized_at(current_epoch)
&& self.pubkey_cache().len() == self.validators().len()
&& self.exit_cache().check_initialized().is_ok()
&& self.slashings_cache_is_initialized()
&& self
.epoch_cache()
.check_validity::<E>(current_epoch, epoch_cache_decision_block_root)
.is_ok()
}
}

View File

@@ -20,6 +20,7 @@ pub fn slash_validator<T: EthSpec>(
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
let epoch = state.current_epoch();
let latest_block_slot = state.latest_block_header().slot;
initiate_validator_exit(state, slashed_index, spec)?;
@@ -44,7 +45,10 @@ pub fn slash_validator<T: EthSpec>(
.safe_div(spec.min_slashing_penalty_quotient_for_state(state))?,
)?;
update_progressive_balances_on_slashing(state, slashed_index)?;
update_progressive_balances_on_slashing(state, slashed_index, validator_effective_balance)?;
state
.slashings_cache_mut()
.record_validator_slashing(latest_block_slot, slashed_index)?;
// Apply proposer and whistleblower rewards
let proposer_index = ctxt.get_proposer_index(state, spec)? as usize;

View File

@@ -7,10 +7,9 @@ use crate::per_epoch_processing::altair::ParticipationCache;
use crate::{BlockProcessingError, EpochProcessingError};
use lighthouse_metrics::set_gauge;
use std::borrow::Cow;
use types::consts::altair::TIMELY_TARGET_FLAG_INDEX;
use types::{
is_progressive_balances_enabled, BeaconState, BeaconStateError, ChainSpec, Epoch, EthSpec,
ParticipationFlags, ProgressiveBalancesCache, VList,
is_progressive_balances_enabled, BeaconState, BeaconStateError, ChainSpec, Epoch,
EpochTotalBalances, EthSpec, ProgressiveBalancesCache,
};
/// Initializes the `ProgressiveBalancesCache` cache using balance values from the
@@ -27,6 +26,7 @@ pub fn initialize_progressive_balances_cache<E: EthSpec>(
return Ok(());
}
// FIXME(sproul): simplify the participation cache
let participation_cache = match maybe_participation_cache {
Some(cache) => Cow::Borrowed(cache),
None => {
@@ -38,19 +38,22 @@ pub fn initialize_progressive_balances_cache<E: EthSpec>(
}
};
let previous_epoch_target_attesting_balance = participation_cache
.previous_epoch_target_attesting_balance_raw()
.map_err(|e| BeaconStateError::ParticipationCacheError(format!("{e:?}")))?;
let current_epoch_target_attesting_balance = participation_cache
.current_epoch_target_attesting_balance_raw()
.map_err(|e| BeaconStateError::ParticipationCacheError(format!("{e:?}")))?;
let current_epoch = state.current_epoch();
let previous_epoch_cache = EpochTotalBalances {
total_flag_balances: participation_cache
.previous_epoch_participation
.total_flag_balances,
};
let current_epoch_cache = EpochTotalBalances {
total_flag_balances: participation_cache
.current_epoch_participation
.total_flag_balances,
};
state.progressive_balances_cache_mut().initialize(
current_epoch,
previous_epoch_target_attesting_balance,
current_epoch_target_attesting_balance,
previous_epoch_cache,
current_epoch_cache,
);
update_progressive_balances_metrics(state.progressive_balances_cache())?;
@@ -62,16 +65,16 @@ pub fn initialize_progressive_balances_cache<E: EthSpec>(
pub fn update_progressive_balances_on_attestation<T: EthSpec>(
state: &mut BeaconState<T>,
epoch: Epoch,
validator_index: usize,
flag_index: usize,
validator_effective_balance: u64,
validator_slashed: bool,
) -> Result<(), BlockProcessingError> {
if is_progressive_balances_enabled(state) {
let validator = state.get_validator(validator_index)?;
if !validator.slashed() {
let validator_effective_balance = validator.effective_balance();
state
.progressive_balances_cache_mut()
.on_new_target_attestation(epoch, validator_effective_balance)?;
}
if is_progressive_balances_enabled(state) && !validator_slashed {
state.progressive_balances_cache_mut().on_new_attestation(
epoch,
flag_index,
validator_effective_balance,
)?;
}
Ok(())
}
@@ -80,21 +83,22 @@ pub fn update_progressive_balances_on_attestation<T: EthSpec>(
pub fn update_progressive_balances_on_slashing<T: EthSpec>(
state: &mut BeaconState<T>,
validator_index: usize,
validator_effective_balance: u64,
) -> Result<(), BlockProcessingError> {
if is_progressive_balances_enabled(state) {
let previous_epoch_participation = state.previous_epoch_participation()?;
let is_previous_epoch_target_attester =
is_target_attester_in_epoch::<T>(previous_epoch_participation, validator_index)?;
let previous_epoch_participation = *state
.previous_epoch_participation()?
.get(validator_index)
.ok_or(BeaconStateError::UnknownValidator(validator_index))?;
let current_epoch_participation = state.current_epoch_participation()?;
let is_current_epoch_target_attester =
is_target_attester_in_epoch::<T>(current_epoch_participation, validator_index)?;
let validator_effective_balance = state.get_effective_balance(validator_index)?;
let current_epoch_participation = *state
.current_epoch_participation()?
.get(validator_index)
.ok_or(BeaconStateError::UnknownValidator(validator_index))?;
state.progressive_balances_cache_mut().on_slashing(
is_previous_epoch_target_attester,
is_current_epoch_target_attester,
previous_epoch_participation,
current_epoch_participation,
validator_effective_balance,
)?;
}
@@ -133,15 +137,3 @@ pub fn update_progressive_balances_metrics(
Ok(())
}
fn is_target_attester_in_epoch<T: EthSpec>(
epoch_participation: &VList<ParticipationFlags, T::ValidatorRegistryLimit>,
validator_index: usize,
) -> Result<bool, BlockProcessingError> {
let participation_flags = epoch_participation
.get(validator_index)
.ok_or(BeaconStateError::UnknownValidator(validator_index))?;
participation_flags
.has_flag(TIMELY_TARGET_FLAG_INDEX)
.map_err(|e| e.into())
}

View File

@@ -1,14 +1,85 @@
use crate::common::altair::BaseRewardPerIncrement;
use crate::common::base::SqrtTotalActiveBalance;
use crate::common::{altair, base};
use safe_arith::SafeArith;
use types::epoch_cache::{EpochCache, EpochCacheError, EpochCacheKey};
use types::{ActivationQueue, BeaconState, ChainSpec, Epoch, EthSpec, Hash256};
use types::{ActivationQueue, BeaconState, ChainSpec, EthSpec, Hash256};
/// Precursor to an `EpochCache`.
pub struct PreEpochCache {
epoch_key: EpochCacheKey,
effective_balances: Vec<u64>,
}
impl PreEpochCache {
pub fn new_for_next_epoch<E: EthSpec>(state: &BeaconState<E>) -> Result<Self, EpochCacheError> {
// The decision block root for the next epoch is the latest block root from this epoch.
let latest_block_header = state.latest_block_header();
// State root should already have been filled in by `process_slot`, except in the case
// of a `partial_state_advance`.
let decision_block_root = latest_block_header.canonical_root();
let epoch_key = EpochCacheKey {
epoch: state.next_epoch()?,
decision_block_root,
};
Ok(Self {
epoch_key,
effective_balances: Vec::with_capacity(state.validators().len()),
})
}
pub fn push_effective_balance(&mut self, effective_balance: u64) {
self.effective_balances.push(effective_balance);
}
pub fn into_epoch_cache(
self,
total_active_balance: u64,
activation_queue: ActivationQueue,
spec: &ChainSpec,
) -> Result<EpochCache, EpochCacheError> {
let epoch = self.epoch_key.epoch;
let sqrt_total_active_balance = SqrtTotalActiveBalance::new(total_active_balance);
let base_reward_per_increment = BaseRewardPerIncrement::new(total_active_balance, spec)?;
let effective_balance_increment = spec.effective_balance_increment;
let max_effective_balance_eth = spec
.max_effective_balance
.safe_div(effective_balance_increment)?;
let mut base_rewards = Vec::with_capacity(max_effective_balance_eth.safe_add(1)? as usize);
for effective_balance_eth in 0..=max_effective_balance_eth {
let effective_balance = effective_balance_eth.safe_mul(effective_balance_increment)?;
let base_reward = if spec
.altair_fork_epoch
.map_or(false, |altair_epoch| epoch < altair_epoch)
{
base::get_base_reward(effective_balance, sqrt_total_active_balance, spec)?
} else {
altair::get_base_reward(effective_balance, base_reward_per_increment, spec)?
};
base_rewards.push(base_reward);
}
Ok(EpochCache::new(
self.epoch_key,
self.effective_balances,
base_rewards,
activation_queue,
spec,
))
}
}
pub fn initialize_epoch_cache<E: EthSpec>(
state: &mut BeaconState<E>,
epoch: Epoch,
spec: &ChainSpec,
) -> Result<(), EpochCacheError> {
let epoch = state.current_epoch();
let epoch_cache: &EpochCache = state.epoch_cache();
let decision_block_root = state
.proposer_shuffling_decision_root(Hash256::zero())
@@ -22,42 +93,30 @@ pub fn initialize_epoch_cache<E: EthSpec>(
return Ok(());
}
// Compute base rewards.
state.build_total_active_balance_cache_at(epoch, spec)?;
let total_active_balance = state.get_total_active_balance_at_epoch(epoch)?;
let sqrt_total_active_balance = SqrtTotalActiveBalance::new(total_active_balance);
let base_reward_per_increment = BaseRewardPerIncrement::new(total_active_balance, spec)?;
let mut base_rewards = Vec::with_capacity(state.validators().len());
// Compute activation queue.
// Collect effective balances and compute activation queue.
let mut effective_balances = Vec::with_capacity(state.validators().len());
let mut activation_queue = ActivationQueue::default();
for (index, validator) in state.validators().iter().enumerate() {
let effective_balance = validator.effective_balance();
let base_reward = if spec
.altair_fork_epoch
.map_or(false, |altair_epoch| epoch < altair_epoch)
{
base::get_base_reward(effective_balance, sqrt_total_active_balance, spec)?
} else {
altair::get_base_reward(effective_balance, base_reward_per_increment, spec)?
};
base_rewards.push(base_reward);
effective_balances.push(validator.effective_balance());
// Add to speculative activation queue.
activation_queue.add_if_could_be_eligible_for_activation(index, validator, epoch, spec);
}
*state.epoch_cache_mut() = EpochCache::new(
EpochCacheKey {
// Compute base rewards.
let pre_epoch_cache = PreEpochCache {
epoch_key: EpochCacheKey {
epoch,
decision_block_root,
},
base_rewards,
activation_queue,
);
effective_balances,
};
*state.epoch_cache_mut() =
pre_epoch_cache.into_epoch_cache(total_active_balance, activation_queue, spec)?;
Ok(())
}

View File

@@ -16,6 +16,7 @@
mod macros;
mod metrics;
pub mod all_caches;
pub mod block_replayer;
pub mod common;
pub mod consensus_context;
@@ -28,6 +29,7 @@ pub mod state_advance;
pub mod upgrade;
pub mod verify_operation;
pub use all_caches::AllCaches;
pub use block_replayer::{BlockReplayError, BlockReplayer, StateProcessingStrategy};
pub use consensus_context::{ConsensusContext, ContextError};
pub use genesis::{

View File

@@ -119,8 +119,9 @@ pub fn per_block_processing<T: EthSpec, Payload: AbstractExecPayload<T>>(
.map_err(BlockProcessingError::InconsistentStateFork)?;
// Build epoch cache if it hasn't already been built, or if it is no longer valid
initialize_epoch_cache(state, state.current_epoch(), spec)?;
initialize_epoch_cache(state, spec)?;
initialize_progressive_balances_cache(state, None, spec)?;
state.build_slashings_cache()?;
let verify_signatures = match block_signature_strategy {
BlockSignatureStrategy::VerifyBulk => {
@@ -242,6 +243,9 @@ pub fn process_block_header<T: EthSpec>(
);
}
state
.slashings_cache_mut()
.update_latest_block_slot(block_header.slot);
*state.latest_block_header_mut() = block_header;
// Verify proposer is not slashed

View File

@@ -98,7 +98,6 @@ pub mod base {
pub mod altair {
use super::*;
use crate::common::update_progressive_balances_cache::update_progressive_balances_on_attestation;
use types::consts::altair::TIMELY_TARGET_FLAG_INDEX;
pub fn process_attestations<T: EthSpec>(
state: &mut BeaconState<T>,
@@ -151,6 +150,9 @@ pub mod altair {
for index in &attesting_indices {
let index = *index as usize;
let validator_effective_balance = state.epoch_cache().get_effective_balance(index)?;
let validator_slashed = state.slashings_cache().is_slashed(index);
for (flag_index, &weight) in PARTICIPATION_FLAG_WEIGHTS.iter().enumerate() {
let epoch_participation = state.get_epoch_participation_mut(
data.target.epoch,
@@ -168,13 +170,13 @@ pub mod altair {
proposer_reward_numerator
.safe_add_assign(state.get_base_reward(index)?.safe_mul(weight)?)?;
if flag_index == TIMELY_TARGET_FLAG_INDEX {
update_progressive_balances_on_attestation(
state,
data.target.epoch,
index,
)?;
}
update_progressive_balances_on_attestation(
state,
data.target.epoch,
flag_index,
validator_effective_balance,
validator_slashed,
)?;
}
}
}
@@ -201,6 +203,8 @@ pub fn process_proposer_slashings<T: EthSpec>(
ctxt: &mut ConsensusContext<T>,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
state.build_slashings_cache()?;
// Verify and apply proposer slashings in series.
// We have to verify in series because an invalid block may contain multiple slashings
// for the same validator, and we need to correctly detect and reject that.
@@ -234,6 +238,8 @@ pub fn process_attester_slashings<T: EthSpec>(
ctxt: &mut ConsensusContext<T>,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
state.build_slashings_cache()?;
for (i, attester_slashing) in attester_slashings.iter().enumerate() {
verify_attester_slashing(state, attester_slashing, verify_signatures, spec)
.map_err(|e| e.into_with_index(i))?;

View File

@@ -1,14 +1,14 @@
#![deny(clippy::wildcard_imports)]
use crate::metrics;
pub use epoch_processing_summary::EpochProcessingSummary;
pub use epoch_processing_summary::{EpochProcessingSummary, ParticipationEpochSummary};
use errors::EpochProcessingError as Error;
pub use justification_and_finalization_state::JustificationAndFinalizationState;
use safe_arith::SafeArith;
use types::{BeaconState, ChainSpec, EthSpec};
pub use registry_updates::process_registry_updates;
pub use slashings::process_slashings;
pub use registry_updates::{process_registry_updates, process_registry_updates_slow};
pub use slashings::{process_slashings, process_slashings_slow};
pub use weigh_justification_and_finalization::weigh_justification_and_finalization;
pub mod altair;
@@ -21,6 +21,7 @@ pub mod historical_roots_update;
pub mod justification_and_finalization_state;
pub mod registry_updates;
pub mod resets;
pub mod single_pass;
pub mod slashings;
pub mod tests;
pub mod weigh_justification_and_finalization;
@@ -42,8 +43,9 @@ pub fn process_epoch<T: EthSpec>(
match state {
BeaconState::Base(_) => base::process_epoch(state, spec),
BeaconState::Altair(_) | BeaconState::Merge(_) => altair::process_epoch(state, spec),
BeaconState::Capella(_) => capella::process_epoch(state, spec),
BeaconState::Altair(_) | BeaconState::Merge(_) | BeaconState::Capella(_) => {
altair::process_epoch(state, spec)
}
}
}

View File

@@ -1,18 +1,19 @@
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};
@@ -27,53 +28,51 @@ pub fn process_epoch<T: EthSpec>(
state: &mut BeaconState<T>,
spec: &ChainSpec,
) -> Result<EpochProcessingSummary<T>, 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_at(state.current_epoch(), spec)?;
initialize_epoch_cache(state, state.current_epoch(), spec)?;
initialize_epoch_cache(state, spec)?;
initialize_progressive_balances_cache::<T>(state, None, spec)?;
// Pre-compute participating indices and total balances.
let mut participation_cache = ParticipationCache::new(state, spec)?;
let sync_committee = state.current_sync_committee()?.clone();
initialize_progressive_balances_cache::<T>(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, &mut 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,
Some(participation_cache.process_slashings_indices()),
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)?;
@@ -82,12 +81,12 @@ pub fn process_epoch<T: EthSpec>(
// Rotate the epoch caches to suit the epoch transition.
state.advance_caches(spec)?;
initialize_epoch_cache(state, state.next_epoch()?, spec)?;
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,
})
}

View File

@@ -1,68 +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<T: EthSpec>(
/// Slow version of `process_inactivity_updates`.
///
/// Should only be used for testing.
pub fn process_inactivity_updates_slow<T: EthSpec>(
state: &mut BeaconState<T>,
participation_cache: &mut 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() == T::genesis_epoch() {
return Ok(());
}
// Fast path: inactivity scores have already been pre-computed.
if let Some(inactivity_score_updates) = participation_cache.inactivity_score_updates.take() {
// We need to flush the existing inactivity scores in case tree hashing hasn't happened in
// a long time (e.g. during state reconstruction).
// FIXME(sproul): re-think this
state.inactivity_scores_mut()?.apply_updates()?;
state
.inactivity_scores_mut()?
.bulk_update(inactivity_score_updates)?;
return Ok(());
}
let is_in_inactivity_leak = state.is_in_inactivity_leak(previous_epoch, spec);
let mut inactivity_scores = state.inactivity_scores_mut()?.iter_cow();
while let Some((index, inactivity_score)) = inactivity_scores.next_cow() {
let validator = match participation_cache.get_validator(index) {
Ok(val) if val.is_eligible => val,
_ => continue,
};
let inactivity_score_mut;
// Increase inactivity score of inactive validators
if validator.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 {
continue;
}
inactivity_score_mut = inactivity_score.to_mut();
inactivity_score_mut.safe_sub_assign(1)?;
} else {
inactivity_score_mut = inactivity_score.to_mut();
inactivity_score_mut.safe_add_assign(spec.inactivity_score_bias)?;
}
// Decrease the score of all validators for forgiveness when not during a leak
if !is_in_inactivity_leak {
inactivity_score_mut.safe_sub_assign(min(
spec.inactivity_score_recovery_rate,
*inactivity_score_mut,
))?;
}
}
process_epoch_single_pass(
state,
spec,
SinglePassConfig {
inactivity_updates: true,
..SinglePassConfig::disable_all()
},
)?;
Ok(())
}

View File

@@ -1,4 +1,3 @@
use super::ParticipationCache;
use crate::per_epoch_processing::Error;
use crate::per_epoch_processing::{
weigh_justification_and_finalization, JustificationAndFinalizationState,
@@ -6,20 +5,23 @@ use crate::per_epoch_processing::{
use safe_arith::SafeArith;
use types::{BeaconState, EthSpec};
/// Update the justified and finalized checkpoints for matching target attestations.
pub fn process_justification_and_finalization<T: EthSpec>(
state: &BeaconState<T>,
participation_cache: &ParticipationCache,
) -> Result<JustificationAndFinalizationState<T>, Error> {
/// Process justification and finalization using the progressive balances cache.
pub fn process_justification_and_finalization<E: EthSpec>(
state: &BeaconState<E>,
) -> Result<JustificationAndFinalizationState<E>, Error> {
let justification_and_finalization_state = JustificationAndFinalizationState::new(state);
if state.current_epoch() <= T::genesis_epoch().safe_add(1)? {
if state.current_epoch() <= E::genesis_epoch().safe_add(1)? {
return Ok(justification_and_finalization_state);
}
let total_active_balance = participation_cache.current_epoch_total_active_balance();
let previous_target_balance = participation_cache.previous_epoch_target_attesting_balance()?;
let current_target_balance = participation_cache.current_epoch_target_attesting_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,

View File

@@ -49,12 +49,12 @@ impl From<ArithError> for Error {
/// Caches the participation values for one epoch (either the previous or current).
#[derive(PartialEq, Debug, Clone)]
struct SingleEpochParticipationCache {
pub(crate) struct SingleEpochParticipationCache {
/// Stores the sum of the balances for all validators in `self.unslashed_participating_indices`
/// for all flags in `NUM_FLAG_INDICES`.
///
/// A flag balance is only incremented if a validator is in that flag set.
total_flag_balances: [Balance; NUM_FLAG_INDICES],
pub(crate) total_flag_balances: [Balance; NUM_FLAG_INDICES],
/// Stores the sum of all balances of all validators in `self.unslashed_participating_indices`
/// (regardless of which flags are set).
total_active_balance: Balance,
@@ -170,10 +170,10 @@ impl ValidatorInfoCache {
pub struct ParticipationCache {
current_epoch: Epoch,
/// Caches information about active validators pertaining to `self.current_epoch`.
current_epoch_participation: SingleEpochParticipationCache,
pub(crate) current_epoch_participation: SingleEpochParticipationCache,
previous_epoch: Epoch,
/// Caches information about active validators pertaining to `self.previous_epoch`.
previous_epoch_participation: SingleEpochParticipationCache,
pub(crate) previous_epoch_participation: SingleEpochParticipationCache,
/// Caches validator information relevant to `process_epoch`.
validators: ValidatorInfoCache,
/// Caches the result of the `get_eligible_validator_indices` function.

View File

@@ -1,61 +1,30 @@
use super::ParticipationCache;
use crate::per_epoch_processing::{
single_pass::{process_epoch_single_pass, SinglePassConfig},
Delta, Error,
};
use safe_arith::SafeArith;
use types::consts::altair::{
PARTICIPATION_FLAG_WEIGHTS, TIMELY_HEAD_FLAG_INDEX, TIMELY_TARGET_FLAG_INDEX,
WEIGHT_DENOMINATOR,
};
use types::{BeaconState, BeaconStateError, ChainSpec, EthSpec};
use crate::common::{decrease_balance_directly, increase_balance_directly};
use crate::per_epoch_processing::{Delta, Error};
use types::{BeaconState, ChainSpec, EthSpec};
/// Apply attester and proposer rewards.
///
/// Spec v1.1.0
pub fn process_rewards_and_penalties<T: EthSpec>(
/// This function should only be used for testing.
pub fn process_rewards_and_penalties_slow<T: EthSpec>(
state: &mut BeaconState<T>,
participation_cache: &ParticipationCache,
spec: &ChainSpec,
) -> Result<(), Error> {
if state.current_epoch() == T::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).
let mut balances = state.balances_mut().iter_cow();
while let Some((i, balance)) = balances.next_cow() {
let delta = deltas
.get(i)
.ok_or(BeaconStateError::BalancesOutOfBounds(i))?;
if delta.rewards == 0 && delta.penalties == 0 {
continue;
}
let balance = balance.to_mut();
increase_balance_directly(balance, delta.rewards)?;
decrease_balance_directly(balance, delta.penalties)?;
}
process_epoch_single_pass(
state,
spec,
SinglePassConfig {
rewards_and_penalties: true,
..SinglePassConfig::disable_all()
},
)?;
Ok(())
}

View File

@@ -25,7 +25,7 @@ pub fn process_epoch<T: EthSpec>(
state.build_committee_cache(RelativeEpoch::Current, spec)?;
state.build_committee_cache(RelativeEpoch::Next, spec)?;
state.build_total_active_balance_cache_at(state.current_epoch(), spec)?;
initialize_epoch_cache(state, state.current_epoch(), spec)?;
initialize_epoch_cache(state, spec)?;
// Load the struct we use to assign validators into sets based on their participation.
//
@@ -47,7 +47,6 @@ pub fn process_epoch<T: EthSpec>(
// Slashings.
process_slashings(
state,
None,
validator_statuses.total_balances.current_epoch(),
spec,
)?;
@@ -56,7 +55,7 @@ pub fn process_epoch<T: 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)?;
@@ -72,7 +71,6 @@ pub fn process_epoch<T: EthSpec>(
// Rotate the epoch caches to suit the epoch transition.
state.advance_caches(spec)?;
initialize_epoch_cache(state, state.next_epoch()?, spec)?;
Ok(EpochProcessingSummary::Base {
total_balances: validator_statuses.total_balances,

View File

@@ -1,89 +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,
};
use crate::epoch_cache::initialize_epoch_cache;
pub use historical_summaries_update::process_historical_summaries_update;
mod historical_summaries_update;
pub fn process_epoch<T: EthSpec>(
state: &mut BeaconState<T>,
spec: &ChainSpec,
) -> Result<EpochProcessingSummary<T>, 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)?;
state.build_total_active_balance_cache_at(state.current_epoch(), spec)?;
initialize_epoch_cache(state, state.current_epoch(), spec)?;
// Pre-compute participating indices and total balances.
let mut 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, &mut 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,
Some(participation_cache.process_slashings_indices()),
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)?;
initialize_epoch_cache(state, state.next_epoch()?, spec)?;
update_progressive_balances_on_epoch_transition(state, spec)?;
Ok(EpochProcessingSummary::Altair {
participation_cache,
sync_committee,
})
}

View File

@@ -1,13 +1,12 @@
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};
pub fn process_effective_balance_updates<T: EthSpec>(
state: &mut BeaconState<T>,
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
@@ -20,8 +19,7 @@ pub fn process_effective_balance_updates<T: EthSpec>(
.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();
let mut validators_iter = validators.iter_cow();
while let Some((index, validator)) = validators_iter.next_cow() {
@@ -47,18 +45,7 @@ pub fn process_effective_balance_updates<T: EthSpec>(
}
if new_effective_balance != validator.effective_balance() {
let old_effective_balance = validator.effective_balance();
validator.to_mut().mutable.effective_balance = new_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,
)?;
}
}
}
@@ -67,21 +54,17 @@ pub fn process_effective_balance_updates<T: EthSpec>(
Ok(())
}
fn update_progressive_balances(
participation_cache: &ParticipationCache,
progressive_balances_cache: &mut ProgressiveBalancesCache,
index: usize,
old_effective_balance: u64,
new_effective_balance: u64,
pub fn process_effective_balance_updates_slow<T: EthSpec>(
state: &mut BeaconState<T>,
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(())
}

View File

@@ -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,
VList, Validator,
};
/// Provides a summary of validator participation during the epoch.
#[derive(PartialEq, Debug)]
@@ -14,15 +15,80 @@ pub enum EpochProcessingSummary<T: EthSpec> {
statuses: Vec<ValidatorStatus>,
},
Altair {
participation_cache: ParticipationCache,
progressive_balances: ProgressiveBalancesCache,
current_epoch_total_active_balance: u64,
participation: ParticipationEpochSummary<T>,
sync_committee: Arc<SyncCommittee<T>>,
},
}
#[derive(PartialEq, Debug)]
pub struct ParticipationEpochSummary<T: EthSpec> {
/// Copy of the validator registry prior to mutation.
validators: VList<Validator, T::ValidatorRegistryLimit>,
/// Copy of the participation flags for the previous epoch.
previous_epoch_participation: VList<ParticipationFlags, T::ValidatorRegistryLimit>,
/// Copy of the participation flags for the current epoch.
current_epoch_participation: VList<ParticipationFlags, T::ValidatorRegistryLimit>,
previous_epoch: Epoch,
current_epoch: Epoch,
}
impl<T: EthSpec> ParticipationEpochSummary<T> {
pub fn new(
validators: VList<Validator, T::ValidatorRegistryLimit>,
previous_epoch_participation: VList<ParticipationFlags, T::ValidatorRegistryLimit>,
current_epoch_participation: VList<ParticipationFlags, T::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<T: EthSpec> EpochProcessingSummary<T> {
/// Updates some Prometheus metrics with some values in `self`.
#[cfg(feature = "metrics")]
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,
@@ -56,35 +122,30 @@ impl<T: EthSpec> EpochProcessingSummary<T> {
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(),
} => progressive_balances.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(),
}
// FIXME(sproul): this is not a useful concept and should be deleted
self.current_epoch_total_active_balance()
}
/// Returns `true` if `val_index` was included in the active validator indices in the current
@@ -98,10 +159,9 @@ impl<T: EthSpec> EpochProcessingSummary<T> {
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),
EpochProcessingSummary::Altair { participation, .. } => {
participation.is_active_and_unslashed(val_index, participation.current_epoch)
}
}
}
@@ -119,34 +179,30 @@ impl<T: EthSpec> EpochProcessingSummary<T> {
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(),
}
}
@@ -157,15 +213,15 @@ impl<T: EthSpec> EpochProcessingSummary<T> {
///
/// - 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(),
}
}
@@ -176,15 +232,15 @@ impl<T: EthSpec> EpochProcessingSummary<T> {
///
/// - 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(),
}
}
@@ -199,10 +255,9 @@ impl<T: EthSpec> EpochProcessingSummary<T> {
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),
EpochProcessingSummary::Altair { participation, .. } => {
participation.is_active_and_unslashed(val_index, participation.previous_epoch)
}
}
}
@@ -215,20 +270,16 @@ impl<T: EthSpec> EpochProcessingSummary<T> {
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,
),
}
}
@@ -246,20 +297,13 @@ impl<T: EthSpec> EpochProcessingSummary<T> {
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),
}
}
@@ -277,20 +321,16 @@ impl<T: EthSpec> EpochProcessingSummary<T> {
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,
),
}
}

View File

@@ -1,3 +1,4 @@
use crate::per_epoch_processing::single_pass::{process_epoch_single_pass, SinglePassConfig};
use crate::{common::initiate_validator_exit, per_epoch_processing::Error};
use safe_arith::SafeArith;
use types::{BeaconState, ChainSpec, EthSpec, Validator};
@@ -54,3 +55,18 @@ pub fn process_registry_updates<T: EthSpec>(
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(())
}

View File

@@ -0,0 +1,628 @@
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, 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, None, spec)?;
state.build_exit_cache(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 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, 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_cow();
let mut balances_iter = balances.iter_cow();
let mut inactivity_scores_iter = inactivity_scores.iter_cow();
// 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, &current_epoch_participation) in izip!(
0..num_validators,
previous_epoch_participation.iter(),
current_epoch_participation.iter(),
) {
let (_, validator_cow) = validators_iter
.next_cow()
.ok_or(BeaconStateError::UnknownValidator(index))?;
let (_, balance_cow) = balances_iter
.next_cow()
.ok_or(BeaconStateError::UnknownValidator(index))?;
let (_, inactivity_score_cow) = inactivity_scores_iter
.next_cow()
.ok_or(BeaconStateError::UnknownValidator(index))?;
let validator = validator_cow.to_mut();
let balance = balance_cow.to_mut();
let inactivity_score = inactivity_score_cow.to_mut();
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 + Epoch::new(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);
*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 {
inactivity_score
.safe_sub_assign(min(spec.inactivity_score_recovery_rate, *inactivity_score))?;
}
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,
)?;
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.mutable.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.mutable.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.mutable.exit_epoch = exit_queue_epoch;
validator.mutable.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.mutable.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_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(())
}

View File

@@ -1,12 +1,14 @@
use crate::common::decrease_balance;
use crate::per_epoch_processing::Error;
use crate::per_epoch_processing::{
single_pass::{process_epoch_single_pass, SinglePassConfig},
Error,
};
use safe_arith::{SafeArith, SafeArithIter};
use types::{BeaconState, ChainSpec, EthSpec, Unsigned};
/// Process slashings.
pub fn process_slashings<T: EthSpec>(
state: &mut BeaconState<T>,
indices: Option<Vec<(usize, u64)>>,
total_balance: u64,
spec: &ChainSpec,
) -> Result<(), Error> {
@@ -20,17 +22,15 @@ pub fn process_slashings<T: EthSpec>(
let target_withdrawable_epoch =
epoch.safe_add(T::EpochsPerSlashingsVector::to_u64().safe_div(2)?)?;
let indices = indices.unwrap_or_else(|| {
state
.validators()
.iter()
.enumerate()
.filter(|(_, validator)| {
validator.slashed() && target_withdrawable_epoch == validator.withdrawable_epoch()
})
.map(|(index, validator)| (index, validator.effective_balance()))
.collect()
});
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)>>();
for (index, validator_effective_balance) in indices {
let increment = spec.effective_balance_increment;
@@ -46,3 +46,18 @@ pub fn process_slashings<T: EthSpec>(
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(())
}

View File

@@ -106,6 +106,7 @@ pub fn upgrade_to_altair<E: EthSpec>(
committee_caches: mem::take(&mut pre.committee_caches),
pubkey_cache: mem::take(&mut pre.pubkey_cache),
exit_cache: mem::take(&mut pre.exit_cache),
slashings_cache: mem::take(&mut pre.slashings_cache),
epoch_cache: EpochCache::default(),
});

View File

@@ -68,6 +68,7 @@ pub fn upgrade_to_capella<E: EthSpec>(
committee_caches: mem::take(&mut pre.committee_caches),
pubkey_cache: mem::take(&mut pre.pubkey_cache),
exit_cache: mem::take(&mut pre.exit_cache),
slashings_cache: mem::take(&mut pre.slashings_cache),
epoch_cache: EpochCache::default(),
});

View File

@@ -64,6 +64,7 @@ pub fn upgrade_to_bellatrix<E: EthSpec>(
committee_caches: mem::take(&mut pre.committee_caches),
pubkey_cache: mem::take(&mut pre.pubkey_cache),
exit_cache: mem::take(&mut pre.exit_cache),
slashings_cache: mem::take(&mut pre.slashings_cache),
epoch_cache: EpochCache::default(),
});