mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-03 00:31:50 +00:00
Remove saturating arith from state_processing (#1644)
## Issue Addressed Resolves #1100 ## Proposed Changes * Implement the `SafeArith` trait for `Slot` and `Epoch`, so that methods like `safe_add` become available. * Tweak the `SafeArith` trait to allow a different `Rhs` type (analagous to `std::ops::Add`, etc). * Add a `legacy-arith` feature to `types` and `state_processing` that conditionally enables implementations of the `std` ops with saturating semantics. * Check compilation of `types` and `state_processing` _without_ `legacy-arith` on CI, thus guaranteeing that they only use the `SafeArith` primitives 🎉 ## Additional Info The `legacy-arith` feature gets turned on by all higher-level crates that depend on `state_processing` or `types`, thus allowing the beacon chain, networking, and other components to continue to rely on the availability of ops like `+`, `-`, `*`, etc. **This is a consensus-breaking change**, but brings us in line with the spec, and our incompatibilities shouldn't have been reachable with any valid configuration of Eth2 parameters.
This commit is contained in:
@@ -27,14 +27,16 @@ log = "0.4.8"
|
||||
safe_arith = { path = "../safe_arith" }
|
||||
tree_hash = "0.1.0"
|
||||
tree_hash_derive = "0.2.0"
|
||||
types = { path = "../types" }
|
||||
types = { path = "../types", default-features = false }
|
||||
rayon = "1.3.0"
|
||||
eth2_hashing = "0.1.0"
|
||||
int_to_bytes = { path = "../int_to_bytes" }
|
||||
arbitrary = { version = "0.4.4", features = ["derive"], optional = true }
|
||||
|
||||
[features]
|
||||
default = ["legacy-arith"]
|
||||
fake_crypto = ["bls/fake_crypto"]
|
||||
legacy-arith = ["types/legacy-arith"]
|
||||
arbitrary-fuzz = [
|
||||
"arbitrary",
|
||||
"types/arbitrary-fuzz",
|
||||
|
||||
@@ -47,7 +47,7 @@ impl DepositDataTree {
|
||||
/// Add a deposit to the merkle tree.
|
||||
pub fn push_leaf(&mut self, leaf: Hash256) -> Result<(), MerkleTreeError> {
|
||||
self.tree.push_leaf(leaf, self.depth)?;
|
||||
self.mix_in_length.increment()?;
|
||||
self.mix_in_length.safe_add_assign(1)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use safe_arith::SafeArith;
|
||||
use std::cmp::max;
|
||||
use types::{BeaconStateError as Error, *};
|
||||
|
||||
@@ -22,7 +23,7 @@ pub fn initiate_validator_exit<T: EthSpec>(
|
||||
state.exit_cache.build(&state.validators, spec)?;
|
||||
|
||||
// Compute exit queue epoch
|
||||
let delayed_epoch = state.compute_activation_exit_epoch(state.current_epoch(), spec);
|
||||
let delayed_epoch = state.compute_activation_exit_epoch(state.current_epoch(), spec)?;
|
||||
let mut exit_queue_epoch = state
|
||||
.exit_cache
|
||||
.max_epoch()?
|
||||
@@ -30,13 +31,13 @@ pub fn initiate_validator_exit<T: EthSpec>(
|
||||
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 += 1;
|
||||
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 =
|
||||
exit_queue_epoch + spec.min_validator_withdrawability_delay;
|
||||
exit_queue_epoch.safe_add(spec.min_validator_withdrawability_delay)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ pub fn slash_validator<T: EthSpec>(
|
||||
state.validators[slashed_index].slashed = true;
|
||||
state.validators[slashed_index].withdrawable_epoch = cmp::max(
|
||||
state.validators[slashed_index].withdrawable_epoch,
|
||||
epoch + Epoch::from(T::EpochsPerSlashingsVector::to_u64()),
|
||||
epoch.safe_add(T::EpochsPerSlashingsVector::to_u64())?,
|
||||
);
|
||||
let validator_effective_balance = state.get_effective_balance(slashed_index, spec)?;
|
||||
state.set_slashings(
|
||||
|
||||
@@ -368,7 +368,7 @@ pub fn process_attestations<T: EthSpec>(
|
||||
let pending_attestation = PendingAttestation {
|
||||
aggregation_bits: attestation.aggregation_bits.clone(),
|
||||
data: attestation.data.clone(),
|
||||
inclusion_delay: (state.slot - attestation.data.slot).as_u64(),
|
||||
inclusion_delay: state.slot.safe_sub(attestation.data.slot)?.as_u64(),
|
||||
proposer_index,
|
||||
};
|
||||
|
||||
@@ -444,7 +444,7 @@ pub fn process_deposit<T: EthSpec>(
|
||||
.map_err(|e| e.into_with_index(deposit_index))?;
|
||||
}
|
||||
|
||||
state.eth1_deposit_index.increment()?;
|
||||
state.eth1_deposit_index.safe_add_assign(1)?;
|
||||
|
||||
// Get an `Option<u64>` where `u64` is the validator index if this deposit public key
|
||||
// already exists in the beacon_state.
|
||||
|
||||
@@ -2,6 +2,7 @@ use super::errors::{AttestationInvalid as Invalid, BlockOperationError};
|
||||
use super::VerifySignatures;
|
||||
use crate::common::get_indexed_attestation;
|
||||
use crate::per_block_processing::is_valid_indexed_attestation;
|
||||
use safe_arith::SafeArith;
|
||||
use types::*;
|
||||
|
||||
type Result<T> = std::result::Result<T, BlockOperationError<Invalid>>;
|
||||
@@ -25,7 +26,7 @@ pub fn verify_attestation_for_block_inclusion<T: EthSpec>(
|
||||
let data = &attestation.data;
|
||||
|
||||
verify!(
|
||||
data.slot + spec.min_attestation_inclusion_delay <= state.slot,
|
||||
data.slot.safe_add(spec.min_attestation_inclusion_delay)? <= state.slot,
|
||||
Invalid::IncludedTooEarly {
|
||||
state: state.slot,
|
||||
delay: spec.min_attestation_inclusion_delay,
|
||||
@@ -33,7 +34,7 @@ pub fn verify_attestation_for_block_inclusion<T: EthSpec>(
|
||||
}
|
||||
);
|
||||
verify!(
|
||||
state.slot <= data.slot + T::slots_per_epoch(),
|
||||
state.slot <= data.slot.safe_add(T::slots_per_epoch())?,
|
||||
Invalid::IncludedTooLate {
|
||||
state: state.slot,
|
||||
attestation: data.slot,
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::per_block_processing::{
|
||||
signature_sets::{exit_signature_set, get_pubkey_from_state},
|
||||
VerifySignatures,
|
||||
};
|
||||
use safe_arith::SafeArith;
|
||||
use types::*;
|
||||
|
||||
type Result<T> = std::result::Result<T, BlockOperationError<ExitInvalid>>;
|
||||
@@ -77,11 +78,14 @@ fn verify_exit_parametric<T: EthSpec>(
|
||||
);
|
||||
|
||||
// Verify the validator has been active long enough.
|
||||
let earliest_exit_epoch = validator
|
||||
.activation_epoch
|
||||
.safe_add(spec.shard_committee_period)?;
|
||||
verify!(
|
||||
state.current_epoch() >= validator.activation_epoch + spec.shard_committee_period,
|
||||
state.current_epoch() >= earliest_exit_epoch,
|
||||
ExitInvalid::TooYoungToExit {
|
||||
current_epoch: state.current_epoch(),
|
||||
earliest_exit_epoch: validator.activation_epoch + spec.shard_committee_period,
|
||||
earliest_exit_epoch,
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ pub fn process_justification_and_finalization<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
total_balances: &TotalBalances,
|
||||
) -> Result<(), Error> {
|
||||
if state.current_epoch() <= T::genesis_epoch() + 1 {
|
||||
if state.current_epoch() <= T::genesis_epoch().safe_add(1)? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -126,25 +126,25 @@ pub fn process_justification_and_finalization<T: EthSpec>(
|
||||
|
||||
// The 2nd/3rd/4th most recent epochs are all justified, the 2nd using the 4th as source.
|
||||
if (1..4).all(|i| bits.get(i).unwrap_or(false))
|
||||
&& old_previous_justified_checkpoint.epoch + 3 == current_epoch
|
||||
&& old_previous_justified_checkpoint.epoch.safe_add(3)? == current_epoch
|
||||
{
|
||||
state.finalized_checkpoint = old_previous_justified_checkpoint;
|
||||
}
|
||||
// The 2nd/3rd most recent epochs are both justified, the 2nd using the 3rd as source.
|
||||
else if (1..3).all(|i| bits.get(i).unwrap_or(false))
|
||||
&& old_previous_justified_checkpoint.epoch + 2 == current_epoch
|
||||
&& old_previous_justified_checkpoint.epoch.safe_add(2)? == current_epoch
|
||||
{
|
||||
state.finalized_checkpoint = old_previous_justified_checkpoint;
|
||||
}
|
||||
// The 1st/2nd/3rd most recent epochs are all justified, the 1st using the 3nd as source.
|
||||
if (0..3).all(|i| bits.get(i).unwrap_or(false))
|
||||
&& old_current_justified_checkpoint.epoch + 2 == current_epoch
|
||||
&& old_current_justified_checkpoint.epoch.safe_add(2)? == current_epoch
|
||||
{
|
||||
state.finalized_checkpoint = old_current_justified_checkpoint;
|
||||
}
|
||||
// The 1st/2nd most recent epochs are both justified, the 1st using the 2nd as source.
|
||||
else if (0..2).all(|i| bits.get(i).unwrap_or(false))
|
||||
&& old_current_justified_checkpoint.epoch + 1 == current_epoch
|
||||
&& old_current_justified_checkpoint.epoch.safe_add(1)? == current_epoch
|
||||
{
|
||||
state.finalized_checkpoint = old_current_justified_checkpoint;
|
||||
}
|
||||
@@ -160,10 +160,15 @@ pub fn process_final_updates<T: EthSpec>(
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
let current_epoch = state.current_epoch();
|
||||
let next_epoch = state.next_epoch();
|
||||
let next_epoch = state.next_epoch()?;
|
||||
|
||||
// Reset eth1 data votes.
|
||||
if (state.slot + 1) % T::SlotsPerEth1VotingPeriod::to_u64() == 0 {
|
||||
if state
|
||||
.slot
|
||||
.safe_add(1)?
|
||||
.safe_rem(T::SlotsPerEth1VotingPeriod::to_u64())?
|
||||
== 0
|
||||
{
|
||||
state.eth1_data_votes = VariableList::empty();
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,10 @@ fn get_attestation_deltas<T: EthSpec>(
|
||||
validator_statuses: &ValidatorStatuses,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<Vec<Delta>, Error> {
|
||||
let finality_delay = (state.previous_epoch() - state.finalized_checkpoint.epoch).as_u64();
|
||||
let finality_delay = state
|
||||
.previous_epoch()
|
||||
.safe_sub(state.finalized_checkpoint.epoch)?
|
||||
.as_u64();
|
||||
|
||||
let mut deltas = vec![Delta::default(); state.validators.len()];
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ pub fn process_slashings<T: EthSpec>(
|
||||
|
||||
for (index, validator) in state.validators.iter().enumerate() {
|
||||
if validator.slashed
|
||||
&& epoch + T::EpochsPerSlashingsVector::to_u64().safe_div(2)?
|
||||
&& epoch.safe_add(T::EpochsPerSlashingsVector::to_u64().safe_div(2)?)?
|
||||
== validator.withdrawable_epoch
|
||||
{
|
||||
let increment = spec.effective_balance_increment;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::super::common::initiate_validator_exit;
|
||||
use super::Error;
|
||||
use crate::{common::initiate_validator_exit, per_epoch_processing::Error};
|
||||
use itertools::Itertools;
|
||||
use safe_arith::SafeArith;
|
||||
use types::*;
|
||||
|
||||
/// Performs a validator registry update, if required.
|
||||
@@ -31,7 +31,7 @@ pub fn process_registry_updates<T: EthSpec>(
|
||||
|
||||
for index in indices_to_update {
|
||||
if state.validators[index].is_eligible_for_activation_queue(spec) {
|
||||
state.validators[index].activation_eligibility_epoch = current_epoch + 1;
|
||||
state.validators[index].activation_eligibility_epoch = current_epoch.safe_add(1)?;
|
||||
}
|
||||
if is_ejectable(&state.validators[index]) {
|
||||
initiate_validator_exit(state, index, spec)?;
|
||||
@@ -50,7 +50,7 @@ pub fn process_registry_updates<T: EthSpec>(
|
||||
|
||||
// Dequeue validators for activation up to churn limit
|
||||
let churn_limit = state.get_churn_limit(spec)? as usize;
|
||||
let delayed_activation_epoch = state.compute_activation_exit_epoch(current_epoch, spec);
|
||||
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;
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
use crate::{per_epoch_processing::EpochProcessingSummary, *};
|
||||
use safe_arith::{ArithError, SafeArith};
|
||||
use types::*;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Error {
|
||||
BeaconStateError(BeaconStateError),
|
||||
EpochProcessingError(EpochProcessingError),
|
||||
ArithError(ArithError),
|
||||
}
|
||||
|
||||
impl From<ArithError> for Error {
|
||||
fn from(e: ArithError) -> Self {
|
||||
Self::ArithError(e)
|
||||
}
|
||||
}
|
||||
|
||||
/// Advances a state forward by one slot, performing per-epoch processing if required.
|
||||
@@ -21,14 +29,15 @@ pub fn per_slot_processing<T: EthSpec>(
|
||||
) -> Result<Option<EpochProcessingSummary>, Error> {
|
||||
cache_state(state, state_root)?;
|
||||
|
||||
let summary = if state.slot > spec.genesis_slot && (state.slot + 1) % T::slots_per_epoch() == 0
|
||||
let summary = if state.slot > spec.genesis_slot
|
||||
&& state.slot.safe_add(1)?.safe_rem(T::slots_per_epoch())? == 0
|
||||
{
|
||||
Some(per_epoch_processing(state, spec)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
state.slot += 1;
|
||||
state.slot.safe_add_assign(1)?;
|
||||
|
||||
Ok(summary)
|
||||
}
|
||||
@@ -48,7 +57,7 @@ fn cache_state<T: EthSpec>(
|
||||
//
|
||||
// This is a bit hacky, however it gets the job safely without lots of code.
|
||||
let previous_slot = state.slot;
|
||||
state.slot += 1;
|
||||
state.slot.safe_add_assign(1)?;
|
||||
|
||||
// Store the previous slot's post state transition root.
|
||||
state.set_state_root(previous_slot, previous_state_root)?;
|
||||
@@ -63,7 +72,7 @@ fn cache_state<T: EthSpec>(
|
||||
state.set_block_root(previous_slot, latest_block_root)?;
|
||||
|
||||
// Set the state slot back to what it should be.
|
||||
state.slot -= 1;
|
||||
state.slot.safe_sub_assign(1)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user