Move get_active_validator_indices to state

This commit is contained in:
Paul Hauner 2019-03-19 09:09:57 +11:00
parent 4c4952e71b
commit 37b8e9f39a
No known key found for this signature in database
GPG Key ID: D362883A9218FCC6
17 changed files with 57 additions and 239 deletions

View File

@ -10,10 +10,7 @@ use db::{
use log::{debug, trace}; use log::{debug, trace};
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use types::{ use types::{BeaconBlock, ChainSpec, Hash256, Slot, SlotHeight};
validator_registry::get_active_validator_indices, BeaconBlock, ChainSpec, Hash256, Slot,
SlotHeight,
};
//TODO: Pruning - Children //TODO: Pruning - Children
//TODO: Handle Syncing //TODO: Handle Syncing
@ -93,10 +90,8 @@ where
.get_deserialized(&state_root)? .get_deserialized(&state_root)?
.ok_or_else(|| ForkChoiceError::MissingBeaconState(*state_root))?; .ok_or_else(|| ForkChoiceError::MissingBeaconState(*state_root))?;
let active_validator_indices = get_active_validator_indices( let active_validator_indices =
&current_state.validator_registry[..], current_state.get_active_validator_indices(block_slot.epoch(spec.slots_per_epoch));
block_slot.epoch(spec.slots_per_epoch),
);
for index in active_validator_indices { for index in active_validator_indices {
let balance = std::cmp::min( let balance = std::cmp::min(

View File

@ -10,10 +10,7 @@ use log::{debug, trace};
use std::cmp::Ordering; use std::cmp::Ordering;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use types::{ use types::{BeaconBlock, ChainSpec, Hash256, Slot, SlotHeight};
validator_registry::get_active_validator_indices, BeaconBlock, ChainSpec, Hash256, Slot,
SlotHeight,
};
//TODO: Pruning - Children //TODO: Pruning - Children
//TODO: Handle Syncing //TODO: Handle Syncing
@ -93,10 +90,8 @@ where
.get_deserialized(&state_root)? .get_deserialized(&state_root)?
.ok_or_else(|| ForkChoiceError::MissingBeaconState(*state_root))?; .ok_or_else(|| ForkChoiceError::MissingBeaconState(*state_root))?;
let active_validator_indices = get_active_validator_indices( let active_validator_indices =
&current_state.validator_registry[..], current_state.get_active_validator_indices(block_slot.epoch(spec.slots_per_epoch));
block_slot.epoch(spec.slots_per_epoch),
);
for index in active_validator_indices { for index in active_validator_indices {
let balance = std::cmp::min( let balance = std::cmp::min(

View File

@ -8,9 +8,7 @@ use db::{
use log::{debug, trace}; use log::{debug, trace};
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use types::{ use types::{BeaconBlock, ChainSpec, Hash256, Slot};
validator_registry::get_active_validator_indices, BeaconBlock, ChainSpec, Hash256, Slot,
};
//TODO: Pruning and syncing //TODO: Pruning and syncing
@ -61,10 +59,8 @@ where
.get_deserialized(&state_root)? .get_deserialized(&state_root)?
.ok_or_else(|| ForkChoiceError::MissingBeaconState(*state_root))?; .ok_or_else(|| ForkChoiceError::MissingBeaconState(*state_root))?;
let active_validator_indices = get_active_validator_indices( let active_validator_indices =
&current_state.validator_registry[..], current_state.get_active_validator_indices(block_slot.epoch(spec.slots_per_epoch));
block_slot.epoch(spec.slots_per_epoch),
);
for index in active_validator_indices { for index in active_validator_indices {
let balance = std::cmp::min( let balance = std::cmp::min(

View File

@ -242,8 +242,9 @@ fn setup_inital_state(
let spec = ChainSpec::foundation(); let spec = ChainSpec::foundation();
let state_builder = let mut state_builder =
TestingBeaconStateBuilder::from_single_keypair(num_validators, &Keypair::random(), &spec); TestingBeaconStateBuilder::from_single_keypair(num_validators, &Keypair::random(), &spec);
state_builder.build_caches(&spec).unwrap();
let (state, _keypairs) = state_builder.build(); let (state, _keypairs) = state_builder.build();
let state_root = state.canonical_root(); let state_root = state.canonical_root();

View File

@ -34,7 +34,7 @@ pub fn get_genesis_state(
// Set all the active index roots to be the genesis active index root. // Set all the active index roots to be the genesis active index root.
let active_validator_indices = state let active_validator_indices = state
.get_active_validator_indices(spec.genesis_epoch, spec)? .get_cached_active_validator_indices(RelativeEpoch::Current, spec)?
.to_vec(); .to_vec();
let genesis_active_index_root = Hash256::from_slice(&active_validator_indices.hash_tree_root()); let genesis_active_index_root = Hash256::from_slice(&active_validator_indices.hash_tree_root());
state.fill_active_index_roots_with(genesis_active_index_root, spec); state.fill_active_index_roots_with(genesis_active_index_root, spec);

View File

@ -7,7 +7,7 @@ use process_validator_registry::process_validator_registry;
use rayon::prelude::*; use rayon::prelude::*;
use ssz::TreeHash; use ssz::TreeHash;
use std::collections::HashMap; use std::collections::HashMap;
use types::{validator_registry::get_active_validator_indices, *}; use types::*;
use validator_statuses::{TotalBalances, ValidatorStatuses}; use validator_statuses::{TotalBalances, ValidatorStatuses};
use winning_root::{winning_root, WinningRoot}; use winning_root::{winning_root, WinningRoot};
@ -70,16 +70,6 @@ pub fn per_epoch_processing(state: &mut BeaconState, spec: &ChainSpec) -> Result
Ok(()) Ok(())
} }
/// Returns a list of active validator indices for the state's current epoch.
///
/// Spec v0.5.0
pub fn calculate_active_validator_indices(state: &BeaconState, spec: &ChainSpec) -> Vec<usize> {
get_active_validator_indices(
&state.validator_registry,
state.slot.epoch(spec.slots_per_epoch),
)
}
/// Calculates various sets of attesters, including: /// Calculates various sets of attesters, including:
/// ///
/// - current epoch attesters /// - current epoch attesters
@ -454,10 +444,9 @@ pub fn update_active_tree_index_roots(
) -> Result<(), Error> { ) -> Result<(), Error> {
let next_epoch = state.next_epoch(spec); let next_epoch = state.next_epoch(spec);
let active_tree_root = get_active_validator_indices( let active_tree_root = state
&state.validator_registry, .get_active_validator_indices(next_epoch + Epoch::from(spec.activation_exit_delay))
next_epoch + Epoch::from(spec.activation_exit_delay), .to_vec()
)
.hash_tree_root(); .hash_tree_root();
state.set_active_index_root(next_epoch, Hash256::from_slice(&active_tree_root[..]), spec)?; state.set_active_index_root(next_epoch, Hash256::from_slice(&active_tree_root[..]), spec)?;

View File

@ -9,7 +9,7 @@ pub fn process_ejections(state: &mut BeaconState, spec: &ChainSpec) -> Result<()
// There is an awkward double (triple?) loop here because we can't loop across the borrowed // There is an awkward double (triple?) loop here because we can't loop across the borrowed
// active validator indices and mutate state in the one loop. // active validator indices and mutate state in the one loop.
let exitable: Vec<usize> = state let exitable: Vec<usize> = state
.get_active_validator_indices(state.current_epoch(spec), spec)? .get_cached_active_validator_indices(RelativeEpoch::Current, spec)?
.iter() .iter()
.filter_map(|&i| { .filter_map(|&i| {
if state.validator_balances[i as usize] < spec.ejection_balance { if state.validator_balances[i as usize] < spec.ejection_balance {

View File

@ -7,7 +7,8 @@ use types::{BeaconStateError as Error, *};
/// Spec v0.4.0 /// Spec v0.4.0
pub fn process_slashings(state: &mut BeaconState, spec: &ChainSpec) -> Result<(), Error> { pub fn process_slashings(state: &mut BeaconState, spec: &ChainSpec) -> Result<(), Error> {
let current_epoch = state.current_epoch(spec); let current_epoch = state.current_epoch(spec);
let active_validator_indices = state.get_active_validator_indices(current_epoch, spec)?; let active_validator_indices =
state.get_cached_active_validator_indices(RelativeEpoch::Current, spec)?;
let total_balance = state.get_total_balance(&active_validator_indices[..], spec)?; let total_balance = state.get_total_balance(&active_validator_indices[..], spec)?;
for (index, validator) in state.validator_registry.iter().enumerate() { for (index, validator) in state.validator_registry.iter().enumerate() {

View File

@ -21,7 +21,7 @@ pub fn process_validator_registry(state: &mut BeaconState, spec: &ChainSpec) ->
state.current_shuffling_start_shard = (state.current_shuffling_start_shard state.current_shuffling_start_shard = (state.current_shuffling_start_shard
+ spec.get_epoch_committee_count( + spec.get_epoch_committee_count(
state state
.get_active_validator_indices(current_epoch, spec)? .get_cached_active_validator_indices(RelativeEpoch::Current, spec)?
.len(), .len(),
) as u64) ) as u64)
% spec.shard_count; % spec.shard_count;
@ -53,7 +53,7 @@ pub fn should_update_validator_registry(
} }
let num_active_validators = state let num_active_validators = state
.get_active_validator_indices(state.current_epoch(spec), spec)? .get_cached_active_validator_indices(RelativeEpoch::Current, spec)?
.len(); .len();
let current_epoch_committee_count = spec.get_epoch_committee_count(num_active_validators); let current_epoch_committee_count = spec.get_epoch_committee_count(num_active_validators);

View File

@ -8,7 +8,8 @@ use types::{BeaconStateError as Error, *};
/// Spec v0.4.0 /// Spec v0.4.0
pub fn update_validator_registry(state: &mut BeaconState, spec: &ChainSpec) -> Result<(), Error> { pub fn update_validator_registry(state: &mut BeaconState, spec: &ChainSpec) -> Result<(), Error> {
let current_epoch = state.current_epoch(spec); let current_epoch = state.current_epoch(spec);
let active_validator_indices = state.get_active_validator_indices(current_epoch, spec)?; let active_validator_indices =
state.get_cached_active_validator_indices(RelativeEpoch::Current, spec)?;
let total_balance = state.get_total_balance(&active_validator_indices[..], spec)?; let total_balance = state.get_total_balance(&active_validator_indices[..], spec)?;
let max_balance_churn = std::cmp::max( let max_balance_churn = std::cmp::max(

View File

@ -1,4 +1,4 @@
use self::epoch_cache::{EpochCache, Error as EpochCacheError}; use self::epoch_cache::{get_active_validator_indices, EpochCache, Error as EpochCacheError};
use crate::test_utils::TestRandom; use crate::test_utils::TestRandom;
use crate::*; use crate::*;
use int_to_bytes::int_to_bytes32; use int_to_bytes::int_to_bytes32;
@ -234,28 +234,31 @@ impl BeaconState {
/// Returns the active validator indices for the given epoch, assuming there is no validator /// Returns the active validator indices for the given epoch, assuming there is no validator
/// registry update in the next epoch. /// 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. /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized.
/// ///
/// Spec v0.5.0 /// Spec v0.5.0
pub fn get_active_validator_indices( pub fn get_cached_active_validator_indices(
&self, &self,
epoch: Epoch, relative_epoch: RelativeEpoch,
spec: &ChainSpec, spec: &ChainSpec,
) -> Result<&[usize], Error> { ) -> Result<&[usize], Error> {
// If the slot is in the next epoch, assume there was no validator registry update.
let relative_epoch =
match RelativeEpoch::from_epoch(self.slot.epoch(spec.slots_per_epoch), epoch) {
Err(RelativeEpochError::AmbiguiousNextEpoch) => {
Ok(RelativeEpoch::NextWithoutRegistryChange)
}
e => e,
}?;
let cache = self.cache(relative_epoch, spec)?; let cache = self.cache(relative_epoch, spec)?;
Ok(&cache.active_validator_indices) Ok(&cache.active_validator_indices)
} }
/// Returns the active validator indices for the given epoch.
///
/// Does not utilize the cache, performs a full iteration over the validator registry.
///
/// Spec v0.5.0
pub fn get_active_validator_indices(&self, epoch: Epoch) -> Vec<usize> {
get_active_validator_indices(&self.validator_registry, epoch)
}
/// Returns the crosslink committees for some slot. /// Returns the crosslink committees for some slot.
/// ///
/// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized.

View File

@ -7,15 +7,19 @@ use swap_or_not_shuffle::shuffle_list;
fn do_sane_cache_test( fn do_sane_cache_test(
state: BeaconState, state: BeaconState,
epoch: Epoch, epoch: Epoch,
relative_epoch: RelativeEpoch,
validator_count: usize, validator_count: usize,
expected_seed: Hash256, expected_seed: Hash256,
expected_shuffling_start: u64, expected_shuffling_start: u64,
spec: &ChainSpec, spec: &ChainSpec,
) { ) {
let active_indices: Vec<usize> = (0..validator_count).collect(); let active_indices: Vec<usize> = (0..validator_count).collect();
assert_eq!( assert_eq!(
&active_indices[..], &active_indices[..],
state.get_active_validator_indices(epoch, &spec).unwrap(), state
.get_cached_active_validator_indices(relative_epoch, &spec)
.unwrap(),
"Validator indices mismatch" "Validator indices mismatch"
); );
@ -101,6 +105,7 @@ fn builds_sane_current_epoch_cache() {
do_sane_cache_test( do_sane_cache_test(
state.clone(), state.clone(),
state.current_epoch(&spec), state.current_epoch(&spec),
RelativeEpoch::Current,
validator_count as usize, validator_count as usize,
state.current_shuffling_seed, state.current_shuffling_seed,
state.current_shuffling_start_shard, state.current_shuffling_start_shard,
@ -117,6 +122,7 @@ fn builds_sane_previous_epoch_cache() {
do_sane_cache_test( do_sane_cache_test(
state.clone(), state.clone(),
state.previous_epoch(&spec), state.previous_epoch(&spec),
RelativeEpoch::Previous,
validator_count as usize, validator_count as usize,
state.previous_shuffling_seed, state.previous_shuffling_seed,
state.previous_shuffling_start_shard, state.previous_shuffling_start_shard,
@ -134,6 +140,7 @@ fn builds_sane_next_without_update_epoch_cache() {
do_sane_cache_test( do_sane_cache_test(
state.clone(), state.clone(),
state.next_epoch(&spec), state.next_epoch(&spec),
RelativeEpoch::NextWithoutRegistryChange,
validator_count as usize, validator_count as usize,
state.current_shuffling_seed, state.current_shuffling_seed,
state.current_shuffling_start_shard, state.current_shuffling_start_shard,

View File

@ -6,13 +6,17 @@ type ValidatorIndex = usize;
#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize)] #[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize)]
pub struct PubkeyCache { pub struct PubkeyCache {
/// Maintain the number of keys added to the map. It is not sufficient to just use the HashMap
/// len, as it does not increase when duplicate keys are added. Duplicate keys are used during
/// testing.
len: usize,
map: HashMap<PublicKey, ValidatorIndex>, map: HashMap<PublicKey, ValidatorIndex>,
} }
impl PubkeyCache { impl PubkeyCache {
/// Returns the number of validator indices already in the map. /// Returns the number of validator indices added to the map so far.
pub fn len(&self) -> ValidatorIndex { pub fn len(&self) -> ValidatorIndex {
self.map.len() self.len
} }
/// Inserts a validator index into the map. /// Inserts a validator index into the map.
@ -20,8 +24,9 @@ impl PubkeyCache {
/// The added index must equal the number of validators already added to the map. This ensures /// The added index must equal the number of validators already added to the map. This ensures
/// that an index is never skipped. /// that an index is never skipped.
pub fn insert(&mut self, pubkey: PublicKey, index: ValidatorIndex) -> bool { pub fn insert(&mut self, pubkey: PublicKey, index: ValidatorIndex) -> bool {
if index == self.map.len() { if index == self.len {
self.map.insert(pubkey, index); self.map.insert(pubkey, index);
self.len += 1;
true true
} else { } else {
false false

View File

@ -117,7 +117,7 @@ pub struct ChainSpec {
impl ChainSpec { impl ChainSpec {
/// Return the number of committees in one epoch. /// Return the number of committees in one epoch.
/// ///
/// Spec v0.4.0 /// Spec v0.5.0
pub fn get_epoch_committee_count(&self, active_validator_count: usize) -> u64 { pub fn get_epoch_committee_count(&self, active_validator_count: usize) -> u64 {
std::cmp::max( std::cmp::max(
1, 1,

View File

@ -34,7 +34,6 @@ pub mod relative_epoch;
pub mod slot_epoch; pub mod slot_epoch;
pub mod slot_height; pub mod slot_height;
pub mod validator; pub mod validator;
pub mod validator_registry;
use ethereum_types::{H160, H256, U256}; use ethereum_types::{H160, H256, U256};
use std::collections::HashMap; use std::collections::HashMap;

View File

@ -9,7 +9,7 @@ use test_random_derive::TestRandom;
/// The data submitted to the deposit contract. /// The data submitted to the deposit contract.
/// ///
/// Spec v0.4.0 /// Spec v0.5.0
#[derive( #[derive(
Debug, Debug,
PartialEq, PartialEq,

View File

@ -1,174 +0,0 @@
/// Contains logic to manipulate a `&[Validator]`.
/// For now, we avoid defining a newtype and just have flat functions here.
use super::validator::*;
use crate::Epoch;
/// Given an indexed sequence of `validators`, return the indices corresponding to validators that are active at `epoch`.
///
/// Spec v0.4.0
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
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
#[test]
fn can_get_empty_active_validator_indices() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let validators = vec![];
let some_epoch = Epoch::random_for_test(&mut rng);
let indices = get_active_validator_indices(&validators, some_epoch);
assert_eq!(indices, vec![]);
}
#[test]
fn can_get_no_active_validator_indices() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let mut validators = vec![];
let count_validators = 10;
for _ in 0..count_validators {
validators.push(Validator::default())
}
let some_epoch = Epoch::random_for_test(&mut rng);
let indices = get_active_validator_indices(&validators, some_epoch);
assert_eq!(indices, vec![]);
}
#[test]
fn can_get_all_active_validator_indices() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let count_validators = 10;
let some_epoch = Epoch::random_for_test(&mut rng);
let mut validators = (0..count_validators)
.into_iter()
.map(|_| {
let mut validator = Validator::default();
let activation_offset = u64::random_for_test(&mut rng);
let exit_offset = u64::random_for_test(&mut rng);
validator.activation_epoch = some_epoch - activation_offset;
validator.exit_epoch = some_epoch + exit_offset;
validator
})
.collect::<Vec<_>>();
// test boundary condition by ensuring that at least one validator in the list just activated
if let Some(validator) = validators.get_mut(0) {
validator.activation_epoch = some_epoch;
}
let indices = get_active_validator_indices(&validators, some_epoch);
assert_eq!(
indices,
(0..count_validators).into_iter().collect::<Vec<_>>()
);
}
fn set_validators_to_default_entry_exit(validators: &mut [Validator]) {
for validator in validators.iter_mut() {
validator.activation_epoch = Epoch::max_value();
validator.exit_epoch = Epoch::max_value();
}
}
// sets all `validators` to be active as of some epoch prior to `epoch`. returns the activation epoch.
fn set_validators_to_activated(validators: &mut [Validator], epoch: Epoch) -> Epoch {
let activation_epoch = epoch - 10;
for validator in validators.iter_mut() {
validator.activation_epoch = activation_epoch;
}
activation_epoch
}
// sets all `validators` to be exited as of some epoch before `epoch`.
fn set_validators_to_exited(
validators: &mut [Validator],
epoch: Epoch,
activation_epoch: Epoch,
) {
assert!(activation_epoch < epoch);
let mut exit_epoch = activation_epoch + 10;
while exit_epoch >= epoch {
exit_epoch -= 1;
}
assert!(activation_epoch < exit_epoch && exit_epoch < epoch);
for validator in validators.iter_mut() {
validator.exit_epoch = exit_epoch;
}
}
#[test]
fn can_get_some_active_validator_indices() {
let mut rng = XorShiftRng::from_seed([42; 16]);
const COUNT_PARTITIONS: usize = 3;
const COUNT_VALIDATORS: usize = 3 * COUNT_PARTITIONS;
let some_epoch: Epoch = Epoch::random_for_test(&mut rng);
let mut validators = (0..COUNT_VALIDATORS)
.into_iter()
.map(|_| {
let mut validator = Validator::default();
let activation_offset = Epoch::random_for_test(&mut rng);
let exit_offset = Epoch::random_for_test(&mut rng);
validator.activation_epoch = some_epoch - activation_offset;
validator.exit_epoch = some_epoch + exit_offset;
validator
})
.collect::<Vec<_>>();
// we partition the set into partitions based on lifecycle:
for (i, chunk) in validators.chunks_exact_mut(COUNT_PARTITIONS).enumerate() {
match i {
0 => {
// 1. not activated (Default::default())
set_validators_to_default_entry_exit(chunk);
}
1 => {
// 2. activated, but not exited
set_validators_to_activated(chunk, some_epoch);
// test boundary condition by ensuring that at least one validator in the list just activated
if let Some(validator) = chunk.get_mut(0) {
validator.activation_epoch = some_epoch;
}
}
2 => {
// 3. exited
let activation_epoch = set_validators_to_activated(chunk, some_epoch);
set_validators_to_exited(chunk, some_epoch, activation_epoch);
// test boundary condition by ensuring that at least one validator in the list just exited
if let Some(validator) = chunk.get_mut(0) {
validator.exit_epoch = some_epoch;
}
}
_ => unreachable!(
"constants local to this test not in sync with generation of test case"
),
}
}
let indices = get_active_validator_indices(&validators, some_epoch);
assert_eq!(indices, vec![3, 4, 5]);
}
}