mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-16 19:32:55 +00:00
Implement tree states & hierarchical state DB
This commit is contained in:
@@ -1,7 +1,5 @@
|
||||
use super::ParticipationCache;
|
||||
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;
|
||||
@@ -11,7 +9,7 @@ use types::eth_spec::EthSpec;
|
||||
|
||||
pub fn process_inactivity_updates<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
participation_cache: &ParticipationCache,
|
||||
participation_cache: &mut ParticipationCache,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), EpochProcessingError> {
|
||||
let previous_epoch = state.previous_epoch();
|
||||
@@ -20,24 +18,50 @@ pub fn process_inactivity_updates<T: EthSpec>(
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let unslashed_indices = participation_cache
|
||||
.get_unslashed_participating_indices(TIMELY_TARGET_FLAG_INDEX, state.previous_epoch())?;
|
||||
// Fast path: inactivity scores have already been pre-computed.
|
||||
if let Some(inactivity_score_updates) = participation_cache.inactivity_score_updates.take() {
|
||||
// We need to flush the existing inactivity scores in case tree hashing hasn't happened in
|
||||
// a long time (e.g. during state reconstruction).
|
||||
// FIXME(sproul): re-think this
|
||||
state.inactivity_scores_mut()?.apply_updates()?;
|
||||
state
|
||||
.inactivity_scores_mut()?
|
||||
.bulk_update(inactivity_score_updates)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let is_in_inactivity_leak = state.is_in_inactivity_leak(previous_epoch, spec);
|
||||
|
||||
let mut inactivity_scores = state.inactivity_scores_mut()?.iter_cow();
|
||||
|
||||
while let Some((index, inactivity_score)) = inactivity_scores.next_cow() {
|
||||
let validator = match participation_cache.get_validator(index) {
|
||||
Ok(val) if val.is_eligible => val,
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
let inactivity_score_mut;
|
||||
|
||||
for &index in participation_cache.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))?;
|
||||
if validator.is_unslashed_participating_index(TIMELY_TARGET_FLAG_INDEX)? {
|
||||
// Avoid mutating when the inactivity score is 0 and can't go any lower -- the common
|
||||
// case.
|
||||
if *inactivity_score == 0 {
|
||||
continue;
|
||||
}
|
||||
inactivity_score_mut = inactivity_score.to_mut();
|
||||
inactivity_score_mut.safe_sub_assign(1)?;
|
||||
} else {
|
||||
state
|
||||
.get_inactivity_score_mut(index)?
|
||||
.safe_add_assign(spec.inactivity_score_bias)?;
|
||||
inactivity_score_mut = inactivity_score.to_mut();
|
||||
inactivity_score_mut.safe_add_assign(spec.inactivity_score_bias)?;
|
||||
}
|
||||
|
||||
// Decrease the score of all validators for forgiveness when not during a leak
|
||||
if !state.is_in_inactivity_leak(previous_epoch, spec) {
|
||||
let inactivity_score = state.get_inactivity_score_mut(index)?;
|
||||
inactivity_score
|
||||
.safe_sub_assign(min(spec.inactivity_score_recovery_rate, *inactivity_score))?;
|
||||
if !is_in_inactivity_leak {
|
||||
inactivity_score_mut.safe_sub_assign(min(
|
||||
spec.inactivity_score_recovery_rate,
|
||||
*inactivity_score_mut,
|
||||
))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -4,7 +4,6 @@ use crate::per_epoch_processing::{
|
||||
weigh_justification_and_finalization, JustificationAndFinalizationState,
|
||||
};
|
||||
use safe_arith::SafeArith;
|
||||
use types::consts::altair::TIMELY_TARGET_FLAG_INDEX;
|
||||
use types::{BeaconState, EthSpec};
|
||||
|
||||
/// Update the justified and finalized checkpoints for matching target attestations.
|
||||
@@ -18,15 +17,9 @@ pub fn process_justification_and_finalization<T: EthSpec>(
|
||||
return Ok(justification_and_finalization_state);
|
||||
}
|
||||
|
||||
let previous_epoch = state.previous_epoch();
|
||||
let current_epoch = state.current_epoch();
|
||||
let previous_indices = participation_cache
|
||||
.get_unslashed_participating_indices(TIMELY_TARGET_FLAG_INDEX, previous_epoch)?;
|
||||
let current_indices = participation_cache
|
||||
.get_unslashed_participating_indices(TIMELY_TARGET_FLAG_INDEX, current_epoch)?;
|
||||
let total_active_balance = participation_cache.current_epoch_total_active_balance();
|
||||
let previous_target_balance = previous_indices.total_balance()?;
|
||||
let current_target_balance = current_indices.total_balance()?;
|
||||
let previous_target_balance = participation_cache.previous_epoch_target_attesting_balance()?;
|
||||
let current_target_balance = participation_cache.current_epoch_target_attesting_balance()?;
|
||||
weigh_justification_and_finalization(
|
||||
justification_and_finalization_state,
|
||||
total_active_balance,
|
||||
|
||||
@@ -11,19 +11,40 @@
|
||||
//! Additionally, this cache is returned from the `altair::process_epoch` function and can be used
|
||||
//! to get useful summaries about the validator participation in an epoch.
|
||||
|
||||
use crate::common::altair::{get_base_reward, BaseRewardPerIncrement};
|
||||
use safe_arith::{ArithError, SafeArith};
|
||||
use types::milhouse::update_map::{MaxMap, UpdateMap};
|
||||
use types::{
|
||||
consts::altair::{
|
||||
NUM_FLAG_INDICES, TIMELY_HEAD_FLAG_INDEX, TIMELY_SOURCE_FLAG_INDEX,
|
||||
TIMELY_TARGET_FLAG_INDEX,
|
||||
},
|
||||
BeaconState, BeaconStateError, ChainSpec, Epoch, EthSpec, ParticipationFlags, RelativeEpoch,
|
||||
Unsigned, Validator,
|
||||
};
|
||||
use vec_map::VecMap;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Error {
|
||||
InvalidFlagIndex(usize),
|
||||
NoUnslashedParticipatingIndices,
|
||||
MissingValidator(usize),
|
||||
BeaconState(BeaconStateError),
|
||||
Arith(ArithError),
|
||||
InvalidValidatorIndex(usize),
|
||||
InconsistentTotalActiveBalance { cached: u64, computed: u64 },
|
||||
}
|
||||
|
||||
impl From<BeaconStateError> for Error {
|
||||
fn from(e: BeaconStateError) -> Self {
|
||||
Self::BeaconState(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ArithError> for Error {
|
||||
fn from(e: ArithError) -> Self {
|
||||
Self::Arith(e)
|
||||
}
|
||||
}
|
||||
|
||||
/// A balance which will never be below the specified `minimum`.
|
||||
@@ -55,16 +76,6 @@ impl Balance {
|
||||
/// Caches the participation values for one epoch (either the previous or current).
|
||||
#[derive(PartialEq, Debug)]
|
||||
struct SingleEpochParticipationCache {
|
||||
/// Maps an active validator index to their participation flags.
|
||||
///
|
||||
/// To reiterate, only active and unslashed validator indices are stored in this map.
|
||||
///
|
||||
/// ## Note
|
||||
///
|
||||
/// It would be ideal to maintain a reference to the `BeaconState` here rather than copying the
|
||||
/// `ParticipationFlags`, however that would cause us to run into mutable reference limitations
|
||||
/// upstream.
|
||||
unslashed_participating_indices: Vec<Option<ParticipationFlags>>,
|
||||
/// Stores the sum of the balances for all validators in `self.unslashed_participating_indices`
|
||||
/// for all flags in `NUM_FLAG_INDICES`.
|
||||
///
|
||||
@@ -76,12 +87,10 @@ struct SingleEpochParticipationCache {
|
||||
}
|
||||
|
||||
impl SingleEpochParticipationCache {
|
||||
fn new<T: EthSpec>(state: &BeaconState<T>, spec: &ChainSpec) -> Self {
|
||||
let num_validators = state.validators().len();
|
||||
fn new(spec: &ChainSpec) -> Self {
|
||||
let zero_balance = Balance::zero(spec.effective_balance_increment);
|
||||
|
||||
Self {
|
||||
unslashed_participating_indices: vec![None; num_validators],
|
||||
total_flag_balances: [zero_balance; NUM_FLAG_INDICES],
|
||||
total_active_balance: zero_balance,
|
||||
}
|
||||
@@ -95,76 +104,41 @@ impl SingleEpochParticipationCache {
|
||||
.ok_or(Error::InvalidFlagIndex(flag_index))
|
||||
}
|
||||
|
||||
/// Returns `true` if `val_index` is active, unslashed and has `flag_index` set.
|
||||
///
|
||||
/// ## Errors
|
||||
///
|
||||
/// May return an error if `flag_index` is out-of-bounds.
|
||||
fn has_flag(&self, val_index: usize, flag_index: usize) -> Result<bool, Error> {
|
||||
let participation_flags = self
|
||||
.unslashed_participating_indices
|
||||
.get(val_index)
|
||||
.ok_or(Error::InvalidValidatorIndex(val_index))?;
|
||||
if let Some(participation_flags) = participation_flags {
|
||||
participation_flags
|
||||
.has_flag(flag_index)
|
||||
.map_err(|_| Error::InvalidFlagIndex(flag_index))
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
/// Process an **active** validator, reading from the `state` with respect to the
|
||||
/// Process an **active** validator, reading from the `epoch_participation` with respect to the
|
||||
/// `relative_epoch`.
|
||||
///
|
||||
/// ## Errors
|
||||
///
|
||||
/// - The provided `state` **must** be Altair. An error will be returned otherwise.
|
||||
/// - An error will be returned if the `val_index` validator is inactive at the given
|
||||
/// `relative_epoch`.
|
||||
fn process_active_validator<T: EthSpec>(
|
||||
fn process_active_validator(
|
||||
&mut self,
|
||||
val_index: usize,
|
||||
state: &BeaconState<T>,
|
||||
validator: &Validator,
|
||||
epoch_participation: &ParticipationFlags,
|
||||
current_epoch: Epoch,
|
||||
relative_epoch: RelativeEpoch,
|
||||
) -> Result<(), BeaconStateError> {
|
||||
let val_balance = state.get_effective_balance(val_index)?;
|
||||
let validator = state.get_validator(val_index)?;
|
||||
|
||||
// Sanity check to ensure the validator is active.
|
||||
let epoch = relative_epoch.into_epoch(current_epoch);
|
||||
if !validator.is_active_at(epoch) {
|
||||
return Err(BeaconStateError::ValidatorIsInactive { val_index });
|
||||
}
|
||||
|
||||
let epoch_participation = match relative_epoch {
|
||||
RelativeEpoch::Current => state.current_epoch_participation(),
|
||||
RelativeEpoch::Previous => state.previous_epoch_participation(),
|
||||
_ => Err(BeaconStateError::EpochOutOfBounds),
|
||||
}?
|
||||
.get(val_index)
|
||||
.ok_or(BeaconStateError::ParticipationOutOfBounds(val_index))?;
|
||||
|
||||
// All active validators increase the total active balance.
|
||||
self.total_active_balance.safe_add_assign(val_balance)?;
|
||||
self.total_active_balance
|
||||
.safe_add_assign(validator.effective_balance())?;
|
||||
|
||||
// Only unslashed validators may proceed.
|
||||
if validator.slashed {
|
||||
if validator.slashed() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Add their `ParticipationFlags` to the map.
|
||||
*self
|
||||
.unslashed_participating_indices
|
||||
.get_mut(val_index)
|
||||
.ok_or(BeaconStateError::UnknownValidator(val_index))? = Some(*epoch_participation);
|
||||
|
||||
// Iterate through all the flags and increment the total flag balances for whichever flags
|
||||
// are set for `val_index`.
|
||||
for (flag, balance) in self.total_flag_balances.iter_mut().enumerate() {
|
||||
if epoch_participation.has_flag(flag)? {
|
||||
balance.safe_add_assign(val_balance)?;
|
||||
balance.safe_add_assign(validator.effective_balance())?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,6 +146,43 @@ impl SingleEpochParticipationCache {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct ValidatorInfo {
|
||||
pub effective_balance: u64,
|
||||
pub base_reward: u64,
|
||||
pub is_eligible: bool,
|
||||
pub is_slashed: bool,
|
||||
pub is_active_current_epoch: bool,
|
||||
pub is_active_previous_epoch: bool,
|
||||
pub previous_epoch_participation: ParticipationFlags,
|
||||
}
|
||||
|
||||
impl ValidatorInfo {
|
||||
#[inline]
|
||||
pub fn is_unslashed_participating_index(&self, flag_index: usize) -> Result<bool, Error> {
|
||||
Ok(self.is_active_previous_epoch
|
||||
&& !self.is_slashed
|
||||
&& self
|
||||
.previous_epoch_participation
|
||||
.has_flag(flag_index)
|
||||
.map_err(|_| Error::InvalidFlagIndex(flag_index))?)
|
||||
}
|
||||
}
|
||||
|
||||
/// Single `HashMap` for validator info relevant to `process_epoch`.
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct ValidatorInfoCache {
|
||||
info: Vec<Option<ValidatorInfo>>,
|
||||
}
|
||||
|
||||
impl ValidatorInfoCache {
|
||||
pub fn new(capacity: usize) -> Self {
|
||||
Self {
|
||||
info: vec![None; capacity],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Maintains a cache to be used during `altair::process_epoch`.
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub struct ParticipationCache {
|
||||
@@ -181,8 +192,15 @@ pub struct ParticipationCache {
|
||||
previous_epoch: Epoch,
|
||||
/// Caches information about active validators pertaining to `self.previous_epoch`.
|
||||
previous_epoch_participation: SingleEpochParticipationCache,
|
||||
/// Caches validator information relevant to `process_epoch`.
|
||||
validators: ValidatorInfoCache,
|
||||
/// Caches the result of the `get_eligible_validator_indices` function.
|
||||
eligible_indices: Vec<usize>,
|
||||
/// Caches the indices and effective balances of validators that need to be processed by
|
||||
/// `process_slashings`.
|
||||
process_slashings_indices: Vec<(usize, u64)>,
|
||||
/// Updates to the inactivity scores if we are definitely not in an inactivity leak.
|
||||
pub inactivity_score_updates: Option<MaxMap<VecMap<u64>>>,
|
||||
}
|
||||
|
||||
impl ParticipationCache {
|
||||
@@ -191,17 +209,21 @@ impl ParticipationCache {
|
||||
/// ## Errors
|
||||
///
|
||||
/// - The provided `state` **must** be an Altair state. An error will be returned otherwise.
|
||||
pub fn new<T: EthSpec>(
|
||||
state: &BeaconState<T>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<Self, BeaconStateError> {
|
||||
pub fn new<T: EthSpec>(state: &BeaconState<T>, spec: &ChainSpec) -> Result<Self, Error> {
|
||||
let current_epoch = state.current_epoch();
|
||||
let previous_epoch = state.previous_epoch();
|
||||
|
||||
// Both the current/previous epoch participations are set to a capacity that is slightly
|
||||
// larger than required. The difference will be due slashed-but-active validators.
|
||||
let mut current_epoch_participation = SingleEpochParticipationCache::new(state, spec);
|
||||
let mut previous_epoch_participation = SingleEpochParticipationCache::new(state, spec);
|
||||
let mut current_epoch_participation = SingleEpochParticipationCache::new(spec);
|
||||
let mut previous_epoch_participation = SingleEpochParticipationCache::new(spec);
|
||||
|
||||
let mut validators = ValidatorInfoCache::new(state.validators().len());
|
||||
|
||||
let current_epoch_total_active_balance = state.get_total_active_balance()?;
|
||||
let base_reward_per_increment =
|
||||
BaseRewardPerIncrement::new(current_epoch_total_active_balance, spec)?;
|
||||
|
||||
// Contains the set of validators which are either:
|
||||
//
|
||||
// - Active in the previous epoch.
|
||||
@@ -211,6 +233,16 @@ impl ParticipationCache {
|
||||
// reallocations.
|
||||
let mut eligible_indices = Vec::with_capacity(state.validators().len());
|
||||
|
||||
let mut process_slashings_indices = vec![];
|
||||
|
||||
// Fast path for inactivity scores update when we are definitely not in an inactivity leak.
|
||||
// This breaks the dependence of `process_inactivity_updates` on the finalization
|
||||
// re-calculation.
|
||||
let definitely_not_in_inactivity_leak =
|
||||
state.finalized_checkpoint().epoch + spec.min_epochs_to_inactivity_penalty + 1
|
||||
>= state.current_epoch();
|
||||
let mut inactivity_score_updates = MaxMap::default();
|
||||
|
||||
// Iterate through all validators, updating:
|
||||
//
|
||||
// 1. Validator participation for current and previous epochs.
|
||||
@@ -218,30 +250,100 @@ impl ParticipationCache {
|
||||
//
|
||||
// Care is taken to ensure that the ordering of `eligible_indices` is the same as the
|
||||
// `get_eligible_validator_indices` function in the spec.
|
||||
for (val_index, val) in state.validators().iter().enumerate() {
|
||||
if val.is_active_at(current_epoch) {
|
||||
let iter = state
|
||||
.validators()
|
||||
.iter()
|
||||
.zip(state.current_epoch_participation()?)
|
||||
.zip(state.previous_epoch_participation()?)
|
||||
.zip(state.inactivity_scores()?)
|
||||
.enumerate();
|
||||
for (val_index, (((val, curr_epoch_flags), prev_epoch_flags), inactivity_score)) in iter {
|
||||
let is_active_current_epoch = val.is_active_at(current_epoch);
|
||||
let is_active_previous_epoch = val.is_active_at(previous_epoch);
|
||||
let is_eligible = state.is_eligible_validator(previous_epoch, val);
|
||||
|
||||
if is_active_current_epoch {
|
||||
current_epoch_participation.process_active_validator(
|
||||
val_index,
|
||||
state,
|
||||
val,
|
||||
curr_epoch_flags,
|
||||
current_epoch,
|
||||
RelativeEpoch::Current,
|
||||
)?;
|
||||
}
|
||||
|
||||
if val.is_active_at(previous_epoch) {
|
||||
if is_active_previous_epoch {
|
||||
assert!(is_eligible);
|
||||
|
||||
previous_epoch_participation.process_active_validator(
|
||||
val_index,
|
||||
state,
|
||||
val,
|
||||
prev_epoch_flags,
|
||||
current_epoch,
|
||||
RelativeEpoch::Previous,
|
||||
)?;
|
||||
}
|
||||
|
||||
// Note: a validator might still be "eligible" whilst returning `false` to
|
||||
// `Validator::is_active_at`.
|
||||
if state.is_eligible_validator(previous_epoch, val_index)? {
|
||||
eligible_indices.push(val_index)
|
||||
if val.slashed()
|
||||
&& current_epoch.safe_add(T::EpochsPerSlashingsVector::to_u64().safe_div(2)?)?
|
||||
== val.withdrawable_epoch()
|
||||
{
|
||||
process_slashings_indices.push((val_index, val.effective_balance()));
|
||||
}
|
||||
|
||||
// Note: a validator might still be "eligible" whilst returning `false` to
|
||||
// `Validator::is_active_at`. It's also possible for a validator to be active
|
||||
// in the current epoch without being eligible (if it was just activated).
|
||||
if is_eligible {
|
||||
eligible_indices.push(val_index);
|
||||
}
|
||||
|
||||
let mut validator_info = ValidatorInfo {
|
||||
effective_balance: val.effective_balance(),
|
||||
base_reward: 0, // not read
|
||||
is_eligible,
|
||||
is_slashed: val.slashed(),
|
||||
is_active_current_epoch,
|
||||
is_active_previous_epoch,
|
||||
previous_epoch_participation: *prev_epoch_flags,
|
||||
};
|
||||
|
||||
// Calculate inactivity updates.
|
||||
if is_eligible && definitely_not_in_inactivity_leak {
|
||||
let mut new_inactivity_score =
|
||||
if validator_info.is_unslashed_participating_index(TIMELY_TARGET_FLAG_INDEX)? {
|
||||
inactivity_score.saturating_sub(1)
|
||||
} else {
|
||||
inactivity_score.safe_add(spec.inactivity_score_bias)?
|
||||
};
|
||||
|
||||
// Decrease the score of all validators for forgiveness when not during a leak
|
||||
new_inactivity_score =
|
||||
new_inactivity_score.saturating_sub(spec.inactivity_score_recovery_rate);
|
||||
|
||||
if new_inactivity_score != *inactivity_score {
|
||||
inactivity_score_updates.insert(val_index, new_inactivity_score);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
if is_eligible || is_active_current_epoch {
|
||||
let effective_balance = val.effective_balance();
|
||||
let base_reward =
|
||||
get_base_reward(effective_balance, base_reward_per_increment, spec)?;
|
||||
validator_info.base_reward = base_reward;
|
||||
validators.info[val_index] = Some(validator_info);
|
||||
}
|
||||
}
|
||||
|
||||
// Sanity check total active balance.
|
||||
if current_epoch_participation.total_active_balance.get()
|
||||
!= current_epoch_total_active_balance
|
||||
{
|
||||
return Err(Error::InconsistentTotalActiveBalance {
|
||||
cached: current_epoch_total_active_balance,
|
||||
computed: current_epoch_participation.total_active_balance.get(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
@@ -249,7 +351,11 @@ impl ParticipationCache {
|
||||
current_epoch_participation,
|
||||
previous_epoch,
|
||||
previous_epoch_participation,
|
||||
validators,
|
||||
eligible_indices,
|
||||
process_slashings_indices,
|
||||
inactivity_score_updates: definitely_not_in_inactivity_leak
|
||||
.then_some(inactivity_score_updates),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -258,24 +364,8 @@ impl ParticipationCache {
|
||||
&self.eligible_indices
|
||||
}
|
||||
|
||||
/// Equivalent to the `get_unslashed_participating_indices` function in the specification.
|
||||
pub fn get_unslashed_participating_indices(
|
||||
&self,
|
||||
flag_index: usize,
|
||||
epoch: Epoch,
|
||||
) -> Result<UnslashedParticipatingIndices, BeaconStateError> {
|
||||
let participation = if epoch == self.current_epoch {
|
||||
&self.current_epoch_participation
|
||||
} else if epoch == self.previous_epoch {
|
||||
&self.previous_epoch_participation
|
||||
} else {
|
||||
return Err(BeaconStateError::EpochOutOfBounds);
|
||||
};
|
||||
|
||||
Ok(UnslashedParticipatingIndices {
|
||||
participation,
|
||||
flag_index,
|
||||
})
|
||||
pub fn process_slashings_indices(&mut self) -> Vec<(usize, u64)> {
|
||||
std::mem::take(&mut self.process_slashings_indices)
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -296,51 +386,63 @@ impl ParticipationCache {
|
||||
}
|
||||
|
||||
pub fn previous_epoch_target_attesting_balance(&self) -> Result<u64, Error> {
|
||||
self.previous_epoch_participation
|
||||
.total_flag_balance(TIMELY_TARGET_FLAG_INDEX)
|
||||
self.previous_epoch_flag_attesting_balance(TIMELY_TARGET_FLAG_INDEX)
|
||||
}
|
||||
|
||||
pub fn previous_epoch_source_attesting_balance(&self) -> Result<u64, Error> {
|
||||
self.previous_epoch_participation
|
||||
.total_flag_balance(TIMELY_SOURCE_FLAG_INDEX)
|
||||
self.previous_epoch_flag_attesting_balance(TIMELY_SOURCE_FLAG_INDEX)
|
||||
}
|
||||
|
||||
pub fn previous_epoch_head_attesting_balance(&self) -> Result<u64, Error> {
|
||||
self.previous_epoch_flag_attesting_balance(TIMELY_HEAD_FLAG_INDEX)
|
||||
}
|
||||
|
||||
pub fn previous_epoch_flag_attesting_balance(&self, flag_index: usize) -> Result<u64, Error> {
|
||||
self.previous_epoch_participation
|
||||
.total_flag_balance(TIMELY_HEAD_FLAG_INDEX)
|
||||
.total_flag_balance(flag_index)
|
||||
}
|
||||
|
||||
/*
|
||||
* Active/Unslashed
|
||||
*/
|
||||
|
||||
/// Returns `None` for an unknown `val_index`.
|
||||
pub fn is_active_unslashed_in_previous_epoch(&self, val_index: usize) -> Option<bool> {
|
||||
self.previous_epoch_participation
|
||||
.unslashed_participating_indices
|
||||
.get(val_index)
|
||||
.map(|flags| flags.is_some())
|
||||
pub fn is_active_unslashed_in_previous_epoch(&self, val_index: usize) -> bool {
|
||||
self.get_validator(val_index).map_or(false, |validator| {
|
||||
validator.is_active_previous_epoch && !validator.is_slashed
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns `None` for an unknown `val_index`.
|
||||
pub fn is_active_unslashed_in_current_epoch(&self, val_index: usize) -> Option<bool> {
|
||||
self.current_epoch_participation
|
||||
.unslashed_participating_indices
|
||||
pub fn is_active_unslashed_in_current_epoch(&self, val_index: usize) -> bool {
|
||||
self.get_validator(val_index).map_or(false, |validator| {
|
||||
validator.is_active_current_epoch && !validator.is_slashed
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_validator(&self, val_index: usize) -> Result<&ValidatorInfo, Error> {
|
||||
self.validators
|
||||
.info
|
||||
.get(val_index)
|
||||
.map(|flags| flags.is_some())
|
||||
.ok_or(Error::MissingValidator(val_index))?
|
||||
.as_ref()
|
||||
.ok_or(Error::MissingValidator(val_index))
|
||||
}
|
||||
|
||||
/*
|
||||
* Flags
|
||||
*/
|
||||
|
||||
/// Always returns false for a slashed validator.
|
||||
pub fn is_previous_epoch_timely_source_attester(
|
||||
&self,
|
||||
val_index: usize,
|
||||
) -> Result<bool, Error> {
|
||||
self.previous_epoch_participation
|
||||
.has_flag(val_index, TIMELY_SOURCE_FLAG_INDEX)
|
||||
self.get_validator(val_index)
|
||||
.map_or(Ok(false), |validator| {
|
||||
Ok(!validator.is_slashed
|
||||
&& validator
|
||||
.previous_epoch_participation
|
||||
.has_flag(TIMELY_SOURCE_FLAG_INDEX)
|
||||
.map_err(|_| Error::InvalidFlagIndex(TIMELY_SOURCE_FLAG_INDEX))?)
|
||||
})
|
||||
}
|
||||
|
||||
/// Always returns false for a slashed validator.
|
||||
@@ -348,63 +450,35 @@ impl ParticipationCache {
|
||||
&self,
|
||||
val_index: usize,
|
||||
) -> Result<bool, Error> {
|
||||
self.previous_epoch_participation
|
||||
.has_flag(val_index, TIMELY_TARGET_FLAG_INDEX)
|
||||
self.get_validator(val_index)
|
||||
.map_or(Ok(false), |validator| {
|
||||
Ok(!validator.is_slashed
|
||||
&& validator
|
||||
.previous_epoch_participation
|
||||
.has_flag(TIMELY_TARGET_FLAG_INDEX)
|
||||
.map_err(|_| Error::InvalidFlagIndex(TIMELY_TARGET_FLAG_INDEX))?)
|
||||
})
|
||||
}
|
||||
|
||||
/// Always returns false for a slashed validator.
|
||||
pub fn is_previous_epoch_timely_head_attester(&self, val_index: usize) -> Result<bool, Error> {
|
||||
self.previous_epoch_participation
|
||||
.has_flag(val_index, TIMELY_HEAD_FLAG_INDEX)
|
||||
self.get_validator(val_index)
|
||||
.map_or(Ok(false), |validator| {
|
||||
Ok(!validator.is_slashed
|
||||
&& validator
|
||||
.previous_epoch_participation
|
||||
.has_flag(TIMELY_HEAD_FLAG_INDEX)
|
||||
.map_err(|_| Error::InvalidFlagIndex(TIMELY_TARGET_FLAG_INDEX))?)
|
||||
})
|
||||
}
|
||||
|
||||
/// Always returns false for a slashed validator.
|
||||
pub fn is_current_epoch_timely_source_attester(&self, val_index: usize) -> Result<bool, Error> {
|
||||
self.current_epoch_participation
|
||||
.has_flag(val_index, TIMELY_SOURCE_FLAG_INDEX)
|
||||
}
|
||||
|
||||
/// Always returns false for a slashed validator.
|
||||
pub fn is_current_epoch_timely_target_attester(&self, val_index: usize) -> Result<bool, Error> {
|
||||
self.current_epoch_participation
|
||||
.has_flag(val_index, TIMELY_TARGET_FLAG_INDEX)
|
||||
}
|
||||
|
||||
/// Always returns false for a slashed validator.
|
||||
pub fn is_current_epoch_timely_head_attester(&self, val_index: usize) -> Result<bool, Error> {
|
||||
self.current_epoch_participation
|
||||
.has_flag(val_index, TIMELY_HEAD_FLAG_INDEX)
|
||||
}
|
||||
}
|
||||
|
||||
/// Imitates the return value of the `get_unslashed_participating_indices` in the
|
||||
/// specification.
|
||||
///
|
||||
/// This struct exists to help make the Lighthouse code read more like the specification.
|
||||
pub struct UnslashedParticipatingIndices<'a> {
|
||||
participation: &'a SingleEpochParticipationCache,
|
||||
flag_index: usize,
|
||||
}
|
||||
|
||||
impl<'a> UnslashedParticipatingIndices<'a> {
|
||||
/// Returns `Ok(true)` if the given `val_index` is both:
|
||||
///
|
||||
/// - An active validator.
|
||||
/// - Has `self.flag_index` set.
|
||||
pub fn contains(&self, val_index: usize) -> Result<bool, Error> {
|
||||
self.participation.has_flag(val_index, self.flag_index)
|
||||
}
|
||||
|
||||
/// Returns the sum of all balances of validators which have `self.flag_index` set.
|
||||
///
|
||||
/// ## Notes
|
||||
///
|
||||
/// Respects the `EFFECTIVE_BALANCE_INCREMENT` minimum.
|
||||
pub fn total_balance(&self) -> Result<u64, Error> {
|
||||
self.participation
|
||||
.total_flag_balances
|
||||
.get(self.flag_index)
|
||||
.ok_or(Error::InvalidFlagIndex(self.flag_index))
|
||||
.map(Balance::get)
|
||||
pub fn is_current_epoch_timely_target_attester(
|
||||
&self,
|
||||
_val_index: usize,
|
||||
) -> Result<bool, Error> {
|
||||
// FIXME(sproul): decide whether it's worth storing the current epoch participation flags
|
||||
// *just* for this call. Perhaps the validator API could source it from the state directly.
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
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;
|
||||
use types::VList;
|
||||
|
||||
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()
|
||||
])?;
|
||||
*state.current_epoch_participation_mut()? =
|
||||
VList::repeat(ParticipationFlags::default(), state.validators().len())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -4,12 +4,9 @@ use types::consts::altair::{
|
||||
PARTICIPATION_FLAG_WEIGHTS, TIMELY_HEAD_FLAG_INDEX, TIMELY_TARGET_FLAG_INDEX,
|
||||
WEIGHT_DENOMINATOR,
|
||||
};
|
||||
use types::{BeaconState, ChainSpec, EthSpec};
|
||||
use types::{BeaconState, BeaconStateError, ChainSpec, EthSpec};
|
||||
|
||||
use crate::common::{
|
||||
altair::{get_base_reward, BaseRewardPerIncrement},
|
||||
decrease_balance, increase_balance,
|
||||
};
|
||||
use crate::common::{decrease_balance_directly, increase_balance_directly};
|
||||
use crate::per_epoch_processing::{Delta, Error};
|
||||
|
||||
/// Apply attester and proposer rewards.
|
||||
@@ -43,9 +40,20 @@ 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.into_iter().enumerate() {
|
||||
increase_balance(state, i, delta.rewards)?;
|
||||
decrease_balance(state, i, delta.penalties)?;
|
||||
let mut balances = state.balances_mut().iter_cow();
|
||||
|
||||
while let Some((i, balance)) = balances.next_cow() {
|
||||
let delta = deltas
|
||||
.get(i)
|
||||
.ok_or(BeaconStateError::BalancesOutOfBounds(i))?;
|
||||
|
||||
if delta.rewards == 0 && delta.penalties == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let balance = balance.to_mut();
|
||||
increase_balance_directly(balance, delta.rewards)?;
|
||||
decrease_balance_directly(balance, delta.penalties)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -62,21 +70,21 @@ pub fn get_flag_index_deltas<T: EthSpec>(
|
||||
participation_cache: &ParticipationCache,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
let previous_epoch = state.previous_epoch();
|
||||
let unslashed_participating_indices =
|
||||
participation_cache.get_unslashed_participating_indices(flag_index, previous_epoch)?;
|
||||
let weight = get_flag_weight(flag_index)?;
|
||||
let unslashed_participating_balance = unslashed_participating_indices.total_balance()?;
|
||||
let unslashed_participating_balance =
|
||||
participation_cache.previous_epoch_flag_attesting_balance(flag_index)?;
|
||||
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)?;
|
||||
let base_reward_per_increment = BaseRewardPerIncrement::new(total_active_balance, spec)?;
|
||||
let previous_epoch = state.previous_epoch();
|
||||
|
||||
for &index in participation_cache.eligible_validator_indices() {
|
||||
let base_reward = get_base_reward(state, index, base_reward_per_increment, spec)?;
|
||||
let validator = participation_cache.get_validator(index)?;
|
||||
let base_reward = validator.base_reward;
|
||||
|
||||
let mut delta = Delta::default();
|
||||
|
||||
if unslashed_participating_indices.contains(index)? {
|
||||
if validator.is_unslashed_participating_index(flag_index)? {
|
||||
if !state.is_in_inactivity_leak(previous_epoch, spec) {
|
||||
let reward_numerator = base_reward
|
||||
.safe_mul(weight)?
|
||||
@@ -110,15 +118,12 @@ pub fn get_inactivity_penalty_deltas<T: EthSpec>(
|
||||
participation_cache: &ParticipationCache,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
let previous_epoch = state.previous_epoch();
|
||||
let matching_target_indices = participation_cache
|
||||
.get_unslashed_participating_indices(TIMELY_TARGET_FLAG_INDEX, previous_epoch)?;
|
||||
for &index in participation_cache.eligible_validator_indices() {
|
||||
let validator = participation_cache.get_validator(index)?;
|
||||
let mut delta = Delta::default();
|
||||
|
||||
if !matching_target_indices.contains(index)? {
|
||||
let penalty_numerator = state
|
||||
.get_validator(index)?
|
||||
if !validator.is_unslashed_participating_index(TIMELY_TARGET_FLAG_INDEX)? {
|
||||
let penalty_numerator = validator
|
||||
.effective_balance
|
||||
.safe_mul(state.get_inactivity_score(index)?)?;
|
||||
let penalty_denominator = spec
|
||||
|
||||
Reference in New Issue
Block a user