Optimisations and bug fixes for state advance

This commit is reasonably performant on Prater!
This commit is contained in:
Michael Sproul
2022-02-17 14:00:57 +11:00
parent f5dae9106e
commit 1db0e32bfb
13 changed files with 171 additions and 56 deletions

View File

@@ -5,15 +5,13 @@ 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,
pub fn get_base_reward(
validator_effective_balance: u64,
// Should be == get_total_active_balance(state, spec)
total_active_balance: u64,
spec: &ChainSpec,
) -> Result<u64, Error> {
state
.get_effective_balance(index)?
validator_effective_balance
.safe_div(spec.effective_balance_increment)?
.safe_mul(get_base_reward_per_increment(total_active_balance, spec)?)
.map_err(Into::into)

View File

@@ -24,8 +24,7 @@ pub fn increase_balance<E: EthSpec>(
index: usize,
delta: u64,
) -> Result<(), BeaconStateError> {
state.get_balance_mut(index)?.safe_add_assign(delta)?;
Ok(())
increase_balance_directly(state.get_balance_mut(index)?, delta)
}
/// Decrease the balance of a validator, saturating upon overflow, as per the spec.
@@ -34,7 +33,17 @@ pub fn decrease_balance<E: EthSpec>(
index: usize,
delta: u64,
) -> Result<(), BeaconStateError> {
let balance = state.get_balance_mut(index)?;
decrease_balance_directly(state.get_balance_mut(index)?, delta)
}
/// Increase the balance of a validator, erroring upon overflow, as per the spec.
pub fn increase_balance_directly(balance: &mut u64, delta: u64) -> Result<(), BeaconStateError> {
balance.safe_add_assign(delta)?;
Ok(())
}
/// Decrease the balance of a validator, saturating upon overflow, as per the spec.
pub fn decrease_balance_directly(balance: &mut u64, delta: u64) -> Result<(), BeaconStateError> {
*balance = balance.saturating_sub(delta);
Ok(())
}

View File

@@ -141,9 +141,11 @@ pub mod altair {
if participation_flag_indices.contains(&flag_index)
&& !validator_participation.has_flag(flag_index)?
{
// FIXME(sproul): add effective balance cache here?
validator_participation.add_flag(flag_index)?;
let effective_balance = state.get_validator(index)?.effective_balance;
proposer_reward_numerator.safe_add_assign(
get_base_reward(state, index, total_active_balance, spec)?
get_base_reward(effective_balance, total_active_balance, spec)?
.safe_mul(weight)?,
)?;
}

View File

@@ -1,9 +1,8 @@
use super::ParticipationCache;
use crate::EpochProcessingError;
use core::result::Result;
use core::result::Result::Ok;
use safe_arith::SafeArith;
use std::cmp::min;
use std::cmp::Ordering;
use types::beacon_state::BeaconState;
use types::chain_spec::ChainSpec;
use types::consts::altair::TIMELY_TARGET_FLAG_INDEX;
@@ -18,23 +17,54 @@ pub fn process_inactivity_updates<T: EthSpec>(
if state.current_epoch() == T::genesis_epoch() {
return Ok(());
}
let is_in_inactivity_leak = state.is_in_inactivity_leak(spec);
let unslashed_indices = participation_cache
.get_unslashed_participating_indices(TIMELY_TARGET_FLAG_INDEX, state.previous_epoch())?;
for &index in participation_cache.eligible_validator_indices() {
let mut eligible_validators_iter = participation_cache
.eligible_validator_indices()
.iter()
.peekable();
// FIXME(sproul): this is a really ugly hack
let mut is_eligible = |index: usize| -> bool {
while let Some(&eligible_index) = eligible_validators_iter.peek() {
match eligible_index.cmp(&index) {
// Should visit every
Ordering::Less => {
unreachable!("should have already visited {}", eligible_index)
}
Ordering::Equal => {
eligible_validators_iter.next();
return true;
}
Ordering::Greater => {
return false;
}
}
}
false
};
let mut inactivity_scores = state.inactivity_scores_mut()?.iter_cow();
while let Some((index, inactivity_score)) = inactivity_scores.next_cow() {
if !is_eligible(index) {
continue;
}
let inactivity_score = inactivity_score.to_mut();
// 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)?;
inactivity_score.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)?;
if !is_in_inactivity_leak {
inactivity_score
.safe_sub_assign(min(spec.inactivity_score_recovery_rate, *inactivity_score))?;
}

View File

@@ -25,6 +25,7 @@ use types::{
#[derive(Debug, PartialEq)]
pub enum Error {
InvalidFlagIndex(usize),
MissingEffectiveBalance(usize),
}
/// A balance which will never be below the specified `minimum`.
@@ -122,6 +123,7 @@ impl SingleEpochParticipationCache {
&mut self,
val_index: usize,
validator: &Validator,
epoch_participation: &ParticipationFlags,
state: &BeaconState<T>,
relative_epoch: RelativeEpoch,
) -> Result<(), BeaconStateError> {
@@ -131,14 +133,6 @@ impl SingleEpochParticipationCache {
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(validator.effective_balance)?;
@@ -173,6 +167,8 @@ pub struct ParticipationCache {
previous_epoch: Epoch,
/// Caches information about active validators pertaining to `self.previous_epoch`.
previous_epoch_participation: SingleEpochParticipationCache,
/// Caches validator effective balances from the start of `process_epoch`.
effective_balances: HashMap<usize, u64>,
/// Caches the result of the `get_eligible_validator_indices` function.
eligible_indices: Vec<usize>,
}
@@ -203,6 +199,9 @@ impl ParticipationCache {
SingleEpochParticipationCache::new(num_current_epoch_active_vals, spec);
let mut previous_epoch_participation =
SingleEpochParticipationCache::new(num_previous_epoch_active_vals, spec);
let mut effective_balances = HashMap::with_capacity(num_current_epoch_active_vals);
// Contains the set of validators which are either:
//
// - Active in the previous epoch.
@@ -219,11 +218,18 @@ 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() {
let iter = state
.validators()
.iter()
.zip(state.current_epoch_participation()?)
.zip(state.previous_epoch_participation()?)
.enumerate();
for (val_index, ((val, curr_epoch_flags), prev_epoch_flags)) in iter {
if val.is_active_at(current_epoch) {
current_epoch_participation.process_active_validator(
val_index,
val,
curr_epoch_flags,
state,
RelativeEpoch::Current,
)?;
@@ -233,6 +239,7 @@ impl ParticipationCache {
previous_epoch_participation.process_active_validator(
val_index,
val,
prev_epoch_flags,
state,
RelativeEpoch::Previous,
)?;
@@ -240,8 +247,9 @@ impl ParticipationCache {
// Note: a validator might still be "eligible" whilst returning `false` to
// `Validator::is_active_at`.
if state.is_eligible_validator(val_index)? {
eligible_indices.push(val_index)
if state.is_eligible_validator(val) {
eligible_indices.push(val_index);
effective_balances.insert(val_index, val.effective_balance);
}
}
@@ -250,6 +258,7 @@ impl ParticipationCache {
current_epoch_participation,
previous_epoch,
previous_epoch_participation,
effective_balances,
eligible_indices,
})
}
@@ -327,6 +336,13 @@ impl ParticipationCache {
.contains_key(&val_index)
}
pub fn get_effective_balance(&self, val_index: usize) -> Result<u64, Error> {
self.effective_balances
.get(&val_index)
.copied()
.ok_or(Error::MissingEffectiveBalance(val_index))
}
/*
* Flags
*/

View File

@@ -4,9 +4,11 @@ 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, decrease_balance, increase_balance};
use crate::common::{
altair::get_base_reward, decrease_balance_directly, increase_balance_directly,
};
use crate::per_epoch_processing::{Delta, Error};
/// Apply attester and proposer rewards.
@@ -40,9 +42,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(())
@@ -69,8 +82,11 @@ pub fn get_flag_index_deltas<T: EthSpec>(
let active_increments = total_active_balance.safe_div(spec.effective_balance_increment)?;
for &index in participation_cache.eligible_validator_indices() {
// FIXME(sproul): compute base reward in participation cache
let base_reward = get_base_reward(state, index, total_active_balance, spec)?;
let base_reward = get_base_reward(
participation_cache.get_effective_balance(index as usize)?,
total_active_balance,
spec,
)?;
let mut delta = Delta::default();
if unslashed_participating_indices.contains(index as usize)? {
@@ -114,9 +130,8 @@ pub fn get_inactivity_penalty_deltas<T: EthSpec>(
let mut delta = Delta::default();
if !matching_target_indices.contains(index)? {
let penalty_numerator = state
.get_validator(index)?
.effective_balance
let penalty_numerator = participation_cache
.get_effective_balance(index)?
.safe_mul(state.get_inactivity_score(index)?)?;
let penalty_denominator = spec
.inactivity_score_bias

View File

@@ -93,7 +93,9 @@ pub fn get_attestation_deltas<T: EthSpec>(
// `get_inclusion_delay_deltas`. It's safe to do so here because any validator that is in
// the unslashed indices of the matching source attestations is active, and therefore
// eligible.
if !state.is_eligible_validator(index)? {
// FIXME(sproul): this is inefficient
let full_validator = state.get_validator(index)?;
if !state.is_eligible_validator(full_validator) {
continue;
}