diff --git a/eth2/types/Cargo.toml b/eth2/types/Cargo.toml index fefd431a3..4c5be65b5 100644 --- a/eth2/types/Cargo.toml +++ b/eth2/types/Cargo.toml @@ -18,7 +18,7 @@ serde_derive = "1.0" serde_json = "1.0" slog = "^2.2.3" ssz = { path = "../utils/ssz" } -fisher_yates_shuffle = { path = "../utils/fisher_yates_shuffle" } +swap_or_not_shuffle = { path = "../utils/swap_or_not_shuffle" } [dev-dependencies] env_logger = "0.6.0" diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index d826344de..22885adfe 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -5,15 +5,18 @@ use crate::{ PendingAttestation, PublicKey, Signature, Slot, Validator, }; use bls::verify_proof_of_possession; -use fisher_yates_shuffle::shuffle; use honey_badger_split::SplitExt; use rand::RngCore; use serde_derive::Serialize; use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash}; +use swap_or_not_shuffle::get_permutated_index; + +mod tests; #[derive(Debug, PartialEq)] pub enum BeaconStateError { EpochOutOfBounds, + UnableToShuffle, InsufficientRandaoMixes, InsufficientValidators, InsufficientBlockRoots, @@ -249,23 +252,36 @@ impl BeaconState { /// committee is itself a list of validator indices. /// /// Spec v0.1 - pub fn get_shuffling(&self, seed: Hash256, epoch: Epoch, spec: &ChainSpec) -> Vec> { + pub fn get_shuffling( + &self, + seed: Hash256, + epoch: Epoch, + spec: &ChainSpec, + ) -> Option>> { let active_validator_indices = get_active_validator_indices(&self.validator_registry, epoch); let committees_per_epoch = self.get_epoch_committee_count(active_validator_indices.len(), spec); - // TODO: check that Hash256::from(u64) matches 'int_to_bytes32'. - let seed = seed ^ Hash256::from(epoch.as_u64()); - // TODO: fix `expect` assert. - let shuffled_active_validator_indices = - shuffle(&seed, active_validator_indices).expect("Max validator count exceed!"); + let mut shuffled_active_validator_indices = + Vec::with_capacity(active_validator_indices.len()); + for &i in &active_validator_indices { + let shuffled_i = get_permutated_index( + i, + active_validator_indices.len(), + &seed[..], + spec.shuffle_round_count, + )?; + shuffled_active_validator_indices[i] = active_validator_indices[shuffled_i] + } - shuffled_active_validator_indices - .honey_badger_split(committees_per_epoch as usize) - .map(|slice: &[usize]| slice.to_vec()) - .collect() + Some( + shuffled_active_validator_indices + .honey_badger_split(committees_per_epoch as usize) + .map(|slice: &[usize]| slice.to_vec()) + .collect(), + ) } /// Return the number of committees in the previous epoch. @@ -401,7 +417,9 @@ impl BeaconState { return Err(BeaconStateError::EpochOutOfBounds); }; - let shuffling = self.get_shuffling(seed, shuffling_epoch, spec); + let shuffling = self + .get_shuffling(seed, shuffling_epoch, spec) + .ok_or_else(|| BeaconStateError::UnableToShuffle)?; let offset = slot.as_u64() % spec.epoch_length; let committees_per_slot = committees_per_epoch / spec.epoch_length; let slot_start_shard = @@ -1064,33 +1082,3 @@ impl TestRandom for BeaconState { } } } - -#[cfg(test)] -mod tests { - use super::*; - use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng}; - use ssz::ssz_encode; - - #[test] - pub fn test_ssz_round_trip() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = BeaconState::random_for_test(&mut rng); - - let bytes = ssz_encode(&original); - let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap(); - - assert_eq!(original, decoded); - } - - #[test] - pub fn test_hash_tree_root() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = BeaconState::random_for_test(&mut rng); - - let result = original.hash_tree_root(); - - assert_eq!(result.len(), 32); - // TODO: Add further tests - // https://github.com/sigp/lighthouse/issues/170 - } -} diff --git a/eth2/types/src/beacon_state/tests.rs b/eth2/types/src/beacon_state/tests.rs new file mode 100644 index 000000000..e069e462e --- /dev/null +++ b/eth2/types/src/beacon_state/tests.rs @@ -0,0 +1,97 @@ +#![cfg(test)] + +use super::*; +use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng}; +use crate::{ + beacon_state::BeaconStateError, BeaconState, ChainSpec, Deposit, DepositData, DepositInput, + Eth1Data, Hash256, Keypair, +}; +use bls::create_proof_of_possession; +use ssz::ssz_encode; + +struct BeaconStateTestBuilder { + pub genesis_time: u64, + pub initial_validator_deposits: Vec, + pub latest_eth1_data: Eth1Data, + pub spec: ChainSpec, + pub keypairs: Vec, +} + +impl BeaconStateTestBuilder { + pub fn with_random_validators(validator_count: usize) -> Self { + let genesis_time = 10_000_000; + let keypairs: Vec = (0..validator_count) + .collect::>() + .iter() + .map(|_| Keypair::random()) + .collect(); + let initial_validator_deposits = keypairs + .iter() + .map(|keypair| Deposit { + branch: vec![], // branch verification is not specified. + index: 0, // index verification is not specified. + deposit_data: DepositData { + amount: 32_000_000_000, // 32 ETH (in Gwei) + timestamp: genesis_time - 1, + deposit_input: DepositInput { + pubkey: keypair.pk.clone(), + withdrawal_credentials: Hash256::zero(), // Withdrawal not possible. + proof_of_possession: create_proof_of_possession(&keypair), + }, + }, + }) + .collect(); + let latest_eth1_data = Eth1Data { + deposit_root: Hash256::zero(), + block_hash: Hash256::zero(), + }; + let spec = ChainSpec::foundation(); + + Self { + genesis_time, + initial_validator_deposits, + latest_eth1_data, + spec, + keypairs, + } + } + + pub fn build(&self) -> Result { + BeaconState::genesis( + self.genesis_time, + self.initial_validator_deposits.clone(), + self.latest_eth1_data.clone(), + &self.spec, + ) + } +} + +#[test] +pub fn can_produce_genesis_block() { + let builder = BeaconStateTestBuilder::with_random_validators(2); + + builder.build().unwrap(); +} + +#[test] +pub fn test_ssz_round_trip() { + let mut rng = XorShiftRng::from_seed([42; 16]); + let original = BeaconState::random_for_test(&mut rng); + + let bytes = ssz_encode(&original); + let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap(); + + assert_eq!(original, decoded); +} + +#[test] +pub fn test_hash_tree_root() { + let mut rng = XorShiftRng::from_seed([42; 16]); + let original = BeaconState::random_for_test(&mut rng); + + let result = original.hash_tree_root(); + + assert_eq!(result.len(), 32); + // TODO: Add further tests + // https://github.com/sigp/lighthouse/issues/170 +} diff --git a/eth2/types/src/beacon_state_tests.rs b/eth2/types/src/beacon_state_tests.rs deleted file mode 100644 index cae815f33..000000000 --- a/eth2/types/src/beacon_state_tests.rs +++ /dev/null @@ -1,72 +0,0 @@ -#[cfg(test)] -mod tests { - use crate::{ - beacon_state::BeaconStateError, BeaconState, ChainSpec, Deposit, DepositData, DepositInput, - Eth1Data, Hash256, Keypair, - }; - use bls::create_proof_of_possession; - - struct BeaconStateTestBuilder { - pub genesis_time: u64, - pub initial_validator_deposits: Vec, - pub latest_eth1_data: Eth1Data, - pub spec: ChainSpec, - pub keypairs: Vec, - } - - impl BeaconStateTestBuilder { - pub fn with_random_validators(validator_count: usize) -> Self { - let genesis_time = 10_000_000; - let keypairs: Vec = (0..validator_count) - .collect::>() - .iter() - .map(|_| Keypair::random()) - .collect(); - let initial_validator_deposits = keypairs - .iter() - .map(|keypair| Deposit { - branch: vec![], // branch verification is not specified. - index: 0, // index verification is not specified. - deposit_data: DepositData { - amount: 32_000_000_000, // 32 ETH (in Gwei) - timestamp: genesis_time - 1, - deposit_input: DepositInput { - pubkey: keypair.pk.clone(), - withdrawal_credentials: Hash256::zero(), // Withdrawal not possible. - proof_of_possession: create_proof_of_possession(&keypair), - }, - }, - }) - .collect(); - let latest_eth1_data = Eth1Data { - deposit_root: Hash256::zero(), - block_hash: Hash256::zero(), - }; - let spec = ChainSpec::foundation(); - - Self { - genesis_time, - initial_validator_deposits, - latest_eth1_data, - spec, - keypairs, - } - } - - pub fn build(&self) -> Result { - BeaconState::genesis( - self.genesis_time, - self.initial_validator_deposits.clone(), - self.latest_eth1_data.clone(), - &self.spec, - ) - } - } - - #[test] - pub fn can_produce_genesis_block() { - let builder = BeaconStateTestBuilder::with_random_validators(2); - - builder.build().unwrap(); - } -} diff --git a/eth2/types/src/lib.rs b/eth2/types/src/lib.rs index d33c48532..233d1cc3e 100644 --- a/eth2/types/src/lib.rs +++ b/eth2/types/src/lib.rs @@ -7,7 +7,6 @@ pub mod attester_slashing; pub mod beacon_block; pub mod beacon_block_body; pub mod beacon_state; -pub mod beacon_state_tests; pub mod casper_slashing; pub mod crosslink; pub mod deposit; diff --git a/eth2/types/src/spec/mod.rs b/eth2/types/src/spec/mod.rs index 53c78a2c2..3632108ca 100644 --- a/eth2/types/src/spec/mod.rs +++ b/eth2/types/src/spec/mod.rs @@ -17,7 +17,7 @@ pub struct ChainSpec { pub beacon_chain_shard_number: u64, pub max_indices_per_slashable_vote: u64, pub max_withdrawals_per_epoch: u64, - pub shuffle_round_count: u64, + pub shuffle_round_count: u8, /* * Deposit contract