From 6a4252b8c641d2b59a186fccdd8735a18cc8d92e Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 28 Jan 2019 19:12:20 +1100 Subject: [PATCH] Add state helpers from #148 --- beacon_node/beacon_chain/src/info.rs | 26 +++- .../beacon_chain/src/state_transition.rs | 4 +- .../beacon_chain/test_harness/src/harness.rs | 5 +- .../src/validator/direct_duties.rs | 6 +- .../beacon_chain/test_harness/tests/chain.rs | 5 +- eth2/genesis/src/beacon_state.rs | 4 +- .../src/beacon_state/epoch_processing.rs | 25 ++-- eth2/types/src/beacon_state/mod.rs | 30 +++-- eth2/types/src/beacon_state/shuffling.rs | 117 ++++++++++++++++-- .../types/src/beacon_state/slot_processing.rs | 10 +- 10 files changed, 167 insertions(+), 65 deletions(-) diff --git a/beacon_node/beacon_chain/src/info.rs b/beacon_node/beacon_chain/src/info.rs index fd23c0906c..27b8440045 100644 --- a/beacon_node/beacon_chain/src/info.rs +++ b/beacon_node/beacon_chain/src/info.rs @@ -1,5 +1,11 @@ use super::{BeaconChain, ClientDB, SlotClock}; -use types::PublicKey; +use types::{beacon_state::Error as BeaconStateError, PublicKey}; + +#[derive(Debug, PartialEq)] +pub enum Error { + SlotClockError, + BeaconStateError(BeaconStateError), +} impl BeaconChain where @@ -39,10 +45,14 @@ where } } - pub fn block_proposer(&self, slot: u64) -> Option { - let present_slot = self.present_slot()?; - let state = self.state(present_slot).ok()?; - state.get_beacon_proposer_index(slot, &self.spec) + pub fn block_proposer(&self, slot: u64) -> Result { + // TODO: fix unwrap + let present_slot = self.present_slot().unwrap(); + // TODO: fix unwrap + let state = self.state(present_slot).unwrap(); + let index = state.get_beacon_proposer_index(slot, &self.spec)?; + + Ok(index) } pub fn justified_slot(&self) -> u64 { @@ -60,3 +70,9 @@ where Some(state.attestation_slot_and_shard_for_validator(validator_index, &self.spec)) } } + +impl From for Error { + fn from(e: BeaconStateError) -> Error { + Error::BeaconStateError(e) + } +} diff --git a/beacon_node/beacon_chain/src/state_transition.rs b/beacon_node/beacon_chain/src/state_transition.rs index 199bf8210f..0d51ab6763 100644 --- a/beacon_node/beacon_chain/src/state_transition.rs +++ b/beacon_node/beacon_chain/src/state_transition.rs @@ -98,7 +98,7 @@ where let block_proposer_index = state .get_beacon_proposer_index(block.slot, &self.spec) - .ok_or(Error::NoBlockProducer)?; + .map_err(|_| Error::NoBlockProducer)?; let block_proposer = &state.validator_registry[block_proposer_index]; if verify_block_signature { @@ -294,7 +294,7 @@ where ); if state.slot % self.spec.epoch_length == 0 { - state.per_epoch_processing(&self.spec); + state.per_epoch_processing(&self.spec).unwrap(); } Ok(state) diff --git a/beacon_node/beacon_chain/test_harness/src/harness.rs b/beacon_node/beacon_chain/test_harness/src/harness.rs index b96636c8d4..3982fd61ed 100644 --- a/beacon_node/beacon_chain/test_harness/src/harness.rs +++ b/beacon_node/beacon_chain/test_harness/src/harness.rs @@ -113,10 +113,7 @@ impl BeaconChainHarness { pub fn produce_block(&mut self) -> BeaconBlock { let present_slot = self.beacon_chain.present_slot().unwrap(); - let proposer = self - .beacon_chain - .block_proposer(present_slot) - .expect("Unable to determine proposer."); + let proposer = self.beacon_chain.block_proposer(present_slot).unwrap(); self.validators[proposer].produce_block().unwrap() } diff --git a/beacon_node/beacon_chain/test_harness/src/validator/direct_duties.rs b/beacon_node/beacon_chain/test_harness/src/validator/direct_duties.rs index 3259817ad1..eddc0d1772 100644 --- a/beacon_node/beacon_chain/test_harness/src/validator/direct_duties.rs +++ b/beacon_node/beacon_chain/test_harness/src/validator/direct_duties.rs @@ -35,9 +35,9 @@ where .ok_or_else(|| ProducerDutiesReaderError::UnknownValidator)?; match self.beacon_chain.block_proposer(slot) { - Some(proposer) if proposer == validator_index => Ok(true), - Some(_) => Ok(false), - None => Err(ProducerDutiesReaderError::UnknownEpoch), + Ok(proposer) if proposer == validator_index => Ok(true), + Ok(_) => Ok(false), + Err(_) => Err(ProducerDutiesReaderError::UnknownEpoch), } } } diff --git a/beacon_node/beacon_chain/test_harness/tests/chain.rs b/beacon_node/beacon_chain/test_harness/tests/chain.rs index bbe52ac0d3..c312dcf433 100644 --- a/beacon_node/beacon_chain/test_harness/tests/chain.rs +++ b/beacon_node/beacon_chain/test_harness/tests/chain.rs @@ -3,8 +3,9 @@ use types::ChainSpec; #[test] fn it_can_build_on_genesis_block() { - let validator_count = 2; - let mut harness = BeaconChainHarness::new(ChainSpec::foundation(), validator_count); + let validator_count = 10; + let spec = ChainSpec::foundation(); + let mut harness = BeaconChainHarness::new(spec, validator_count); harness.advance_chain_with_block(); } diff --git a/eth2/genesis/src/beacon_state.rs b/eth2/genesis/src/beacon_state.rs index 6e697af650..7d7cc7665f 100644 --- a/eth2/genesis/src/beacon_state.rs +++ b/eth2/genesis/src/beacon_state.rs @@ -55,8 +55,8 @@ pub fn genesis_beacon_state(spec: &ChainSpec) -> Result { current_epoch_start_shard: spec.genesis_start_shard, previous_epoch_calculation_slot: spec.genesis_slot, current_epoch_calculation_slot: spec.genesis_slot, - previous_epoch_randao_mix: spec.zero_hash, - current_epoch_randao_mix: spec.zero_hash, + previous_epoch_seed: spec.zero_hash, + current_epoch_seed: spec.zero_hash, /* * Custody challenges */ diff --git a/eth2/types/src/beacon_state/epoch_processing.rs b/eth2/types/src/beacon_state/epoch_processing.rs index 948da15d7e..d9c1e23133 100644 --- a/eth2/types/src/beacon_state/epoch_processing.rs +++ b/eth2/types/src/beacon_state/epoch_processing.rs @@ -7,6 +7,7 @@ use integer_sqrt::IntegerSquareRoot; use std::collections::{HashMap, HashSet}; use std::iter::FromIterator; +#[derive(Debug, PartialEq)] pub enum Error { UnableToDetermineProducer, NoBlockRoots, @@ -303,8 +304,8 @@ impl BeaconState { for slot in self.slot.saturating_sub(2 * spec.epoch_length)..self.slot { let crosslink_committees_at_slot = self - .get_crosslink_committees_at_slot(slot) - .ok_or_else(|| Error::UnableToGetCrosslinkCommittees)?; + .get_crosslink_committees_at_slot(slot, spec) + .map_err(|_| Error::UnableToGetCrosslinkCommittees)?; for (crosslink_committee, shard) in crosslink_committees_at_slot { let shard = shard as u64; @@ -454,7 +455,7 @@ impl BeaconState { }; let proposer_index = self .get_beacon_proposer_index(inclusion_slot, spec) - .ok_or_else(|| Error::UnableToDetermineProducer)?; + .map_err(|_| Error::UnableToDetermineProducer)?; let base_reward = self.base_reward(proposer_index, base_reward_quotient, spec); safe_add_assign!( self.validator_balances[proposer_index], @@ -467,8 +468,8 @@ impl BeaconState { */ for slot in self.slot.saturating_sub(2 * spec.epoch_length)..self.slot { let crosslink_committees_at_slot = self - .get_crosslink_committees_at_slot(slot) - .ok_or_else(|| Error::UnableToGetCrosslinkCommittees)?; + .get_crosslink_committees_at_slot(slot, spec) + .map_err(|_| Error::UnableToGetCrosslinkCommittees)?; for (_crosslink_committee, shard) in crosslink_committees_at_slot { let shard = shard as u64; @@ -514,7 +515,7 @@ impl BeaconState { */ self.previous_epoch_calculation_slot = self.current_epoch_calculation_slot; self.previous_epoch_start_shard = self.current_epoch_start_shard; - self.previous_epoch_randao_mix = self.current_epoch_randao_mix; + self.previous_epoch_seed = self.current_epoch_seed; let should_update_validator_registy = if self.finalized_slot > self.validator_registry_update_slot @@ -534,7 +535,7 @@ impl BeaconState { self.current_epoch_start_shard = (self.current_epoch_start_shard + self.get_current_epoch_committee_count_per_slot(spec) as u64 * spec.epoch_length) % spec.shard_count; - self.current_epoch_randao_mix = self.get_randao_mix( + self.current_epoch_seed = self.get_randao_mix( self.current_epoch_calculation_slot .saturating_sub(spec.seed_lookahead), spec, @@ -544,7 +545,7 @@ impl BeaconState { (self.slot - self.validator_registry_update_slot) / spec.epoch_length; if epochs_since_last_registry_change.is_power_of_two() { self.current_epoch_calculation_slot = self.slot; - self.current_epoch_randao_mix = self.get_randao_mix( + self.current_epoch_seed = self.get_randao_mix( self.current_epoch_calculation_slot .saturating_sub(spec.seed_lookahead), spec, @@ -709,14 +710,6 @@ impl BeaconState { (slot - slot % spec.epoch_length) + spec.epoch_length + spec.entry_exit_delay } - fn get_current_epoch_committee_count_per_slot(&self, spec: &ChainSpec) -> usize { - let current_active_validators = get_active_validator_indices( - &self.validator_registry, - self.current_epoch_calculation_slot, - ); - self.get_committee_count_per_slot(current_active_validators.len(), spec) - } - fn process_ejections(&self) { //TODO: stubbed out. } diff --git a/eth2/types/src/beacon_state/mod.rs b/eth2/types/src/beacon_state/mod.rs index 0032f275f4..85397ee112 100644 --- a/eth2/types/src/beacon_state/mod.rs +++ b/eth2/types/src/beacon_state/mod.rs @@ -20,6 +20,12 @@ pub use self::attestation_validation::Error as AttestationValidationError; pub use self::epoch_processing::Error as EpochProcessingError; pub use self::slot_processing::Error as SlotProcessingError; +#[derive(Debug, PartialEq)] +pub enum Error { + InvalidSlot, + InsufficientNumberOfValidators, +} + // Custody will not be added to the specs until Phase 1 (Sharding Phase) so dummy class used. type CustodyChallenge = usize; @@ -44,8 +50,8 @@ pub struct BeaconState { pub current_epoch_start_shard: u64, pub previous_epoch_calculation_slot: u64, pub current_epoch_calculation_slot: u64, - pub previous_epoch_randao_mix: Hash256, - pub current_epoch_randao_mix: Hash256, + pub previous_epoch_seed: Hash256, + pub current_epoch_seed: Hash256, // Custody challenges pub custody_challenges: Vec, @@ -90,8 +96,8 @@ impl Encodable for BeaconState { s.append(&self.current_epoch_start_shard); s.append(&self.previous_epoch_calculation_slot); s.append(&self.current_epoch_calculation_slot); - s.append(&self.previous_epoch_randao_mix); - s.append(&self.current_epoch_randao_mix); + s.append(&self.previous_epoch_seed); + s.append(&self.current_epoch_seed); s.append(&self.custody_challenges); s.append(&self.previous_justified_slot); s.append(&self.justified_slot); @@ -123,8 +129,8 @@ impl Decodable for BeaconState { let (current_epoch_start_shard, i) = <_>::ssz_decode(bytes, i)?; let (previous_epoch_calculation_slot, i) = <_>::ssz_decode(bytes, i)?; let (current_epoch_calculation_slot, i) = <_>::ssz_decode(bytes, i)?; - let (previous_epoch_randao_mix, i) = <_>::ssz_decode(bytes, i)?; - let (current_epoch_randao_mix, i) = <_>::ssz_decode(bytes, i)?; + let (previous_epoch_seed, i) = <_>::ssz_decode(bytes, i)?; + let (current_epoch_seed, i) = <_>::ssz_decode(bytes, i)?; let (custody_challenges, i) = <_>::ssz_decode(bytes, i)?; let (previous_justified_slot, i) = <_>::ssz_decode(bytes, i)?; let (justified_slot, i) = <_>::ssz_decode(bytes, i)?; @@ -154,8 +160,8 @@ impl Decodable for BeaconState { current_epoch_start_shard, previous_epoch_calculation_slot, current_epoch_calculation_slot, - previous_epoch_randao_mix, - current_epoch_randao_mix, + previous_epoch_seed, + current_epoch_seed, custody_challenges, previous_justified_slot, justified_slot, @@ -191,8 +197,8 @@ impl TreeHash for BeaconState { result.append(&mut self.current_epoch_start_shard.hash_tree_root()); result.append(&mut self.previous_epoch_calculation_slot.hash_tree_root()); result.append(&mut self.current_epoch_calculation_slot.hash_tree_root()); - result.append(&mut self.previous_epoch_randao_mix.hash_tree_root()); - result.append(&mut self.current_epoch_randao_mix.hash_tree_root()); + result.append(&mut self.previous_epoch_seed.hash_tree_root()); + result.append(&mut self.current_epoch_seed.hash_tree_root()); result.append(&mut self.custody_challenges.hash_tree_root()); result.append(&mut self.previous_justified_slot.hash_tree_root()); result.append(&mut self.justified_slot.hash_tree_root()); @@ -226,8 +232,8 @@ impl TestRandom for BeaconState { current_epoch_start_shard: <_>::random_for_test(rng), previous_epoch_calculation_slot: <_>::random_for_test(rng), current_epoch_calculation_slot: <_>::random_for_test(rng), - previous_epoch_randao_mix: <_>::random_for_test(rng), - current_epoch_randao_mix: <_>::random_for_test(rng), + previous_epoch_seed: <_>::random_for_test(rng), + current_epoch_seed: <_>::random_for_test(rng), custody_challenges: <_>::random_for_test(rng), previous_justified_slot: <_>::random_for_test(rng), justified_slot: <_>::random_for_test(rng), diff --git a/eth2/types/src/beacon_state/shuffling.rs b/eth2/types/src/beacon_state/shuffling.rs index eb93947f3e..95b402f8a9 100644 --- a/eth2/types/src/beacon_state/shuffling.rs +++ b/eth2/types/src/beacon_state/shuffling.rs @@ -1,9 +1,15 @@ +use super::Error; use crate::{validator_registry::get_active_validator_indices, BeaconState, ChainSpec, Hash256}; use honey_badger_split::SplitExt; +use std::ops::Range; use vec_shuffle::shuffle; -type CrosslinkCommittee = (Vec, usize); -type CrosslinkCommittees = Vec; +// utility function pending this functionality being stabilized on the `Range` type. +fn range_contains(range: &Range, target: T) -> bool { + range.start <= target && target < range.end +} + +type Result = std::result::Result; impl BeaconState { pub fn get_shuffling(&self, seed: Hash256, slot: u64, spec: &ChainSpec) -> Vec> { @@ -20,7 +26,7 @@ impl BeaconState { shuffle(&seed, active_validator_indices).expect("Max validator count exceed!"); shuffled_active_validator_indices - .honey_badger_split(committees_per_slot * spec.epoch_length as usize) + .honey_badger_split((committees_per_slot * spec.epoch_length) as usize) .filter_map(|slice: &[usize]| Some(slice.to_vec())) .collect() } @@ -29,19 +35,110 @@ impl BeaconState { &self, active_validator_count: usize, spec: &ChainSpec, - ) -> usize { + ) -> u64 { std::cmp::max( 1, std::cmp::min( - spec.shard_count as usize / spec.epoch_length as usize, - active_validator_count - / spec.epoch_length as usize - / spec.target_committee_size as usize, + spec.shard_count / spec.epoch_length, + active_validator_count as u64 / spec.epoch_length / spec.target_committee_size, ), ) } - pub fn get_crosslink_committees_at_slot(&self, _slot: u64) -> Option { - Some(vec![(vec![0], 0)]) + /// Returns the start slot and end slot of the current epoch containing `self.slot`. + fn get_current_epoch_boundaries(&self, epoch_length: u64) -> Range { + let slot_in_epoch = self.slot % epoch_length; + let start = self.slot - slot_in_epoch; + let end = self.slot + (epoch_length - slot_in_epoch); + start..end + } + + fn get_previous_epoch_committee_count_per_slot( + &self, + spec: &ChainSpec, + /* + shard_count: u64, + epoch_length: u64, + target_committee_size: u64, + */ + ) -> u64 { + let previous_active_validators = get_active_validator_indices( + &self.validator_registry, + self.previous_epoch_calculation_slot, + ); + self.get_committee_count_per_slot(previous_active_validators.len(), spec) as u64 + } + + pub fn get_current_epoch_committee_count_per_slot(&self, spec: &ChainSpec) -> u64 { + let current_active_validators = get_active_validator_indices( + &self.validator_registry, + self.current_epoch_calculation_slot, + ); + self.get_committee_count_per_slot(current_active_validators.len(), spec) + } + + pub fn get_crosslink_committees_at_slot( + &self, + slot: u64, + spec: &ChainSpec, + /* + epoch_length: u64, + shard_count: u64, + target_committee_size: u64, + */ + ) -> Result, u64)>> { + let current_epoch_range = self.get_current_epoch_boundaries(spec.epoch_length); + if !range_contains(¤t_epoch_range, slot) { + return Err(Error::InvalidSlot); + } + let state_epoch_slot = current_epoch_range.start; + let offset = slot % spec.epoch_length; + + let (committees_per_slot, shuffling, slot_start_shard) = if slot < state_epoch_slot { + let committees_per_slot = self.get_previous_epoch_committee_count_per_slot(spec); + let shuffling = self.get_shuffling( + self.previous_epoch_seed, + self.previous_epoch_calculation_slot, + spec, + ); + let slot_start_shard = + (self.previous_epoch_start_shard + committees_per_slot * offset) % spec.shard_count; + (committees_per_slot, shuffling, slot_start_shard) + } else { + let committees_per_slot = self.get_current_epoch_committee_count_per_slot(spec); + let shuffling = self.get_shuffling( + self.current_epoch_seed, + self.current_epoch_calculation_slot, + spec, + ); + let slot_start_shard = + (self.current_epoch_start_shard + committees_per_slot * offset) % spec.shard_count; + (committees_per_slot, shuffling, slot_start_shard) + }; + + let shard_range = slot_start_shard..; + Ok(shuffling + .into_iter() + .skip((committees_per_slot * offset) as usize) + .zip(shard_range.into_iter()) + .take(committees_per_slot as usize) + .map(|(committees, shard_number)| (committees, shard_number % spec.shard_count)) + .collect::>()) + } + + /// 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. + pub fn get_beacon_proposer_index(&self, slot: u64, spec: &ChainSpec) -> Result { + let committees = self.get_crosslink_committees_at_slot(slot, spec)?; + committees + .first() + .ok_or(Error::InsufficientNumberOfValidators) + .and_then(|(first_committee, _)| { + let index = (slot as usize) + .checked_rem(first_committee.len()) + .ok_or(Error::InsufficientNumberOfValidators)?; + // NOTE: next index will not panic as we have already returned if this is the case + Ok(first_committee[index]) + }) } } diff --git a/eth2/types/src/beacon_state/slot_processing.rs b/eth2/types/src/beacon_state/slot_processing.rs index 2070d57cb0..5dc351dae1 100644 --- a/eth2/types/src/beacon_state/slot_processing.rs +++ b/eth2/types/src/beacon_state/slot_processing.rs @@ -14,7 +14,7 @@ impl BeaconState { let block_proposer = self .get_beacon_proposer_index(self.slot, spec) - .ok_or_else(|| Error::UnableToDetermineProducer)?; + .map_err(|_| Error::UnableToDetermineProducer)?; self.validator_registry[block_proposer].proposer_slots += 1; self.latest_randao_mixes[(self.slot % spec.latest_randao_mixes_length) as usize] = @@ -31,14 +31,6 @@ impl BeaconState { Ok(()) } - pub fn get_beacon_proposer_index(&self, slot: u64, spec: &ChainSpec) -> Option { - // TODO: this is a stub; implement it properly. - // - // https://github.com/sigp/lighthouse/pull/148/files - let validator_count = self.validator_registry.len(); - Some((slot as usize) % validator_count) - } - pub fn attestation_slot_and_shard_for_validator( &self, validator_index: usize,