diff --git a/eth2/state_processing/src/common/exit_validator.rs b/eth2/state_processing/src/common/exit_validator.rs new file mode 100644 index 0000000000..8ab530b18a --- /dev/null +++ b/eth2/state_processing/src/common/exit_validator.rs @@ -0,0 +1,22 @@ +use types::{BeaconStateError as Error, *}; + +/// Exit the validator of the given `index`. +/// +/// Spec v0.5.0 +pub fn exit_validator( + state: &mut BeaconState, + validator_index: usize, + spec: &ChainSpec, +) -> Result<(), Error> { + if validator_index >= state.validator_registry.len() { + return Err(Error::UnknownValidator); + } + + let delayed_epoch = state.get_delayed_activation_exit_epoch(state.current_epoch(spec), spec); + + if state.validator_registry[validator_index].exit_epoch > delayed_epoch { + state.validator_registry[validator_index].exit_epoch = delayed_epoch; + } + + Ok(()) +} diff --git a/eth2/state_processing/src/common/mod.rs b/eth2/state_processing/src/common/mod.rs new file mode 100644 index 0000000000..49898d10f7 --- /dev/null +++ b/eth2/state_processing/src/common/mod.rs @@ -0,0 +1,7 @@ +mod exit_validator; +mod slash_validator; +mod verify_bitfield; + +pub use exit_validator::exit_validator; +pub use slash_validator::slash_validator; +pub use verify_bitfield::verify_bitfield_length; diff --git a/eth2/state_processing/src/common/slash_validator.rs b/eth2/state_processing/src/common/slash_validator.rs new file mode 100644 index 0000000000..9be87b978e --- /dev/null +++ b/eth2/state_processing/src/common/slash_validator.rs @@ -0,0 +1,62 @@ +use crate::common::exit_validator; +use types::{BeaconStateError as Error, *}; + +/// Slash the validator with index ``index``. +/// +/// Spec v0.5.0 +pub fn slash_validator( + state: &mut BeaconState, + validator_index: usize, + spec: &ChainSpec, +) -> Result<(), Error> { + let current_epoch = state.current_epoch(spec); + + if (validator_index >= state.validator_registry.len()) + | (validator_index >= state.validator_balances.len()) + { + return Err(BeaconStateError::UnknownValidator); + } + + let validator = &state.validator_registry[validator_index]; + + let effective_balance = state.get_effective_balance(validator_index, spec)?; + + // A validator that is withdrawn cannot be slashed. + // + // This constraint will be lifted in Phase 0. + if state.slot + >= validator + .withdrawable_epoch + .start_slot(spec.slots_per_epoch) + { + return Err(Error::ValidatorIsWithdrawable); + } + + exit_validator(state, validator_index, spec)?; + + state.set_slashed_balance( + current_epoch, + state.get_slashed_balance(current_epoch, spec)? + effective_balance, + spec, + )?; + + let whistleblower_index = + state.get_beacon_proposer_index(state.slot, RelativeEpoch::Current, spec)?; + let whistleblower_reward = effective_balance / spec.whistleblower_reward_quotient; + + safe_add_assign!( + state.validator_balances[whistleblower_index as usize], + whistleblower_reward + ); + safe_sub_assign!( + state.validator_balances[validator_index], + whistleblower_reward + ); + + state.validator_registry[validator_index].slashed = true; + + state.validator_registry[validator_index].withdrawable_epoch = + current_epoch + Epoch::from(spec.latest_slashed_exit_length); + + Ok(()) +} diff --git a/eth2/types/src/beacon_state/helpers.rs b/eth2/state_processing/src/common/verify_bitfield.rs similarity index 96% rename from eth2/types/src/beacon_state/helpers.rs rename to eth2/state_processing/src/common/verify_bitfield.rs index adae7bab49..8ff5c96ca3 100644 --- a/eth2/types/src/beacon_state/helpers.rs +++ b/eth2/state_processing/src/common/verify_bitfield.rs @@ -1,4 +1,4 @@ -use crate::*; +use types::*; /// Verify ``bitfield`` against the ``committee_size``. /// diff --git a/eth2/state_processing/src/get_genesis_state.rs b/eth2/state_processing/src/get_genesis_state.rs index 3c6612349a..bfcf822164 100644 --- a/eth2/state_processing/src/get_genesis_state.rs +++ b/eth2/state_processing/src/get_genesis_state.rs @@ -37,8 +37,7 @@ pub fn get_genesis_state( .get_active_validator_indices(spec.genesis_epoch, spec)? .to_vec(); let genesis_active_index_root = Hash256::from_slice(&active_validator_indices.hash_tree_root()); - state.latest_active_index_roots = - vec![genesis_active_index_root; spec.latest_active_index_roots_length as usize]; + state.fill_active_index_roots_with(genesis_active_index_root, spec); // Generate the current shuffling seed. state.current_shuffling_seed = state.generate_seed(spec.genesis_epoch, spec)?; diff --git a/eth2/state_processing/src/lib.rs b/eth2/state_processing/src/lib.rs index 78dc7270d2..6757b5dbd9 100644 --- a/eth2/state_processing/src/lib.rs +++ b/eth2/state_processing/src/lib.rs @@ -1,6 +1,7 @@ #[macro_use] mod macros; +pub mod common; pub mod get_genesis_state; pub mod per_block_processing; pub mod per_epoch_processing; diff --git a/eth2/state_processing/src/per_block_processing.rs b/eth2/state_processing/src/per_block_processing.rs index 14c72c53be..33f953b711 100644 --- a/eth2/state_processing/src/per_block_processing.rs +++ b/eth2/state_processing/src/per_block_processing.rs @@ -1,4 +1,5 @@ use self::verify_proposer_slashing::verify_proposer_slashing; +use crate::common::slash_validator; use errors::{BlockInvalid as Invalid, BlockProcessingError as Error, IntoWithIndex}; use rayon::prelude::*; use ssz::{SignedRoot, TreeHash}; @@ -222,7 +223,7 @@ pub fn process_proposer_slashings( // Update the state. for proposer_slashing in proposer_slashings { - state.slash_validator(proposer_slashing.proposer_index as usize, spec)?; + slash_validator(state, proposer_slashing.proposer_index as usize, spec)?; } Ok(()) @@ -279,7 +280,7 @@ pub fn process_attester_slashings( .map_err(|e| e.into_with_index(i))?; for i in slashable_indices { - state.slash_validator(i as usize, spec)?; + slash_validator(state, i as usize, spec)?; } } diff --git a/eth2/state_processing/src/per_block_processing/validate_attestation.rs b/eth2/state_processing/src/per_block_processing/validate_attestation.rs index 272eeb18b8..113dbc4ced 100644 --- a/eth2/state_processing/src/per_block_processing/validate_attestation.rs +++ b/eth2/state_processing/src/per_block_processing/validate_attestation.rs @@ -1,6 +1,6 @@ use super::errors::{AttestationInvalid as Invalid, AttestationValidationError as Error}; +use crate::common::verify_bitfield_length; use ssz::TreeHash; -use types::beacon_state::helpers::*; use types::*; /// Indicates if an `Attestation` is valid to be included in a block in the current epoch of the diff --git a/eth2/state_processing/src/per_block_processing/verify_slashable_attestation.rs b/eth2/state_processing/src/per_block_processing/verify_slashable_attestation.rs index aa9a321969..d3ab5e398f 100644 --- a/eth2/state_processing/src/per_block_processing/verify_slashable_attestation.rs +++ b/eth2/state_processing/src/per_block_processing/verify_slashable_attestation.rs @@ -1,8 +1,8 @@ use super::errors::{ SlashableAttestationInvalid as Invalid, SlashableAttestationValidationError as Error, }; +use crate::common::verify_bitfield_length; use ssz::TreeHash; -use types::beacon_state::helpers::verify_bitfield_length; use types::*; /// Indicates if a `SlashableAttestation` is valid to be included in a block in the current epoch of the given diff --git a/eth2/state_processing/src/per_epoch_processing.rs b/eth2/state_processing/src/per_epoch_processing.rs index d1bb4269a7..97a0e9987f 100644 --- a/eth2/state_processing/src/per_epoch_processing.rs +++ b/eth2/state_processing/src/per_epoch_processing.rs @@ -1,5 +1,8 @@ use errors::EpochProcessingError as Error; use integer_sqrt::IntegerSquareRoot; +use process_ejections::process_ejections; +use process_exit_queue::process_exit_queue; +use process_slashings::process_slashings; use process_validator_registry::process_validator_registry; use rayon::prelude::*; use ssz::TreeHash; @@ -11,8 +14,12 @@ use winning_root::{winning_root, WinningRoot}; pub mod errors; pub mod get_attestation_participants; pub mod inclusion_distance; +pub mod process_ejections; +pub mod process_exit_queue; +pub mod process_slashings; pub mod process_validator_registry; pub mod tests; +pub mod update_validator_registry; pub mod validator_statuses; pub mod winning_root; @@ -45,14 +52,16 @@ pub fn per_epoch_processing(state: &mut BeaconState, spec: &ChainSpec) -> Result process_rewards_and_penalities(state, &mut statuses, &winning_root_for_shards, spec)?; // Ejections - state.process_ejections(spec)?; + process_ejections(state, spec)?; // Validator Registry process_validator_registry(state, spec)?; + process_slashings(state, spec)?; + process_exit_queue(state, spec); // Final updates update_active_tree_index_roots(state, spec)?; - update_latest_slashed_balances(state, spec); + update_latest_slashed_balances(state, spec)?; clean_attestations(state); // Rotate the epoch caches to suit the epoch transition. @@ -451,9 +460,7 @@ pub fn update_active_tree_index_roots( ) .hash_tree_root(); - state.latest_active_index_roots[(next_epoch.as_usize() - + spec.activation_exit_delay as usize) - % spec.latest_active_index_roots_length] = Hash256::from_slice(&active_tree_root[..]); + state.set_active_index_root(next_epoch, Hash256::from_slice(&active_tree_root[..]), spec)?; Ok(()) } @@ -461,12 +468,20 @@ pub fn update_active_tree_index_roots( /// Advances the state's `latest_slashed_balances` field. /// /// Spec v0.4.0 -pub fn update_latest_slashed_balances(state: &mut BeaconState, spec: &ChainSpec) { +pub fn update_latest_slashed_balances( + state: &mut BeaconState, + spec: &ChainSpec, +) -> Result<(), Error> { let current_epoch = state.current_epoch(spec); let next_epoch = state.next_epoch(spec); - state.latest_slashed_balances[next_epoch.as_usize() % spec.latest_slashed_exit_length] = - state.latest_slashed_balances[current_epoch.as_usize() % spec.latest_slashed_exit_length]; + state.set_slashed_balance( + next_epoch, + state.get_slashed_balance(current_epoch, spec)?, + spec, + )?; + + Ok(()) } /// Removes all pending attestations from the previous epoch. diff --git a/eth2/state_processing/src/per_epoch_processing/get_attestation_participants.rs b/eth2/state_processing/src/per_epoch_processing/get_attestation_participants.rs index d822e434d1..3e52776b10 100644 --- a/eth2/state_processing/src/per_epoch_processing/get_attestation_participants.rs +++ b/eth2/state_processing/src/per_epoch_processing/get_attestation_participants.rs @@ -1,4 +1,5 @@ -use types::{beacon_state::helpers::verify_bitfield_length, *}; +use crate::common::verify_bitfield_length; +use types::*; /// Returns validator indices which participated in the attestation. /// diff --git a/eth2/state_processing/src/per_epoch_processing/process_ejections.rs b/eth2/state_processing/src/per_epoch_processing/process_ejections.rs new file mode 100644 index 0000000000..27dd37479d --- /dev/null +++ b/eth2/state_processing/src/per_epoch_processing/process_ejections.rs @@ -0,0 +1,28 @@ +use crate::common::exit_validator; +use types::{BeaconStateError as Error, *}; + +/// Iterate through the validator registry and eject active validators with balance below +/// ``EJECTION_BALANCE``. +/// +/// Spec v0.5.0 +pub fn process_ejections(state: &mut BeaconState, spec: &ChainSpec) -> Result<(), Error> { + // There is an awkward double (triple?) loop here because we can't loop across the borrowed + // active validator indices and mutate state in the one loop. + let exitable: Vec = state + .get_active_validator_indices(state.current_epoch(spec), spec)? + .iter() + .filter_map(|&i| { + if state.validator_balances[i as usize] < spec.ejection_balance { + Some(i) + } else { + None + } + }) + .collect(); + + for validator_index in exitable { + exit_validator(state, validator_index, spec)? + } + + Ok(()) +} diff --git a/eth2/state_processing/src/per_epoch_processing/process_exit_queue.rs b/eth2/state_processing/src/per_epoch_processing/process_exit_queue.rs new file mode 100644 index 0000000000..f672c97be3 --- /dev/null +++ b/eth2/state_processing/src/per_epoch_processing/process_exit_queue.rs @@ -0,0 +1,42 @@ +use types::*; + +/// Process the exit queue. +/// +/// Spec v0.5.0 +pub fn process_exit_queue(state: &mut BeaconState, spec: &ChainSpec) { + let current_epoch = state.current_epoch(spec); + + let eligible = |index: usize| { + let validator = &state.validator_registry[index]; + + if validator.withdrawable_epoch != spec.far_future_epoch { + false + } else { + current_epoch >= validator.exit_epoch + spec.min_validator_withdrawability_delay + } + }; + + let mut eligable_indices: Vec = (0..state.validator_registry.len()) + .filter(|i| eligible(*i)) + .collect(); + eligable_indices.sort_by_key(|i| state.validator_registry[*i].exit_epoch); + + for (withdrawn_so_far, index) in eligable_indices.iter().enumerate() { + if withdrawn_so_far as u64 >= spec.max_exit_dequeues_per_epoch { + break; + } + prepare_validator_for_withdrawal(state, *index, spec); + } +} + +/// Initiate an exit for the validator of the given `index`. +/// +/// Spec v0.5.0 +fn prepare_validator_for_withdrawal( + state: &mut BeaconState, + validator_index: usize, + spec: &ChainSpec, +) { + state.validator_registry[validator_index].withdrawable_epoch = + state.current_epoch(spec) + spec.min_validator_withdrawability_delay; +} diff --git a/eth2/state_processing/src/per_epoch_processing/process_slashings.rs b/eth2/state_processing/src/per_epoch_processing/process_slashings.rs new file mode 100644 index 0000000000..b14a9ee373 --- /dev/null +++ b/eth2/state_processing/src/per_epoch_processing/process_slashings.rs @@ -0,0 +1,36 @@ +use types::{BeaconStateError as Error, *}; + +/// Process slashings. +/// +/// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. +/// +/// Spec v0.4.0 +pub fn process_slashings(state: &mut BeaconState, spec: &ChainSpec) -> Result<(), Error> { + let current_epoch = state.current_epoch(spec); + let active_validator_indices = state.get_active_validator_indices(current_epoch, spec)?; + let total_balance = state.get_total_balance(&active_validator_indices[..], spec)?; + + for (index, validator) in state.validator_registry.iter().enumerate() { + if validator.slashed + && (current_epoch + == validator.withdrawable_epoch - Epoch::from(spec.latest_slashed_exit_length / 2)) + { + // TODO: check the following two lines are correct. + let total_at_start = state.get_slashed_balance(current_epoch + 1, spec)?; + let total_at_end = state.get_slashed_balance(current_epoch, spec)?; + + let total_penalities = total_at_end.saturating_sub(total_at_start); + + let effective_balance = state.get_effective_balance(index, spec)?; + let penalty = std::cmp::max( + effective_balance * std::cmp::min(total_penalities * 3, total_balance) + / total_balance, + effective_balance / spec.min_penalty_quotient, + ); + + safe_sub_assign!(state.validator_balances[index], penalty); + } + } + + Ok(()) +} diff --git a/eth2/state_processing/src/per_epoch_processing/process_validator_registry.rs b/eth2/state_processing/src/per_epoch_processing/process_validator_registry.rs index 26ebd60b33..2eb39711d2 100644 --- a/eth2/state_processing/src/per_epoch_processing/process_validator_registry.rs +++ b/eth2/state_processing/src/per_epoch_processing/process_validator_registry.rs @@ -1,3 +1,4 @@ +use super::update_validator_registry::update_validator_registry; use super::Error; use types::*; @@ -14,7 +15,7 @@ pub fn process_validator_registry(state: &mut BeaconState, spec: &ChainSpec) -> state.previous_shuffling_seed = state.current_shuffling_seed; if should_update_validator_registry(state, spec)? { - state.update_validator_registry(spec)?; + update_validator_registry(state, spec)?; state.current_shuffling_epoch = next_epoch; state.current_shuffling_start_shard = (state.current_shuffling_start_shard @@ -37,9 +38,6 @@ pub fn process_validator_registry(state: &mut BeaconState, spec: &ChainSpec) -> } } - state.process_slashings(spec)?; - state.process_exit_queue(spec); - Ok(()) } diff --git a/eth2/state_processing/src/per_epoch_processing/update_validator_registry.rs b/eth2/state_processing/src/per_epoch_processing/update_validator_registry.rs new file mode 100644 index 0000000000..8b612c3463 --- /dev/null +++ b/eth2/state_processing/src/per_epoch_processing/update_validator_registry.rs @@ -0,0 +1,51 @@ +use crate::common::exit_validator; +use types::{BeaconStateError as Error, *}; + +/// Update validator registry, activating/exiting validators if possible. +/// +/// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. +/// +/// Spec v0.4.0 +pub fn update_validator_registry(state: &mut BeaconState, spec: &ChainSpec) -> Result<(), Error> { + let current_epoch = state.current_epoch(spec); + let active_validator_indices = state.get_active_validator_indices(current_epoch, spec)?; + let total_balance = state.get_total_balance(&active_validator_indices[..], spec)?; + + let max_balance_churn = std::cmp::max( + spec.max_deposit_amount, + total_balance / (2 * spec.max_balance_churn_quotient), + ); + + let mut balance_churn = 0; + for index in 0..state.validator_registry.len() { + let validator = &state.validator_registry[index]; + + if (validator.activation_epoch == spec.far_future_epoch) + & (state.validator_balances[index] == spec.max_deposit_amount) + { + balance_churn += state.get_effective_balance(index, spec)?; + if balance_churn > max_balance_churn { + break; + } + state.activate_validator(index, false, spec); + } + } + + let mut balance_churn = 0; + for index in 0..state.validator_registry.len() { + let validator = &state.validator_registry[index]; + + if (validator.exit_epoch == spec.far_future_epoch) & (validator.initiated_exit) { + balance_churn += state.get_effective_balance(index, spec)?; + if balance_churn > max_balance_churn { + break; + } + + exit_validator(state, index, spec)?; + } + } + + state.validator_registry_update_epoch = current_epoch; + + Ok(()) +} diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index 1b2424774d..30f95c02c2 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -10,7 +10,6 @@ use ssz_derive::{Decode, Encode, TreeHash}; use test_random_derive::TestRandom; mod epoch_cache; -pub mod helpers; mod pubkey_cache; mod tests; @@ -19,17 +18,13 @@ pub const CACHED_EPOCHS: usize = 4; #[derive(Debug, PartialEq)] pub enum Error { EpochOutOfBounds, - /// The supplied shard is unknown. It may be larger than the maximum shard count, or not in a - /// committee for the given slot. SlotOutOfBounds, ShardOutOfBounds, - UnableToShuffle, UnknownValidator, + UnableToDetermineProducer, InvalidBitfield, ValidatorIsWithdrawable, InsufficientRandaoMixes, - NoValidators, - UnableToDetermineProducer, InsufficientBlockRoots, InsufficientIndexRoots, InsufficientAttestations, @@ -37,27 +32,16 @@ pub enum Error { InsufficientSlashedBalances, InsufficientStateRoots, NoCommitteeForShard, - EpochCacheUninitialized(RelativeEpoch), PubkeyCacheInconsistent, PubkeyCacheIncomplete { cache_len: usize, registry_len: usize, }, + EpochCacheUninitialized(RelativeEpoch), RelativeEpochError(RelativeEpochError), EpochCacheError(EpochCacheError), } -macro_rules! safe_add_assign { - ($a: expr, $b: expr) => { - $a = $a.saturating_add($b); - }; -} -macro_rules! safe_sub_assign { - ($a: expr, $b: expr) => { - $a = $a.saturating_sub($b); - }; -} - /// The state of the `BeaconChain` at some slot. /// /// Spec v0.5.0 @@ -95,10 +79,10 @@ pub struct BeaconState { // Recent state pub latest_crosslinks: Vec, - pub latest_block_roots: Vec, - pub latest_state_roots: Vec, - pub latest_active_index_roots: Vec, - pub latest_slashed_balances: Vec, + latest_block_roots: Vec, + latest_state_roots: Vec, + latest_active_index_roots: Vec, + latest_slashed_balances: Vec, pub latest_block_header: BeaconBlockHeader, pub historical_roots: Vec, @@ -209,6 +193,458 @@ impl BeaconState { Hash256::from_slice(&self.hash_tree_root()[..]) } + /// If a validator pubkey exists in the validator registry, returns `Some(i)`, otherwise + /// returns `None`. + /// + /// Requires a fully up-to-date `pubkey_cache`, returns an error if this is not the case. + pub fn get_validator_index(&self, pubkey: &PublicKey) -> Result, Error> { + if self.pubkey_cache.len() == self.validator_registry.len() { + Ok(self.pubkey_cache.get(pubkey)) + } else { + Err(Error::PubkeyCacheIncomplete { + cache_len: self.pubkey_cache.len(), + registry_len: self.validator_registry.len(), + }) + } + } + + /// The epoch corresponding to `self.slot`. + /// + /// Spec v0.5.0 + pub fn current_epoch(&self, spec: &ChainSpec) -> Epoch { + self.slot.epoch(spec.slots_per_epoch) + } + + /// The epoch prior to `self.current_epoch()`. + /// + /// If the current epoch is the genesis epoch, the genesis_epoch is returned. + /// + /// Spec v0.5.0 + pub fn previous_epoch(&self, spec: &ChainSpec) -> Epoch { + self.current_epoch(&spec) - 1 + } + + /// The epoch following `self.current_epoch()`. + /// + /// Spec v0.5.0 + pub fn next_epoch(&self, spec: &ChainSpec) -> Epoch { + self.current_epoch(spec) + 1 + } + + /// Returns the active validator indices for the given epoch, assuming there is no validator + /// registry update in the next epoch. + /// + /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. + /// + /// Spec v0.5.0 + pub fn get_active_validator_indices( + &self, + epoch: Epoch, + spec: &ChainSpec, + ) -> Result<&[usize], Error> { + // If the slot is in the next epoch, assume there was no validator registry update. + let relative_epoch = + match RelativeEpoch::from_epoch(self.slot.epoch(spec.slots_per_epoch), epoch) { + Err(RelativeEpochError::AmbiguiousNextEpoch) => { + Ok(RelativeEpoch::NextWithoutRegistryChange) + } + e => e, + }?; + + let cache = self.cache(relative_epoch, spec)?; + + Ok(&cache.active_validator_indices) + } + + /// Returns the crosslink committees for some slot. + /// + /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. + /// + /// Spec v0.5.0 + pub fn get_crosslink_committees_at_slot( + &self, + slot: Slot, + spec: &ChainSpec, + ) -> Result<&Vec, Error> { + // If the slot is in the next epoch, assume there was no validator registry update. + let relative_epoch = match RelativeEpoch::from_slot(self.slot, slot, spec) { + Err(RelativeEpochError::AmbiguiousNextEpoch) => { + Ok(RelativeEpoch::NextWithoutRegistryChange) + } + e => e, + }?; + + let cache = self.cache(relative_epoch, spec)?; + + Ok(cache + .get_crosslink_committees_at_slot(slot, spec) + .ok_or_else(|| Error::SlotOutOfBounds)?) + } + + /// Returns the crosslink committees for some shard in an epoch. + /// + /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. + /// + /// Spec v0.5.0 + pub fn get_crosslink_committee_for_shard( + &self, + epoch: Epoch, + shard: Shard, + spec: &ChainSpec, + ) -> Result<&CrosslinkCommittee, Error> { + // If the slot is in the next epoch, assume there was no validator registry update. + let relative_epoch = match RelativeEpoch::from_epoch(self.current_epoch(spec), epoch) { + Err(RelativeEpochError::AmbiguiousNextEpoch) => { + Ok(RelativeEpoch::NextWithoutRegistryChange) + } + e => e, + }?; + + let cache = self.cache(relative_epoch, spec)?; + + Ok(cache + .get_crosslink_committee_for_shard(shard, spec) + .ok_or_else(|| Error::NoCommitteeForShard)?) + } + + /// Returns the beacon proposer index for the `slot`. + /// + /// If the state does not contain an index for a beacon proposer at the requested `slot`, then `None` is returned. + /// + /// Spec v0.5.0 + pub fn get_beacon_proposer_index( + &self, + slot: Slot, + relative_epoch: RelativeEpoch, + spec: &ChainSpec, + ) -> Result { + let cache = self.cache(relative_epoch, spec)?; + + let committees = cache + .get_crosslink_committees_at_slot(slot, spec) + .ok_or_else(|| Error::SlotOutOfBounds)?; + + let epoch = slot.epoch(spec.slots_per_epoch); + + committees + .first() + .ok_or(Error::UnableToDetermineProducer) + .and_then(|first| { + let index = epoch + .as_usize() + .checked_rem(first.committee.len()) + .ok_or(Error::UnableToDetermineProducer)?; + Ok(first.committee[index]) + }) + } + + /// Safely obtains the index for latest block roots, given some `slot`. + /// + /// Spec v0.5.0 + fn get_latest_block_roots_index(&self, slot: Slot, spec: &ChainSpec) -> Result { + if (slot < self.slot) && (self.slot <= slot + spec.slots_per_historical_root as u64) { + let i = slot.as_usize() % spec.slots_per_historical_root; + if i >= self.latest_block_roots.len() { + Err(Error::InsufficientStateRoots) + } else { + Ok(i) + } + } else { + Err(BeaconStateError::SlotOutOfBounds) + } + } + + /// Return the block root at a recent `slot`. + /// + /// Spec v0.5.0 + pub fn get_block_root( + &self, + slot: Slot, + spec: &ChainSpec, + ) -> Result<&Hash256, BeaconStateError> { + let i = self.get_latest_block_roots_index(slot, spec)?; + Ok(&self.latest_block_roots[i]) + } + + /// Sets the block root for some given slot. + /// + /// Spec v0.5.0 + pub fn set_block_root( + &mut self, + slot: Slot, + block_root: Hash256, + spec: &ChainSpec, + ) -> Result<(), BeaconStateError> { + let i = self.get_latest_block_roots_index(slot, spec)?; + Ok(self.latest_block_roots[i] = block_root) + } + + /// XOR-assigns the existing `epoch` randao mix with the hash of the `signature`. + /// + /// # Errors: + /// + /// See `Self::get_randao_mix`. + /// + /// Spec v0.5.0 + pub fn update_randao_mix( + &mut self, + epoch: Epoch, + signature: &Signature, + spec: &ChainSpec, + ) -> Result<(), Error> { + let i = epoch.as_usize() % spec.latest_randao_mixes_length; + + let signature_hash = Hash256::from_slice(&hash(&ssz_encode(signature))); + + self.latest_randao_mixes[i] = *self.get_randao_mix(epoch, spec)? ^ signature_hash; + + Ok(()) + } + + /// Return the randao mix at a recent ``epoch``. + /// + /// # Errors: + /// - `InsufficientRandaoMixes` if `self.latest_randao_mixes` is shorter than + /// `spec.latest_randao_mixes_length`. + /// - `EpochOutOfBounds` if the state no longer stores randao mixes for the given `epoch`. + /// + /// Spec v0.5.0 + pub fn get_randao_mix(&self, epoch: Epoch, spec: &ChainSpec) -> Result<&Hash256, Error> { + let current_epoch = self.current_epoch(spec); + + if (current_epoch - (spec.latest_randao_mixes_length as u64) < epoch) + & (epoch <= current_epoch) + { + self.latest_randao_mixes + .get(epoch.as_usize() % spec.latest_randao_mixes_length) + .ok_or_else(|| Error::InsufficientRandaoMixes) + } else { + Err(Error::EpochOutOfBounds) + } + } + + /// Safely obtains the index for `latest_active_index_roots`, given some `epoch`. + /// + /// Spec v0.5.0 + fn get_active_index_root_index(&self, epoch: Epoch, spec: &ChainSpec) -> Result { + let current_epoch = self.current_epoch(spec); + + if (current_epoch - spec.latest_active_index_roots_length as u64 + + spec.activation_exit_delay + < epoch) + & (epoch <= current_epoch + spec.activation_exit_delay) + { + let i = epoch.as_usize() % spec.latest_active_index_roots_length; + if i < self.latest_active_index_roots.len() { + Ok(i) + } else { + Err(Error::InsufficientIndexRoots) + } + } else { + Err(Error::EpochOutOfBounds) + } + } + + /// Return the `active_index_root` at a recent `epoch`. + /// + /// Spec v0.5.0 + pub fn get_active_index_root(&self, epoch: Epoch, spec: &ChainSpec) -> Result { + let i = self.get_active_index_root_index(epoch, spec)?; + Ok(self.latest_active_index_roots[i]) + } + + /// Set the `active_index_root` at a recent `epoch`. + /// + /// Spec v0.5.0 + pub fn set_active_index_root( + &mut self, + epoch: Epoch, + index_root: Hash256, + spec: &ChainSpec, + ) -> Result<(), Error> { + let i = self.get_active_index_root_index(epoch, spec)?; + Ok(self.latest_active_index_roots[i] = index_root) + } + + /// Replace `active_index_roots` with clones of `index_root`. + /// + /// Spec v0.5.0 + pub fn fill_active_index_roots_with(&mut self, index_root: Hash256, spec: &ChainSpec) { + self.latest_active_index_roots = + vec![index_root; spec.latest_active_index_roots_length as usize] + } + + /// Safely obtains the index for latest state roots, given some `slot`. + /// + /// Spec v0.5.0 + fn get_latest_state_roots_index(&self, slot: Slot, spec: &ChainSpec) -> Result { + if (slot < self.slot) && (self.slot <= slot + spec.slots_per_historical_root as u64) { + let i = slot.as_usize() % spec.slots_per_historical_root; + if i >= self.latest_state_roots.len() { + Err(Error::InsufficientStateRoots) + } else { + Ok(i) + } + } else { + Err(BeaconStateError::SlotOutOfBounds) + } + } + + /// Gets the state root for some slot. + /// + /// Spec v0.5.0 + pub fn get_state_root(&mut self, slot: Slot, spec: &ChainSpec) -> Result<&Hash256, Error> { + let i = self.get_latest_state_roots_index(slot, spec)?; + Ok(&self.latest_state_roots[i]) + } + + /// Sets the latest state root for slot. + /// + /// Spec v0.5.0 + pub fn set_state_root( + &mut self, + slot: Slot, + state_root: Hash256, + spec: &ChainSpec, + ) -> Result<(), Error> { + let i = self.get_latest_state_roots_index(slot, spec)?; + Ok(self.latest_state_roots[i] = state_root) + } + + /// Safely obtains the index for `latest_slashed_balances`, given some `epoch`. + /// + /// Spec v0.5.0 + fn get_slashed_balance_index(&self, epoch: Epoch, spec: &ChainSpec) -> Result { + let i = epoch.as_usize() % spec.latest_slashed_exit_length; + + // NOTE: the validity of the epoch is not checked. It is not in the spec but it's probably + // useful to have. + if i < self.latest_slashed_balances.len() { + Ok(i) + } else { + Err(Error::InsufficientSlashedBalances) + } + } + + /// Gets the total slashed balances for some epoch. + /// + /// Spec v0.5.0 + pub fn get_slashed_balance(&self, epoch: Epoch, spec: &ChainSpec) -> Result { + let i = self.get_slashed_balance_index(epoch, spec)?; + Ok(self.latest_slashed_balances[i]) + } + + /// Sets the total slashed balances for some epoch. + /// + /// Spec v0.5.0 + pub fn set_slashed_balance( + &mut self, + epoch: Epoch, + balance: u64, + spec: &ChainSpec, + ) -> Result<(), Error> { + let i = self.get_slashed_balance_index(epoch, spec)?; + Ok(self.latest_slashed_balances[i] = balance) + } + + /// Generate a seed for the given `epoch`. + /// + /// Spec v0.5.0 + pub fn generate_seed(&self, epoch: Epoch, spec: &ChainSpec) -> Result { + let mut input = self + .get_randao_mix(epoch - spec.min_seed_lookahead, spec)? + .as_bytes() + .to_vec(); + + input.append(&mut self.get_active_index_root(epoch, spec)?.as_bytes().to_vec()); + + input.append(&mut int_to_bytes32(epoch.as_u64())); + + Ok(Hash256::from_slice(&hash(&input[..])[..])) + } + + /// Return the effective balance (also known as "balance at stake") for a validator with the given ``index``. + /// + /// Spec v0.5.0 + pub fn get_effective_balance( + &self, + validator_index: usize, + spec: &ChainSpec, + ) -> Result { + let balance = self + .validator_balances + .get(validator_index) + .ok_or_else(|| Error::UnknownValidator)?; + Ok(std::cmp::min(*balance, spec.max_deposit_amount)) + } + + /// Return the epoch at which an activation or exit triggered in ``epoch`` takes effect. + /// + /// Spec v0.5.0 + pub fn get_delayed_activation_exit_epoch(&self, epoch: Epoch, spec: &ChainSpec) -> Epoch { + epoch + 1 + spec.activation_exit_delay + } + + /// Activate the validator of the given ``index``. + /// + /// Spec v0.5.0 + pub fn activate_validator( + &mut self, + validator_index: usize, + is_genesis: bool, + spec: &ChainSpec, + ) { + let current_epoch = self.current_epoch(spec); + + self.validator_registry[validator_index].activation_epoch = if is_genesis { + spec.genesis_epoch + } else { + self.get_delayed_activation_exit_epoch(current_epoch, spec) + } + } + + /// Initiate an exit for the validator of the given `index`. + /// + /// Spec v0.5.0 + pub fn initiate_validator_exit(&mut self, validator_index: usize) { + self.validator_registry[validator_index].initiated_exit = true; + } + + /// Returns the `slot`, `shard` and `committee_index` for which a validator must produce an + /// attestation. + /// + /// Only reads the current epoch. + /// + /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. + /// + /// Spec v0.5.0 + pub fn get_attestation_duties( + &self, + validator_index: usize, + spec: &ChainSpec, + ) -> Result<&Option, Error> { + let cache = self.cache(RelativeEpoch::Current, spec)?; + + Ok(cache + .attestation_duties + .get(validator_index) + .ok_or_else(|| Error::UnknownValidator)?) + } + + /// Return the combined effective balance of an array of validators. + /// + /// Spec v0.5.0 + pub fn get_total_balance( + &self, + validator_indices: &[usize], + spec: &ChainSpec, + ) -> Result { + validator_indices.iter().try_fold(0_u64, |acc, i| { + self.get_effective_balance(*i, spec) + .and_then(|bal| Ok(bal + acc)) + }) + } + /// Build an epoch cache, unless it is has already been built. pub fn build_epoch_cache( &mut self, @@ -311,633 +747,6 @@ impl BeaconState { pub fn drop_pubkey_cache(&mut self) { self.pubkey_cache = PubkeyCache::default() } - - /// If a validator pubkey exists in the validator registry, returns `Some(i)`, otherwise - /// returns `None`. - /// - /// Requires a fully up-to-date `pubkey_cache`, returns an error if this is not the case. - pub fn get_validator_index(&self, pubkey: &PublicKey) -> Result, Error> { - if self.pubkey_cache.len() == self.validator_registry.len() { - Ok(self.pubkey_cache.get(pubkey)) - } else { - Err(Error::PubkeyCacheIncomplete { - cache_len: self.pubkey_cache.len(), - registry_len: self.validator_registry.len(), - }) - } - } - - /// The epoch corresponding to `self.slot`. - /// - /// Spec v0.5.0 - pub fn current_epoch(&self, spec: &ChainSpec) -> Epoch { - self.slot.epoch(spec.slots_per_epoch) - } - - /// The epoch prior to `self.current_epoch()`. - /// - /// If the current epoch is the genesis epoch, the genesis_epoch is returned. - /// - /// Spec v0.5.0 - pub fn previous_epoch(&self, spec: &ChainSpec) -> Epoch { - self.current_epoch(&spec) - 1 - } - - /// The epoch following `self.current_epoch()`. - /// - /// Spec v0.5.0 - pub fn next_epoch(&self, spec: &ChainSpec) -> Epoch { - self.current_epoch(spec) + 1 - } - - /// Returns the active validator indices for the given epoch, assuming there is no validator - /// registry update in the next epoch. - /// - /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. - /// - /// Spec v0.5.0 - pub fn get_active_validator_indices( - &self, - epoch: Epoch, - spec: &ChainSpec, - ) -> Result<&[usize], Error> { - // If the slot is in the next epoch, assume there was no validator registry update. - let relative_epoch = - match RelativeEpoch::from_epoch(self.slot.epoch(spec.slots_per_epoch), epoch) { - Err(RelativeEpochError::AmbiguiousNextEpoch) => { - Ok(RelativeEpoch::NextWithoutRegistryChange) - } - e => e, - }?; - - let cache = self.cache(relative_epoch, spec)?; - - Ok(&cache.active_validator_indices) - } - - /// Returns the crosslink committees for some slot. - /// - /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. - /// - /// Spec v0.5.0 - pub fn get_crosslink_committees_at_slot( - &self, - slot: Slot, - spec: &ChainSpec, - ) -> Result<&Vec, Error> { - // If the slot is in the next epoch, assume there was no validator registry update. - let relative_epoch = match RelativeEpoch::from_slot(self.slot, slot, spec) { - Err(RelativeEpochError::AmbiguiousNextEpoch) => { - Ok(RelativeEpoch::NextWithoutRegistryChange) - } - e => e, - }?; - - let cache = self.cache(relative_epoch, spec)?; - - Ok(cache - .get_crosslink_committees_at_slot(slot, spec) - .ok_or_else(|| Error::SlotOutOfBounds)?) - } - - /// Returns the crosslink committees for some shard in an epoch. - /// - /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. - /// - /// Spec v0.4.0 - pub fn get_crosslink_committee_for_shard( - &self, - epoch: Epoch, - shard: Shard, - spec: &ChainSpec, - ) -> Result<&CrosslinkCommittee, Error> { - // If the slot is in the next epoch, assume there was no validator registry update. - let relative_epoch = match RelativeEpoch::from_epoch(self.current_epoch(spec), epoch) { - Err(RelativeEpochError::AmbiguiousNextEpoch) => { - Ok(RelativeEpoch::NextWithoutRegistryChange) - } - e => e, - }?; - - let cache = self.cache(relative_epoch, spec)?; - - Ok(cache - .get_crosslink_committee_for_shard(shard, spec) - .ok_or_else(|| Error::NoCommitteeForShard)?) - } - - /// Safely obtains the index for latest block roots, given some `slot`. - /// - /// Spec v0.5.0 - fn get_latest_block_roots_index(&self, slot: Slot, spec: &ChainSpec) -> Result { - if (slot < self.slot) && (self.slot <= slot + spec.slots_per_historical_root as u64) { - let i = slot.as_usize() % spec.slots_per_historical_root; - if i >= self.latest_block_roots.len() { - Err(Error::InsufficientStateRoots) - } else { - Ok(i) - } - } else { - Err(BeaconStateError::SlotOutOfBounds) - } - } - - /// Return the block root at a recent `slot`. - /// - /// Spec v0.5.0 - pub fn get_block_root( - &self, - slot: Slot, - spec: &ChainSpec, - ) -> Result<&Hash256, BeaconStateError> { - let i = self.get_latest_block_roots_index(slot, spec)?; - Ok(&self.latest_block_roots[i]) - } - - /// Sets the block root for some given slot. - /// - /// Spec v0.5.0 - pub fn set_block_root( - &mut self, - slot: Slot, - block_root: Hash256, - spec: &ChainSpec, - ) -> Result<(), BeaconStateError> { - let i = self.get_latest_block_roots_index(slot, spec)?; - Ok(self.latest_block_roots[i] = block_root) - } - - /// XOR-assigns the existing `epoch` randao mix with the hash of the `signature`. - /// - /// # Errors: - /// - /// See `Self::get_randao_mix`. - /// - /// Spec v0.5.0 - pub fn update_randao_mix( - &mut self, - epoch: Epoch, - signature: &Signature, - spec: &ChainSpec, - ) -> Result<(), Error> { - let i = epoch.as_usize() % spec.latest_randao_mixes_length; - - let signature_hash = Hash256::from_slice(&hash(&ssz_encode(signature))); - - self.latest_randao_mixes[i] = *self.get_randao_mix(epoch, spec)? ^ signature_hash; - - Ok(()) - } - - /// Return the randao mix at a recent ``epoch``. - /// - /// # Errors: - /// - `InsufficientRandaoMixes` if `self.latest_randao_mixes` is shorter than - /// `spec.latest_randao_mixes_length`. - /// - `EpochOutOfBounds` if the state no longer stores randao mixes for the given `epoch`. - /// - /// Spec v0.5.0 - pub fn get_randao_mix(&self, epoch: Epoch, spec: &ChainSpec) -> Result<&Hash256, Error> { - let current_epoch = self.current_epoch(spec); - - if (current_epoch - (spec.latest_randao_mixes_length as u64) < epoch) - & (epoch <= current_epoch) - { - self.latest_randao_mixes - .get(epoch.as_usize() % spec.latest_randao_mixes_length) - .ok_or_else(|| Error::InsufficientRandaoMixes) - } else { - Err(Error::EpochOutOfBounds) - } - } - - /// Return the index root at a recent `epoch`. - /// - /// Spec v0.4.0 - pub fn get_active_index_root(&self, epoch: Epoch, spec: &ChainSpec) -> Option { - let current_epoch = self.current_epoch(spec); - - if (current_epoch - spec.latest_active_index_roots_length as u64 - + spec.activation_exit_delay - < epoch) - & (epoch <= current_epoch + spec.activation_exit_delay) - { - Some( - self.latest_active_index_roots - [epoch.as_usize() % spec.latest_active_index_roots_length], - ) - } else { - None - } - } - - /// Safely obtains the index for latest state roots, given some `slot`. - /// - /// Spec v0.5.0 - fn get_latest_state_roots_index(&self, slot: Slot, spec: &ChainSpec) -> Result { - if (slot < self.slot) && (self.slot <= slot + spec.slots_per_historical_root as u64) { - let i = slot.as_usize() % spec.slots_per_historical_root; - if i >= self.latest_state_roots.len() { - Err(Error::InsufficientStateRoots) - } else { - Ok(i) - } - } else { - Err(BeaconStateError::SlotOutOfBounds) - } - } - - /// Gets the state root for some slot. - /// - /// Spec v0.5.0 - pub fn get_state_root(&mut self, slot: Slot, spec: &ChainSpec) -> Result<&Hash256, Error> { - let i = self.get_latest_state_roots_index(slot, spec)?; - Ok(&self.latest_state_roots[i]) - } - - /// Sets the latest state root for slot. - /// - /// Spec v0.5.0 - pub fn set_state_root( - &mut self, - slot: Slot, - state_root: Hash256, - spec: &ChainSpec, - ) -> Result<(), Error> { - let i = self.get_latest_state_roots_index(slot, spec)?; - Ok(self.latest_state_roots[i] = state_root) - } - - /// Generate a seed for the given `epoch`. - /// - /// Spec v0.4.0 - pub fn generate_seed(&self, epoch: Epoch, spec: &ChainSpec) -> Result { - let mut input = self - .get_randao_mix(epoch - spec.min_seed_lookahead, spec)? - .as_bytes() - .to_vec(); - - input.append( - &mut self - .get_active_index_root(epoch, spec) - .ok_or_else(|| Error::InsufficientIndexRoots)? - .as_bytes() - .to_vec(), - ); - - input.append(&mut int_to_bytes32(epoch.as_u64())); - - Ok(Hash256::from_slice(&hash(&input[..])[..])) - } - - /// Returns the beacon proposer index for the `slot`. - /// - /// If the state does not contain an index for a beacon proposer at the requested `slot`, then `None` is returned. - /// - /// Spec v0.5.0 - pub fn get_beacon_proposer_index( - &self, - slot: Slot, - relative_epoch: RelativeEpoch, - spec: &ChainSpec, - ) -> Result { - let cache = self.cache(relative_epoch, spec)?; - - let committees = cache - .get_crosslink_committees_at_slot(slot, spec) - .ok_or_else(|| Error::SlotOutOfBounds)?; - - let epoch = slot.epoch(spec.slots_per_epoch); - - committees - .first() - .ok_or(Error::UnableToDetermineProducer) - .and_then(|first| { - let index = epoch - .as_usize() - .checked_rem(first.committee.len()) - .ok_or(Error::UnableToDetermineProducer)?; - Ok(first.committee[index]) - }) - } - - /// Return the effective balance (also known as "balance at stake") for a validator with the given ``index``. - /// - /// Spec v0.4.0 - pub fn get_effective_balance( - &self, - validator_index: usize, - spec: &ChainSpec, - ) -> Result { - let balance = self - .validator_balances - .get(validator_index) - .ok_or_else(|| Error::UnknownValidator)?; - Ok(std::cmp::min(*balance, spec.max_deposit_amount)) - } - - /// Return the epoch at which an activation or exit triggered in ``epoch`` takes effect. - /// - /// Spec v0.4.0 - pub fn get_delayed_activation_exit_epoch(&self, epoch: Epoch, spec: &ChainSpec) -> Epoch { - epoch + 1 + spec.activation_exit_delay - } - - /// Activate the validator of the given ``index``. - /// - /// Spec v0.5.0 - pub fn activate_validator( - &mut self, - validator_index: usize, - is_genesis: bool, - spec: &ChainSpec, - ) { - let current_epoch = self.current_epoch(spec); - - self.validator_registry[validator_index].activation_epoch = if is_genesis { - spec.genesis_epoch - } else { - self.get_delayed_activation_exit_epoch(current_epoch, spec) - } - } - - /// Initiate an exit for the validator of the given `index`. - /// - /// Spec v0.5.0 - pub fn initiate_validator_exit(&mut self, validator_index: usize) { - self.validator_registry[validator_index].initiated_exit = true; - } - - /// Exit the validator of the given `index`. - /// - /// Spec v0.4.0 - fn exit_validator(&mut self, validator_index: usize, spec: &ChainSpec) { - let current_epoch = self.current_epoch(spec); - let delayed_epoch = self.get_delayed_activation_exit_epoch(current_epoch, spec); - - if self.validator_registry[validator_index].exit_epoch <= delayed_epoch { - return; - } - - self.validator_registry[validator_index].exit_epoch = delayed_epoch; - } - - /// Slash the validator with index ``index``. - /// - /// Spec v0.5.0 - pub fn slash_validator( - &mut self, - validator_index: usize, - spec: &ChainSpec, - ) -> Result<(), Error> { - let current_epoch = self.current_epoch(spec); - - let validator = &self - .validator_registry - .get(validator_index) - .ok_or_else(|| Error::UnknownValidator)?; - let effective_balance = self.get_effective_balance(validator_index, spec)?; - - // A validator that is withdrawn cannot be slashed. - // - // This constraint will be lifted in Phase 0. - if self.slot - >= validator - .withdrawable_epoch - .start_slot(spec.slots_per_epoch) - { - return Err(Error::ValidatorIsWithdrawable); - } - - self.exit_validator(validator_index, spec); - - self.increment_current_epoch_slashed_balances(effective_balance, spec)?; - - let whistleblower_index = - self.get_beacon_proposer_index(self.slot, RelativeEpoch::Current, spec)?; - let whistleblower_reward = effective_balance / spec.whistleblower_reward_quotient; - - safe_add_assign!( - self.validator_balances[whistleblower_index as usize], - whistleblower_reward - ); - safe_sub_assign!( - self.validator_balances[validator_index], - whistleblower_reward - ); - - self.validator_registry[validator_index].slashed = true; - - self.validator_registry[validator_index].withdrawable_epoch = - current_epoch + Epoch::from(spec.latest_slashed_exit_length); - - Ok(()) - } - - /// Increment `self.latest_slashed_balances` with a slashing from the current epoch. - /// - /// Spec v0.5.0. - fn increment_current_epoch_slashed_balances( - &mut self, - increment: u64, - spec: &ChainSpec, - ) -> Result<(), Error> { - let current_epoch = self.current_epoch(spec); - - let slashed_balances_index = current_epoch.as_usize() % spec.latest_slashed_exit_length; - if slashed_balances_index >= self.latest_slashed_balances.len() { - return Err(Error::InsufficientSlashedBalances); - } - - self.latest_slashed_balances[slashed_balances_index] += increment; - - Ok(()) - } - - /// Initiate an exit for the validator of the given `index`. - /// - /// Spec v0.4.0 - pub fn prepare_validator_for_withdrawal(&mut self, validator_index: usize, spec: &ChainSpec) { - //TODO: we're not ANDing here, we're setting. Potentially wrong. - self.validator_registry[validator_index].withdrawable_epoch = - self.current_epoch(spec) + spec.min_validator_withdrawability_delay; - } - - /// Returns the `slot`, `shard` and `committee_index` for which a validator must produce an - /// attestation. - /// - /// Only reads the current epoch. - /// - /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. - /// - /// Spec v0.4.0 - pub fn get_attestation_duties( - &self, - validator_index: usize, - spec: &ChainSpec, - ) -> Result<&Option, Error> { - let cache = self.cache(RelativeEpoch::Current, spec)?; - - Ok(cache - .attestation_duties - .get(validator_index) - .ok_or_else(|| Error::UnknownValidator)?) - } - - /// Process slashings. - /// - /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. - /// - /// Spec v0.4.0 - pub fn process_slashings(&mut self, spec: &ChainSpec) -> Result<(), Error> { - let current_epoch = self.current_epoch(spec); - let active_validator_indices = self.get_active_validator_indices(current_epoch, spec)?; - let total_balance = self.get_total_balance(&active_validator_indices[..], spec)?; - - for (index, validator) in self.validator_registry.iter().enumerate() { - if validator.slashed - && (current_epoch - == validator.withdrawable_epoch - - Epoch::from(spec.latest_slashed_exit_length / 2)) - { - let epoch_index: usize = current_epoch.as_usize() % spec.latest_slashed_exit_length; - - let total_at_start = self.latest_slashed_balances - [(epoch_index + 1) % spec.latest_slashed_exit_length]; - let total_at_end = self.latest_slashed_balances[epoch_index]; - let total_penalities = total_at_end.saturating_sub(total_at_start); - - let effective_balance = self.get_effective_balance(index, spec)?; - let penalty = std::cmp::max( - effective_balance * std::cmp::min(total_penalities * 3, total_balance) - / total_balance, - effective_balance / spec.min_penalty_quotient, - ); - - safe_sub_assign!(self.validator_balances[index], penalty); - } - } - - Ok(()) - } - - /// Process the exit queue. - /// - /// Spec v0.4.0 - pub fn process_exit_queue(&mut self, spec: &ChainSpec) { - let current_epoch = self.current_epoch(spec); - - let eligible = |index: usize| { - let validator = &self.validator_registry[index]; - - if validator.withdrawable_epoch != spec.far_future_epoch { - false - } else { - current_epoch >= validator.exit_epoch + spec.min_validator_withdrawability_delay - } - }; - - let mut eligable_indices: Vec = (0..self.validator_registry.len()) - .filter(|i| eligible(*i)) - .collect(); - eligable_indices.sort_by_key(|i| self.validator_registry[*i].exit_epoch); - - for (withdrawn_so_far, index) in eligable_indices.iter().enumerate() { - if withdrawn_so_far as u64 >= spec.max_exit_dequeues_per_epoch { - break; - } - self.prepare_validator_for_withdrawal(*index, spec); - } - } - - /// Update validator registry, activating/exiting validators if possible. - /// - /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. - /// - /// Spec v0.4.0 - pub fn update_validator_registry(&mut self, spec: &ChainSpec) -> Result<(), Error> { - let current_epoch = self.current_epoch(spec); - let active_validator_indices = self.get_active_validator_indices(current_epoch, spec)?; - let total_balance = self.get_total_balance(&active_validator_indices[..], spec)?; - - let max_balance_churn = std::cmp::max( - spec.max_deposit_amount, - total_balance / (2 * spec.max_balance_churn_quotient), - ); - - let mut balance_churn = 0; - for index in 0..self.validator_registry.len() { - let validator = &self.validator_registry[index]; - - if (validator.activation_epoch == spec.far_future_epoch) - & (self.validator_balances[index] == spec.max_deposit_amount) - { - balance_churn += self.get_effective_balance(index, spec)?; - if balance_churn > max_balance_churn { - break; - } - self.activate_validator(index, false, spec); - } - } - - let mut balance_churn = 0; - for index in 0..self.validator_registry.len() { - let validator = &self.validator_registry[index]; - - if (validator.exit_epoch == spec.far_future_epoch) & (validator.initiated_exit) { - balance_churn += self.get_effective_balance(index, spec)?; - if balance_churn > max_balance_churn { - break; - } - - self.exit_validator(index, spec); - } - } - - self.validator_registry_update_epoch = current_epoch; - - Ok(()) - } - - /// Iterate through the validator registry and eject active validators with balance below - /// ``EJECTION_BALANCE``. - /// - /// Spec v0.5.0 - pub fn process_ejections(&mut self, spec: &ChainSpec) -> Result<(), Error> { - // There is an awkward double (triple?) loop here because we can't loop across the borrowed - // active validator indices and mutate state in the one loop. - let exitable: Vec = self - .get_active_validator_indices(self.current_epoch(spec), spec)? - .iter() - .filter_map(|&i| { - if self.validator_balances[i as usize] < spec.ejection_balance { - Some(i) - } else { - None - } - }) - .collect(); - - for validator_index in exitable { - self.exit_validator(validator_index, spec) - } - - Ok(()) - } - - /// Return the combined effective balance of an array of validators. - /// - /// Spec v0.5.0 - pub fn get_total_balance( - &self, - validator_indices: &[usize], - spec: &ChainSpec, - ) -> Result { - validator_indices.iter().try_fold(0_u64, |acc, i| { - self.get_effective_balance(*i, spec) - .and_then(|bal| Ok(bal + acc)) - }) - } } impl From for Error {