mirror of
https://github.com/sigp/lighthouse.git
synced 2026-05-31 05:07:12 +00:00
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:
@@ -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,
|
||||
})
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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(¤t_indices, spec)?;
|
||||
weigh_justification_and_finalization(
|
||||
state,
|
||||
total_active_balance,
|
||||
previous_target_balance,
|
||||
current_target_balance,
|
||||
)
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
76
consensus/state_processing/src/per_epoch_processing/base.rs
Normal file
76
consensus/state_processing/src/per_epoch_processing/base.rs
Normal 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,
|
||||
})
|
||||
}
|
||||
@@ -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(),
|
||||
)
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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)?)?;
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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(())
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
Reference in New Issue
Block a user