spec: v0.6.1 scaffold for updated epoch cache

This commit is contained in:
Michael Sproul 2019-05-15 17:16:53 +10:00
parent 137afa9131
commit 0885d56b36
No known key found for this signature in database
GPG Key ID: 77B1309D2E54E914
2 changed files with 143 additions and 345 deletions

View File

@ -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<T: EthSpec> BeaconState<T> {
/*
* 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<T: EthSpec> BeaconState<T> {
+ 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<T: EthSpec> BeaconState<T> {
slot: Slot,
spec: &ChainSpec,
) -> Result<&Vec<CrosslinkCommittee>, 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<T: EthSpec> BeaconState<T> {
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<usize, Error> {
pub fn get_beacon_proposer_index(&self, spec: &ChainSpec) -> Result<usize, Error> {
unimplemented!("FIXME(sproul)")
/*
let cache = self.cache(relative_epoch, spec)?;
let committees = cache
@ -457,6 +408,7 @@ impl<T: EthSpec> BeaconState<T> {
.ok_or(Error::UnableToDetermineProducer)?;
Ok(first.committee[index])
})
*/
}
/// Safely obtains the index for latest block roots, given some `slot`.
@ -791,10 +743,8 @@ impl<T: EthSpec> BeaconState<T> {
/// 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<T: EthSpec> BeaconState<T> {
}
/// 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<T: EthSpec> BeaconState<T> {
}
}
/// 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<T: EthSpec> BeaconState<T> {
}
}
impl From<RelativeEpochError> for Error {
fn from(e: RelativeEpochError) -> Error {
Error::RelativeEpochError(e)
}
}
impl From<EpochCacheError> for Error {
fn from(e: EpochCacheError) -> Error {
Error::EpochCacheError(e)

View File

@ -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<Option<AttestationDuty>>,
/// Maps a shard to an index of `self.committees`.
pub shard_committee_indices: Vec<Option<(Slot, usize)>>,
/// Indices of all active validators in the epoch
pub active_validator_indices: Vec<usize>,
}
@ -30,72 +29,42 @@ impl EpochCache {
/// Return a new, fully initialized cache.
pub fn initialized<T: EthSpec>(
state: &BeaconState<T>,
relative_epoch: RelativeEpoch,
epoch: Epoch,
seed: Hash256,
epoch_start_shard: u64,
spec: &ChainSpec,
) -> Result<EpochCache, Error> {
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<usize> {
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<Vec<CrosslinkCommittee>>,
/// 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<CrosslinkCommittee>,
}
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<usize>,
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<usize>,
committees_per_epoch: u64,
}
impl EpochCrosslinkCommitteesBuilder {
/// Instantiates a builder that will build for the `state`'s previous epoch.
pub fn for_previous_epoch<T: EthSpec>(
state: &BeaconState<T>,
active_validator_indices: Vec<usize>,
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<T: EthSpec>(
state: &BeaconState<T>,
active_validator_indices: Vec<usize>,
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<T: EthSpec>(
state: &BeaconState<T>,
active_validator_indices: Vec<usize>,
registry_change: bool,
spec: &ChainSpec,
) -> Result<Self, Error> {
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<EpochCrosslinkCommittees, Error> {
// 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<Vec<usize>> = 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)
}
}