Altair consensus changes and refactors (#2279)

## Proposed Changes

Implement the consensus changes necessary for the upcoming Altair hard fork.

## Additional Info

This is quite a heavy refactor, with pivotal types like the `BeaconState` and `BeaconBlock` changing from structs to enums. This ripples through the whole codebase with field accesses changing to methods, e.g. `state.slot` => `state.slot()`.


Co-authored-by: realbigsean <seananderson33@gmail.com>
This commit is contained in:
Michael Sproul
2021-07-09 06:15:32 +00:00
parent 89361573d4
commit b4689e20c6
271 changed files with 9652 additions and 8444 deletions

View File

@@ -0,0 +1,82 @@
use super::{process_registry_updates, process_slashings, EpochProcessingSummary, Error};
use crate::per_epoch_processing::{
effective_balance_updates::process_effective_balance_updates,
historical_roots_update::process_historical_roots_update,
resets::{process_eth1_data_reset, process_randao_mixes_reset, process_slashings_reset},
validator_statuses::ValidatorStatuses,
};
pub use inactivity_updates::process_inactivity_updates;
pub use justification_and_finalization::process_justification_and_finalization;
pub use participation_flag_updates::process_participation_flag_updates;
pub use rewards_and_penalties::process_rewards_and_penalties;
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_flag_updates;
pub mod rewards_and_penalties;
pub mod sync_committee_updates;
pub fn process_epoch<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)?;
// Justification and finalization.
process_justification_and_finalization(state, spec)?;
process_inactivity_updates(state, spec)?;
// Rewards and Penalties.
process_rewards_and_penalties(state, spec)?;
// Registry Updates.
process_registry_updates(state, spec)?;
// Slashings.
process_slashings(
state,
state.get_total_active_balance(spec)?,
spec.proportional_slashing_multiplier_altair,
spec,
)?;
// Reset eth1 data votes.
process_eth1_data_reset(state)?;
// Update effective balances with hysteresis (lag).
process_effective_balance_updates(state, spec)?;
// Reset slashings
process_slashings_reset(state)?;
// Set randao mix
process_randao_mixes_reset(state)?;
// Set historical root accumulator
process_historical_roots_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()?;
// FIXME(altair): this is an incorrect dummy value, we should think harder
// about how we want to unify validator statuses between phase0 & altair.
// We should benchmark the new state transition and work out whether Altair could
// be accelerated by some similar cache.
let validator_statuses = ValidatorStatuses::new(state, spec)?;
Ok(EpochProcessingSummary {
total_balances: validator_statuses.total_balances,
statuses: validator_statuses.statuses,
})
}

View File

@@ -0,0 +1,44 @@
use crate::EpochProcessingError;
use core::result::Result;
use core::result::Result::Ok;
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>(
state: &mut BeaconState<T>,
spec: &ChainSpec,
) -> Result<(), EpochProcessingError> {
// Score updates based on previous epoch participation, skip genesis epoch
if state.current_epoch() == T::genesis_epoch() {
return Ok(());
}
let unslashed_indices = state.get_unslashed_participating_indices(
TIMELY_TARGET_FLAG_INDEX,
state.previous_epoch(),
spec,
)?;
for index in state.get_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(spec) {
let inactivity_score = state.get_inactivity_score_mut(index)?;
inactivity_score
.safe_sub_assign(min(spec.inactivity_score_recovery_rate, *inactivity_score))?;
}
}
Ok(())
}

View File

@@ -0,0 +1,39 @@
use crate::per_epoch_processing::weigh_justification_and_finalization;
use crate::per_epoch_processing::Error;
use safe_arith::SafeArith;
use types::consts::altair::TIMELY_TARGET_FLAG_INDEX;
use types::{BeaconState, ChainSpec, EthSpec};
/// Update the justified and finalized checkpoints for matching target attestations.
pub fn process_justification_and_finalization<T: EthSpec>(
state: &mut BeaconState<T>,
spec: &ChainSpec,
) -> 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 previous_indices = state.get_unslashed_participating_indices(
TIMELY_TARGET_FLAG_INDEX,
previous_epoch,
spec,
)?;
let current_indices =
state.get_unslashed_participating_indices(TIMELY_TARGET_FLAG_INDEX, current_epoch, spec)?;
let total_active_balance = state.get_total_balance(
state
.get_active_validator_indices(current_epoch, spec)?
.as_slice(),
spec,
)?;
let previous_target_balance = state.get_total_balance(&previous_indices, spec)?;
let current_target_balance = state.get_total_balance(&current_indices, spec)?;
weigh_justification_and_finalization(
state,
total_active_balance,
previous_target_balance,
current_target_balance,
)
}

