Use checked arithmetic in types and state proc (#1009)

This commit is contained in:
Michael Sproul
2020-04-20 12:35:11 +10:00
committed by GitHub
parent 50ef0d7fbf
commit 32074f0d09
49 changed files with 525 additions and 169 deletions

View File

@@ -1,6 +1,7 @@
use super::super::common::get_base_reward;
use super::validator_statuses::{TotalBalances, ValidatorStatus, ValidatorStatuses};
use super::Error;
use safe_arith::SafeArith;
use types::*;
@@ -13,21 +14,21 @@ pub struct Delta {
impl Delta {
/// Reward the validator with the `reward`.
pub fn reward(&mut self, reward: u64) {
self.rewards += 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) {
self.penalties += penalty;
pub fn penalize(&mut self, penalty: u64) -> Result<(), Error> {
self.penalties = self.penalties.safe_add(penalty)?;
Ok(())
}
}
impl std::ops::AddAssign for Delta {
/// Use wrapping addition as that is how it's defined in the spec.
fn add_assign(&mut self, other: Delta) {
self.rewards += other.rewards;
self.penalties += other.penalties;
/// Combine two deltas.
fn combine(&mut self, other: Delta) -> Result<(), Error> {
self.reward(other.rewards)?;
self.penalize(other.penalties)
}
}
@@ -56,9 +57,10 @@ pub fn process_rewards_and_penalties<T: EthSpec>(
get_proposer_deltas(&mut deltas, state, validator_statuses, spec)?;
// Apply the deltas, over-flowing but not under-flowing (saturating at 0 instead).
// 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] += delta.rewards;
state.balances[i] = state.balances[i].safe_add(delta.rewards)?;
state.balances[i] = state.balances[i].saturating_sub(delta.penalties);
}
@@ -91,7 +93,8 @@ fn get_proposer_deltas<T: EthSpec>(
return Err(Error::ValidatorStatusesInconsistent);
}
deltas[inclusion.proposer_index].reward(base_reward / spec.proposer_reward_quotient);
deltas[inclusion.proposer_index]
.reward(base_reward.safe_div(spec.proposer_reward_quotient)?)?;
}
}
@@ -123,9 +126,9 @@ fn get_attestation_deltas<T: EthSpec>(
base_reward,
finality_delay,
spec,
);
)?;
deltas[index] += delta;
deltas[index].combine(delta)?;
}
Ok(())
@@ -140,7 +143,7 @@ fn get_attestation_delta<T: EthSpec>(
base_reward: u64,
finality_delay: u64,
spec: &ChainSpec,
) -> Delta {
) -> Result<Delta, Error> {
let mut delta = Delta::default();
// Is this validator eligible to be rewarded or penalized?
@@ -149,7 +152,7 @@ fn get_attestation_delta<T: EthSpec>(
|| (validator.is_slashed && !validator.is_withdrawable_in_current_epoch);
if !is_eligible {
return delta;
return Ok(delta);
}
// Handle integer overflow by dividing these quantities by EFFECTIVE_BALANCE_INCREMENT
@@ -157,59 +160,78 @@ fn get_attestation_delta<T: EthSpec>(
// - increment = EFFECTIVE_BALANCE_INCREMENT
// - reward_numerator = get_base_reward(state, index) * (attesting_balance // increment)
// - rewards[index] = reward_numerator // (total_balance // increment)
let total_balance_ebi = total_balances.current_epoch() / spec.effective_balance_increment;
let total_attesting_balance_ebi =
total_balances.previous_epoch_attesters() / spec.effective_balance_increment;
let matching_target_balance_ebi =
total_balances.previous_epoch_target_attesters() / spec.effective_balance_increment;
let matching_head_balance_ebi =
total_balances.previous_epoch_head_attesters() / spec.effective_balance_increment;
let total_balance_ebi = total_balances
.current_epoch()
.safe_div(spec.effective_balance_increment)?;
let total_attesting_balance_ebi = total_balances
.previous_epoch_attesters()
.safe_div(spec.effective_balance_increment)?;
let matching_target_balance_ebi = total_balances
.previous_epoch_target_attesters()
.safe_div(spec.effective_balance_increment)?;
let matching_head_balance_ebi = total_balances
.previous_epoch_head_attesters()
.safe_div(spec.effective_balance_increment)?;
// Expected FFG source.
// Spec:
// - validator index in `get_unslashed_attesting_indices(state, matching_source_attestations)`
if validator.is_previous_epoch_attester && !validator.is_slashed {
delta.reward(base_reward * total_attesting_balance_ebi / total_balance_ebi);
delta.reward(
base_reward
.safe_mul(total_attesting_balance_ebi)?
.safe_div(total_balance_ebi)?,
)?;
// Inclusion speed bonus
let proposer_reward = base_reward / spec.proposer_reward_quotient;
let max_attester_reward = base_reward - proposer_reward;
let proposer_reward = base_reward.safe_div(spec.proposer_reward_quotient)?;
let max_attester_reward = base_reward.safe_sub(proposer_reward)?;
let inclusion = validator
.inclusion_info
.expect("It is a logic error for an attester not to have an inclusion delay.");
delta.reward(max_attester_reward / inclusion.delay);
delta.reward(max_attester_reward.safe_div(inclusion.delay)?)?;
} else {
delta.penalize(base_reward);
delta.penalize(base_reward)?;
}
// Expected FFG target.
// Spec:
// - validator index in `get_unslashed_attesting_indices(state, matching_target_attestations)`
if validator.is_previous_epoch_target_attester && !validator.is_slashed {
delta.reward(base_reward * matching_target_balance_ebi / total_balance_ebi);
delta.reward(
base_reward
.safe_mul(matching_target_balance_ebi)?
.safe_div(total_balance_ebi)?,
)?;
} else {
delta.penalize(base_reward);
delta.penalize(base_reward)?;
}
// Expected head.
// Spec:
// - validator index in `get_unslashed_attesting_indices(state, matching_head_attestations)`
if validator.is_previous_epoch_head_attester && !validator.is_slashed {
delta.reward(base_reward * matching_head_balance_ebi / total_balance_ebi);
delta.reward(
base_reward
.safe_mul(matching_head_balance_ebi)?
.safe_div(total_balance_ebi)?,
)?;
} else {
delta.penalize(base_reward);
delta.penalize(base_reward)?;
}
// Inactivity penalty
if finality_delay > spec.min_epochs_to_inactivity_penalty {
// All eligible validators are penalized
delta.penalize(spec.base_rewards_per_epoch * base_reward);
delta.penalize(spec.base_rewards_per_epoch.safe_mul(base_reward)?)?;
// Additionally, all validators whose FFG target didn't match are penalized extra
if !validator.is_previous_epoch_target_attester {
delta.penalize(
validator.current_epoch_effective_balance * finality_delay
/ spec.inactivity_penalty_quotient,
);
validator
.current_epoch_effective_balance
.safe_mul(finality_delay)?
.safe_div(spec.inactivity_penalty_quotient)?,
)?;
}
}
@@ -218,5 +240,5 @@ fn get_attestation_delta<T: EthSpec>(
// This function only computes the delta for a single validator, so it cannot also return a
// delta for a validator.
delta
Ok(delta)
}

View File

@@ -18,6 +18,7 @@ pub enum EpochProcessingError {
BeaconStateError(BeaconStateError),
InclusionError(InclusionError),
SszTypesError(ssz_types::Error),
ArithError(safe_arith::ArithError),
}
impl From<InclusionError> for EpochProcessingError {
@@ -38,6 +39,12 @@ impl From<ssz_types::Error> for EpochProcessingError {
}
}
impl From<safe_arith::ArithError> for EpochProcessingError {
fn from(e: safe_arith::ArithError) -> EpochProcessingError {
EpochProcessingError::ArithError(e)
}
}
#[derive(Debug, PartialEq)]
pub enum InclusionError {
/// The validator did not participate in an attestation in this period.

View File

@@ -1,3 +1,4 @@
use safe_arith::SafeArith;
use types::{BeaconStateError as Error, *};
/// Process slashings.
@@ -13,12 +14,17 @@ pub fn process_slashings<T: EthSpec>(
for (index, validator) in state.validators.iter().enumerate() {
if validator.slashed
&& epoch + T::EpochsPerSlashingsVector::to_u64() / 2 == validator.withdrawable_epoch
&& epoch + T::EpochsPerSlashingsVector::to_u64().safe_div(2)?
== validator.withdrawable_epoch
{
let increment = spec.effective_balance_increment;
let penalty_numerator = validator.effective_balance / increment
* std::cmp::min(sum_slashings * 3, total_balance);
let penalty = penalty_numerator / total_balance * increment;
let penalty_numerator = validator
.effective_balance
.safe_div(increment)?
.safe_mul(std::cmp::min(sum_slashings.safe_mul(3)?, total_balance))?;
let penalty = penalty_numerator
.safe_div(total_balance)?
.safe_mul(increment)?;
safe_sub_assign!(state.balances[index], penalty);
}

View File

@@ -1,4 +1,5 @@
use crate::common::get_attesting_indices;
use safe_arith::SafeArith;
use types::*;
/// Sets the boolean `var` on `self` to be true if it is true on `other`. Otherwise leaves `self`
@@ -198,12 +199,16 @@ impl ValidatorStatuses {
if validator.is_active_at(state.current_epoch()) {
status.is_active_in_current_epoch = true;
total_balances.current_epoch += effective_balance;
total_balances
.current_epoch
.safe_add_assign(effective_balance)?;
}
if validator.is_active_at(state.previous_epoch()) {
status.is_active_in_previous_epoch = true;
total_balances.previous_epoch += effective_balance;
total_balances
.previous_epoch
.safe_add_assign(effective_balance)?;
}
statuses.push(status);
@@ -275,19 +280,29 @@ impl ValidatorStatuses {
let validator_balance = state.get_effective_balance(index, spec)?;
if v.is_current_epoch_attester {
self.total_balances.current_epoch_attesters += validator_balance;
self.total_balances
.current_epoch_attesters
.safe_add_assign(validator_balance)?;
}
if v.is_current_epoch_target_attester {
self.total_balances.current_epoch_target_attesters += validator_balance;
self.total_balances
.current_epoch_target_attesters
.safe_add_assign(validator_balance)?;
}
if v.is_previous_epoch_attester {
self.total_balances.previous_epoch_attesters += validator_balance;
self.total_balances
.previous_epoch_attesters
.safe_add_assign(validator_balance)?;
}
if v.is_previous_epoch_target_attester {
self.total_balances.previous_epoch_target_attesters += validator_balance;
self.total_balances
.previous_epoch_target_attesters
.safe_add_assign(validator_balance)?;
}
if v.is_previous_epoch_head_attester {
self.total_balances.previous_epoch_head_attesters += validator_balance;
self.total_balances
.previous_epoch_head_attesters
.safe_add_assign(validator_balance)?;
}
}
}