Fix epoch cache, add tests

This commit is contained in:
Paul Hauner 2019-05-17 16:30:12 +10:00
parent 86c3dad3e7
commit 089febb944
No known key found for this signature in database
GPG Key ID: 5E2CFF9B75FA63DF
8 changed files with 227 additions and 118 deletions

View File

@ -362,6 +362,21 @@ impl<T: EthSpec> BeaconState<T> {
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<Option<&CrosslinkCommittee>, 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<T: EthSpec> BeaconState<T> {
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<T: EthSpec> BeaconState<T> {
fn get_active_index_root_index(&self, epoch: Epoch, spec: &ChainSpec) -> Result<usize, Error> {
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)

View File

@ -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<EpochCache, BeaconStateError> {
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<T: EthSpec>(
pub fn compute_epoch_committees<T: EthSpec>(
epoch: Epoch,
state: &BeaconState<T>,
active_validator_indices: Vec<usize>,
@ -141,15 +148,17 @@ pub fn compute_epoch_commitees<T: EthSpec>(
.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,

View File

@ -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<T: EthSpec>(
fn execute_sane_cache_test<T: EthSpec>(
state: BeaconState<T>,
epoch: Epoch,
relative_epoch: RelativeEpoch,
validator_count: usize,
spec: &ChainSpec,
) {
let active_indices: Vec<usize> = (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<T: EthSpec>(
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<T: EthSpec>(validator_count: usize, spec: &ChainSpec) -> BeaconState<T> {
let mut builder =
TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(validator_count, spec);
fn sane_cache_test<T: EthSpec>(
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<T>, _) = builder.build();
state
.build_epoch_cache(RelativeEpoch::Previous, spec)
@ -76,88 +70,42 @@ fn setup_sane_cache_test<T: EthSpec>(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<T: EthSpec>(cached_epoch: RelativeEpoch) {
let spec = T::spec();
let validator_count = (spec.shard_count * spec.target_committee_size) + 1;
let state: BeaconState<FewValidatorsEthSpec> =
setup_sane_cache_test(validator_count as usize, &spec);
sane_cache_test::<T>(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::<T>(
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<FewValidatorsEthSpec> =
setup_sane_cache_test(validator_count as usize, &spec);
do_sane_cache_test(
state.clone(),
state.current_epoch(&spec),
RelativeEpoch::Current,
sane_cache_test::<T>(
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<FewValidatorsEthSpec> =
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::<FewValidatorsEthSpec>(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<FewValidatorsEthSpec> =
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::<FewValidatorsEthSpec>(RelativeEpoch::Previous);
}
#[test]
fn next_epoch_suite() {
sane_cache_test_suite::<FewValidatorsEthSpec>(RelativeEpoch::Next);
}
*/

View File

@ -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<T: EthSpec>(current_epoch: Epoch) -> RangeInclusive<Epoch> {
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<T: EthSpec>(state_slot: Slot) {
let spec = T::spec();
let builder: TestingBeaconStateBuilder<T> =
TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(16, &spec);
let (mut state, _keypairs) = builder.build();
state.slot = state_slot;
let range = active_index_range::<T>(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::<FoundationEthSpec>(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::<FoundationEthSpec>(slot);
}
/*
/// Test that
///

View File

@ -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);

View File

@ -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<Self, Error> {
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 {

View File

@ -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<usize> {
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;

View File

@ -52,6 +52,38 @@ impl<T> SplitExt<T> for [T] {
mod tests {
use super::*;
fn alternative_split_at_index<T>(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<T: Clone>(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<usize> = (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() {
/*