diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index 4c6f8d2a23..80677f5636 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -47,8 +47,8 @@ pub enum Error { cache_len: usize, registry_len: usize, }, - EpochCacheUninitialized(RelativeEpoch), - RelativeEpochError(RelativeEpochError), + PreviousEpochCacheUninitialized, + CurrentEpochCacheUnintialized, EpochCacheError(EpochCacheError), TreeHashCacheError(TreeHashCacheError), } @@ -117,13 +117,13 @@ where #[ssz(skip_deserializing)] #[tree_hash(skip_hashing)] #[test_random(default)] - pub cache_index_offset: usize, + pub previous_epoch_cache: EpochCache, #[serde(default)] #[ssz(skip_serializing)] #[ssz(skip_deserializing)] #[tree_hash(skip_hashing)] #[test_random(default)] - pub caches: [EpochCache; CACHED_EPOCHS], + pub current_epoch_cache: EpochCache, #[serde(default)] #[ssz(skip_serializing)] #[ssz(skip_deserializing)] @@ -214,13 +214,8 @@ impl BeaconState { /* * Caching (not in spec) */ - cache_index_offset: 0, - caches: [ - EpochCache::default(), - EpochCache::default(), - EpochCache::default(), - EpochCache::default(), - ], + previous_epoch_cache: EpochCache::default(), + current_epoch_cache: EpochCache::default(), pubkey_cache: PubkeyCache::default(), tree_hash_cache: TreeHashCache::default(), exit_cache: ExitCache::default(), @@ -338,24 +333,17 @@ impl BeaconState { + offset / (committee_count / spec.slots_per_epoch)) } - /// Returns the active validator indices for the given epoch, assuming there is no validator - /// registry update in the next epoch. - /// - /// This uses the cache, so it saves an iteration over the validator registry, however it can - /// not return a result for any epoch before the previous epoch. - /// - /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. - /// - /// Spec v0.5.1 + // FIXME(sproul): get_cached_current_active_validator_indices + /* pub fn get_cached_active_validator_indices( &self, - relative_epoch: RelativeEpoch, spec: &ChainSpec, ) -> Result<&[usize], Error> { let cache = self.cache(relative_epoch, spec)?; Ok(&cache.active_validator_indices) } + */ /// Returns the active validator indices for the given epoch. /// @@ -376,22 +364,14 @@ impl BeaconState { 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)?) + unimplemented!("FIXME(sproul)") } - // FIXME(sproul): implement this + /// Return the crosslink committeee for `shard` in `epoch`. + /// + /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. + /// + /// Spec v0.6.1 pub fn get_crosslink_committee( &self, epoch: Epoch, @@ -402,43 +382,14 @@ impl BeaconState { unimplemented!() } - /// 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.1 - 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.1 - pub fn get_beacon_proposer_index( - &self, - slot: Slot, - relative_epoch: RelativeEpoch, - spec: &ChainSpec, - ) -> Result { + pub fn get_beacon_proposer_index(&self, spec: &ChainSpec) -> Result { + unimplemented!("FIXME(sproul)") + /* let cache = self.cache(relative_epoch, spec)?; let committees = cache @@ -457,6 +408,7 @@ impl BeaconState { .ok_or(Error::UnableToDetermineProducer)?; Ok(first.committee[index]) }) + */ } /// Safely obtains the index for latest block roots, given some `slot`. @@ -791,10 +743,8 @@ impl BeaconState { /// Build all the caches, if they need to be built. pub fn build_all_caches(&mut self, spec: &ChainSpec) -> Result<(), Error> { - self.build_epoch_cache(RelativeEpoch::Previous, spec)?; - self.build_epoch_cache(RelativeEpoch::Current, spec)?; - self.build_epoch_cache(RelativeEpoch::NextWithoutRegistryChange, spec)?; - self.build_epoch_cache(RelativeEpoch::NextWithRegistryChange, spec)?; + self.build_previous_epoch_cache(spec)?; + self.build_current_epoch_cache(spec)?; self.update_pubkey_cache()?; self.update_tree_hash_cache()?; self.exit_cache @@ -804,13 +754,7 @@ impl BeaconState { } /// Build an epoch cache, unless it is has already been built. - pub fn build_epoch_cache( - &mut self, - relative_epoch: RelativeEpoch, - spec: &ChainSpec, - ) -> Result<(), Error> { - let cache_index = self.cache_index(relative_epoch); - + pub fn build_previous_epoch_cache(&mut self, spec: &ChainSpec) -> Result<(), Error> { if self.caches[cache_index].initialized_epoch == Some(self.slot.epoch(spec.slots_per_epoch)) { Ok(()) @@ -819,67 +763,42 @@ impl BeaconState { } } - /// Always builds an epoch cache, even if it is already initialized. - pub fn force_build_epoch_cache( - &mut self, - relative_epoch: RelativeEpoch, - spec: &ChainSpec, - ) -> Result<(), Error> { - let cache_index = self.cache_index(relative_epoch); - - self.caches[cache_index] = EpochCache::initialized(&self, relative_epoch, spec)?; + /// Always builds the previous epoch cache, even if it is already initialized. + pub fn force_build_previous_epoch_cache(&mut self, spec: &ChainSpec) -> Result<(), Error> { + let epoch = self.previous_epoch(spec); + self.previous_epoch_cache = EpochCache::initialized( + &self, + epoch, + self.generate_seed(epoch, spec)?, + self.get_epoch_start_shard(epoch, spec)?, + spec, + )?; + Ok(()) + } + /// Always builds the current epoch cache, even if it is already initialized. + pub fn force_build_current_epoch_cache(&mut self, spec: &ChainSpec) -> Result<(), Error> { + let epoch = self.current_epoch(spec); + self.current_epoch_cache = EpochCache::initialized( + &self, + epoch, + self.generate_seed(epoch, spec)?, + self.get_epoch_start_shard(epoch, spec)?, + spec, + )?; Ok(()) } /// 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. - /// - /// The `Next` cache becomes the `Current` and the `Current` cache becomes the `Previous`. The - /// `Previous` cache is abandoned. - /// - /// Care should be taken to update the `Current` epoch in case a registry update is performed - /// -- `Next` epoch is always _without_ a registry change. If you perform a registry update, - /// you should rebuild the `Current` cache so it uses the new seed. pub fn advance_caches(&mut self) { - self.drop_cache(RelativeEpoch::Previous); - - self.cache_index_offset += 1; - self.cache_index_offset %= CACHED_EPOCHS; + self.previous_epoch_cache = + std::mem::replace(&mut self.current_epoch_cache, EpochCache::default()); + self.force_build_current_epoch_cache(); } - /// Removes the specified cache and sets it to uninitialized. - pub fn drop_cache(&mut self, relative_epoch: RelativeEpoch) { - let previous_cache_index = self.cache_index(relative_epoch); - self.caches[previous_cache_index] = EpochCache::default(); - } - - /// Returns the index of `self.caches` for some `RelativeEpoch`. - fn cache_index(&self, relative_epoch: RelativeEpoch) -> usize { - let base_index = match relative_epoch { - RelativeEpoch::Previous => 0, - RelativeEpoch::Current => 1, - RelativeEpoch::NextWithoutRegistryChange => 2, - RelativeEpoch::NextWithRegistryChange => 3, - }; - - (base_index + self.cache_index_offset) % CACHED_EPOCHS - } - - /// Returns the cache for some `RelativeEpoch`. Returns an error if the cache has not been - /// initialized. - fn cache(&self, relative_epoch: RelativeEpoch, spec: &ChainSpec) -> Result<&EpochCache, Error> { - let cache = &self.caches[self.cache_index(relative_epoch)]; - - let epoch = relative_epoch.into_epoch(self.slot.epoch(spec.slots_per_epoch)); - - if cache.initialized_epoch == Some(epoch) { - Ok(cache) - } else { - Err(Error::EpochCacheUninitialized(relative_epoch)) - } - } + // FIXME(sproul): drop_previous/current_epoch_cache /// Updates the pubkey cache, if required. /// @@ -940,12 +859,6 @@ impl BeaconState { } } -impl From for Error { - fn from(e: RelativeEpochError) -> Error { - Error::RelativeEpochError(e) - } -} - impl From for Error { fn from(e: EpochCacheError) -> Error { Error::EpochCacheError(e) diff --git a/eth2/types/src/beacon_state/epoch_cache.rs b/eth2/types/src/beacon_state/epoch_cache.rs index 1c461d9438..d8f199002f 100644 --- a/eth2/types/src/beacon_state/epoch_cache.rs +++ b/eth2/types/src/beacon_state/epoch_cache.rs @@ -6,6 +6,7 @@ use swap_or_not_shuffle::shuffle_list; #[derive(Debug, PartialEq)] pub enum Error { + EpochOutOfBounds, UnableToShuffle, UnableToGenerateSeed, } @@ -20,8 +21,6 @@ pub struct EpochCache { 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>, /// Indices of all active validators in the epoch pub active_validator_indices: Vec, } @@ -30,72 +29,42 @@ impl EpochCache { /// Return a new, fully initialized cache. pub fn initialized( state: &BeaconState, - relative_epoch: RelativeEpoch, + epoch: Epoch, + seed: Hash256, + epoch_start_shard: u64, spec: &ChainSpec, ) -> Result { - let epoch = relative_epoch.into_epoch(state.slot.epoch(spec.slots_per_epoch)); + if epoch != state.previous_epoch(spec) && epoch != state.current_epoch(spec) { + return Err(Error::EpochOutOfBounds); + } 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.clone(), - spec, - ), - RelativeEpoch::Current => EpochCrosslinkCommitteesBuilder::for_current_epoch( - state, - active_validator_indices.clone(), - spec, - ), - RelativeEpoch::NextWithRegistryChange => { - EpochCrosslinkCommitteesBuilder::for_next_epoch( - state, - active_validator_indices.clone(), - true, - spec, - )? - } - RelativeEpoch::NextWithoutRegistryChange => { - EpochCrosslinkCommitteesBuilder::for_next_epoch( - state, - active_validator_indices.clone(), - false, - spec, - )? - } - }; - let epoch_crosslink_committees = builder.build(spec)?; + let epoch_crosslink_committees = EpochCrosslinkCommittees::new( + epoch, + active_validator_indices.clone(), + seed, + epoch_start_shard, + state.get_epoch_committee_count(epoch, spec), + spec, + ); - // Loop through all the validators in the committees and create the following maps: + // Loop through all the validators in the committees and create the following map: // - // 1. `attestation_duties`: maps `ValidatorIndex` to `AttestationDuty`. - // 2. `shard_committee_indices`: maps `Shard` into a `CrosslinkCommittee` in - // `EpochCrosslinkCommittees`. + // `attestation_duties`: maps `ValidatorIndex` to `AttestationDuty`. let mut attestation_duties = vec![None; state.validator_registry.len()]; - let mut shard_committee_indices = vec![None; 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; - - for (j, crosslink_committee) in slot_committees.iter().enumerate() { - let shard = crosslink_committee.shard; - - shard_committee_indices[shard as usize] = Some((slot, j)); - - for (k, validator_index) in crosslink_committee.committee.iter().enumerate() { - let attestation_duty = AttestationDuty { - slot, - shard, - committee_index: k, - committee_len: crosslink_committee.committee.len(), - }; - attestation_duties[*validator_index] = Some(attestation_duty) - } + for crosslink_committee in epoch_crosslink_committees.crosslink_committees.iter() { + for (committee_index, validator_index) in + crosslink_committee.committee.iter().enumerate() + { + let attestation_duty = AttestationDuty { + slot: crosslink_committee.slot, + shard: crosslink_committee.shard, + committee_index, + committee_len: crosslink_committee.committee.len(), + }; + attestation_duties[*validator_index] = Some(attestation_duty); } } @@ -103,7 +72,6 @@ impl EpochCache { initialized_epoch: Some(epoch), epoch_crosslink_committees, attestation_duties, - shard_committee_indices, active_validator_indices, }) } @@ -138,7 +106,7 @@ impl EpochCache { /// Returns a list of all `validator_registry` indices where the validator is active at the given /// `epoch`. /// -/// Spec v0.5.1 +/// Spec v0.6.1 pub fn get_active_validator_indices(validators: &[Validator], epoch: Epoch) -> Vec { let mut active = Vec::with_capacity(validators.len()); @@ -158,17 +126,76 @@ pub fn get_active_validator_indices(validators: &[Validator], epoch: Epoch) -> V pub struct EpochCrosslinkCommittees { /// The epoch the committees are present in. epoch: Epoch, - /// Each commitee for each slot of the epoch. - pub crosslink_committees: Vec>, + /// Committees indexed by the `index` parameter of `compute_committee` from the spec. + /// + /// The length of the vector is equal to the number of committees in the epoch + /// i.e. `state.get_epoch_committee_count(self.epoch)` + pub crosslink_committees: Vec, +} + +fn crosslink_committee_slot( + shard: u64, + epoch: Epoch, + epoch_start_shard: u64, + epoch_committee_count: u64, + spec: &ChainSpec, +) -> Slot { + // Excerpt from `get_attestation_slot` in the spec. + let offset = (shard + spec.shard_count - epoch_start_shard) % spec.shard_count; + epoch.start_slot(spec.slots_per_epoch) + offset / (epoch_committee_count / spec.slots_per_epoch) } impl EpochCrosslinkCommittees { - /// Return a new instances where all slots have zero committees. - fn new(epoch: Epoch, spec: &ChainSpec) -> Self { - Self { + fn new( + epoch: Epoch, + active_validator_indices: Vec, + seed: Hash256, + epoch_start_shard: u64, + epoch_committee_count: u64, + spec: &ChainSpec, + ) -> Self { + // The shuffler fails on a empty list, so if there are no active validator indices, simply + // return an empty list. + let shuffled_active_validator_indices = if active_validator_indices.is_empty() { + vec![] + } else { + shuffle_list( + active_validator_indices, + spec.shuffle_round_count, + &seed[..], + false, + ) + .ok_or_else(|| Error::UnableToShuffle)? + }; + + let committee_size = + shuffled_active_validator_indices.len() / epoch_committee_count as usize; + + let crosslink_committees = shuffled_active_validator_indices + .into_iter() + .chunks(committee_size) + .enumerate() + .map(|(index, committee)| { + let shard = (epoch_start_start_shard + index) % spec.shard_count; + let slot = crosslink_committee_slot( + shard, + epoch, + epoch_start_shard, + epoch_committee_count, + spec, + ); + CrosslinkCommittee { + slot, + shard, + committee: committee.to_vec(), + } + }) + .collect(); + + Ok(Self { epoch, - crosslink_committees: vec![vec![]; spec.slots_per_epoch as usize], - } + crosslink_committees, + }) } /// Return a vec of `CrosslinkCommittee` for a given slot. @@ -188,145 +215,3 @@ impl EpochCrosslinkCommittees { } } } - -/// Builds an `EpochCrosslinkCommittees` object. -pub struct EpochCrosslinkCommitteesBuilder { - epoch: Epoch, - shuffling_start_shard: Shard, - shuffling_seed: Hash256, - active_validator_indices: Vec, - committees_per_epoch: u64, -} - -impl EpochCrosslinkCommitteesBuilder { - /// Instantiates a builder that will build for the `state`'s previous epoch. - pub fn for_previous_epoch( - state: &BeaconState, - active_validator_indices: Vec, - spec: &ChainSpec, - ) -> Self { - Self { - epoch: state.previous_epoch(spec), - // FIXME(sproul) - shuffling_start_shard: 0, - shuffling_seed: spec.zero_hash, - committees_per_epoch: spec.get_epoch_committee_count(active_validator_indices.len()), - active_validator_indices, - } - } - - /// Instantiates a builder that will build for the `state`'s next epoch. - pub fn for_current_epoch( - state: &BeaconState, - active_validator_indices: Vec, - spec: &ChainSpec, - ) -> Self { - Self { - epoch: state.current_epoch(spec), - // FIXME(sproul) - shuffling_start_shard: 0, - shuffling_seed: spec.zero_hash, - committees_per_epoch: spec.get_epoch_committee_count(active_validator_indices.len()), - active_validator_indices, - } - } - - /// Instantiates a builder that will build for the `state`'s next epoch. - /// - /// Note: there are two possible epoch builds for the next epoch, one where there is a registry - /// change and one where there is not. - 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()); - - // FIXME(sproul) - // current_epoch - state.validator_registry_update_epoch; - let epochs_since_last_registry_update = 0u64; - - let (seed, shuffling_start_shard) = if registry_change { - let next_seed = state - .generate_seed(next_epoch, spec) - .map_err(|_| Error::UnableToGenerateSeed)?; - ( - next_seed, - 0, - // FIXME(sproul) - // (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) - .map_err(|_| Error::UnableToGenerateSeed)?; - ( - next_seed, 0, /* FIXME(sproul) state.current_shuffling_start_shard*/ - ) - } else { - ( - spec.zero_hash, // state.current_shuffling_seed, - 0 // state.current_shuffling_start_shard, - ) - }; - - Ok(Self { - epoch: state.next_epoch(spec), - shuffling_start_shard, - shuffling_seed: seed, - active_validator_indices, - committees_per_epoch, - }) - } - - /// Consumes the builder, returning a fully-build `EpochCrosslinkCommittee`. - pub fn build(self, spec: &ChainSpec) -> Result { - // The shuffler fails on a empty list, so if there are no active validator indices, simply - // return an empty list. - let shuffled_active_validator_indices = if self.active_validator_indices.is_empty() { - vec![] - } else { - shuffle_list( - self.active_validator_indices, - spec.shuffle_round_count, - &self.shuffling_seed[..], - false, - ) - .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, slot) in self.epoch.slot_iter(spec.slots_per_epoch).enumerate() { - for j in (0..committees.len()) - .skip(i * committees_per_slot) - .take(committees_per_slot) - { - let crosslink_committee = CrosslinkCommittee { - slot, - shard, - committee: committees[j].drain(..).collect(), - }; - epoch_crosslink_committees.crosslink_committees[i].push(crosslink_committee); - - shard += 1; - shard %= spec.shard_count; - } - } - - Ok(epoch_crosslink_committees) - } -}