Refactor shuffling generation

This commit is contained in:
Paul Hauner 2019-03-16 19:14:49 +11:00
parent d6456a9486
commit 7f4af20212
No known key found for this signature in database
GPG Key ID: D362883A9218FCC6
11 changed files with 476 additions and 464 deletions

View File

@ -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,
}

View File

@ -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<usize>;
pub type CrosslinkCommittees = Vec<(Committee, u64)>;
pub type Shard = u64;
pub type CommitteeIndex = u64;
pub type AttestationDuty = (Slot, Shard, CommitteeIndex);
pub type AttestationDutyMap = HashMap<u64, AttestationDuty>;
pub type ShardCommitteeIndexMap = HashMap<Shard, (usize, usize)>;
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<RelativeEpoch, Error> {
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<CrosslinkCommittee>, 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<usize, Error> {
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<usize, Error> {
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<Vec<usize>, 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<u64, Error> {
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<Vec<Vec<usize>>, 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<Vec<Vec<usize>>, 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<usize> = 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<Vec<u64>, 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<Option<(Slot, u64, u64)>, Error> {
let cache = self.cache(RelativeEpoch::Current)?;
spec: &ChainSpec,
) -> Result<&Option<AttestationDuty>, 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<u64, Error> {
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<u64, Error> {
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<Vec<usize>, Error> {
let mut all_participants = attestations
.iter()
.try_fold::<_, _, Result<Vec<usize>, 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<u64, Error> {
validator_indices.iter().try_fold(0_u64, |acc, i| {
self.get_effective_balance(*i, spec)
.and_then(|bal| Ok(bal + acc))
})
}
}
impl From<RelativeEpochError> for Error {
fn from(e: RelativeEpochError) -> Error {
Error::RelativeEpochError(e)
}
}

View File

@ -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.

View File

@ -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<CrosslinkCommittees>,
/// `Some(epoch)` if the cache is initialized, where `epoch` is the cache it holds.
pub initialized_epoch: Option<Epoch>,
/// 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<Option<AttestationDuty>>,
/// 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<EpochCache, Error> {
let mut epoch_committees: Vec<CrosslinkCommittees> =
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<usize>, 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<CrosslinkCommittee>> {
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<usize> {
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<Vec<CrosslinkCommittee>>,
}
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<CrosslinkCommittee>> {
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<usize>,
committees_per_epoch: u64,
}
impl EpochCrosslinkCommitteesBuilder {
pub fn for_previous_epoch(
state: &BeaconState,
active_validator_indices: Vec<usize>,
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<usize>,
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<usize>,
registry_change: bool,
spec: &ChainSpec,
) -> Result<Self, BeaconStateError> {
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<EpochCrosslinkCommittees, BeaconStateError> {
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<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 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)
}
}

View File

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

View File

@ -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<usize>,
}

View File

View File

@ -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<usize>;
pub type CrosslinkCommittees = Vec<(Committee, u64)>;
pub type Hash256 = H256;
pub type Address = H160;
pub type EthBalance = U256;

View File

@ -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<Self, Error> {
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, Error> {
Self::from_epoch(
base.epoch(spec.slots_per_epoch),
other.epoch(spec.slots_per_epoch),
)
}
}

View File

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

View File

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