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,32 @@
use integer_sqrt::IntegerSquareRoot;
use safe_arith::{ArithError, SafeArith};
use types::*;
/// Returns the base reward for some validator.
///
/// Spec v1.1.0
pub fn get_base_reward<T: EthSpec>(
state: &BeaconState<T>,
index: usize,
// Should be == get_total_active_balance(state, spec)
total_active_balance: u64,
spec: &ChainSpec,
) -> Result<u64, Error> {
state
.get_effective_balance(index)?
.safe_div(spec.effective_balance_increment)?
.safe_mul(get_base_reward_per_increment(total_active_balance, spec)?)
.map_err(Into::into)
}
/// Returns the base reward for some validator.
///
/// Spec v1.1.0
pub fn get_base_reward_per_increment(
total_active_balance: u64,
spec: &ChainSpec,
) -> Result<u64, ArithError> {
spec.effective_balance_increment
.safe_mul(spec.base_reward_factor)?
.safe_div(total_active_balance.integer_sqrt())
}

View File

@@ -3,8 +3,6 @@ use safe_arith::SafeArith;
use types::*;
/// Returns the base reward for some validator.
///
/// Spec v0.12.1
pub fn get_base_reward<T: EthSpec>(
state: &BeaconState<T>,
index: usize,
@@ -12,13 +10,10 @@ pub fn get_base_reward<T: EthSpec>(
total_active_balance: u64,
spec: &ChainSpec,
) -> Result<u64, BeaconStateError> {
if total_active_balance == 0 {
Ok(0)
} else {
Ok(state
.get_effective_balance(index, spec)?
.safe_mul(spec.base_reward_factor)?
.safe_div(total_active_balance.integer_sqrt())?
.safe_div(spec.base_rewards_per_epoch)?)
}
state
.get_effective_balance(index)?
.safe_mul(spec.base_reward_factor)?
.safe_div(total_active_balance.integer_sqrt())?
.safe_div(spec.base_rewards_per_epoch)
.map_err(Into::into)
}

View File

@@ -0,0 +1,54 @@
use integer_sqrt::IntegerSquareRoot;
use smallvec::SmallVec;
use types::{
consts::altair::{
NUM_FLAG_INDICES, TIMELY_HEAD_FLAG_INDEX, TIMELY_SOURCE_FLAG_INDEX,
TIMELY_TARGET_FLAG_INDEX,
},
BeaconStateError as Error,
};
use types::{AttestationData, BeaconState, ChainSpec, EthSpec};
/// Get the participation flags for a valid attestation.
///
/// You should have called `verify_attestation_for_block_inclusion` or similar before
/// calling this function, in order to ensure that the attestation's source is correct.
///
/// This function will return an error if the source of the attestation doesn't match the
/// state's relevant justified checkpoint.
pub fn get_attestation_participation_flag_indices<T: EthSpec>(
state: &BeaconState<T>,
data: &AttestationData,
inclusion_delay: u64,
spec: &ChainSpec,
) -> Result<SmallVec<[usize; NUM_FLAG_INDICES]>, Error> {
let justified_checkpoint = if data.target.epoch == state.current_epoch() {
state.current_justified_checkpoint()
} else {
state.previous_justified_checkpoint()
};
// Matching roots.
let is_matching_source = data.source == justified_checkpoint;
let is_matching_target = is_matching_source
&& data.target.root == *state.get_block_root_at_epoch(data.target.epoch)?;
let is_matching_head =
is_matching_target && data.beacon_block_root == *state.get_block_root(data.slot)?;
if !is_matching_source {
return Err(Error::IncorrectAttestationSource);
}
// Participation flag indices
let mut participation_flag_indices = SmallVec::new();
if is_matching_source && inclusion_delay <= T::slots_per_epoch().integer_sqrt() {
participation_flag_indices.push(TIMELY_SOURCE_FLAG_INDEX);
}
if is_matching_target && inclusion_delay <= T::slots_per_epoch() {
participation_flag_indices.push(TIMELY_TARGET_FLAG_INDEX);
}
if is_matching_head && inclusion_delay == spec.min_attestation_inclusion_delay {
participation_flag_indices.push(TIMELY_HEAD_FLAG_INDEX);
}
Ok(participation_flag_indices)
}

View File

@@ -3,40 +3,36 @@ use std::cmp::max;
use types::{BeaconStateError as Error, *};
/// Initiate the exit of the validator of the given `index`.
///
/// Spec v0.12.1
pub fn initiate_validator_exit<T: EthSpec>(
state: &mut BeaconState<T>,
index: usize,
spec: &ChainSpec,
) -> Result<(), Error> {
if index >= state.validators.len() {
return Err(Error::UnknownValidator(index as u64));
}
// Return if the validator already initiated exit
if state.validators[index].exit_epoch != spec.far_future_epoch {
if state.get_validator(index)?.exit_epoch != spec.far_future_epoch {
return Ok(());
}
// Ensure the exit cache is built.
state.exit_cache.build(&state.validators, spec)?;
state.build_exit_cache(spec)?;
// Compute exit queue epoch
let delayed_epoch = state.compute_activation_exit_epoch(state.current_epoch(), spec)?;
let mut exit_queue_epoch = state
.exit_cache
.exit_cache()
.max_epoch()?
.map_or(delayed_epoch, |epoch| max(epoch, delayed_epoch));
let exit_queue_churn = state.exit_cache.get_churn_at(exit_queue_epoch)?;
let exit_queue_churn = state.exit_cache().get_churn_at(exit_queue_epoch)?;
if exit_queue_churn >= state.get_churn_limit(spec)? {
exit_queue_epoch.safe_add_assign(1)?;
}
state.exit_cache.record_validator_exit(exit_queue_epoch)?;
state.validators[index].exit_epoch = exit_queue_epoch;
state.validators[index].withdrawable_epoch =
state
.exit_cache_mut()
.record_validator_exit(exit_queue_epoch)?;
state.get_validator_mut(index)?.exit_epoch = exit_queue_epoch;
state.get_validator_mut(index)?.withdrawable_epoch =
exit_queue_epoch.safe_add(spec.min_validator_withdrawability_delay)?;
Ok(())

View File

@@ -1,34 +1,40 @@
mod deposit_data_tree;
mod get_attestation_participation;
mod get_attesting_indices;
mod get_base_reward;
mod get_indexed_attestation;
mod initiate_validator_exit;
mod slash_validator;
pub mod altair;
pub mod base;
pub use deposit_data_tree::DepositDataTree;
pub use get_attestation_participation::get_attestation_participation_flag_indices;
pub use get_attesting_indices::get_attesting_indices;
pub use get_base_reward::get_base_reward;
pub use get_indexed_attestation::get_indexed_attestation;
pub use initiate_validator_exit::initiate_validator_exit;
pub use slash_validator::slash_validator;
use safe_arith::{ArithError, SafeArith};
use types::{BeaconState, EthSpec};
use safe_arith::SafeArith;
use types::{BeaconState, BeaconStateError, EthSpec};
/// Increase the balance of a validator, erroring upon overflow, as per the spec.
///
/// Spec v0.12.1
pub fn increase_balance<E: EthSpec>(
state: &mut BeaconState<E>,
index: usize,
delta: u64,
) -> Result<(), ArithError> {
state.balances[index].safe_add_assign(delta)
) -> Result<(), BeaconStateError> {
state.get_balance_mut(index)?.safe_add_assign(delta)?;
Ok(())
}
/// Decrease the balance of a validator, saturating upon overflow, as per the spec.
///
/// Spec v0.12.1
pub fn decrease_balance<E: EthSpec>(state: &mut BeaconState<E>, index: usize, delta: u64) {
state.balances[index] = state.balances[index].saturating_sub(delta);
pub fn decrease_balance<E: EthSpec>(
state: &mut BeaconState<E>,
index: usize,
delta: u64,
) -> Result<(), BeaconStateError> {
let balance = state.get_balance_mut(index)?;
*balance = balance.saturating_sub(delta);
Ok(())
}

View File

@@ -1,55 +1,61 @@
use crate::common::{decrease_balance, increase_balance, initiate_validator_exit};
use safe_arith::SafeArith;
use std::cmp;
use types::{BeaconStateError as Error, *};
use types::{
consts::altair::{PROPOSER_WEIGHT, WEIGHT_DENOMINATOR},
BeaconStateError as Error, *,
};
/// Slash the validator with index ``index``.
///
/// Spec v0.12.1
/// Slash the validator with index `slashed_index`.
pub fn slash_validator<T: EthSpec>(
state: &mut BeaconState<T>,
slashed_index: usize,
opt_whistleblower_index: Option<usize>,
spec: &ChainSpec,
) -> Result<(), Error> {
if slashed_index >= state.validators.len() || slashed_index >= state.balances.len() {
return Err(BeaconStateError::UnknownValidator(slashed_index as u64));
}
let epoch = state.current_epoch();
initiate_validator_exit(state, slashed_index, spec)?;
state.validators[slashed_index].slashed = true;
state.validators[slashed_index].withdrawable_epoch = cmp::max(
state.validators[slashed_index].withdrawable_epoch,
let validator = state.get_validator_mut(slashed_index)?;
validator.slashed = true;
validator.withdrawable_epoch = cmp::max(
validator.withdrawable_epoch,
epoch.safe_add(T::EpochsPerSlashingsVector::to_u64())?,
);
let validator_effective_balance = state.get_effective_balance(slashed_index, spec)?;
let validator_effective_balance = validator.effective_balance;
state.set_slashings(
epoch,
state
.get_slashings(epoch)?
.safe_add(validator_effective_balance)?,
)?;
let min_slashing_penalty_quotient = match state {
BeaconState::Base(_) => spec.min_slashing_penalty_quotient,
BeaconState::Altair(_) => spec.min_slashing_penalty_quotient_altair,
};
decrease_balance(
state,
slashed_index,
validator_effective_balance.safe_div(spec.min_slashing_penalty_quotient)?,
);
validator_effective_balance.safe_div(min_slashing_penalty_quotient)?,
)?;
// Apply proposer and whistleblower rewards
let proposer_index = state.get_beacon_proposer_index(state.slot, spec)?;
let proposer_index = state.get_beacon_proposer_index(state.slot(), spec)?;
let whistleblower_index = opt_whistleblower_index.unwrap_or(proposer_index);
let whistleblower_reward =
validator_effective_balance.safe_div(spec.whistleblower_reward_quotient)?;
let proposer_reward = whistleblower_reward.safe_div(spec.proposer_reward_quotient)?;
let proposer_reward = match state {
BeaconState::Base(_) => whistleblower_reward.safe_div(spec.proposer_reward_quotient)?,
BeaconState::Altair(_) => whistleblower_reward
.safe_mul(PROPOSER_WEIGHT)?
.safe_div(WEIGHT_DENOMINATOR)?,
};
// Ensure the whistleblower index is in the validator registry.
if state.validators.get(whistleblower_index).is_none() {
return Err(BeaconStateError::UnknownValidator(
whistleblower_index as u64,
));
if state.validators().get(whistleblower_index).is_none() {
return Err(BeaconStateError::UnknownValidator(whistleblower_index));
}
increase_balance(state, proposer_index, proposer_reward)?;