View File

@@ -0,0 +1,20 @@
use crate::EpochProcessingError;
use core::result::Result;
use core::result::Result::Ok;
use types::beacon_state::BeaconState;
use types::eth_spec::EthSpec;
use types::participation_flags::ParticipationFlags;
use types::VariableList;
pub fn process_participation_flag_updates<T: EthSpec>(
state: &mut BeaconState<T>,
) -> Result<(), EpochProcessingError> {
*state.previous_epoch_participation_mut()? =
std::mem::take(state.current_epoch_participation_mut()?);
*state.current_epoch_participation_mut()? = VariableList::new(vec![
ParticipationFlags::default(
);
state.validators().len()
])?;
Ok(())
}

View File

@@ -0,0 +1,124 @@
use safe_arith::SafeArith;
use types::consts::altair::{
PARTICIPATION_FLAG_WEIGHTS, TIMELY_HEAD_FLAG_INDEX, TIMELY_TARGET_FLAG_INDEX,
WEIGHT_DENOMINATOR,
};
use types::{BeaconState, ChainSpec, EthSpec};
use crate::common::{altair::get_base_reward, 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<T: EthSpec>(
state: &mut BeaconState<T>,
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 = state.get_total_active_balance(spec)?;
for flag_index in 0..PARTICIPATION_FLAG_WEIGHTS.len() {
get_flag_index_deltas(&mut deltas, state, flag_index, total_active_balance, spec)?;
}
get_inactivity_penalty_deltas(&mut deltas, state, 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<T: EthSpec>(
deltas: &mut Vec<Delta>,
state: &BeaconState<T>,
flag_index: usize,
total_active_balance: u64,
spec: &ChainSpec,
) -> Result<(), Error> {
let previous_epoch = state.previous_epoch();
let unslashed_participating_indices =
state.get_unslashed_participating_indices(flag_index, previous_epoch, spec)?;
let weight = get_flag_weight(flag_index)?;
let unslashed_participating_balance =
state.get_total_balance(&unslashed_participating_indices, spec)?;
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)?;
for index in state.get_eligible_validator_indices()? {
let base_reward = get_base_reward(state, index, total_active_balance, spec)?;
let mut delta = Delta::default();
if unslashed_participating_indices.contains(&(index as usize)) {
if !state.is_in_inactivity_leak(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 as usize)
.ok_or(Error::DeltaOutOfBounds(index as usize))?
.combine(delta)?;
}
Ok(())
}
/// Get the weight for a `flag_index` from the constant list of all weights.
pub fn get_flag_weight(flag_index: usize) -> Result<u64, Error> {
PARTICIPATION_FLAG_WEIGHTS
.get(flag_index)
.copied()
.ok_or(Error::InvalidFlagIndex(flag_index))
}
pub fn get_inactivity_penalty_deltas<T: EthSpec>(
deltas: &mut Vec<Delta>,
state: &BeaconState<T>,
spec: &ChainSpec,
) -> Result<(), Error> {
let previous_epoch = state.previous_epoch();
let matching_target_indices = state.get_unslashed_participating_indices(
TIMELY_TARGET_FLAG_INDEX,
previous_epoch,
spec,
)?;
for index in state.get_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_altair)?;
delta.penalize(penalty_numerator.safe_div(penalty_denominator)?)?;
}
deltas
.get_mut(index)
.ok_or(Error::DeltaOutOfBounds(index))?
.combine(delta)?;
}
Ok(())
}

View File

@@ -0,0 +1,18 @@
use crate::EpochProcessingError;
use safe_arith::SafeArith;
use types::beacon_state::BeaconState;
use types::chain_spec::ChainSpec;
use types::eth_spec::EthSpec;
pub fn process_sync_committee_updates<T: EthSpec>(
state: &mut BeaconState<T>,
spec: &ChainSpec,
) -> Result<(), EpochProcessingError> {
let next_epoch = state.next_epoch()?;
if next_epoch.safe_rem(spec.epochs_per_sync_committee_period)? == 0 {
*state.current_sync_committee_mut()? = state.next_sync_committee()?.clone();
*state.next_sync_committee_mut()? = state.get_next_sync_committee(spec)?;
}
Ok(())
}

View File

