mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-07 18:51:45 +00:00
## Issue Addressed Resolves #1100 ## Proposed Changes * Implement the `SafeArith` trait for `Slot` and `Epoch`, so that methods like `safe_add` become available. * Tweak the `SafeArith` trait to allow a different `Rhs` type (analagous to `std::ops::Add`, etc). * Add a `legacy-arith` feature to `types` and `state_processing` that conditionally enables implementations of the `std` ops with saturating semantics. * Check compilation of `types` and `state_processing` _without_ `legacy-arith` on CI, thus guaranteeing that they only use the `SafeArith` primitives 🎉 ## Additional Info The `legacy-arith` feature gets turned on by all higher-level crates that depend on `state_processing` or `types`, thus allowing the beacon chain, networking, and other components to continue to rely on the availability of ops like `+`, `-`, `*`, etc. **This is a consensus-breaking change**, but brings us in line with the spec, and our incompatibilities shouldn't have been reachable with any valid configuration of Eth2 parameters.
218 lines
7.3 KiB
Rust
218 lines
7.3 KiB
Rust
use errors::EpochProcessingError as Error;
|
|
use safe_arith::SafeArith;
|
|
use tree_hash::TreeHash;
|
|
use types::*;
|
|
|
|
pub mod apply_rewards;
|
|
pub mod errors;
|
|
pub mod process_slashings;
|
|
pub mod registry_updates;
|
|
pub mod tests;
|
|
pub mod validator_statuses;
|
|
|
|
pub use apply_rewards::process_rewards_and_penalties;
|
|
pub use process_slashings::process_slashings;
|
|
pub use registry_updates::process_registry_updates;
|
|
pub use validator_statuses::{TotalBalances, ValidatorStatus, ValidatorStatuses};
|
|
|
|
/// Provides a summary of validator participation during the epoch.
|
|
pub struct EpochProcessingSummary {
|
|
pub total_balances: TotalBalances,
|
|
}
|
|
|
|
/// Performs per-epoch processing on some BeaconState.
|
|
///
|
|
/// Mutates the given `BeaconState`, returning early if an error is encountered. If an error is
|
|
/// returned, a state might be "half-processed" and therefore in an invalid state.
|
|
///
|
|
/// Spec v0.12.1
|
|
pub fn per_epoch_processing<T: EthSpec>(
|
|
state: &mut BeaconState<T>,
|
|
spec: &ChainSpec,
|
|
) -> Result<EpochProcessingSummary, 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)?;
|
|
|
|
// Load the struct we use to assign validators into sets based on their participation.
|
|
//
|
|
// E.g., attestation in the previous epoch, attested to the head, etc.
|
|
let mut validator_statuses = ValidatorStatuses::new(state, spec)?;
|
|
validator_statuses.process_attestations(&state, spec)?;
|
|
|
|
// Justification and finalization.
|
|
process_justification_and_finalization(state, &validator_statuses.total_balances)?;
|
|
|
|
// Rewards and Penalties.
|
|
process_rewards_and_penalties(state, &mut validator_statuses, spec)?;
|
|
|
|
// Registry Updates.
|
|
process_registry_updates(state, spec)?;
|
|
|
|
// Slashings.
|
|
process_slashings(
|
|
state,
|
|
validator_statuses.total_balances.current_epoch(),
|
|
spec,
|
|
)?;
|
|
|
|
// Final updates.
|
|
process_final_updates(state, spec)?;
|
|
|
|
// Rotate the epoch caches to suit the epoch transition.
|
|
state.advance_caches();
|
|
|
|
Ok(EpochProcessingSummary {
|
|
total_balances: validator_statuses.total_balances,
|
|
})
|
|
}
|
|
|
|
/// Update the following fields on the `BeaconState`:
|
|
///
|
|
/// - `justification_bitfield`.
|
|
/// - `previous_justified_epoch`
|
|
/// - `previous_justified_root`
|
|
/// - `current_justified_epoch`
|
|
/// - `current_justified_root`
|
|
/// - `finalized_epoch`
|
|
/// - `finalized_root`
|
|
///
|
|
/// Spec v0.12.1
|
|
#[allow(clippy::if_same_then_else)] // For readability and consistency with spec.
|
|
pub fn process_justification_and_finalization<T: EthSpec>(
|
|
state: &mut BeaconState<T>,
|
|
total_balances: &TotalBalances,
|
|
) -> Result<(), Error> {
|
|
if state.current_epoch() <= T::genesis_epoch().safe_add(1)? {
|
|
return Ok(());
|
|
}
|
|
|
|
let previous_epoch = state.previous_epoch();
|
|
let current_epoch = state.current_epoch();
|
|
|
|
let old_previous_justified_checkpoint = state.previous_justified_checkpoint;
|
|
let old_current_justified_checkpoint = state.current_justified_checkpoint;
|
|
|
|
// Process justifications
|
|
state.previous_justified_checkpoint = state.current_justified_checkpoint;
|
|
state.justification_bits.shift_up(1)?;
|
|
|
|
if total_balances
|
|
.previous_epoch_target_attesters()
|
|
.safe_mul(3)?
|
|
>= total_balances.current_epoch().safe_mul(2)?
|
|
{
|
|
state.current_justified_checkpoint = Checkpoint {
|
|
epoch: previous_epoch,
|
|
root: *state.get_block_root_at_epoch(previous_epoch)?,
|
|
};
|
|
state.justification_bits.set(1, true)?;
|
|
}
|
|
// If the current epoch gets justified, fill the last bit.
|
|
if total_balances
|
|
.current_epoch_target_attesters()
|
|
.safe_mul(3)?
|
|
>= total_balances.current_epoch().safe_mul(2)?
|
|
{
|
|
state.current_justified_checkpoint = Checkpoint {
|
|
epoch: current_epoch,
|
|
root: *state.get_block_root_at_epoch(current_epoch)?,
|
|
};
|
|
state.justification_bits.set(0, true)?;
|
|
}
|
|
|
|
let bits = &state.justification_bits;
|
|
|
|
// The 2nd/3rd/4th most recent epochs are all justified, the 2nd using the 4th as source.
|
|
if (1..4).all(|i| bits.get(i).unwrap_or(false))
|
|
&& old_previous_justified_checkpoint.epoch.safe_add(3)? == current_epoch
|
|
{
|
|
state.finalized_checkpoint = old_previous_justified_checkpoint;
|
|
}
|
|
// The 2nd/3rd most recent epochs are both justified, the 2nd using the 3rd as source.
|
|
else if (1..3).all(|i| bits.get(i).unwrap_or(false))
|
|
&& old_previous_justified_checkpoint.epoch.safe_add(2)? == current_epoch
|
|
{
|
|
state.finalized_checkpoint = old_previous_justified_checkpoint;
|
|
}
|
|
// The 1st/2nd/3rd most recent epochs are all justified, the 1st using the 3nd as source.
|
|
if (0..3).all(|i| bits.get(i).unwrap_or(false))
|
|
&& old_current_justified_checkpoint.epoch.safe_add(2)? == current_epoch
|
|
{
|
|
state.finalized_checkpoint = old_current_justified_checkpoint;
|
|
}
|
|
// The 1st/2nd most recent epochs are both justified, the 1st using the 2nd as source.
|
|
else if (0..2).all(|i| bits.get(i).unwrap_or(false))
|
|
&& old_current_justified_checkpoint.epoch.safe_add(1)? == current_epoch
|
|
{
|
|
state.finalized_checkpoint = old_current_justified_checkpoint;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Finish up an epoch update.
|
|
///
|
|
/// Spec v0.12.1
|
|
pub fn process_final_updates<T: EthSpec>(
|
|
state: &mut BeaconState<T>,
|
|
spec: &ChainSpec,
|
|
) -> Result<(), Error> {
|
|
let current_epoch = state.current_epoch();
|
|
let next_epoch = state.next_epoch()?;
|
|
|
|
// Reset eth1 data votes.
|
|
if state
|
|
.slot
|
|
.safe_add(1)?
|
|
.safe_rem(T::SlotsPerEth1VotingPeriod::to_u64())?
|
|
== 0
|
|
{
|
|
state.eth1_data_votes = VariableList::empty();
|
|
}
|
|
|
|
// Update effective balances with hysteresis (lag).
|
|
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)?;
|
|
for (index, validator) in state.validators.iter_mut().enumerate() {
|
|
let balance = state.balances[index];
|
|
|
|
if balance.safe_add(downward_threshold)? < validator.effective_balance
|
|
|| validator.effective_balance.safe_add(upward_threshold)? < balance
|
|
{
|
|
validator.effective_balance = std::cmp::min(
|
|
balance.safe_sub(balance.safe_rem(spec.effective_balance_increment)?)?,
|
|
spec.max_effective_balance,
|
|
);
|
|
}
|
|
}
|
|
|
|
// Reset slashings
|
|
state.set_slashings(next_epoch, 0)?;
|
|
|
|
// Set randao mix
|
|
state.set_randao_mix(next_epoch, *state.get_randao_mix(current_epoch)?)?;
|
|
|
|
// Set historical root accumulator
|
|
if next_epoch
|
|
.as_u64()
|
|
.safe_rem(T::SlotsPerHistoricalRoot::to_u64().safe_div(T::slots_per_epoch())?)?
|
|
== 0
|
|
{
|
|
let historical_batch = state.historical_batch();
|
|
state
|
|
.historical_roots
|
|
.push(historical_batch.tree_hash_root())?;
|
|
}
|
|
|
|
// Rotate current/previous epoch attestations
|
|
state.previous_epoch_attestations =
|
|
std::mem::replace(&mut state.current_epoch_attestations, VariableList::empty());
|
|
|
|
Ok(())
|
|
}
|