diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index 7eb7537c7..a21efa67f 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -362,6 +362,21 @@ impl BeaconState { unimplemented!("FIXME(sproul)") } + /// Returns the crosslink committees for some shard in some cached epoch. + /// + /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. + /// + /// Spec v0.6.1 + pub fn get_crosslink_committee_for_shard( + &self, + shard: u64, + epoch: Epoch, + ) -> Result, Error> { + let cache = self.cache(epoch)?; + + Ok(cache.get_crosslink_committee_for_shard(shard)) + } + /// Return the crosslink committeee for `shard` in `epoch`. /// /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. @@ -457,7 +472,7 @@ impl BeaconState { let current_epoch = self.current_epoch(); let len = T::LatestRandaoMixesLength::to_u64(); - if (current_epoch - len < epoch) & (epoch <= current_epoch) { + if (epoch + len > current_epoch) & (epoch <= current_epoch) { Ok(epoch.as_usize() % len as usize) } else { Err(Error::EpochOutOfBounds) @@ -504,10 +519,10 @@ impl BeaconState { fn get_active_index_root_index(&self, epoch: Epoch, spec: &ChainSpec) -> Result { let current_epoch = self.current_epoch(); - if current_epoch - self.latest_active_index_roots.len() as u64 + spec.activation_exit_delay - < epoch - && epoch <= current_epoch + spec.activation_exit_delay - { + let lookahead = spec.activation_exit_delay; + let lookback = self.latest_active_index_roots.len() as u64 - lookahead; + + if (epoch + lookback > current_epoch) && (current_epoch + lookahead >= epoch) { Ok(epoch.as_usize() % self.latest_active_index_roots.len()) } else { Err(Error::EpochOutOfBounds) diff --git a/eth2/types/src/beacon_state/epoch_cache.rs b/eth2/types/src/beacon_state/epoch_cache.rs index 7efb7fc0d..386b6c920 100644 --- a/eth2/types/src/beacon_state/epoch_cache.rs +++ b/eth2/types/src/beacon_state/epoch_cache.rs @@ -1,5 +1,6 @@ use super::BeaconState; use crate::*; +use honey_badger_split::SplitExt; use serde_derive::{Deserialize, Serialize}; use swap_or_not_shuffle::shuffle_list; @@ -35,7 +36,7 @@ impl EpochCache { epoch: Epoch, spec: &ChainSpec, ) -> Result { - if epoch != state.previous_epoch() && epoch != state.current_epoch() { + if (epoch < state.previous_epoch()) || (epoch > state.next_epoch()) { return Err(BeaconStateError::EpochOutOfBounds); } @@ -44,7 +45,7 @@ impl EpochCache { let epoch_committee_count = state.get_epoch_committee_count(epoch, spec); - let crosslink_committees = compute_epoch_commitees( + let crosslink_committees = compute_epoch_committees( epoch, state, active_validator_indices.clone(), @@ -58,7 +59,11 @@ impl EpochCache { for (i, crosslink_committee) in crosslink_committees.iter().enumerate() { shard_crosslink_committees[crosslink_committee.shard as usize] = Some(i); - slot_crosslink_committees[crosslink_committee.slot.as_usize()] = Some(i); + + let slot_index = epoch + .position(crosslink_committee.slot, spec.slots_per_epoch) + .ok_or_else(|| BeaconStateError::SlotOutOfBounds)?; + slot_crosslink_committees[slot_index] = Some(i); // Loop through each validator in the committee and store its attestation duties. for (committee_index, validator_index) in @@ -74,6 +79,8 @@ impl EpochCache { } } + dbg!(&shard_crosslink_committees); + Ok(EpochCache { initialized_epoch: Some(epoch), crosslink_committees, @@ -118,7 +125,7 @@ pub fn get_active_validator_indices(validators: &[Validator], epoch: Epoch) -> V active } -pub fn compute_epoch_commitees( +pub fn compute_epoch_committees( epoch: Epoch, state: &BeaconState, active_validator_indices: Vec, @@ -141,15 +148,17 @@ pub fn compute_epoch_commitees( .ok_or_else(|| Error::UnableToShuffle)? }; - let committee_size = shuffled_active_validator_indices.len() / epoch_committee_count as usize; - let epoch_start_shard = state.get_epoch_start_shard(epoch, spec)?; Ok(shuffled_active_validator_indices - .chunks(committee_size) + .honey_badger_split(epoch_committee_count as usize) .enumerate() .map(|(index, committee)| { let shard = (epoch_start_shard + index as u64) % spec.shard_count; + + dbg!(index); + dbg!(shard); + let slot = crosslink_committee_slot( shard, epoch, diff --git a/eth2/types/src/beacon_state/epoch_cache/tests.rs b/eth2/types/src/beacon_state/epoch_cache/tests.rs index f3ad37886..9b7a34f15 100644 --- a/eth2/types/src/beacon_state/epoch_cache/tests.rs +++ b/eth2/types/src/beacon_state/epoch_cache/tests.rs @@ -5,16 +5,15 @@ use crate::beacon_state::FewValidatorsEthSpec; use crate::test_utils::*; use swap_or_not_shuffle::shuffle_list; -fn do_sane_cache_test( +fn execute_sane_cache_test( state: BeaconState, epoch: Epoch, - relative_epoch: RelativeEpoch, validator_count: usize, spec: &ChainSpec, ) { let active_indices: Vec = (0..validator_count).collect(); let seed = state.generate_seed(epoch, spec).unwrap(); - let expected_shuffling_start = state.get_epoch_start_shard(epoch, spec).unwrap(); + let start_shard = state.get_epoch_start_shard(epoch, spec).unwrap(); assert_eq!( &active_indices[..], @@ -25,48 +24,43 @@ fn do_sane_cache_test( let shuffling = shuffle_list(active_indices, spec.shuffle_round_count, &seed[..], false).unwrap(); - let committees_per_epoch = spec.get_epoch_committee_count(shuffling.len()); - let committees_per_slot = committees_per_epoch / spec.slots_per_epoch; - let mut expected_indices_iter = shuffling.iter(); - let mut shard_counter = expected_shuffling_start; - for (i, slot) in epoch.slot_iter(spec.slots_per_epoch).enumerate() { - let crosslink_committees_at_slot = - state.get_crosslink_committees_at_slot(slot, &spec).unwrap(); + for i in 0..T::shard_count() { + let shard = (i + start_shard as usize) % T::shard_count(); - assert_eq!( - crosslink_committees_at_slot.len(), - committees_per_slot as usize, - "Bad committees per slot ({})", - i - ); + dbg!(shard); + dbg!(start_shard); - for c in crosslink_committees_at_slot { - assert_eq!(c.shard, shard_counter, "Bad shard"); - shard_counter += 1; - shard_counter %= spec.shard_count; + let c = state + .get_crosslink_committee_for_shard(shard as u64, epoch) + .unwrap() + .unwrap(); - for &i in &c.committee { - assert_eq!( - i, - *expected_indices_iter.next().unwrap(), - "Non-sequential validators." - ); - } + for &i in &c.committee { + assert_eq!( + i, + *expected_indices_iter.next().unwrap(), + "Non-sequential validators." + ); } } } -fn setup_sane_cache_test(validator_count: usize, spec: &ChainSpec) -> BeaconState { - let mut builder = - TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(validator_count, spec); +fn sane_cache_test( + validator_count: usize, + state_epoch: Epoch, + cache_epoch: RelativeEpoch, +) { + let spec = &T::spec(); - let epoch = spec.genesis_epoch + 4; - let slot = epoch.start_slot(spec.slots_per_epoch); + let mut builder = + TestingBeaconStateBuilder::from_single_keypair(validator_count, &Keypair::random(), spec); + + let slot = state_epoch.start_slot(spec.slots_per_epoch); builder.teleport_to_slot(slot, spec); - let (mut state, _keypairs) = builder.build(); + let (mut state, _keypairs): (BeaconState, _) = builder.build(); state .build_epoch_cache(RelativeEpoch::Previous, spec) @@ -76,88 +70,42 @@ fn setup_sane_cache_test(validator_count: usize, spec: &ChainSpec) - .unwrap(); state.build_epoch_cache(RelativeEpoch::Next, spec).unwrap(); - state + let cache_epoch = cache_epoch.into_epoch(state_epoch); + + execute_sane_cache_test(state, cache_epoch, validator_count as usize, &spec); } -#[test] -fn builds_sane_current_epoch_cache() { - let mut spec = FewValidatorsEthSpec::spec(); +fn sane_cache_test_suite(cached_epoch: RelativeEpoch) { + let spec = T::spec(); + let validator_count = (spec.shard_count * spec.target_committee_size) + 1; - let state: BeaconState = - setup_sane_cache_test(validator_count as usize, &spec); + sane_cache_test::(validator_count as usize, Epoch::new(0), cached_epoch); - let epoch = state.current_epoch(); - - do_sane_cache_test( - state, - epoch, - RelativeEpoch::Current, + sane_cache_test::( validator_count as usize, - &spec, + spec.genesis_epoch + 4, + cached_epoch, ); -} -/* - -#[test] -fn builds_sane_current_epoch_cache() { - let mut spec = FewValidatorsEthSpec::spec(); - spec.shard_count = 4; - let validator_count = (spec.shard_count * spec.target_committee_size) + 1; - - let state: BeaconState = - setup_sane_cache_test(validator_count as usize, &spec); - - do_sane_cache_test( - state.clone(), - state.current_epoch(&spec), - RelativeEpoch::Current, + sane_cache_test::( validator_count as usize, - state.current_shuffling_seed, - state.current_shuffling_start_shard, - &spec, + spec.genesis_epoch + T::slots_per_historical_root() as u64 * T::slots_per_epoch() * 4, + cached_epoch, ); } #[test] -fn builds_sane_previous_epoch_cache() { - let mut spec = FewValidatorsEthSpec::spec(); - spec.shard_count = 2; - let validator_count = (spec.shard_count * spec.target_committee_size) + 1; - - let state: BeaconState = - setup_sane_cache_test(validator_count as usize, &spec); - - do_sane_cache_test( - state.clone(), - state.previous_epoch(&spec), - RelativeEpoch::Previous, - validator_count as usize, - state.previous_shuffling_seed, - state.previous_shuffling_start_shard, - &spec, - ); +fn current_epoch_suite() { + sane_cache_test_suite::(RelativeEpoch::Current); } #[test] -fn builds_sane_next_without_update_epoch_cache() { - let mut spec = FewValidatorsEthSpec::spec(); - spec.shard_count = 2; - let validator_count = (spec.shard_count * spec.target_committee_size) + 1; - - let mut state: BeaconState = - setup_sane_cache_test(validator_count as usize, &spec); - - state.validator_registry_update_epoch = state.slot.epoch(spec.slots_per_epoch); - do_sane_cache_test( - state.clone(), - state.next_epoch(&spec), - RelativeEpoch::NextWithoutRegistryChange, - validator_count as usize, - state.current_shuffling_seed, - state.current_shuffling_start_shard, - &spec, - ); +fn previous_epoch_suite() { + sane_cache_test_suite::(RelativeEpoch::Previous); +} + +#[test] +fn next_epoch_suite() { + sane_cache_test_suite::(RelativeEpoch::Next); } -*/ diff --git a/eth2/types/src/beacon_state/tests.rs b/eth2/types/src/beacon_state/tests.rs index aff49d53e..3a937a9bc 100644 --- a/eth2/types/src/beacon_state/tests.rs +++ b/eth2/types/src/beacon_state/tests.rs @@ -2,10 +2,77 @@ use super::*; use crate::beacon_state::FewValidatorsEthSpec; use crate::test_utils::*; +use std::ops::RangeInclusive; ssz_tests!(FoundationBeaconState); cached_tree_hash_tests!(FoundationBeaconState); +/// Should produce (note the set notation brackets): +/// +/// (current_epoch - LATEST_ACTIVE_INDEX_ROOTS_LENGTH + ACTIVATION_EXIT_DELAY, current_epoch + +/// ACTIVATION_EXIT_DELAY] +fn active_index_range(current_epoch: Epoch) -> RangeInclusive { + let delay = T::spec().activation_exit_delay; + + let start: i32 = + current_epoch.as_u64() as i32 - T::latest_active_index_roots() as i32 + delay as i32; + let end = current_epoch + delay; + + let start: Epoch = if start < 0 { + Epoch::new(0) + } else { + Epoch::from(start as u64 + 1) + }; + + start..=end +} + +/// Test getting an active index root at the start and end of the valid range, and one either side +/// of that range. +fn test_active_index(state_slot: Slot) { + let spec = T::spec(); + let builder: TestingBeaconStateBuilder = + TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(16, &spec); + let (mut state, _keypairs) = builder.build(); + state.slot = state_slot; + + let range = active_index_range::(state.current_epoch()); + + let modulo = |epoch: Epoch| epoch.as_usize() % T::latest_active_index_roots(); + + // Test the start and end of the range. + assert_eq!( + state.get_active_index_root_index(*range.start(), &spec), + Ok(modulo(*range.start())) + ); + assert_eq!( + state.get_active_index_root_index(*range.end(), &spec), + Ok(modulo(*range.end())) + ); + + // One either side of the range. + if state.current_epoch() > 0 { + // Test is invalid on epoch zero, cannot subtract from zero. + assert_eq!( + state.get_active_index_root_index(*range.start() - 1, &spec), + Err(Error::EpochOutOfBounds) + ); + } + assert_eq!( + state.get_active_index_root_index(*range.end() + 1, &spec), + Err(Error::EpochOutOfBounds) + ); +} + +#[test] +fn get_active_index_root_index() { + test_active_index::(Slot::new(0)); + + let epoch = Epoch::from(FoundationEthSpec::latest_active_index_roots() * 4); + let slot = epoch.start_slot(FoundationEthSpec::slots_per_epoch()); + test_active_index::(slot); +} + /* /// Test that /// diff --git a/eth2/types/src/chain_spec.rs b/eth2/types/src/chain_spec.rs index 3dcfee61b..a2ee15d07 100644 --- a/eth2/types/src/chain_spec.rs +++ b/eth2/types/src/chain_spec.rs @@ -258,7 +258,7 @@ impl ChainSpec { /// Returns a `ChainSpec` compatible with the specification suitable for 8 validators. pub(crate) fn few_validators() -> Self { - let genesis_slot = Slot::new(2_u64.pow(32)); + let genesis_slot = Slot::new(0); let slots_per_epoch = 8; let genesis_epoch = genesis_slot.epoch(slots_per_epoch); diff --git a/eth2/types/src/relative_epoch.rs b/eth2/types/src/relative_epoch.rs index 4a4ed4ce5..58e3427cc 100644 --- a/eth2/types/src/relative_epoch.rs +++ b/eth2/types/src/relative_epoch.rs @@ -26,8 +26,9 @@ impl RelativeEpoch { /// Spec v0.6.1 pub fn into_epoch(self, base: Epoch) -> Epoch { match self { - RelativeEpoch::Previous => base - 1, + // Due to saturating nature of epoch, check for current first. RelativeEpoch::Current => base, + RelativeEpoch::Previous => base - 1, RelativeEpoch::Next => base + 1, } } @@ -41,10 +42,11 @@ impl RelativeEpoch { /// /// Spec v0.6.1 pub fn from_epoch(base: Epoch, other: Epoch) -> Result { - if other == base - 1 { - Ok(RelativeEpoch::Previous) - } else if other == base { + // Due to saturating nature of epoch, check for current first. + if other == base { Ok(RelativeEpoch::Current) + } else if other == base - 1 { + Ok(RelativeEpoch::Previous) } else if other == base + 1 { Ok(RelativeEpoch::Next) } else if other < base { diff --git a/eth2/types/src/slot_epoch.rs b/eth2/types/src/slot_epoch.rs index b0f4bdcf9..82cee0d75 100644 --- a/eth2/types/src/slot_epoch.rs +++ b/eth2/types/src/slot_epoch.rs @@ -58,10 +58,12 @@ impl Epoch { Epoch(u64::max_value()) } + /// The first slot in the epoch. pub fn start_slot(self, slots_per_epoch: u64) -> Slot { Slot::from(self.0.saturating_mul(slots_per_epoch)) } + /// The last slot in the epoch. pub fn end_slot(self, slots_per_epoch: u64) -> Slot { Slot::from( self.0 @@ -71,6 +73,20 @@ impl Epoch { ) } + /// Position of some slot inside an epoch, if any. + /// + /// E.g., the first `slot` in `epoch` is at position `0`. + pub fn position(&self, slot: Slot, slots_per_epoch: u64) -> Option { + let start = self.start_slot(slots_per_epoch); + let end = self.end_slot(slots_per_epoch); + + if (slot >= start) && (slot <= end) { + Some(slot.as_usize() - start.as_usize()) + } else { + None + } + } + pub fn slot_iter(&self, slots_per_epoch: u64) -> SlotIter { SlotIter { current_iteration: 0, @@ -124,6 +140,26 @@ mod epoch_tests { assert_eq!(epoch.end_slot(slots_per_epoch), Slot::new(7)); } + #[test] + fn position() { + let slots_per_epoch = 8; + + let epoch = Epoch::new(0); + assert_eq!(epoch.position(Slot::new(0), slots_per_epoch), Some(0)); + assert_eq!(epoch.position(Slot::new(1), slots_per_epoch), Some(1)); + assert_eq!(epoch.position(Slot::new(2), slots_per_epoch), Some(2)); + assert_eq!(epoch.position(Slot::new(3), slots_per_epoch), Some(3)); + assert_eq!(epoch.position(Slot::new(4), slots_per_epoch), Some(4)); + assert_eq!(epoch.position(Slot::new(5), slots_per_epoch), Some(5)); + assert_eq!(epoch.position(Slot::new(6), slots_per_epoch), Some(6)); + assert_eq!(epoch.position(Slot::new(7), slots_per_epoch), Some(7)); + assert_eq!(epoch.position(Slot::new(8), slots_per_epoch), None); + + let epoch = Epoch::new(1); + assert_eq!(epoch.position(Slot::new(7), slots_per_epoch), None); + assert_eq!(epoch.position(Slot::new(8), slots_per_epoch), Some(0)); + } + #[test] fn slot_iter() { let slots_per_epoch = 8; diff --git a/eth2/utils/honey-badger-split/src/lib.rs b/eth2/utils/honey-badger-split/src/lib.rs index b7097584f..ca02b5c01 100644 --- a/eth2/utils/honey-badger-split/src/lib.rs +++ b/eth2/utils/honey-badger-split/src/lib.rs @@ -52,6 +52,38 @@ impl SplitExt for [T] { mod tests { use super::*; + fn alternative_split_at_index(indices: &[T], index: usize, count: usize) -> &[T] { + let start = (indices.len() * index) / count; + let end = (indices.len() * (index + 1)) / count; + + &indices[start..end] + } + + fn alternative_split(input: &[T], n: usize) -> Vec<&[T]> { + (0..n) + .into_iter() + .map(|i| alternative_split_at_index(&input, i, n)) + .collect() + } + + fn honey_badger_vs_alternative_fn(num_items: usize, num_chunks: usize) { + let input: Vec = (0..num_items).collect(); + + let hb: Vec<&[usize]> = input.honey_badger_split(num_chunks).collect(); + let spec: Vec<&[usize]> = alternative_split(&input, num_chunks); + + assert_eq!(hb, spec); + } + + #[test] + fn vs_eth_spec_fn() { + for i in 0..10 { + for j in 0..10 { + honey_badger_vs_alternative_fn(i, j); + } + } + } + #[test] fn test_honey_badger_split() { /*