@@ -0,0 +1,76 @@
use super::{process_registry_updates, process_slashings, EpochProcessingSummary, Error};
pub use crate::per_epoch_processing::validator_statuses::{
TotalBalances, ValidatorStatus, ValidatorStatuses,
};
use crate::per_epoch_processing::{
effective_balance_updates::process_effective_balance_updates,
historical_roots_update::process_historical_roots_update,
resets::{process_eth1_data_reset, process_randao_mixes_reset, process_slashings_reset},
};
pub use justification_and_finalization::process_justification_and_finalization;
pub use participation_record_updates::process_participation_record_updates;
pub use rewards_and_penalties::process_rewards_and_penalties;
use types::{BeaconState, ChainSpec, EthSpec, RelativeEpoch};
pub mod justification_and_finalization;
pub mod participation_record_updates;
pub mod rewards_and_penalties;
pub fn process_epoch<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)?;
// Justification and finalization.
process_justification_and_finalization(state, &validator_statuses.total_balances, spec)?;
// 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.proportional_slashing_multiplier,
spec,
)?;
// Reset eth1 data votes.
process_eth1_data_reset(state)?;
// Update effective balances with hysteresis (lag).
process_effective_balance_updates(state, spec)?;
// Reset slashings
process_slashings_reset(state)?;
// Set randao mix
process_randao_mixes_reset(state)?;
// Set historical root accumulator
process_historical_roots_update(state)?;
// Rotate current/previous epoch attestations
process_participation_record_updates(state)?;
// Rotate the epoch caches to suit the epoch transition.
state.advance_caches()?;
Ok(EpochProcessingSummary {
total_balances: validator_statuses.total_balances,
statuses: validator_statuses.statuses,
})
}

View File

@@ -0,0 +1,23 @@
use crate::per_epoch_processing::base::TotalBalances;
use crate::per_epoch_processing::weigh_justification_and_finalization;
use crate::per_epoch_processing::Error;
use safe_arith::SafeArith;
use types::{BeaconState, ChainSpec, EthSpec};
/// Update the justified and finalized checkpoints for matching target attestations.
pub fn process_justification_and_finalization<T: EthSpec>(
state: &mut BeaconState<T>,
total_balances: &TotalBalances,
_spec: &ChainSpec,
) -> Result<(), Error> {
if state.current_epoch() <= T::genesis_epoch().safe_add(1)? {
return Ok(());
}
weigh_justification_and_finalization(
state,
total_balances.current_epoch(),
total_balances.previous_epoch_target_attesters(),
total_balances.current_epoch_target_attesters(),
)
}

View File

@@ -0,0 +1,12 @@
use crate::EpochProcessingError;
use types::beacon_state::BeaconState;
use types::eth_spec::EthSpec;
pub fn process_participation_record_updates<T: EthSpec>(
state: &mut BeaconState<T>,
) -> Result<(), EpochProcessingError> {
let base_state = state.as_base_mut()?;
base_state.previous_epoch_attestations =
std::mem::take(&mut base_state.current_epoch_attestations);
Ok(())
}

View File

