diff --git a/eth2/types/src/attestation_duty.rs b/eth2/types/src/attestation_duty.rs new file mode 100644 index 000000000..f6e86d263 --- /dev/null +++ b/eth2/types/src/attestation_duty.rs @@ -0,0 +1,9 @@ +use crate::*; +use serde_derive::{Deserialize, Serialize}; + +#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize)] +pub struct AttestationDuty { + pub slot: Slot, + pub shard: Shard, + pub committee_index: usize, +} diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index ba9c5cd4d..32f8204e3 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -1,17 +1,14 @@ use self::epoch_cache::EpochCache; use crate::test_utils::TestRandom; use crate::{validator_registry::get_active_validator_indices, *}; -use helpers::*; -use honey_badger_split::SplitExt; use int_to_bytes::int_to_bytes32; -use log::{debug, error, trace}; +use log::{debug, trace}; use pubkey_cache::PubkeyCache; use rand::RngCore; use serde_derive::{Deserialize, Serialize}; -use ssz::{hash, SignedRoot}; +use ssz::{hash, SignedRoot, TreeHash}; use ssz_derive::{Decode, Encode, TreeHash}; use std::collections::HashMap; -use swap_or_not_shuffle::shuffle_list; use test_random_derive::TestRandom; pub use builder::BeaconStateBuilder; @@ -22,22 +19,7 @@ pub mod helpers; mod pubkey_cache; mod tests; -pub type Committee = Vec; -pub type CrosslinkCommittees = Vec<(Committee, u64)>; -pub type Shard = u64; -pub type CommitteeIndex = u64; -pub type AttestationDuty = (Slot, Shard, CommitteeIndex); -pub type AttestationDutyMap = HashMap; -pub type ShardCommitteeIndexMap = HashMap; - -pub const CACHED_EPOCHS: usize = 3; - -#[derive(Debug, PartialEq, Clone, Copy)] -pub enum RelativeEpoch { - Previous, - Current, - Next, -} +pub const CACHED_EPOCHS: usize = 4; #[derive(Debug, PartialEq)] pub enum Error { @@ -61,6 +43,7 @@ pub enum Error { cache_len: usize, registry_len: usize, }, + RelativeEpochError(RelativeEpochError), } macro_rules! safe_add_assign { @@ -212,13 +195,12 @@ impl BeaconState { EpochCache::default(), EpochCache::default(), EpochCache::default(), + EpochCache::default(), ], pubkey_cache: PubkeyCache::default(), } } - /* - /// Returns the `hash_tree_root` of the state. /// /// Spec v0.5.0 @@ -226,8 +208,6 @@ impl BeaconState { Hash256::from_slice(&self.hash_tree_root()[..]) } - */ - /// Build an epoch cache, unless it is has already been built. pub fn build_epoch_cache( &mut self, @@ -236,7 +216,8 @@ impl BeaconState { ) -> Result<(), Error> { let cache_index = self.cache_index(relative_epoch); - if self.caches[cache_index].initialized { + if self.caches[cache_index].initialized_epoch == Some(self.slot.epoch(spec.slots_per_epoch)) + { Ok(()) } else { self.force_build_epoch_cache(relative_epoch, spec) @@ -249,36 +230,13 @@ impl BeaconState { relative_epoch: RelativeEpoch, spec: &ChainSpec, ) -> Result<(), Error> { - let epoch = self.absolute_epoch(relative_epoch, spec); let cache_index = self.cache_index(relative_epoch); - self.caches[cache_index] = EpochCache::initialized(&self, epoch, spec)?; + self.caches[cache_index] = EpochCache::initialized(&self, relative_epoch, spec)?; Ok(()) } - /// Converts a `RelativeEpoch` into an `Epoch` with respect to the epoch of this state. - fn absolute_epoch(&self, relative_epoch: RelativeEpoch, spec: &ChainSpec) -> Epoch { - match relative_epoch { - RelativeEpoch::Previous => self.previous_epoch(spec), - RelativeEpoch::Current => self.current_epoch(spec), - RelativeEpoch::Next => self.next_epoch(spec), - } - } - - /// Converts an `Epoch` into a `RelativeEpoch` with respect to the epoch of this state. - /// - /// Returns an error if the given `epoch` not "previous", "current" or "next" compared to the - /// epoch of this tate. - fn relative_epoch(&self, epoch: Epoch, spec: &ChainSpec) -> Result { - match epoch { - e if e == self.current_epoch(spec) => Ok(RelativeEpoch::Current), - e if e == self.previous_epoch(spec) => Ok(RelativeEpoch::Previous), - e if e == self.next_epoch(spec) => Ok(RelativeEpoch::Next), - _ => Err(Error::EpochOutOfBounds), - } - } - /// Advances the cache for this state into the next epoch. /// /// This should be used if the `slot` of this state is advanced beyond an epoch boundary. @@ -305,9 +263,10 @@ impl BeaconState { /// Returns the index of `self.caches` for some `RelativeEpoch`. fn cache_index(&self, relative_epoch: RelativeEpoch) -> usize { let base_index = match relative_epoch { - RelativeEpoch::Current => 1, RelativeEpoch::Previous => 0, - RelativeEpoch::Next => 2, + RelativeEpoch::Current => 1, + RelativeEpoch::NextWithoutRegistryChange => 2, + RelativeEpoch::NextWithRegistryChange => 3, }; (base_index + self.cache_index_offset) % CACHED_EPOCHS @@ -315,10 +274,10 @@ impl BeaconState { /// Returns the cache for some `RelativeEpoch`. Returns an error if the cache has not been /// initialized. - fn cache(&self, relative_epoch: RelativeEpoch) -> Result<&EpochCache, Error> { + fn cache(&self, relative_epoch: RelativeEpoch, spec: &ChainSpec) -> Result<&EpochCache, Error> { let cache = &self.caches[self.cache_index(relative_epoch)]; - if cache.initialized { + if cache.initialized_epoch == Some(self.slot.epoch(spec.slots_per_epoch)) { Ok(cache) } else { Err(Error::EpochCacheUninitialized(relative_epoch)) @@ -367,7 +326,7 @@ impl BeaconState { /// The epoch corresponding to `self.slot`. /// - /// Spec v0.4.0 + /// Spec v0.5.0 pub fn current_epoch(&self, spec: &ChainSpec) -> Epoch { self.slot.epoch(spec.slots_per_epoch) } @@ -376,58 +335,16 @@ impl BeaconState { /// /// If the current epoch is the genesis epoch, the genesis_epoch is returned. /// - /// Spec v0.4.0 + /// Spec v0.5.0 pub fn previous_epoch(&self, spec: &ChainSpec) -> Epoch { - let current_epoch = self.current_epoch(&spec); - std::cmp::max(current_epoch - 1, spec.genesis_epoch) + self.current_epoch(&spec) - 1 } /// The epoch following `self.current_epoch()`. /// - /// Spec v0.4.0 + /// Spec v0.5.0 pub fn next_epoch(&self, spec: &ChainSpec) -> Epoch { - self.current_epoch(spec).saturating_add(1_u64) - } - - /// The first slot of the epoch corresponding to `self.slot`. - /// - /// Spec v0.4.0 - pub fn current_epoch_start_slot(&self, spec: &ChainSpec) -> Slot { - self.current_epoch(spec).start_slot(spec.slots_per_epoch) - } - - /// The first slot of the epoch preceding the one corresponding to `self.slot`. - /// - /// Spec v0.4.0 - pub fn previous_epoch_start_slot(&self, spec: &ChainSpec) -> Slot { - self.previous_epoch(spec).start_slot(spec.slots_per_epoch) - } - - /// Return the number of committees in the previous epoch. - /// - /// Spec v0.4.0 - fn get_previous_epoch_committee_count(&self, spec: &ChainSpec) -> u64 { - let previous_active_validators = - get_active_validator_indices(&self.validator_registry, self.previous_shuffling_epoch); - spec.get_epoch_committee_count(previous_active_validators.len()) - } - - /// Return the number of committees in the current epoch. - /// - /// Spec v0.4.0 - pub fn get_current_epoch_committee_count(&self, spec: &ChainSpec) -> u64 { - let current_active_validators = - get_active_validator_indices(&self.validator_registry, self.current_shuffling_epoch); - spec.get_epoch_committee_count(current_active_validators.len()) - } - - /// Return the number of committees in the next epoch. - /// - /// Spec v0.4.0 - pub fn get_next_epoch_committee_count(&self, spec: &ChainSpec) -> u64 { - let next_active_validators = - get_active_validator_indices(&self.validator_registry, self.next_epoch(spec)); - spec.get_epoch_committee_count(next_active_validators.len()) + self.current_epoch(spec) + 1 } /// Returns the crosslink committees for some slot. @@ -438,15 +355,14 @@ impl BeaconState { pub fn get_crosslink_committees_at_slot( &self, slot: Slot, + relative_epoch: RelativeEpoch, spec: &ChainSpec, - ) -> Result<&CrosslinkCommittees, Error> { - let epoch = slot.epoch(spec.slots_per_epoch); - let relative_epoch = self.relative_epoch(epoch, spec)?; - let cache = self.cache(relative_epoch)?; + ) -> Result<&Vec, Error> { + let cache = self.cache(relative_epoch, spec)?; - let slot_offset = slot - epoch.start_slot(spec.slots_per_epoch); - - Ok(&cache.committees[slot_offset.as_usize()]) + Ok(cache + .get_crosslink_committees_at_slot(slot, spec) + .ok_or_else(|| Error::SlotOutOfBounds)?) } /// Return the block root at a recent `slot`. @@ -525,8 +441,13 @@ impl BeaconState { /// If the state does not contain an index for a beacon proposer at the requested `slot`, then `None` is returned. /// /// Spec v0.4.0 - pub fn get_beacon_proposer_index(&self, slot: Slot, spec: &ChainSpec) -> Result { - let committees = self.get_crosslink_committees_at_slot(slot, spec)?; + pub fn get_beacon_proposer_index( + &self, + slot: Slot, + relative_epoch: RelativeEpoch, + spec: &ChainSpec, + ) -> Result { + let committees = self.get_crosslink_committees_at_slot(slot, relative_epoch, spec)?; trace!( "get_beacon_proposer_index: slot: {}, committees_count: {}", slot, @@ -535,71 +456,28 @@ impl BeaconState { committees .first() .ok_or(Error::InsufficientValidators) - .and_then(|(first_committee, _)| { + .and_then(|first| { let index = slot .as_usize() - .checked_rem(first_committee.len()) + .checked_rem(first.committee.len()) .ok_or(Error::InsufficientValidators)?; - Ok(first_committee[index]) + Ok(first.committee[index]) }) } - /// Returns the list of validator indices which participiated in the attestation. - /// - /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. - /// - /// Spec v0.4.0 - pub fn get_attestation_participants( - &self, - attestation_data: &AttestationData, - bitfield: &Bitfield, - spec: &ChainSpec, - ) -> Result, Error> { - let epoch = attestation_data.slot.epoch(spec.slots_per_epoch); - let relative_epoch = self.relative_epoch(epoch, spec)?; - let cache = self.cache(relative_epoch)?; - - let (committee_slot_index, committee_index) = cache - .shard_committee_indices - .get(attestation_data.shard as usize) - .ok_or_else(|| Error::ShardOutOfBounds)?; - let (committee, shard) = &cache.committees[*committee_slot_index][*committee_index]; - - assert_eq!(*shard, attestation_data.shard, "Bad epoch cache build."); - - if !verify_bitfield_length(&bitfield, committee.len()) { - return Err(Error::InvalidBitfield); - } - - let mut participants = Vec::with_capacity(committee.len()); - for (i, validator_index) in committee.iter().enumerate() { - match bitfield.get(i) { - Ok(bit) if bit == true => participants.push(*validator_index), - _ => {} - } - } - participants.shrink_to_fit(); - - Ok(participants) - } - /// 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) -> u64 { - std::cmp::min( - self.validator_balances[validator_index], - spec.max_deposit_amount, - ) - } - - /// Return the combined effective balance of an array of validators. - /// - /// Spec v0.4.0 - pub fn get_total_balance(&self, validator_indices: &[usize], spec: &ChainSpec) -> u64 { - validator_indices - .iter() - .fold(0, |acc, i| acc + self.get_effective_balance(*i, spec)) + 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. @@ -767,11 +645,15 @@ impl BeaconState { self.exit_validator(validator_index, spec); - self.latest_slashed_balances[current_epoch.as_usize() % spec.latest_slashed_exit_length] += - self.get_effective_balance(validator_index, spec); + let effective_balance = self.get_effective_balance(validator_index, spec)?; - let whistleblower_index = self.get_beacon_proposer_index(self.slot, spec)?; - let whistleblower_reward = self.get_effective_balance(validator_index, spec); + self.latest_slashed_balances[current_epoch.as_usize() % spec.latest_slashed_exit_length] += + effective_balance; + + let whistleblower_index = + self.get_beacon_proposer_index(self.slot, RelativeEpoch::Current, spec)?; + + let whistleblower_reward = effective_balance; safe_add_assign!( self.validator_balances[whistleblower_index as usize], whistleblower_reward @@ -801,166 +683,6 @@ impl BeaconState { self.current_epoch(spec) + spec.min_validator_withdrawability_delay; } - /// Returns the crosslink committees for some slot. - /// - /// Utilizes the cache and will fail if the appropriate cache is not initialized. - /// - /// Spec v0.4.0 - pub(crate) fn get_shuffling_for_slot( - &self, - slot: Slot, - registry_change: bool, - spec: &ChainSpec, - ) -> Result>, Error> { - let (_committees_per_epoch, seed, shuffling_epoch, _shuffling_start_shard) = - self.get_committee_params_at_slot(slot, registry_change, spec)?; - - self.get_shuffling(seed, shuffling_epoch, spec) - } - - /// Shuffle ``validators`` into crosslink committees seeded by ``seed`` and ``epoch``. - /// - /// Return a list of ``committees_per_epoch`` committees where each - /// committee is itself a list of validator indices. - /// - /// Spec v0.4.0 - pub(crate) fn get_shuffling( - &self, - seed: Hash256, - epoch: Epoch, - spec: &ChainSpec, - ) -> Result>, Error> { - let active_validator_indices = - get_active_validator_indices(&self.validator_registry, epoch); - if active_validator_indices.is_empty() { - error!("get_shuffling: no validators."); - return Err(Error::InsufficientValidators); - } - - debug!("Shuffling {} validators...", active_validator_indices.len()); - - let committees_per_epoch = spec.get_epoch_committee_count(active_validator_indices.len()); - - trace!( - "get_shuffling: active_validator_indices.len() == {}, committees_per_epoch: {}", - active_validator_indices.len(), - committees_per_epoch - ); - - let active_validator_indices: Vec = active_validator_indices.to_vec(); - - let shuffled_active_validator_indices = shuffle_list( - active_validator_indices, - spec.shuffle_round_count, - &seed[..], - true, - ) - .ok_or_else(|| Error::UnableToShuffle)?; - - Ok(shuffled_active_validator_indices - .honey_badger_split(committees_per_epoch as usize) - .map(|slice: &[usize]| slice.to_vec()) - .collect()) - } - - /// Returns the following params for the given slot: - /// - /// - epoch committee count - /// - epoch seed - /// - calculation epoch - /// - start shard - /// - /// In the spec, this functionality is included in the `get_crosslink_committees_at_slot(..)` - /// function. It is separated here to allow the division of shuffling and committee building, - /// as is required for efficient operations. - /// - /// Spec v0.4.0 - pub(crate) fn get_committee_params_at_slot( - &self, - slot: Slot, - registry_change: bool, - spec: &ChainSpec, - ) -> Result<(u64, Hash256, Epoch, u64), Error> { - let epoch = slot.epoch(spec.slots_per_epoch); - let current_epoch = self.current_epoch(spec); - let previous_epoch = self.previous_epoch(spec); - let next_epoch = self.next_epoch(spec); - - if epoch == current_epoch { - Ok(( - self.get_current_epoch_committee_count(spec), - self.current_shuffling_seed, - self.current_shuffling_epoch, - self.current_shuffling_start_shard, - )) - } else if epoch == previous_epoch { - Ok(( - self.get_previous_epoch_committee_count(spec), - self.previous_shuffling_seed, - self.previous_shuffling_epoch, - self.previous_shuffling_start_shard, - )) - } else if epoch == next_epoch { - let current_committees_per_epoch = self.get_current_epoch_committee_count(spec); - let epochs_since_last_registry_update = - current_epoch - self.validator_registry_update_epoch; - let (seed, shuffling_start_shard) = if registry_change { - let next_seed = self.generate_seed(next_epoch, spec)?; - ( - next_seed, - (self.current_shuffling_start_shard + current_committees_per_epoch) - % spec.shard_count, - ) - } else if (epochs_since_last_registry_update > 1) - & epochs_since_last_registry_update.is_power_of_two() - { - let next_seed = self.generate_seed(next_epoch, spec)?; - (next_seed, self.current_shuffling_start_shard) - } else { - ( - self.current_shuffling_seed, - self.current_shuffling_start_shard, - ) - }; - Ok(( - self.get_next_epoch_committee_count(spec), - seed, - next_epoch, - shuffling_start_shard, - )) - } else { - Err(Error::EpochOutOfBounds) - } - } - - /// Return the ordered list of shards tuples for the `slot`. - /// - /// Note: There are two possible shufflings for crosslink committees for a - /// `slot` in the next epoch: with and without a `registry_change` - /// - /// Spec v0.4.0 - pub(crate) fn get_shards_for_slot( - &self, - slot: Slot, - registry_change: bool, - spec: &ChainSpec, - ) -> Result, Error> { - let (committees_per_epoch, _seed, _shuffling_epoch, shuffling_start_shard) = - self.get_committee_params_at_slot(slot, registry_change, spec)?; - - let offset = slot.as_u64() % spec.slots_per_epoch; - let committees_per_slot = committees_per_epoch / spec.slots_per_epoch; - let slot_start_shard = - (shuffling_start_shard + committees_per_slot * offset) % spec.shard_count; - - let mut shards_at_slot = vec![]; - for i in 0..committees_per_slot { - shards_at_slot.push((slot_start_shard + i) % spec.shard_count) - } - - Ok(shards_at_slot) - } - /// Returns the `slot`, `shard` and `committee_index` for which a validator must produce an /// attestation. /// @@ -969,14 +691,14 @@ impl BeaconState { /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. /// /// Spec v0.4.0 - pub fn attestation_slot_and_shard_for_validator( + pub fn get_attestation_duties( &self, validator_index: usize, - _spec: &ChainSpec, - ) -> Result, Error> { - let cache = self.cache(RelativeEpoch::Current)?; + spec: &ChainSpec, + ) -> Result<&Option, Error> { + let cache = self.cache(RelativeEpoch::Current, spec)?; - Ok(*cache + Ok(cache .attestation_duties .get(validator_index) .ok_or_else(|| Error::UnknownValidator)?) @@ -985,11 +707,11 @@ impl BeaconState { /// Process the slashings. /// /// Spec v0.4.0 - pub fn process_slashings(&mut self, spec: &ChainSpec) { + pub fn process_slashings(&mut self, spec: &ChainSpec) -> Result<(), Error> { let current_epoch = self.current_epoch(spec); let active_validator_indices = get_active_validator_indices(&self.validator_registry, current_epoch); - let total_balance = self.get_total_balance(&active_validator_indices[..], spec); + let total_balance = self.get_total_balance(&active_validator_indices[..], spec)?; for (index, validator) in self.validator_registry.iter().enumerate() { if validator.slashed @@ -1003,16 +725,19 @@ impl BeaconState { [(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( - self.get_effective_balance(index, spec) - * std::cmp::min(total_penalities * 3, total_balance) + effective_balance * std::cmp::min(total_penalities * 3, total_balance) / total_balance, - self.get_effective_balance(index, spec) / spec.min_penalty_quotient, + effective_balance / spec.min_penalty_quotient, ); safe_sub_assign!(self.validator_balances[index], penalty); } } + + Ok(()) } /// Process the exit queue. @@ -1047,11 +772,11 @@ impl BeaconState { /// Update validator registry, activating/exiting validators if possible. /// /// Spec v0.4.0 - pub fn update_validator_registry(&mut self, spec: &ChainSpec) { + pub fn update_validator_registry(&mut self, spec: &ChainSpec) -> Result<(), Error> { let current_epoch = self.current_epoch(spec); let active_validator_indices = get_active_validator_indices(&self.validator_registry, current_epoch); - let total_balance = self.get_total_balance(&active_validator_indices[..], spec); + let total_balance = self.get_total_balance(&active_validator_indices[..], spec)?; let max_balance_churn = std::cmp::max( spec.max_deposit_amount, @@ -1065,7 +790,7 @@ impl BeaconState { if (validator.activation_epoch == spec.far_future_epoch) & (self.validator_balances[index] == spec.max_deposit_amount) { - balance_churn += self.get_effective_balance(index, spec); + balance_churn += self.get_effective_balance(index, spec)?; if balance_churn > max_balance_churn { break; } @@ -1078,7 +803,7 @@ impl BeaconState { 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); + balance_churn += self.get_effective_balance(index, spec)?; if balance_churn > max_balance_churn { break; } @@ -1088,6 +813,8 @@ impl BeaconState { } self.validator_registry_update_epoch = current_epoch; + + Ok(()) } /// Iterate through the validator registry and eject active validators with balance below @@ -1115,12 +842,13 @@ impl BeaconState { epochs_since_finality: Epoch, base_reward_quotient: u64, spec: &ChainSpec, - ) -> u64 { - let effective_balance = self.get_effective_balance(validator_index, spec); - self.base_reward(validator_index, base_reward_quotient, spec) + ) -> Result { + let effective_balance = self.get_effective_balance(validator_index, spec)?; + let base_reward = self.base_reward(validator_index, base_reward_quotient, spec)?; + Ok(base_reward + effective_balance * epochs_since_finality.as_u64() / spec.inactivity_penalty_quotient - / 2 + / 2) } /// Returns the base reward for some validator. @@ -1133,30 +861,27 @@ impl BeaconState { validator_index: usize, base_reward_quotient: u64, spec: &ChainSpec, - ) -> u64 { - self.get_effective_balance(validator_index, spec) / base_reward_quotient / 5 + ) -> Result { + Ok(self.get_effective_balance(validator_index, spec)? / base_reward_quotient / 5) } - /// Returns the union of all participants in the provided attestations + /// Return the combined effective balance of an array of validators. /// /// Spec v0.4.0 - pub fn get_attestation_participants_union( + pub fn get_total_balance( &self, - attestations: &[&PendingAttestation], + validator_indices: &[usize], spec: &ChainSpec, - ) -> Result, Error> { - let mut all_participants = attestations - .iter() - .try_fold::<_, _, Result, Error>>(vec![], |mut acc, a| { - acc.append(&mut self.get_attestation_participants( - &a.data, - &a.aggregation_bitfield, - spec, - )?); - Ok(acc) - })?; - all_participants.sort_unstable(); - all_participants.dedup(); - Ok(all_participants) + ) -> 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 { + fn from(e: RelativeEpochError) -> Error { + Error::RelativeEpochError(e) } } diff --git a/eth2/types/src/beacon_state/builder.rs b/eth2/types/src/beacon_state/builder.rs index 22ca3e622..780ec9b8b 100644 --- a/eth2/types/src/beacon_state/builder.rs +++ b/eth2/types/src/beacon_state/builder.rs @@ -43,12 +43,14 @@ impl BeaconStateBuilder { self.state.deposit_index = initial_validator_deposits.len() as u64; } - fn activate_genesis_validators(&mut self, spec: &ChainSpec) { + fn activate_genesis_validators(&mut self, spec: &ChainSpec) -> Result<(), BeaconStateError> { for validator_index in 0..self.state.validator_registry.len() { - if self.state.get_effective_balance(validator_index, spec) >= spec.max_deposit_amount { + if self.state.get_effective_balance(validator_index, spec)? >= spec.max_deposit_amount { self.state.activate_validator(validator_index, true, spec); } } + + Ok(()) } /// Instantiate the validator registry from a YAML file. diff --git a/eth2/types/src/beacon_state/epoch_cache.rs b/eth2/types/src/beacon_state/epoch_cache.rs index ddcca0a9a..6312ea5a5 100644 --- a/eth2/types/src/beacon_state/epoch_cache.rs +++ b/eth2/types/src/beacon_state/epoch_cache.rs @@ -1,69 +1,283 @@ -use super::{AttestationDuty, BeaconState, CrosslinkCommittees, Error}; -use crate::{ChainSpec, Epoch}; +use super::{BeaconState, Error}; +use crate::*; +use honey_badger_split::SplitExt; use serde_derive::{Deserialize, Serialize}; +use swap_or_not_shuffle::shuffle_list; #[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize)] pub struct EpochCache { - /// True if this cache has been initialized. - pub initialized: bool, - /// The crosslink committees for an epoch. - pub committees: Vec, + /// `Some(epoch)` if the cache is initialized, where `epoch` is the cache it holds. + pub initialized_epoch: Option, + /// All crosslink committees for an epoch. + pub epoch_crosslink_committees: EpochCrosslinkCommittees, /// Maps validator index to a slot, shard and committee index for attestation. pub attestation_duties: Vec>, /// Maps a shard to an index of `self.committees`. - pub shard_committee_indices: Vec<(usize, usize)>, + pub shard_committee_indices: Vec<(Slot, usize)>, } impl EpochCache { /// Return a new, fully initialized cache. pub fn initialized( state: &BeaconState, - epoch: Epoch, + relative_epoch: RelativeEpoch, spec: &ChainSpec, ) -> Result { - let mut epoch_committees: Vec = - Vec::with_capacity(spec.slots_per_epoch as usize); + let epoch = relative_epoch.into_epoch(state.slot.epoch(spec.slots_per_epoch)); + let active_validator_indices = + get_active_validator_indices(&state.validator_registry, epoch); + + let builder = match relative_epoch { + RelativeEpoch::Previous => EpochCrosslinkCommitteesBuilder::for_previous_epoch( + state, + active_validator_indices, + spec, + ), + RelativeEpoch::Current => EpochCrosslinkCommitteesBuilder::for_current_epoch( + state, + active_validator_indices, + spec, + ), + RelativeEpoch::NextWithRegistryChange => { + EpochCrosslinkCommitteesBuilder::for_next_epoch( + state, + active_validator_indices, + true, + spec, + )? + } + RelativeEpoch::NextWithoutRegistryChange => { + EpochCrosslinkCommitteesBuilder::for_next_epoch( + state, + active_validator_indices, + false, + spec, + )? + } + }; + let epoch_crosslink_committees = builder.build(spec)?; + + // Loop through all the validators in the committees and create the following maps: + // + // 1. `attestation_duties`: maps `ValidatorIndex` to `AttestationDuty`. + // 2. `shard_committee_indices`: maps `Shard` into a `CrosslinkCommittee` in + // `EpochCrosslinkCommittees`. let mut attestation_duties = vec![None; state.validator_registry.len()]; + let mut shard_committee_indices = vec![(Slot::default(), 0); spec.shard_count as usize]; + for (i, slot_committees) in epoch_crosslink_committees + .crosslink_committees + .iter() + .enumerate() + { + let slot = epoch.start_slot(spec.slots_per_epoch) + i as u64; - let mut shard_committee_indices = vec![(0, 0); spec.shard_count as usize]; + for (j, crosslink_committee) in slot_committees.iter().enumerate() { + let shard = crosslink_committee.shard; - let mut shuffling = - state.get_shuffling_for_slot(epoch.start_slot(spec.slots_per_epoch), false, spec)?; + shard_committee_indices[shard as usize] = (slot, j); - for (epoch_committees_index, slot) in epoch.slot_iter(spec.slots_per_epoch).enumerate() { - let mut slot_committees: Vec<(Vec, u64)> = vec![]; - - let shards = state.get_shards_for_slot(slot, false, spec)?; - for shard in shards { - let committee = shuffling.remove(0); - slot_committees.push((committee, shard)); - } - - for (slot_committees_index, (committee, shard)) in slot_committees.iter().enumerate() { - if committee.is_empty() { - return Err(Error::InsufficientValidators); - } - - // Store the slot and committee index for this shard. - shard_committee_indices[*shard as usize] = - (epoch_committees_index, slot_committees_index); - - // For each validator, store their attestation duties. - for (committee_index, validator_index) in committee.iter().enumerate() { - attestation_duties[*validator_index] = - Some((slot, *shard, committee_index as u64)) + for (k, validator_index) in crosslink_committee.committee.iter().enumerate() { + let attestation_duty = AttestationDuty { + slot, + shard, + committee_index: k, + }; + attestation_duties[*validator_index] = Some(attestation_duty) } } - - epoch_committees.push(slot_committees) } Ok(EpochCache { - initialized: true, - committees: epoch_committees, + initialized_epoch: Some(epoch), + epoch_crosslink_committees, attestation_duties, shard_committee_indices, }) } + + pub fn get_crosslink_committees_at_slot( + &self, + slot: Slot, + spec: &ChainSpec, + ) -> Option<&Vec> { + self.epoch_crosslink_committees + .get_crosslink_committees_at_slot(slot, spec) + } + + pub fn get_crosslink_committee_for_shard( + &self, + shard: Shard, + spec: &ChainSpec, + ) -> Option<&CrosslinkCommittee> { + let (slot, committee) = self.shard_committee_indices.get(shard as usize)?; + let slot_committees = self.get_crosslink_committees_at_slot(*slot, spec)?; + slot_committees.get(*committee) + } +} + +pub fn get_active_validator_indices(validators: &[Validator], epoch: Epoch) -> Vec { + let mut active = Vec::with_capacity(validators.len()); + + for (index, validator) in validators.iter().enumerate() { + if validator.is_active_at(epoch) { + active.push(index) + } + } + + active.shrink_to_fit(); + + active +} + +#[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize)] +pub struct EpochCrosslinkCommittees { + epoch: Epoch, + pub crosslink_committees: Vec>, +} + +impl EpochCrosslinkCommittees { + fn new(epoch: Epoch, spec: &ChainSpec) -> Self { + Self { + epoch, + crosslink_committees: vec![vec![]; spec.slots_per_epoch as usize], + } + } + + fn get_crosslink_committees_at_slot( + &self, + slot: Slot, + spec: &ChainSpec, + ) -> Option<&Vec> { + let epoch_start_slot = self.epoch.start_slot(spec.slots_per_epoch); + let epoch_end_slot = self.epoch.end_slot(spec.slots_per_epoch); + + if (epoch_start_slot < slot) && (slot <= epoch_end_slot) { + let index = slot - epoch_start_slot; + self.crosslink_committees.get(index.as_usize()) + } else { + None + } + } +} + +pub struct EpochCrosslinkCommitteesBuilder { + epoch: Epoch, + shuffling_start_shard: Shard, + shuffling_seed: Hash256, + active_validator_indices: Vec, + committees_per_epoch: u64, +} + +impl EpochCrosslinkCommitteesBuilder { + pub fn for_previous_epoch( + state: &BeaconState, + active_validator_indices: Vec, + spec: &ChainSpec, + ) -> Self { + Self { + epoch: state.previous_epoch(spec), + shuffling_start_shard: state.previous_shuffling_start_shard, + shuffling_seed: state.previous_shuffling_seed, + committees_per_epoch: spec.get_epoch_committee_count(active_validator_indices.len()), + active_validator_indices, + } + } + + pub fn for_current_epoch( + state: &BeaconState, + active_validator_indices: Vec, + spec: &ChainSpec, + ) -> Self { + Self { + epoch: state.current_epoch(spec), + shuffling_start_shard: state.current_shuffling_start_shard, + shuffling_seed: state.current_shuffling_seed, + committees_per_epoch: spec.get_epoch_committee_count(active_validator_indices.len()), + active_validator_indices, + } + } + + pub fn for_next_epoch( + state: &BeaconState, + active_validator_indices: Vec, + registry_change: bool, + spec: &ChainSpec, + ) -> Result { + let current_epoch = state.current_epoch(spec); + let next_epoch = state.next_epoch(spec); + let committees_per_epoch = spec.get_epoch_committee_count(active_validator_indices.len()); + + let epochs_since_last_registry_update = + current_epoch - state.validator_registry_update_epoch; + + let (seed, shuffling_start_shard) = if registry_change { + let next_seed = state.generate_seed(next_epoch, spec)?; + ( + next_seed, + (state.current_shuffling_start_shard + committees_per_epoch) % spec.shard_count, + ) + } else if (epochs_since_last_registry_update > 1) + & epochs_since_last_registry_update.is_power_of_two() + { + let next_seed = state.generate_seed(next_epoch, spec)?; + (next_seed, state.current_shuffling_start_shard) + } else { + ( + state.current_shuffling_seed, + state.current_shuffling_start_shard, + ) + }; + + Ok(Self { + epoch: state.next_epoch(spec), + shuffling_start_shard, + shuffling_seed: seed, + active_validator_indices, + committees_per_epoch, + }) + } + + pub fn build(self, spec: &ChainSpec) -> Result { + if self.active_validator_indices.is_empty() { + return Err(Error::InsufficientValidators); + } + + let shuffled_active_validator_indices = shuffle_list( + self.active_validator_indices, + spec.shuffle_round_count, + &self.shuffling_seed[..], + true, + ) + .ok_or_else(|| Error::UnableToShuffle)?; + + let mut committees: Vec> = shuffled_active_validator_indices + .honey_badger_split(self.committees_per_epoch as usize) + .map(|slice: &[usize]| slice.to_vec()) + .collect(); + + let mut epoch_crosslink_committees = EpochCrosslinkCommittees::new(self.epoch, spec); + let mut shard = self.shuffling_start_shard; + + let committees_per_slot = (self.committees_per_epoch / spec.slots_per_epoch) as usize; + + for i in 0..spec.slots_per_epoch as usize { + for j in (0..committees.len()) + .into_iter() + .skip(i * committees_per_slot) + .take(committees_per_slot) + { + let crosslink_committee = CrosslinkCommittee { + shard, + committee: committees.remove(j), + }; + epoch_crosslink_committees.crosslink_committees[i].push(crosslink_committee); + + shard += 1; + shard %= spec.shard_count; + } + } + + Ok(epoch_crosslink_committees) + } } diff --git a/eth2/types/src/beacon_state/tests.rs b/eth2/types/src/beacon_state/tests.rs index 1e1a555fd..6c10ebe86 100644 --- a/eth2/types/src/beacon_state/tests.rs +++ b/eth2/types/src/beacon_state/tests.rs @@ -1,53 +1,5 @@ #![cfg(test)] use super::*; -use crate::test_utils::TestingBeaconStateBuilder; -use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng}; -use crate::{BeaconState, ChainSpec}; - -/// Tests that `get_attestation_participants` is consistent with the result of -/// get_crosslink_committees_at_slot` with a full bitfield. -#[test] -pub fn get_attestation_participants_consistency() { - let mut rng = XorShiftRng::from_seed([42; 16]); - - let spec = ChainSpec::few_validators(); - let builder = TestingBeaconStateBuilder::from_deterministic_keypairs(8, &spec); - let (mut state, _keypairs) = builder.build(); - - state - .build_epoch_cache(RelativeEpoch::Previous, &spec) - .unwrap(); - state - .build_epoch_cache(RelativeEpoch::Current, &spec) - .unwrap(); - state.build_epoch_cache(RelativeEpoch::Next, &spec).unwrap(); - - for slot in state - .slot - .epoch(spec.slots_per_epoch) - .slot_iter(spec.slots_per_epoch) - { - let committees = state.get_crosslink_committees_at_slot(slot, &spec).unwrap(); - - for (committee, shard) in committees { - let mut attestation_data = AttestationData::random_for_test(&mut rng); - attestation_data.slot = slot; - attestation_data.shard = *shard; - - let mut bitfield = Bitfield::new(); - for (i, _) in committee.iter().enumerate() { - bitfield.set(i, true); - } - - assert_eq!( - state - .get_attestation_participants(&attestation_data, &bitfield, &spec) - .unwrap(), - *committee - ); - } - } -} ssz_tests!(BeaconState); diff --git a/eth2/types/src/crosslink_committee.rs b/eth2/types/src/crosslink_committee.rs new file mode 100644 index 000000000..06a6562fc --- /dev/null +++ b/eth2/types/src/crosslink_committee.rs @@ -0,0 +1,9 @@ +use crate::*; +use serde_derive::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode, TreeHash}; + +#[derive(Default, Clone, Debug, PartialEq, Serialize, Deserialize, Decode, Encode, TreeHash)] +pub struct CrosslinkCommittee { + pub shard: Shard, + pub committee: Vec, +} diff --git a/eth2/types/src/epoch_cache.rs b/eth2/types/src/epoch_cache.rs new file mode 100644 index 000000000..e69de29bb diff --git a/eth2/types/src/lib.rs b/eth2/types/src/lib.rs index c38fa8031..05f8254d5 100644 --- a/eth2/types/src/lib.rs +++ b/eth2/types/src/lib.rs @@ -6,6 +6,7 @@ pub mod test_utils; pub mod attestation; pub mod attestation_data; pub mod attestation_data_and_custody_bit; +pub mod attestation_duty; pub mod attester_slashing; pub mod beacon_block; pub mod beacon_block_body; @@ -13,6 +14,7 @@ pub mod beacon_block_header; pub mod beacon_state; pub mod chain_spec; pub mod crosslink; +pub mod crosslink_committee; pub mod deposit; pub mod deposit_data; pub mod deposit_input; @@ -28,6 +30,7 @@ pub mod transfer; pub mod voluntary_exit; #[macro_use] pub mod slot_epoch_macros; +pub mod relative_epoch; pub mod slot_epoch; pub mod slot_height; pub mod validator; @@ -39,13 +42,15 @@ use std::collections::HashMap; pub use crate::attestation::Attestation; pub use crate::attestation_data::AttestationData; pub use crate::attestation_data_and_custody_bit::AttestationDataAndCustodyBit; +pub use crate::attestation_duty::AttestationDuty; pub use crate::attester_slashing::AttesterSlashing; pub use crate::beacon_block::BeaconBlock; pub use crate::beacon_block_body::BeaconBlockBody; pub use crate::beacon_block_header::BeaconBlockHeader; -pub use crate::beacon_state::{BeaconState, Error as BeaconStateError, RelativeEpoch}; +pub use crate::beacon_state::{BeaconState, Error as BeaconStateError}; pub use crate::chain_spec::{ChainSpec, Domain}; pub use crate::crosslink::Crosslink; +pub use crate::crosslink_committee::CrosslinkCommittee; pub use crate::deposit::Deposit; pub use crate::deposit_data::DepositData; pub use crate::deposit_input::DepositInput; @@ -56,6 +61,7 @@ pub use crate::free_attestation::FreeAttestation; pub use crate::historical_batch::HistoricalBatch; pub use crate::pending_attestation::PendingAttestation; pub use crate::proposer_slashing::ProposerSlashing; +pub use crate::relative_epoch::{Error as RelativeEpochError, RelativeEpoch}; pub use crate::slashable_attestation::SlashableAttestation; pub use crate::slot_epoch::{Epoch, Slot}; pub use crate::slot_height::SlotHeight; @@ -63,6 +69,10 @@ pub use crate::transfer::Transfer; pub use crate::validator::Validator; pub use crate::voluntary_exit::VoluntaryExit; +pub type Shard = u64; +pub type Committee = Vec; +pub type CrosslinkCommittees = Vec<(Committee, u64)>; + pub type Hash256 = H256; pub type Address = H160; pub type EthBalance = U256; diff --git a/eth2/types/src/relative_epoch.rs b/eth2/types/src/relative_epoch.rs new file mode 100644 index 000000000..943936605 --- /dev/null +++ b/eth2/types/src/relative_epoch.rs @@ -0,0 +1,76 @@ +use crate::*; + +#[derive(Debug, PartialEq, Clone, Copy)] +pub enum Error { + EpochTooLow { base: Epoch, other: Epoch }, + EpochTooHigh { base: Epoch, other: Epoch }, + AmbiguiousNextEpoch, +} + +/// Defines the epochs relative to some epoch. Most useful when referring to the committees prior +/// to and following some epoch. +/// +/// Spec v0.5.0 +#[derive(Debug, PartialEq, Clone, Copy)] +pub enum RelativeEpoch { + /// The prior epoch. + Previous, + /// The current epoch. + Current, + /// The next epoch if there _is_ a validator registry update. + /// + /// If the validator registry is updated during an epoch transition, a new shuffling seed is + /// generated, this changes the attestation and proposal roles. + NextWithRegistryChange, + /// The next epoch if there _is not_ a validator registry update. + /// + /// If the validator registry _is not_ updated during an epoch transition, the shuffling stays + /// the same. + NextWithoutRegistryChange, +} + +impl RelativeEpoch { + /// Returns the `epoch` that `self` refers to, with respect to the `base` epoch. + /// + /// Spec v0.5.0 + pub fn into_epoch(&self, base: Epoch) -> Epoch { + match self { + RelativeEpoch::Previous => base - 1, + RelativeEpoch::Current => base, + RelativeEpoch::NextWithoutRegistryChange => base + 1, + RelativeEpoch::NextWithRegistryChange => base + 1, + } + } + + /// Converts the `other` epoch into a `RelativeEpoch`, with respect to `base` + /// + /// ## Errors + /// Returns an error when: + /// - `EpochTooLow` when `other` is more than 1 prior to `base`. + /// - `EpochTooHigh` when `other` is more than 1 after `base`. + /// - `AmbiguiousNextEpoch` whenever `other` is one after `base`, because it's unknowable if + /// there will be a registry change. + /// + /// Spec v0.5.0 + pub fn from_epoch(base: Epoch, other: Epoch) -> Result { + if other == base - 1 { + Ok(RelativeEpoch::Previous) + } else if other == base { + Ok(RelativeEpoch::Current) + } else if other == base + 1 { + Err(Error::AmbiguiousNextEpoch) + } else if other < base { + Err(Error::EpochTooLow { base, other }) + } else { + Err(Error::EpochTooHigh { base, other }) + } + } + + /// Convenience function for `Self::from_epoch` where both slots are converted into epochs. + pub fn from_slot(base: Slot, other: Slot, spec: &ChainSpec) -> Result { + Self::from_epoch( + base.epoch(spec.slots_per_epoch), + other.epoch(spec.slots_per_epoch), + ) + } +} diff --git a/eth2/types/src/test_utils/testing_beacon_block_builder.rs b/eth2/types/src/test_utils/testing_beacon_block_builder.rs index 7fb3d8e09..402bd79d6 100644 --- a/eth2/types/src/test_utils/testing_beacon_block_builder.rs +++ b/eth2/types/src/test_utils/testing_beacon_block_builder.rs @@ -109,12 +109,20 @@ impl TestingBeaconBlockBuilder { break; } - for (committee, shard) in state.get_crosslink_committees_at_slot(slot, spec)? { + let relative_epoch = RelativeEpoch::from_slot(state.slot, slot, spec).unwrap(); + for crosslink_committee in + state.get_crosslink_committees_at_slot(slot, relative_epoch, spec)? + { if attestations_added >= num_attestations { break; } - committees.push((slot, committee.clone(), committee.clone(), *shard)); + committees.push(( + slot, + crosslink_committee.committee.clone(), + crosslink_committee.committee.clone(), + crosslink_committee.shard, + )); attestations_added += 1; } diff --git a/eth2/types/src/test_utils/testing_beacon_state_builder.rs b/eth2/types/src/test_utils/testing_beacon_state_builder.rs index 8ef4f76ce..9e613f0e9 100644 --- a/eth2/types/src/test_utils/testing_beacon_state_builder.rs +++ b/eth2/types/src/test_utils/testing_beacon_state_builder.rs @@ -159,7 +159,8 @@ impl TestingBeaconStateBuilder { state.build_epoch_cache(RelativeEpoch::Previous, &spec)?; state.build_epoch_cache(RelativeEpoch::Current, &spec)?; - state.build_epoch_cache(RelativeEpoch::Next, &spec)?; + state.build_epoch_cache(RelativeEpoch::NextWithRegistryChange, &spec)?; + state.build_epoch_cache(RelativeEpoch::NextWithoutRegistryChange, &spec)?; state.update_pubkey_cache()?; @@ -222,15 +223,21 @@ impl TestingBeaconStateBuilder { for slot in first_slot..last_slot + 1 { let slot = Slot::from(slot); + let relative_epoch = RelativeEpoch::from_slot(state.slot, slot, spec).unwrap(); let committees = state - .get_crosslink_committees_at_slot(slot, spec) + .get_crosslink_committees_at_slot(slot, relative_epoch, spec) .unwrap() .clone(); - for (committee, shard) in committees { - let mut builder = TestingPendingAttestationBuilder::new(state, shard, slot, spec); + for crosslink_committee in committees { + let mut builder = TestingPendingAttestationBuilder::new( + state, + crosslink_committee.shard, + slot, + spec, + ); // The entire committee should have signed the pending attestation. - let signers = vec![true; committee.len()]; + let signers = vec![true; crosslink_committee.committee.len()]; builder.add_committee_participation(signers); let attestation = builder.build();