@@ -1,40 +1,49 @@
use super::super::common::get_base_reward;
use super::validator_statuses::{TotalBalances, ValidatorStatus, ValidatorStatuses};
use super::Error;
use crate::common::{base::get_base_reward, decrease_balance, increase_balance};
use crate::per_epoch_processing::validator_statuses::{
TotalBalances, ValidatorStatus, ValidatorStatuses,
};
use crate::per_epoch_processing::{Delta, Error};
use safe_arith::SafeArith;
use std::array::IntoIter as ArrayIter;
use types::{BeaconState, ChainSpec, EthSpec};
use types::*;
/// Use to track the changes to a validators balance.
/// Combination of several deltas for different components of an attestation reward.
///
/// Exists only for compatibility with EF rewards tests.
#[derive(Default, Clone)]
pub struct Delta {
rewards: u64,
penalties: u64,
pub struct AttestationDelta {
pub source_delta: Delta,
pub target_delta: Delta,
pub head_delta: Delta,
pub inclusion_delay_delta: Delta,
pub inactivity_penalty_delta: Delta,
}
impl Delta {
/// Reward the validator with the `reward`.
pub fn reward(&mut self, reward: u64) -> Result<(), Error> {
self.rewards = self.rewards.safe_add(reward)?;
Ok(())
}
/// Penalize the validator with the `penalty`.
pub fn penalize(&mut self, penalty: u64) -> Result<(), Error> {
self.penalties = self.penalties.safe_add(penalty)?;
Ok(())
}
/// Combine two deltas.
fn combine(&mut self, other: Delta) -> Result<(), Error> {
self.reward(other.rewards)?;
self.penalize(other.penalties)
impl AttestationDelta {
/// Flatten into a single delta.
pub fn flatten(self) -> Result<Delta, Error> {
let AttestationDelta {
source_delta,
target_delta,
head_delta,
inclusion_delay_delta,
inactivity_penalty_delta,
} = self;
let mut result = Delta::default();
for delta in ArrayIter::new([
source_delta,
target_delta,
head_delta,
inclusion_delay_delta,
inactivity_penalty_delta,
]) {
result.combine(delta)?;
}
Ok(result)
}
}
/// Apply attester and proposer rewards.
///
/// Spec v0.12.1
pub fn process_rewards_and_penalties<T: EthSpec>(
state: &mut BeaconState<T>,
validator_statuses: &mut ValidatorStatuses,
@@ -45,8 +54,8 @@ pub fn process_rewards_and_penalties<T: EthSpec>(
}
// Guard against an out-of-bounds during the validator balance update.
if validator_statuses.statuses.len() != state.balances.len()
|| validator_statuses.statuses.len() != state.validators.len()
if validator_statuses.statuses.len() != state.balances().len()
|| validator_statuses.statuses.len() != state.validators().len()
{
return Err(Error::ValidatorStatusesInconsistent);
}
@@ -55,28 +64,27 @@ pub fn process_rewards_and_penalties<T: EthSpec>(
// Apply the deltas, erroring on overflow above but not on overflow below (saturating at 0
// instead).
for (i, delta) in deltas.iter().enumerate() {
state.balances[i] = state.balances[i].safe_add(delta.rewards)?;
state.balances[i] = state.balances[i].saturating_sub(delta.penalties);
for (i, delta) in deltas.into_iter().enumerate() {
let combined_delta = delta.flatten()?;
increase_balance(state, i, combined_delta.rewards)?;
decrease_balance(state, i, combined_delta.penalties)?;
}
Ok(())
}
/// Apply rewards for participation in attestations during the previous epoch.
///
/// Spec v0.12.1
fn get_attestation_deltas<T: EthSpec>(
pub fn get_attestation_deltas<T: EthSpec>(
state: &BeaconState<T>,
validator_statuses: &ValidatorStatuses,
spec: &ChainSpec,
) -> Result<Vec<Delta>, Error> {
) -> Result<Vec<AttestationDelta>, Error> {
let finality_delay = state
.previous_epoch()
.safe_sub(state.finalized_checkpoint.epoch)?
.safe_sub(state.finalized_checkpoint().epoch)?
.as_u64();
let mut deltas = vec![Delta::default(); state.validators.len()];
let mut deltas = vec![AttestationDelta::default(); state.validators().len()];
let total_balances = &validator_statuses.total_balances;
@@ -102,18 +110,23 @@ fn get_attestation_deltas<T: EthSpec>(
let inactivity_penalty_delta =
get_inactivity_penalty_delta(validator, base_reward, finality_delay, spec)?;
deltas[index].combine(source_delta)?;
deltas[index].combine(target_delta)?;
deltas[index].combine(head_delta)?;
deltas[index].combine(inclusion_delay_delta)?;
deltas[index].combine(inactivity_penalty_delta)?;
let delta = deltas
.get_mut(index)
.ok_or(Error::DeltaOutOfBounds(index))?;
delta.source_delta.combine(source_delta)?;
delta.target_delta.combine(target_delta)?;
delta.head_delta.combine(head_delta)?;
delta.inclusion_delay_delta.combine(inclusion_delay_delta)?;
delta
.inactivity_penalty_delta
.combine(inactivity_penalty_delta)?;
if let Some((proposer_index, proposer_delta)) = proposer_delta {
if proposer_index >= deltas.len() {
return Err(Error::ValidatorStatusesInconsistent);
}
deltas[proposer_index].combine(proposer_delta)?;
deltas
.get_mut(proposer_index)
.ok_or(Error::ValidatorStatusesInconsistent)?
.inclusion_delay_delta
.combine(proposer_delta)?;
}
}
@@ -219,7 +232,6 @@ fn get_inclusion_delay_delta(
let proposer_reward = get_proposer_reward(base_reward, spec)?;
proposer_delta.reward(proposer_reward)?;
let max_attester_reward = base_reward.safe_sub(proposer_reward)?;
delta.reward(max_attester_reward.safe_div(inclusion_info.delay)?)?;

View File

@@ -0,0 +1,33 @@
use super::errors::EpochProcessingError;
use safe_arith::SafeArith;
use types::beacon_state::BeaconState;
use types::chain_spec::ChainSpec;
use types::{BeaconStateError, EthSpec};
pub fn process_effective_balance_updates<T: EthSpec>(
state: &mut BeaconState<T>,
spec: &ChainSpec,
) -> Result<(), EpochProcessingError> {
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) = state.validators_and_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
|| 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,
);
}
}
Ok(())
}

View File

@@ -1,4 +1,4 @@
use types::*;
use types::{BeaconStateError, InconsistentFork};
#[derive(Debug, PartialEq)]
pub enum EpochProcessingError {
@@ -10,6 +10,7 @@ pub enum EpochProcessingError {
InclusionDistanceZero,
ValidatorStatusesInconsistent,
DeltasInconsistent,
DeltaOutOfBounds(usize),
/// Unable to get the inclusion distance for a validator that should have an inclusion
/// distance. This indicates an internal inconsistency.
///
@@ -19,6 +20,9 @@ pub enum EpochProcessingError {
InclusionError(InclusionError),
SszTypesError(ssz_types::Error),
ArithError(safe_arith::ArithError),
InconsistentStateFork(InconsistentFork),
InvalidJustificationBit(ssz_types::Error),
InvalidFlagIndex(usize),
}
impl From<InclusionError> for EpochProcessingError {

View File

@@ -0,0 +1,25 @@
use super::errors::EpochProcessingError;
use core::result::Result;
use core::result::Result::Ok;
use safe_arith::SafeArith;
use tree_hash::TreeHash;
use types::beacon_state::BeaconState;
use types::eth_spec::EthSpec;
use types::Unsigned;
pub fn process_historical_roots_update<T: EthSpec>(
state: &mut BeaconState<T>,
) -> Result<(), EpochProcessingError> {
let next_epoch = state.next_epoch()?;
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_mut()
.push(historical_batch.tree_hash_root())?;
}
Ok(())
}

View File

@@ -1,11 +1,11 @@
use crate::{common::initiate_validator_exit, per_epoch_processing::Error};
use itertools::Itertools;
use safe_arith::SafeArith;
use types::*;
use types::{BeaconState, ChainSpec, EthSpec, Validator};
/// Performs a validator registry update, if required.
///
/// Spec v0.12.1
/// NOTE: unchanged in Altair
pub fn process_registry_updates<T: EthSpec>(
state: &mut BeaconState<T>,
spec: &ChainSpec,
@@ -20,7 +20,7 @@ pub fn process_registry_updates<T: EthSpec>(
&& validator.effective_balance <= spec.ejection_balance
};
let indices_to_update: Vec<_> = state
.validators
.validators()
.iter()
.enumerate()
.filter(|(_, validator)| {
@@ -30,17 +30,18 @@ pub fn process_registry_updates<T: EthSpec>(
.collect();
for index in indices_to_update {
if state.validators[index].is_eligible_for_activation_queue(spec) {
state.validators[index].activation_eligibility_epoch = current_epoch.safe_add(1)?;
let validator = state.get_validator_mut(index)?;
if validator.is_eligible_for_activation_queue(spec) {
validator.activation_eligibility_epoch = current_epoch.safe_add(1)?;
}
if is_ejectable(&state.validators[index]) {
if is_ejectable(validator) {
initiate_validator_exit(state, index, spec)?;
}
}
// Queue validators eligible for activation and not dequeued for activation prior to finalized epoch
let activation_queue = state
.validators
.validators()
.iter()
.enumerate()
.filter(|(_, validator)| validator.is_eligible_for_activation(state, spec))
@@ -52,8 +53,7 @@ pub fn process_registry_updates<T: EthSpec>(
let churn_limit = state.get_churn_limit(spec)? as usize;
let delayed_activation_epoch = state.compute_activation_exit_epoch(current_epoch, spec)?;
for index in activation_queue.into_iter().take(churn_limit) {
let validator = &mut state.validators[index];
validator.activation_epoch = delayed_activation_epoch;
state.get_validator_mut(index)?.activation_epoch = delayed_activation_epoch;
}
Ok(())

View File

@@ -0,0 +1,38 @@
use super::errors::EpochProcessingError;
use core::result::Result;
use core::result::Result::Ok;
use safe_arith::SafeArith;
use types::beacon_state::BeaconState;
use types::eth_spec::EthSpec;
use types::{Unsigned, VariableList};
pub fn process_eth1_data_reset<T: EthSpec>(
state: &mut BeaconState<T>,
) -> Result<(), EpochProcessingError> {
if state
.slot()
.safe_add(1)?
.safe_rem(T::SlotsPerEth1VotingPeriod::to_u64())?
== 0
{
*state.eth1_data_votes_mut() = VariableList::empty();
}
Ok(())
}
pub fn process_slashings_reset<T: EthSpec>(
state: &mut BeaconState<T>,
) -> Result<(), EpochProcessingError> {
let next_epoch = state.next_epoch()?;
state.set_slashings(next_epoch, 0)?;
Ok(())
}
pub fn process_randao_mixes_reset<T: EthSpec>(
state: &mut BeaconState<T>,
) -> Result<(), EpochProcessingError> {
let current_epoch = state.current_epoch();
let next_epoch = state.next_epoch()?;
state.set_randao_mix(next_epoch, *state.get_randao_mix(current_epoch)?)?;
Ok(())
}

View File

@@ -1,22 +1,22 @@
use crate::per_epoch_processing::Error;
use safe_arith::{SafeArith, SafeArithIter};
use types::{BeaconStateError as Error, *};
use types::{BeaconState, BeaconStateError, ChainSpec, EthSpec, Unsigned};
/// Process slashings.
///
/// Spec v0.12.1
pub fn process_slashings<T: EthSpec>(
state: &mut BeaconState<T>,
total_balance: u64,
slashing_multiplier: u64,
spec: &ChainSpec,
) -> Result<(), Error> {
let epoch = state.current_epoch();
let sum_slashings = state.get_all_slashings().iter().copied().safe_sum()?;
let adjusted_total_slashing_balance = std::cmp::min(
sum_slashings.safe_mul(spec.proportional_slashing_multiplier)?,
total_balance,
);
for (index, validator) in state.validators.iter().enumerate() {
let adjusted_total_slashing_balance =
std::cmp::min(sum_slashings.safe_mul(slashing_multiplier)?, total_balance);
let (validators, balances) = state.validators_and_balances_mut();
for (index, validator) in validators.iter().enumerate() {
if validator.slashed
&& epoch.safe_add(T::EpochsPerSlashingsVector::to_u64().safe_div(2)?)?
== validator.withdrawable_epoch
@@ -31,7 +31,10 @@ pub fn process_slashings<T: EthSpec>(
.safe_mul(increment)?;
// Equivalent to `decrease_balance(state, index, penalty)`, but avoids borrowing `state`.
state.balances[index] = state.balances[index].saturating_sub(penalty);
let balance = balances
.get_mut(index)
.ok_or(BeaconStateError::BalancesOutOfBounds(index))?;
*balance = balance.saturating_sub(penalty);
}
}

View File

@@ -1,23 +1,165 @@
#![cfg(test)]
use crate::per_epoch_processing::per_epoch_processing;
use crate::per_epoch_processing::process_epoch;
use beacon_chain::store::StoreConfig;
use beacon_chain::test_utils::BeaconChainHarness;
use beacon_chain::types::{EthSpec, MinimalEthSpec};
use bls::Hash256;
use env_logger::{Builder, Env};
use types::test_utils::TestingBeaconStateBuilder;
use types::*;
use types::Slot;
#[test]
fn runs_without_error() {
Builder::from_env(Env::default().default_filter_or("error")).init();
let harness = BeaconChainHarness::new_with_store_config(
MinimalEthSpec,
None,
types::test_utils::generate_deterministic_keypairs(8),
StoreConfig::default(),
);
harness.advance_slot();
let spec = MinimalEthSpec::default_spec();
let mut builder: TestingBeaconStateBuilder<MinimalEthSpec> =
TestingBeaconStateBuilder::from_deterministic_keypairs(8, &spec);
let target_slot =
(MinimalEthSpec::genesis_epoch() + 4).end_slot(MinimalEthSpec::slots_per_epoch());
builder.teleport_to_slot(target_slot);
let (mut state, _keypairs) = builder.build();
let state = harness.get_current_state();
harness.add_attested_blocks_at_slots(
state,
Hash256::zero(),
(1..target_slot.as_u64())
.map(Slot::new)
.collect::<Vec<_>>()
.as_slice(),
(0..8).collect::<Vec<_>>().as_slice(),
);
let mut new_head_state = harness.get_current_state();
per_epoch_processing(&mut state, &spec).unwrap();
process_epoch(&mut new_head_state, &spec).unwrap();
}
#[cfg(not(debug_assertions))]
mod release_tests {
use super::*;
use crate::{
per_slot_processing::per_slot_processing, EpochProcessingError, SlotProcessingError,
};
use beacon_chain::test_utils::{AttestationStrategy, BlockStrategy};
use types::{Epoch, ForkName, InconsistentFork, MainnetEthSpec};
#[test]
fn altair_state_on_base_fork() {
let mut spec = MainnetEthSpec::default_spec();
let slots_per_epoch = MainnetEthSpec::slots_per_epoch();
// The Altair fork happens at epoch 1.
spec.altair_fork_epoch = Some(Epoch::new(1));
let altair_state = {
let harness = BeaconChainHarness::new(
MainnetEthSpec,
Some(spec.clone()),
types::test_utils::generate_deterministic_keypairs(8),
);
harness.advance_slot();
harness.extend_chain(
// Build out enough blocks so we get an Altair block at the very end of an epoch.
(slots_per_epoch * 2 - 1) as usize,
BlockStrategy::OnCanonicalHead,
AttestationStrategy::AllValidators,
);
harness.get_current_state()
};
// Pre-conditions for a valid test.
assert_eq!(altair_state.fork_name(&spec).unwrap(), ForkName::Altair);
assert_eq!(
altair_state.slot(),
altair_state.current_epoch().end_slot(slots_per_epoch)
);
// Check the state is valid before starting this test.
process_epoch(&mut altair_state.clone(), &spec)
.expect("state passes intial epoch processing");
per_slot_processing(&mut altair_state.clone(), None, &spec)
.expect("state passes intial slot processing");
// Modify the spec so altair never happens.
spec.altair_fork_epoch = None;
let expected_err = InconsistentFork {
fork_at_slot: ForkName::Base,
object_fork: ForkName::Altair,
};
assert_eq!(altair_state.fork_name(&spec), Err(expected_err));
assert_eq!(
process_epoch(&mut altair_state.clone(), &spec),
Err(EpochProcessingError::InconsistentStateFork(expected_err))
);
assert_eq!(
per_slot_processing(&mut altair_state.clone(), None, &spec),
Err(SlotProcessingError::InconsistentStateFork(expected_err))
);
}
#[test]
fn base_state_on_altair_fork() {
let mut spec = MainnetEthSpec::default_spec();
let slots_per_epoch = MainnetEthSpec::slots_per_epoch();
// The Altair fork never happens.
spec.altair_fork_epoch = None;
let base_state = {
let harness = BeaconChainHarness::new(
MainnetEthSpec,
Some(spec.clone()),
types::test_utils::generate_deterministic_keypairs(8),
);
harness.advance_slot();
harness.extend_chain(
// Build out enough blocks so we get a block at the very end of an epoch.
(slots_per_epoch * 2 - 1) as usize,
BlockStrategy::OnCanonicalHead,
AttestationStrategy::AllValidators,
);
harness.get_current_state()
};
// Pre-conditions for a valid test.
assert_eq!(base_state.fork_name(&spec).unwrap(), ForkName::Base);
assert_eq!(
base_state.slot(),
base_state.current_epoch().end_slot(slots_per_epoch)
);
// Check the state is valid before starting this test.
process_epoch(&mut base_state.clone(), &spec)
.expect("state passes intial epoch processing");
per_slot_processing(&mut base_state.clone(), None, &spec)
.expect("state passes intial slot processing");
// Modify the spec so Altair happens at the first epoch.
spec.altair_fork_epoch = Some(Epoch::new(1));
let expected_err = InconsistentFork {
fork_at_slot: ForkName::Altair,
object_fork: ForkName::Base,
};
assert_eq!(base_state.fork_name(&spec), Err(expected_err));
assert_eq!(
process_epoch(&mut base_state.clone(), &spec),
Err(EpochProcessingError::InconsistentStateFork(expected_err))
);
assert_eq!(
per_slot_processing(&mut base_state.clone(), None, &spec),
Err(SlotProcessingError::InconsistentStateFork(expected_err))
);
}
}

View File

@@ -1,6 +1,6 @@
use crate::common::get_attesting_indices;
use safe_arith::SafeArith;
use types::*;
use types::{BeaconState, BeaconStateError, ChainSpec, Epoch, EthSpec, PendingAttestation};
#[cfg(feature = "arbitrary-fuzz")]
use arbitrary::Arbitrary;
@@ -17,7 +17,7 @@ macro_rules! set_self_if_other_is_true {
/// The information required to reward a block producer for including an attestation in a block.
#[cfg_attr(feature = "arbitrary-fuzz", derive(Arbitrary))]
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct InclusionInfo {
/// The distance between the attestation slot and the slot that attestation was included in a
/// block.
@@ -49,7 +49,7 @@ impl InclusionInfo {
/// Information required to reward some validator during the current and previous epoch.
#[cfg_attr(feature = "arbitrary-fuzz", derive(Arbitrary))]
#[derive(Debug, Default, Clone)]
#[derive(Debug, Default, Clone, PartialEq)]
pub struct ValidatorStatus {
/// True if the validator has been slashed, ever.
pub is_slashed: bool,
@@ -114,7 +114,7 @@ impl ValidatorStatus {
/// The total effective balances for different sets of validators during the previous and current
/// epochs.
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "arbitrary-fuzz", derive(Arbitrary))]
pub struct TotalBalances {
/// The effective balance increment from the spec.
@@ -192,11 +192,11 @@ impl ValidatorStatuses {
state: &BeaconState<T>,
spec: &ChainSpec,
) -> Result<Self, BeaconStateError> {
let mut statuses = Vec::with_capacity(state.validators.len());
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, spec)?;
for (i, validator) in state.validators().iter().enumerate() {
let effective_balance = state.get_effective_balance(i)?;
let mut status = ValidatorStatus {
is_slashed: validator.slashed,
is_withdrawable_in_current_epoch: validator
@@ -235,12 +235,12 @@ impl ValidatorStatuses {
pub fn process_attestations<T: EthSpec>(
&mut self,
state: &BeaconState<T>,
spec: &ChainSpec,
) -> Result<(), BeaconStateError> {
for a in state
let base_state = state.as_base()?;
for a in base_state
.previous_epoch_attestations
.iter()
.chain(state.current_epoch_attestations.iter())
.chain(base_state.current_epoch_attestations.iter())
{
let committee = state.get_beacon_committee(a.data.slot, a.data.index)?;
let attesting_indices =
@@ -277,7 +277,10 @@ impl ValidatorStatuses {
// Loop through the participating validator indices and update the status vec.
for validator_index in attesting_indices {
self.statuses[validator_index].update(&status);
self.statuses
.get_mut(validator_index)
.ok_or(BeaconStateError::UnknownValidator(validator_index))?
.update(&status);
}
}
@@ -285,7 +288,7 @@ impl ValidatorStatuses {
for (index, v) in self.statuses.iter().enumerate() {
// According to the spec, we only count unslashed validators towards the totals.
if !v.is_slashed {
let validator_balance = state.get_effective_balance(index, spec)?;
let validator_balance = state.get_effective_balance(index)?;
if v.is_current_epoch_attester {
self.total_balances

View File

@@ -0,0 +1,70 @@
use crate::per_epoch_processing::Error;
use safe_arith::SafeArith;
use std::ops::Range;
use types::{BeaconState, Checkpoint, EthSpec};
/// Update the justified and finalized checkpoints for matching target attestations.
#[allow(clippy::if_same_then_else)] // For readability and consistency with spec.
pub fn weigh_justification_and_finalization<T: EthSpec>(
state: &mut BeaconState<T>,
total_active_balance: u64,
previous_target_balance: u64,
current_target_balance: u64,
) -> Result<(), Error> {
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_mut() = state.current_justified_checkpoint();
state.justification_bits_mut().shift_up(1)?;
if previous_target_balance.safe_mul(3)? >= total_active_balance.safe_mul(2)? {
*state.current_justified_checkpoint_mut() = Checkpoint {
epoch: previous_epoch,
root: *state.get_block_root_at_epoch(previous_epoch)?,
};
state.justification_bits_mut().set(1, true)?;
}
// If the current epoch gets justified, fill the last bit.
if current_target_balance.safe_mul(3)? >= total_active_balance.safe_mul(2)? {
*state.current_justified_checkpoint_mut() = Checkpoint {
epoch: current_epoch,
root: *state.get_block_root_at_epoch(current_epoch)?,
};
state.justification_bits_mut().set(0, true)?;
}
let bits = state.justification_bits().clone();
let all_bits_set = |range: Range<usize>| -> Result<bool, Error> {
for i in range {
if !bits.get(i).map_err(Error::InvalidJustificationBit)? {
return Ok(false);
}
}
Ok(true)
};
// The 2nd/3rd/4th most recent epochs are all justified, the 2nd using the 4th as source.
if all_bits_set(1..4)? && old_previous_justified_checkpoint.epoch.safe_add(3)? == current_epoch
{
*state.finalized_checkpoint_mut() = old_previous_justified_checkpoint;
}
// The 2nd/3rd most recent epochs are both justified, the 2nd using the 3rd as source.
if all_bits_set(1..3)? && old_previous_justified_checkpoint.epoch.safe_add(2)? == current_epoch
{
*state.finalized_checkpoint_mut() = old_previous_justified_checkpoint;
}
// The 1st/2nd/3rd most recent epochs are all justified, the 1st using the 3nd as source.
if all_bits_set(0..3)? && old_current_justified_checkpoint.epoch.safe_add(2)? == current_epoch {
*state.finalized_checkpoint_mut() = old_current_justified_checkpoint;
}
// The 1st/2nd most recent epochs are both justified, the 1st using the 2nd as source.
if all_bits_set(0..2)? && old_current_justified_checkpoint.epoch.safe_add(1)? == current_epoch {
*state.finalized_checkpoint_mut() = old_current_justified_checkpoint;
}
Ok(())
}