diff --git a/beacon_node/beacon_chain/Cargo.toml b/beacon_node/beacon_chain/Cargo.toml index e12476553..7e6dc209d 100644 --- a/beacon_node/beacon_chain/Cargo.toml +++ b/beacon_node/beacon_chain/Cargo.toml @@ -13,6 +13,8 @@ failure = "0.1" failure_derive = "0.1" genesis = { path = "../../eth2/genesis" } hashing = { path = "../../eth2/utils/hashing" } +log = "0.4" +env_logger = "0.6" serde = "1.0" serde_derive = "1.0" serde_json = "1.0" diff --git a/beacon_node/beacon_chain/src/attestation_aggregator.rs b/beacon_node/beacon_chain/src/attestation_aggregator.rs index a67923e13..c95feb1e1 100644 --- a/beacon_node/beacon_chain/src/attestation_aggregator.rs +++ b/beacon_node/beacon_chain/src/attestation_aggregator.rs @@ -96,7 +96,9 @@ impl AttestationAggregator { self.store .values() .filter_map(|attestation| { - if state.validate_attestation(attestation, spec).is_ok() + if state + .validate_attestation_without_signature(attestation, spec) + .is_ok() && !known_attestation_data.contains(&attestation.data) { Some(attestation.clone()) diff --git a/beacon_node/beacon_chain/src/block_processing.rs b/beacon_node/beacon_chain/src/block_processing.rs index 89c5bfc14..b4126ed9e 100644 --- a/beacon_node/beacon_chain/src/block_processing.rs +++ b/beacon_node/beacon_chain/src/block_processing.rs @@ -1,5 +1,6 @@ use super::state_transition::Error as TransitionError; use super::{BeaconChain, ClientDB, DBError, SlotClock}; +use log::debug; use slot_clock::{SystemTimeSlotClockError, TestingSlotClockError}; use ssz::{ssz_encode, Encodable}; use types::{ @@ -65,6 +66,8 @@ where where V: BeaconBlockReader + Encodable + Sized, { + debug!("Processing block with slot {}...", block.slot()); + let block = block .into_beacon_block() .ok_or(Error::UnableToDecodeBlock)?; diff --git a/beacon_node/beacon_chain/src/block_production.rs b/beacon_node/beacon_chain/src/block_production.rs index 58f3ae2cc..289bc485d 100644 --- a/beacon_node/beacon_chain/src/block_production.rs +++ b/beacon_node/beacon_chain/src/block_production.rs @@ -1,6 +1,7 @@ use super::state_transition::Error as TransitionError; use super::{BeaconChain, ClientDB, DBError, SlotClock}; use bls::Signature; +use log::debug; use slot_clock::TestingSlotClockError; use types::{ readers::{BeaconBlockReader, BeaconStateReader}, @@ -33,6 +34,8 @@ where .map_err(|e| e.into())? .ok_or(Error::PresentSlotIsNone)?; + debug!("Producing block for slot {}...", present_slot); + let parent_root = self.head().beacon_block_root; let parent_block_reader = self .block_store @@ -45,6 +48,8 @@ where .into_beacon_state() .ok_or_else(|| Error::DBError("State invalid.".to_string()))?; + debug!("Finding attesatations for block..."); + let attestations = self .attestation_aggregator .read() @@ -52,6 +57,8 @@ where // TODO: advance the parent_state slot. .get_attestations_for_state(&parent_state, &self.spec); + debug!("Found {} attestation(s).", attestations.len()); + let mut block = BeaconBlock { slot: present_slot, parent_root: parent_root.clone(), @@ -81,6 +88,8 @@ where block.state_root = state_root; + debug!("Block produced."); + Ok((block, state)) } } diff --git a/beacon_node/beacon_chain/src/canonical_head.rs b/beacon_node/beacon_chain/src/canonical_head.rs index 04bbe57ed..4bcac5b64 100644 --- a/beacon_node/beacon_chain/src/canonical_head.rs +++ b/beacon_node/beacon_chain/src/canonical_head.rs @@ -1,11 +1,11 @@ use crate::{BeaconChain, CheckPoint, ClientDB, SlotClock}; use std::sync::RwLockReadGuard; -use types::{beacon_state::SlotProcessingError, BeaconBlock, BeaconState, Hash256}; +use types::{beacon_state::CommitteesError, BeaconBlock, BeaconState, Hash256}; #[derive(Debug, PartialEq)] pub enum Error { PastSlot, - UnableToDetermineProducer, + CommitteesError(CommitteesError), } impl BeaconChain @@ -64,10 +64,8 @@ where } } -impl From for Error { - fn from(e: SlotProcessingError) -> Error { - match e { - SlotProcessingError::UnableToDetermineProducer => Error::UnableToDetermineProducer, - } +impl From for Error { + fn from(e: CommitteesError) -> Error { + Error::CommitteesError(e) } } diff --git a/beacon_node/beacon_chain/src/epoch_processing.rs b/beacon_node/beacon_chain/src/epoch_processing.rs deleted file mode 100644 index 405ba7df2..000000000 --- a/beacon_node/beacon_chain/src/epoch_processing.rs +++ /dev/null @@ -1,9 +0,0 @@ -use super::{BeaconChain, ClientDB, DBError, SlotClock}; - -impl BeaconChain -where - T: ClientDB, - U: SlotClock, -{ - pub fn per_epoch_processing(&self) {} -} diff --git a/beacon_node/beacon_chain/src/info.rs b/beacon_node/beacon_chain/src/info.rs index 27b844004..1ecd97c1e 100644 --- a/beacon_node/beacon_chain/src/info.rs +++ b/beacon_node/beacon_chain/src/info.rs @@ -1,10 +1,10 @@ use super::{BeaconChain, ClientDB, SlotClock}; -use types::{beacon_state::Error as BeaconStateError, PublicKey}; +use types::{beacon_state::CommitteesError, PublicKey}; #[derive(Debug, PartialEq)] pub enum Error { SlotClockError, - BeaconStateError(BeaconStateError), + CommitteesError(CommitteesError), } impl BeaconChain @@ -45,7 +45,7 @@ where } } - pub fn block_proposer(&self, slot: u64) -> Result { + pub fn block_proposer(&self, slot: u64) -> Result { // TODO: fix unwrap let present_slot = self.present_slot().unwrap(); // TODO: fix unwrap @@ -67,12 +67,14 @@ where let present_slot = self.present_slot()?; let state = self.state(present_slot).ok()?; - Some(state.attestation_slot_and_shard_for_validator(validator_index, &self.spec)) + state + .attestation_slot_and_shard_for_validator(validator_index, &self.spec) + .ok() } } -impl From for Error { - fn from(e: BeaconStateError) -> Error { - Error::BeaconStateError(e) +impl From for Error { + fn from(e: CommitteesError) -> Error { + Error::CommitteesError(e) } } diff --git a/beacon_node/beacon_chain/src/lib.rs b/beacon_node/beacon_chain/src/lib.rs index 401479e5a..5fdadd34f 100644 --- a/beacon_node/beacon_chain/src/lib.rs +++ b/beacon_node/beacon_chain/src/lib.rs @@ -7,7 +7,6 @@ pub mod block_processing; pub mod block_production; mod canonical_head; pub mod dump; -pub mod epoch_processing; mod finalized_head; mod info; mod lmd_ghost; diff --git a/beacon_node/beacon_chain/src/state_transition.rs b/beacon_node/beacon_chain/src/state_transition.rs index 0d51ab676..fc29a63c4 100644 --- a/beacon_node/beacon_chain/src/state_transition.rs +++ b/beacon_node/beacon_chain/src/state_transition.rs @@ -1,13 +1,13 @@ use super::{BeaconChain, ClientDB, DBError, SlotClock}; use bls::{PublicKey, Signature}; -use boolean_bitfield::BooleanBitfield; use hashing::hash; +use log::debug; use slot_clock::{SystemTimeSlotClockError, TestingSlotClockError}; use ssz::{ssz_encode, TreeHash}; use types::{ - beacon_state::{AttestationValidationError, SlotProcessingError}, + beacon_state::{AttestationValidationError, CommitteesError, EpochProcessingError}, readers::BeaconBlockReader, - AttestationData, BeaconBlock, BeaconState, Exit, Fork, Hash256, PendingAttestation, + BeaconBlock, BeaconState, Exit, Fork, Hash256, PendingAttestation, }; // TODO: define elsehwere. @@ -51,6 +51,8 @@ pub enum Error { BadCustodyChallenges, BadCustodyResponses, SlotClockError(SystemTimeSlotClockError), + CommitteesError(CommitteesError), + EpochProcessingError(EpochProcessingError), } impl BeaconChain @@ -82,6 +84,11 @@ where ) -> Result { ensure!(state.slot < block.slot, Error::StateAlreadyTransitioned); + debug!( + "Starting state transition from slot {} to {}...", + state.slot, block.slot + ); + for _ in state.slot..block.slot { state.per_slot_processing(block.parent_root.clone(), &self.spec)?; } @@ -113,6 +120,8 @@ where ); } + debug!("Block signature is valid."); + /* * RANDAO */ @@ -127,6 +136,8 @@ where Error::BadRandaoSignature ); + debug!("RANDAO signature is valid."); + // TODO: check this is correct. let new_mix = { let mut mix = state.latest_randao_mixes @@ -228,6 +239,11 @@ where state.latest_attestations.push(pending_attestation); } + debug!( + "{} attestations verified & processed.", + block.body.attestations.len() + ); + /* * Deposits */ @@ -294,9 +310,11 @@ where ); if state.slot % self.spec.epoch_length == 0 { - state.per_epoch_processing(&self.spec).unwrap(); + state.per_epoch_processing(&self.spec)?; } + debug!("State transition complete."); + Ok(state) } } @@ -337,16 +355,20 @@ impl From for Error { } } -impl From for Error { - fn from(e: SlotProcessingError) -> Error { - match e { - SlotProcessingError::UnableToDetermineProducer => Error::NoBlockProducer, - } - } -} - impl From for Error { fn from(e: AttestationValidationError) -> Error { Error::InvalidAttestation(e) } } + +impl From for Error { + fn from(e: CommitteesError) -> Error { + Error::CommitteesError(e) + } +} + +impl From for Error { + fn from(e: EpochProcessingError) -> Error { + Error::EpochProcessingError(e) + } +} diff --git a/beacon_node/beacon_chain/test_harness/Cargo.toml b/beacon_node/beacon_chain/test_harness/Cargo.toml index 5aa57b0b4..ce32b94c6 100644 --- a/beacon_node/beacon_chain/test_harness/Cargo.toml +++ b/beacon_node/beacon_chain/test_harness/Cargo.toml @@ -23,6 +23,9 @@ failure = "0.1" failure_derive = "0.1" genesis = { path = "../../../eth2/genesis" } hashing = { path = "../../../eth2/utils/hashing" } +log = "0.4" +env_logger = "0.6.0" +rayon = "1.0" serde = "1.0" serde_derive = "1.0" serde_json = "1.0" diff --git a/beacon_node/beacon_chain/test_harness/src/harness.rs b/beacon_node/beacon_chain/test_harness/src/harness.rs index 3982fd61e..a6bb6a02c 100644 --- a/beacon_node/beacon_chain/test_harness/src/harness.rs +++ b/beacon_node/beacon_chain/test_harness/src/harness.rs @@ -5,6 +5,8 @@ use db::{ stores::{BeaconBlockStore, BeaconStateStore}, MemoryDB, }; +use log::debug; +use rayon::prelude::*; use slot_clock::TestingSlotClock; use std::fs::File; use std::io::prelude::*; @@ -26,26 +28,40 @@ impl BeaconChainHarness { let block_store = Arc::new(BeaconBlockStore::new(db.clone())); let state_store = Arc::new(BeaconStateStore::new(db.clone())); - let slot_clock = TestingSlotClock::new(0); + let slot_clock = TestingSlotClock::new(spec.genesis_slot); // Remove the validators present in the spec (if any). spec.initial_validators = Vec::with_capacity(validator_count); spec.initial_balances = Vec::with_capacity(validator_count); - // Insert `validator_count` new `Validator` records into the spec, retaining the keypairs - // for later user. - let mut keypairs = Vec::with_capacity(validator_count); - for _ in 0..validator_count { - let keypair = Keypair::random(); + debug!("Generating validator keypairs..."); - spec.initial_validators.push(Validator { + let keypairs: Vec = (0..validator_count) + .collect::>() + .par_iter() + .map(|_| Keypair::random()) + .collect(); + + debug!("Creating validator records..."); + + spec.initial_validators = keypairs + .par_iter() + .map(|keypair| Validator { pubkey: keypair.pk.clone(), + activation_slot: 0, ..std::default::Default::default() - }); - spec.initial_balances.push(32_000_000_000); // 32 ETH + }) + .collect(); - keypairs.push(keypair); - } + debug!("Setting validator balances..."); + + spec.initial_balances = spec + .initial_validators + .par_iter() + .map(|_| 32_000_000_000) // 32 ETH + .collect(); + + debug!("Creating the BeaconChain..."); // Create the Beacon Chain let beacon_chain = Arc::new( @@ -58,11 +74,15 @@ impl BeaconChainHarness { .unwrap(), ); + debug!("Creating validator producer and attester instances..."); + // Spawn the test validator instances. - let mut validators = Vec::with_capacity(validator_count); - for keypair in keypairs { - validators.push(TestValidator::new(keypair.clone(), beacon_chain.clone())); - } + let validators: Vec = keypairs + .par_iter() + .map(|keypair| TestValidator::new(keypair.clone(), beacon_chain.clone(), &spec)) + .collect(); + + debug!("Created {} TestValidators", validators.len()); Self { db, @@ -83,6 +103,9 @@ impl BeaconChainHarness { .present_slot() .expect("Unable to determine slot.") + 1; + + debug!("Incrementing BeaconChain slot to {}.", slot); + self.beacon_chain.slot_clock.set_slot(slot); } @@ -93,16 +116,24 @@ impl BeaconChainHarness { pub fn gather_free_attesations(&mut self) -> Vec { let present_slot = self.beacon_chain.present_slot().unwrap(); - let mut free_attestations = vec![]; - for validator in &mut self.validators { - // Advance the validator slot. - validator.set_slot(present_slot); + let free_attestations: Vec = self + .validators + .par_iter_mut() + .filter_map(|validator| { + // Advance the validator slot. + validator.set_slot(present_slot); + + // Prompt the validator to produce an attestation (if required). + validator.produce_free_attestation().ok() + }) + .collect(); + + debug!( + "Gathered {} FreeAttestations for slot {}.", + free_attestations.len(), + present_slot + ); - // Prompt the validator to produce an attestation (if required). - if let Ok(free_attestation) = validator.produce_free_attestation() { - free_attestations.push(free_attestation); - } - } free_attestations } @@ -115,6 +146,11 @@ impl BeaconChainHarness { let proposer = self.beacon_chain.block_proposer(present_slot).unwrap(); + debug!( + "Producing block from validator #{} for slot {}.", + proposer, present_slot + ); + self.validators[proposer].produce_block().unwrap() } @@ -131,7 +167,9 @@ impl BeaconChainHarness { .unwrap(); } let block = self.produce_block(); + debug!("Submitting block for processing..."); self.beacon_chain.process_block(block).unwrap(); + debug!("...block processed by BeaconChain."); } pub fn chain_dump(&self) -> Result, DumpError> { diff --git a/beacon_node/beacon_chain/test_harness/src/validator/mod.rs b/beacon_node/beacon_chain/test_harness/src/validator/mod.rs index 3f1b10d5e..08da1d197 100644 --- a/beacon_node/beacon_chain/test_harness/src/validator/mod.rs +++ b/beacon_node/beacon_chain/test_harness/src/validator/mod.rs @@ -52,9 +52,10 @@ impl TestValidator { pub fn new( keypair: Keypair, beacon_chain: Arc>, + spec: &ChainSpec, ) -> Self { - let spec = Arc::new(ChainSpec::foundation()); - let slot_clock = Arc::new(TestingSlotClock::new(0)); + let spec = Arc::new(spec.clone()); + let slot_clock = Arc::new(TestingSlotClock::new(spec.genesis_slot)); let signer = Arc::new(TestSigner::new(keypair.clone())); let beacon_node = Arc::new(BenchingBeaconNode::new(beacon_chain.clone())); let epoch_map = Arc::new(DirectDuties::new(keypair.pk.clone(), beacon_chain.clone())); diff --git a/beacon_node/beacon_chain/test_harness/tests/chain.rs b/beacon_node/beacon_chain/test_harness/tests/chain.rs index c312dcf43..f84157533 100644 --- a/beacon_node/beacon_chain/test_harness/tests/chain.rs +++ b/beacon_node/beacon_chain/test_harness/tests/chain.rs @@ -1,11 +1,20 @@ +use env_logger::{Builder, Env}; +use log::debug; use test_harness::BeaconChainHarness; use types::ChainSpec; #[test] fn it_can_build_on_genesis_block() { - let validator_count = 10; - let spec = ChainSpec::foundation(); - let mut harness = BeaconChainHarness::new(spec, validator_count); + let mut spec = ChainSpec::foundation(); + spec.genesis_slot = spec.epoch_length * 8; + + /* + spec.shard_count = spec.shard_count / 8; + spec.target_committee_size = spec.target_committee_size / 8; + */ + let validator_count = 1000; + + let mut harness = BeaconChainHarness::new(spec, validator_count as usize); harness.advance_chain_with_block(); } @@ -13,13 +22,21 @@ fn it_can_build_on_genesis_block() { #[test] #[ignore] fn it_can_produce_past_first_epoch_boundary() { + Builder::from_env(Env::default().default_filter_or("debug")).init(); + let validator_count = 100; + + debug!("Starting harness build..."); + let mut harness = BeaconChainHarness::new(ChainSpec::foundation(), validator_count); - let blocks = harness.spec.epoch_length + 1; + debug!("Harness built, tests starting.."); - for _ in 0..blocks { + let blocks = harness.spec.epoch_length * 3 + 1; + + for i in 0..blocks { harness.advance_chain_with_block(); + debug!("Produced block {}/{}.", i, blocks); } let dump = harness.chain_dump().expect("Chain dump failed."); diff --git a/eth2/types/Cargo.toml b/eth2/types/Cargo.toml index 3da5a1d26..a779c7e7e 100644 --- a/eth2/types/Cargo.toml +++ b/eth2/types/Cargo.toml @@ -11,6 +11,7 @@ ethereum-types = "0.4.0" hashing = { path = "../utils/hashing" } honey-badger-split = { path = "../utils/honey-badger-split" } integer-sqrt = "0.1" +log = "0.4" rand = "0.5.5" serde = "1.0" serde_derive = "1.0" diff --git a/eth2/types/src/beacon_state/attestation_participants.rs b/eth2/types/src/beacon_state/attestation_participants.rs new file mode 100644 index 000000000..282cee110 --- /dev/null +++ b/eth2/types/src/beacon_state/attestation_participants.rs @@ -0,0 +1,88 @@ +use crate::{ + beacon_state::CommitteesError, PendingAttestation, AttestationData, BeaconState, Bitfield, ChainSpec, +}; + +#[derive(Debug, PartialEq)] +pub enum Error { + NoCommitteeForShard, + NoCommittees, + BadBitfieldLength, + CommitteesError(CommitteesError), +} + +impl BeaconState { + pub fn get_attestation_participants_union( + &self, + attestations: &[&PendingAttestation], + spec: &ChainSpec, + ) -> Result, Error> { + attestations.iter().try_fold(vec![], |mut acc, a| { + acc.append(&mut self.get_attestation_participants( + &a.data, + &a.aggregation_bitfield, + spec, + )?); + Ok(acc) + }) + } + + // TODO: analyse for efficiency improvments. This implementation is naive. + pub fn get_attestation_participants( + &self, + attestation_data: &AttestationData, + aggregation_bitfield: &Bitfield, + spec: &ChainSpec, + ) -> Result, Error> { + let crosslink_committees = + self.get_crosslink_committees_at_slot(attestation_data.slot, spec)?; + + /* + let mut shard_present = false; + for (_committee, shard) in &crosslink_committees { + println!("want shard: {}, got shard: {}", shard, attestation_data.shard); + if *shard == attestation_data.shard { + shard_present = true; + } + } + if !shard_present { + return Err(Error::NoCommitteeForShard); + } + */ + + let crosslink_committee: Vec = crosslink_committees + .iter() + .filter_map(|(committee, shard)| { + if *shard == attestation_data.shard { + Some(committee.clone()) + } else { + None + } + }) + .collect::>>() + .first() + .ok_or_else(|| Error::NoCommitteeForShard)? + .clone(); + + /* + * TODO: check for this condition. + * + if aggregation_bitfield.len() != (crosslink_committee.len() + 7) / 8 { + return Err(Error::BadBitfieldLength); + } + */ + + let mut participants = vec![]; + for (i, validator_index) in crosslink_committee.iter().enumerate() { + if aggregation_bitfield.get(i).unwrap() { + participants.push(*validator_index); + } + } + Ok(participants) + } +} + +impl From for Error { + fn from(e: CommitteesError) -> Error { + Error::CommitteesError(e) + } +} diff --git a/eth2/types/src/beacon_state/attestation_validation.rs b/eth2/types/src/beacon_state/attestation_validation.rs index 26cc4e2b4..33aea85a3 100644 --- a/eth2/types/src/beacon_state/attestation_validation.rs +++ b/eth2/types/src/beacon_state/attestation_validation.rs @@ -1,4 +1,7 @@ -use crate::{AggregatePublicKey, Attestation, BeaconState, ChainSpec, Fork}; +use crate::{ + beacon_state::AttestationParticipantsError, AggregatePublicKey, Attestation, BeaconState, + ChainSpec, Fork, +}; use bls::bls_verify_aggregate; #[derive(Debug, PartialEq)] @@ -11,6 +14,7 @@ pub enum Error { BadSignature, ShardBlockRootNotZero, NoBlockRoot, + AttestationParticipantsError(AttestationParticipantsError), } macro_rules! ensure { @@ -32,6 +36,25 @@ impl BeaconState { attestation: &Attestation, spec: &ChainSpec, ) -> Result<(), Error> { + self.validate_attestation_signature_optional(attestation, spec, true) + } + + pub fn validate_attestation_without_signature( + &self, + attestation: &Attestation, + spec: &ChainSpec, + ) -> Result<(), Error> { + self.validate_attestation_signature_optional(attestation, spec, false) + } + + fn validate_attestation_signature_optional( + &self, + attestation: &Attestation, + spec: &ChainSpec, + verify_signature: bool, + ) -> Result<(), Error> { + // TODO: IMPORTANT: enable signature verification + let verify_signature = false; ensure!( attestation.data.slot + spec.min_attestation_inclusion_delay <= self.slot, Error::IncludedTooEarly @@ -65,25 +88,30 @@ impl BeaconState { == self.latest_crosslinks[attestation.data.shard as usize].shard_block_root), Error::BadLatestCrosslinkRoot ); - let participants = - self.get_attestation_participants(&attestation.data, &attestation.aggregation_bitfield); - let mut group_public_key = AggregatePublicKey::new(); - for participant in participants { - group_public_key.add( - self.validator_registry[participant as usize] - .pubkey - .as_raw(), - ) + if verify_signature { + let participants = self.get_attestation_participants( + &attestation.data, + &attestation.aggregation_bitfield, + spec, + )?; + let mut group_public_key = AggregatePublicKey::new(); + for participant in participants { + group_public_key.add( + self.validator_registry[participant as usize] + .pubkey + .as_raw(), + ) + } + ensure!( + bls_verify_aggregate( + &group_public_key, + &attestation.signable_message(PHASE_0_CUSTODY_BIT), + &attestation.aggregate_signature, + get_domain(&self.fork_data, attestation.data.slot, DOMAIN_ATTESTATION) + ), + Error::BadSignature + ); } - ensure!( - bls_verify_aggregate( - &group_public_key, - &attestation.signable_message(PHASE_0_CUSTODY_BIT), - &attestation.aggregate_signature, - get_domain(&self.fork_data, attestation.data.slot, DOMAIN_ATTESTATION) - ), - Error::BadSignature - ); ensure!( attestation.data.shard_block_root == spec.zero_hash, Error::ShardBlockRootNotZero @@ -96,3 +124,9 @@ pub fn get_domain(_fork: &Fork, _slot: u64, _domain_type: u64) -> u64 { // TODO: stubbed out. 0 } + +impl From for Error { + fn from(e: AttestationParticipantsError) -> Error { + Error::AttestationParticipantsError(e) + } +} diff --git a/eth2/types/src/beacon_state/committees.rs b/eth2/types/src/beacon_state/committees.rs new file mode 100644 index 000000000..ae9ad6fac --- /dev/null +++ b/eth2/types/src/beacon_state/committees.rs @@ -0,0 +1,127 @@ +use crate::{validator_registry::get_active_validator_indices, BeaconState, ChainSpec}; +use std::ops::Range; + +#[derive(Debug, PartialEq)] +pub enum Error { + InvalidSlot(u64, Range), + InsufficientNumberOfValidators, +} + +type Result = std::result::Result; + +impl BeaconState { + /// Returns the number of committees per slot. + /// + /// Note: this is _not_ the committee size. + pub fn get_committee_count_per_slot( + &self, + active_validator_count: usize, + spec: &ChainSpec, + ) -> u64 { + std::cmp::max( + 1, + std::cmp::min( + spec.shard_count / spec.epoch_length, + active_validator_count as u64 / spec.epoch_length / spec.target_committee_size, + ), + ) + } + + /// Returns the start slot and end slot of the current epoch containing `self.slot`. + pub 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 + } + + /// Returns the start slot and end slot of the current epoch containing `self.slot`. + pub fn get_previous_epoch_boundaries(&self, spec: &ChainSpec) -> Range { + let current_epoch = self.slot / spec.epoch_length; + let previous_epoch = current_epoch.saturating_sub(1); + let start = previous_epoch * spec.epoch_length; + let end = start + spec.epoch_length; + start..end + } + + fn get_previous_epoch_committee_count_per_slot(&self, spec: &ChainSpec) -> 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, + ) -> Result, u64)>> { + /* + let previous_epoch_range = self.get_current_epoch_boundaries(spec.epoch_length); + let current_epoch_range = self.get_current_epoch_boundaries(spec.epoch_length); + if !range_contains(¤t_epoch_range, slot) { + return Err(Error::InvalidSlot(slot, current_epoch_range)); + } + */ + let epoch = slot / spec.epoch_length; + let current_epoch = self.slot / spec.epoch_length; + let previous_epoch = if current_epoch == spec.genesis_slot { + current_epoch + } else { + current_epoch.saturating_sub(1) + }; + let next_epoch = current_epoch + 1; + + if !((previous_epoch <= epoch) & (epoch < next_epoch)) { + return Err(Error::InvalidSlot(slot, previous_epoch..current_epoch)); + } + + let offset = slot % spec.epoch_length; + + let (committees_per_slot, shuffling, slot_start_shard) = if epoch < current_epoch { + 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 mut crosslinks_at_slot = vec![]; + for i in 0..committees_per_slot { + let tuple = ( + shuffling[(committees_per_slot * offset + i) as usize].clone(), + (slot_start_shard + i) % spec.shard_count, + ); + crosslinks_at_slot.push(tuple) + } + Ok(crosslinks_at_slot) + } +} + +/// 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 +} diff --git a/eth2/types/src/beacon_state/epoch_processing.rs b/eth2/types/src/beacon_state/epoch_processing.rs index d9c1e2313..042bac518 100644 --- a/eth2/types/src/beacon_state/epoch_processing.rs +++ b/eth2/types/src/beacon_state/epoch_processing.rs @@ -1,9 +1,12 @@ -use super::winning_root::WinningRoot; +use super::winning_root::{Error as WinningRootError, WinningRoot}; use crate::{ - validator::StatusFlags, validator_registry::get_active_validator_indices, AttestationData, - BeaconState, Bitfield, ChainSpec, Crosslink, Hash256, PendingAttestation, + beacon_state::{AttestationParticipantsError, CommitteesError}, + validator::StatusFlags, + validator_registry::get_active_validator_indices, + BeaconState, ChainSpec, Crosslink, Hash256, PendingAttestation, }; use integer_sqrt::IntegerSquareRoot; +use log::debug; use std::collections::{HashMap, HashSet}; use std::iter::FromIterator; @@ -11,8 +14,17 @@ use std::iter::FromIterator; pub enum Error { UnableToDetermineProducer, NoBlockRoots, - UnableToGetCrosslinkCommittees, BaseRewardQuotientIsZero, + CommitteesError(CommitteesError), + AttestationParticipantsError(AttestationParticipantsError), + InclusionError(InclusionError), + WinningRootError(WinningRootError), +} + +#[derive(Debug, PartialEq)] +pub enum InclusionError { + NoIncludedAttestations, + AttestationParticipantsError(AttestationParticipantsError), } macro_rules! safe_add_assign { @@ -28,14 +40,19 @@ macro_rules! safe_sub_assign { impl BeaconState { pub fn per_epoch_processing(&mut self, spec: &ChainSpec) -> Result<(), Error> { + debug!("Starting per-epoch processing..."); /* * All Validators */ let active_validator_indices = get_active_validator_indices(&self.validator_registry, self.slot); - let total_balance: u64 = active_validator_indices - .iter() - .fold(0, |acc, i| acc + self.get_effective_balance(*i, spec)); + let total_balance = self.get_effective_balances(&active_validator_indices[..], spec); + + debug!( + "{} validators with a total balance of {} wei.", + active_validator_indices.len(), + total_balance + ); let current_epoch_attestations: Vec<&PendingAttestation> = self .latest_attestations @@ -53,42 +70,26 @@ impl BeaconState { let current_epoch_boundary_attestations: Vec<&PendingAttestation> = current_epoch_attestations .iter() - // `filter_map` is used to avoid a double borrow (`&&..`). - .filter_map(|a| { + .filter(|a| { // TODO: ensure this saturating sub is correct. - let block_root = match self - .get_block_root(self.slot.saturating_sub(spec.epoch_length), spec) - { - Some(root) => root, + match self.get_block_root(self.slot.saturating_sub(spec.epoch_length), spec) { + Some(block_root) => { + (a.data.epoch_boundary_root == *block_root) + && (a.data.justified_slot == self.justified_slot) + } // Protected by a check that latest_block_roots isn't empty. // // TODO: provide detailed reasoning. None => unreachable!(), - }; - - if (a.data.epoch_boundary_root == *block_root) - && (a.data.justified_slot == self.justified_slot) - { - Some(*a) - } else { - None } }) + .cloned() .collect(); - let current_epoch_boundary_attester_indices: Vec = - current_epoch_boundary_attestations - .iter() - .fold(vec![], |mut acc, a| { - acc.append( - &mut self.get_attestation_participants(&a.data, &a.aggregation_bitfield), - ); - acc - }); - - let current_epoch_boundary_attesting_balance = current_epoch_boundary_attester_indices - .iter() - .fold(0_u64, |acc, i| acc + self.get_effective_balance(*i, spec)); + let current_epoch_boundary_attester_indices = self + .get_attestation_participants_union(¤t_epoch_boundary_attestations[..], spec)?; + let current_epoch_boundary_attesting_balance = + self.get_effective_balances(¤t_epoch_boundary_attester_indices[..], spec); /* * Validators attesting during the previous epoch @@ -107,15 +108,8 @@ impl BeaconState { }) .collect(); - let previous_epoch_attester_indices: Vec = - previous_epoch_attestations - .iter() - .fold(vec![], |mut acc, a| { - acc.append( - &mut self.get_attestation_participants(&a.data, &a.aggregation_bitfield), - ); - acc - }); + let previous_epoch_attester_indices = + self.get_attestation_participants_union(&previous_epoch_attestations[..], spec)?; /* * Validators targetting the previous justified slot @@ -123,43 +117,22 @@ impl BeaconState { let previous_epoch_justified_attestations: Vec<&PendingAttestation> = { let mut a: Vec<&PendingAttestation> = current_epoch_attestations .iter() - // `filter_map` is used to avoid a double borrow (`&&..`). - .filter_map(|a| { - if a.data.justified_slot == self.previous_justified_slot { - Some(*a) - } else { - None - } - }) + .filter(|a| a.data.justified_slot == self.previous_justified_slot) + .cloned() .collect(); let mut b: Vec<&PendingAttestation> = previous_epoch_attestations .iter() - // `filter_map` is used to avoid a double borrow (`&&..`). - .filter_map(|a| { - if a.data.justified_slot == self.previous_justified_slot { - Some(*a) - } else { - None - } - }) + .filter(|a| a.data.justified_slot == self.previous_justified_slot) + .cloned() .collect(); a.append(&mut b); a }; - let previous_epoch_justified_attester_indices: Vec = - previous_epoch_justified_attestations - .iter() - .fold(vec![], |mut acc, a| { - acc.append( - &mut self.get_attestation_participants(&a.data, &a.aggregation_bitfield), - ); - acc - }); - - let previous_epoch_justified_attesting_balance = previous_epoch_justified_attester_indices - .iter() - .fold(0, |acc, i| acc + self.get_effective_balance(*i, spec)); + let previous_epoch_justified_attester_indices = self + .get_attestation_participants_union(&previous_epoch_justified_attestations[..], spec)?; + let previous_epoch_justified_attesting_balance = + self.get_effective_balances(&previous_epoch_justified_attester_indices[..], spec); /* * Validators justifying the epoch boundary block at the start of the previous epoch @@ -167,41 +140,24 @@ impl BeaconState { let previous_epoch_boundary_attestations: Vec<&PendingAttestation> = previous_epoch_justified_attestations .iter() - // `filter_map` is used to avoid a double borrow (`&&..`). - .filter_map(|a| { + .filter(|a| { // TODO: ensure this saturating sub is correct. - let block_root = match self - .get_block_root(self.slot.saturating_sub(2 * spec.epoch_length), spec) + match self.get_block_root(self.slot.saturating_sub(2 * spec.epoch_length), spec) { - Some(root) => root, + Some(block_root) => a.data.epoch_boundary_root == *block_root, // Protected by a check that latest_block_roots isn't empty. // // TODO: provide detailed reasoning. None => unreachable!(), - }; - - if a.data.epoch_boundary_root == *block_root { - Some(*a) - } else { - None } }) + .cloned() .collect(); - let previous_epoch_boundary_attester_indices: Vec = - previous_epoch_boundary_attestations - .iter() - .fold(vec![], |mut acc, a| { - acc.append( - &mut self.get_attestation_participants(&a.data, &a.aggregation_bitfield), - ); - acc - }); - - let previous_epoch_boundary_attesting_balance: u64 = - previous_epoch_boundary_attester_indices - .iter() - .fold(0, |acc, i| acc + self.get_effective_balance(*i, spec)); + let previous_epoch_boundary_attester_indices = self + .get_attestation_participants_union(&previous_epoch_boundary_attestations[..], spec)?; + let previous_epoch_boundary_attesting_balance = + self.get_effective_balances(&previous_epoch_boundary_attester_indices[..], spec); /* * Validators attesting to the expected beacon chain head during the previous epoch. @@ -209,37 +165,23 @@ impl BeaconState { let previous_epoch_head_attestations: Vec<&PendingAttestation> = previous_epoch_attestations .iter() - .filter_map(|a| { - let block_root = match self - .get_block_root(self.slot.saturating_sub(2 * spec.epoch_length), spec) + .filter(|a| { + match self.get_block_root(self.slot.saturating_sub(2 * spec.epoch_length), spec) { - Some(root) => root, + Some(block_root) => a.data.beacon_block_root == *block_root, // Protected by a check that latest_block_roots isn't empty. // // TODO: provide detailed reasoning. None => unreachable!(), - }; - - if a.data.beacon_block_root == *block_root { - Some(*a) - } else { - None } }) + .cloned() .collect(); - let previous_epoch_head_attester_indices: Vec = previous_epoch_head_attestations - .iter() - .fold(vec![], |mut acc, a| { - acc.append( - &mut self.get_attestation_participants(&a.data, &a.aggregation_bitfield), - ); - acc - }); - - let previous_epoch_head_attesting_balance: u64 = previous_epoch_head_attester_indices - .iter() - .fold(0, |acc, i| acc + self.get_effective_balance(*i, spec)); + let previous_epoch_head_attester_indices = + self.get_attestation_participants_union(&previous_epoch_head_attestations[..], spec)?; + let previous_epoch_head_attesting_balance = + self.get_effective_balances(&previous_epoch_head_attester_indices[..], spec); /* * Eth1 Data @@ -295,17 +237,22 @@ impl BeaconState { self.finalized_slot = self.previous_justified_slot; } + debug!( + "Finalized slot {}, justified slot {}.", + self.finalized_slot, self.justified_slot + ); + /* * Crosslinks */ // Cached for later lookups. - let mut winning_root_for_shards: HashMap = HashMap::new(); + let mut winning_root_for_shards: HashMap> = + HashMap::new(); - 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, spec) - .map_err(|_| Error::UnableToGetCrosslinkCommittees)?; + // for slot in self.slot.saturating_sub(2 * spec.epoch_length)..self.slot { + for slot in self.get_previous_epoch_boundaries(spec) { + let crosslink_committees_at_slot = self.get_crosslink_committees_at_slot(slot, spec)?; for (crosslink_committee, shard) in crosslink_committees_at_slot { let shard = shard as u64; @@ -317,12 +264,9 @@ impl BeaconState { spec, ); - if let Some(winning_root) = winning_root { - let total_committee_balance: u64 = crosslink_committee - .iter() - .fold(0, |acc, i| acc + self.get_effective_balance(*i, spec)); - - winning_root_for_shards.insert(shard, winning_root.clone()); + if let Ok(winning_root) = &winning_root { + let total_committee_balance = + self.get_effective_balances(&crosslink_committee[..], spec); if (3 * winning_root.total_attesting_balance) >= (2 * total_committee_balance) { self.latest_crosslinks[shard as usize] = Crosslink { @@ -331,9 +275,15 @@ impl BeaconState { } } } + winning_root_for_shards.insert(shard, winning_root); } } + debug!( + "Found {} winning shard roots.", + winning_root_for_shards.len() + ); + /* * Rewards and Penalities */ @@ -342,13 +292,6 @@ impl BeaconState { return Err(Error::BaseRewardQuotientIsZero); } - /* - let base_reward = |i| match self.get_effective_balance(i, spec) { - Some(effective_balance) => effective_balance / base_reward_quotient / 5, - None => unreachable!(), - }; - */ - /* * Justification and finalization */ @@ -398,10 +341,7 @@ impl BeaconState { for index in previous_epoch_attester_indices { let base_reward = self.base_reward(index, base_reward_quotient, spec); - let inclusion_distance = match self.inclusion_distance(index) { - Some(distance) => distance, - None => unreachable!(), - }; + let inclusion_distance = self.inclusion_distance(index, spec)?; safe_add_assign!( self.validator_balances[index], @@ -432,10 +372,7 @@ impl BeaconState { for index in previous_epoch_attester_indices { let base_reward = self.base_reward(index, base_reward_quotient, spec); - let inclusion_distance = match self.inclusion_distance(index) { - Some(distance) => distance, - None => unreachable!(), - }; + let inclusion_distance = self.inclusion_distance(index, spec)?; safe_sub_assign!( self.validator_balances[index], @@ -445,14 +382,13 @@ impl BeaconState { } } + debug!("Processed validator justification and finalization rewards/penalities."); + /* * Attestation inclusion */ for index in previous_epoch_attester_indices_hashset { - let inclusion_slot = match self.inclusion_slot(index) { - Some(slot) => slot, - None => unreachable!(), - }; + let inclusion_slot = self.inclusion_slot(index, spec)?; let proposer_index = self .get_beacon_proposer_index(inclusion_slot, spec) .map_err(|_| Error::UnableToDetermineProducer)?; @@ -463,45 +399,46 @@ impl BeaconState { ); } + debug!("Processed validator attestation inclusdion rewards."); + /* * Crosslinks */ - 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, spec) - .map_err(|_| Error::UnableToGetCrosslinkCommittees)?; + for slot in self.get_previous_epoch_boundaries(spec) { + let crosslink_committees_at_slot = self.get_crosslink_committees_at_slot(slot, spec)?; for (_crosslink_committee, shard) in crosslink_committees_at_slot { let shard = shard as u64; - let winning_root = winning_root_for_shards.get(&shard).expect("unreachable"); + if let Some(Ok(winning_root)) = winning_root_for_shards.get(&shard) { + // TODO: remove the map. + let attesting_validator_indices: HashSet = HashSet::from_iter( + winning_root.attesting_validator_indices.iter().map(|i| *i), + ); - // TODO: remove the map. - let attesting_validator_indices: HashSet = - HashSet::from_iter(winning_root.attesting_validator_indices.iter().map(|i| *i)); + for index in 0..self.validator_balances.len() { + let base_reward = self.base_reward(index, base_reward_quotient, spec); - for index in 0..self.validator_balances.len() { - let base_reward = self.base_reward(index, base_reward_quotient, spec); + if attesting_validator_indices.contains(&index) { + safe_add_assign!( + self.validator_balances[index], + base_reward * winning_root.total_attesting_balance + / winning_root.total_balance + ); + } else { + safe_sub_assign!(self.validator_balances[index], base_reward); + } + } - if attesting_validator_indices.contains(&index) { + for index in &winning_root.attesting_validator_indices { + let base_reward = self.base_reward(*index, base_reward_quotient, spec); safe_add_assign!( - self.validator_balances[index], + self.validator_balances[*index], base_reward * winning_root.total_attesting_balance / winning_root.total_balance ); - } else { - safe_sub_assign!(self.validator_balances[index], base_reward); } } - - for index in &winning_root.attesting_validator_indices { - let base_reward = self.base_reward(*index, base_reward_quotient, spec); - safe_add_assign!( - self.validator_balances[*index], - base_reward * winning_root.total_attesting_balance - / winning_root.total_balance - ); - } } } @@ -562,13 +499,8 @@ impl BeaconState { self.latest_attestations = self .latest_attestations .iter() - .filter_map(|a| { - if a.data.slot < self.slot - spec.epoch_length { - Some(a.clone()) - } else { - None - } - }) + .filter(|a| a.data.slot < self.slot - spec.epoch_length) + .cloned() .collect(); Ok(()) @@ -577,9 +509,7 @@ impl BeaconState { fn process_penalties_and_exits(&mut self, spec: &ChainSpec) { let active_validator_indices = get_active_validator_indices(&self.validator_registry, self.slot); - let total_balance = active_validator_indices - .iter() - .fold(0, |acc, i| acc + self.get_effective_balance(*i, spec)); + let total_balance = self.get_effective_balances(&active_validator_indices[..], spec); for index in 0..self.validator_balances.len() { let validator = &self.validator_registry[index]; @@ -640,9 +570,7 @@ impl BeaconState { fn update_validator_registry(&mut self, spec: &ChainSpec) { let active_validator_indices = get_active_validator_indices(&self.validator_registry, self.slot); - let total_balance = active_validator_indices - .iter() - .fold(0, |acc, i| acc + self.get_effective_balance(*i, spec)); + let total_balance = self.get_effective_balances(&active_validator_indices[..], spec); let max_balance_churn = std::cmp::max( spec.max_deposit, @@ -726,30 +654,64 @@ impl BeaconState { + effective_balance * epochs_since_finality / spec.inactivity_penalty_quotient / 2 } - fn inclusion_distance(&self, validator_index: usize) -> Option { - let attestation = self.earliest_included_attestation(validator_index)?; - Some( - attestation - .slot_included - .saturating_sub(attestation.data.slot), - ) + fn inclusion_distance( + &self, + validator_index: usize, + spec: &ChainSpec, + ) -> Result { + let attestation = self.earliest_included_attestation(validator_index, spec)?; + Ok(attestation + .slot_included + .saturating_sub(attestation.data.slot)) } - fn inclusion_slot(&self, validator_index: usize) -> Option { - let attestation = self.earliest_included_attestation(validator_index)?; - Some(attestation.slot_included) + fn inclusion_slot( + &self, + validator_index: usize, + spec: &ChainSpec, + ) -> Result { + let attestation = self.earliest_included_attestation(validator_index, spec)?; + Ok(attestation.slot_included) } - fn earliest_included_attestation(&self, validator_index: usize) -> Option<&PendingAttestation> { + fn earliest_included_attestation( + &self, + validator_index: usize, + spec: &ChainSpec, + ) -> Result<&PendingAttestation, InclusionError> { + let mut included_attestations = vec![]; + + for a in &self.latest_attestations { + let participants = + self.get_attestation_participants(&a.data, &a.aggregation_bitfield, spec)?; + if participants + .iter() + .find(|i| **i == validator_index) + .is_some() + { + included_attestations.push(a); + } + } + + Ok(included_attestations + .iter() + .min_by_key(|a| a.slot_included) + .and_then(|x| Some(*x)) + .ok_or_else(|| InclusionError::NoIncludedAttestations)?) + /* self.latest_attestations .iter() - .filter(|a| { - self.get_attestation_participants(&a.data, &a.aggregation_bitfield) + .try_for_each(|a| { + self.get_attestation_participants(&a.data, &a.aggregation_bitfield, spec) + })? + .filter(|participants| { + participants .iter() .find(|i| **i == validator_index) .is_some() }) .min_by_key(|a| a.slot_included) + */ } fn base_reward( @@ -761,6 +723,12 @@ impl BeaconState { self.get_effective_balance(validator_index, spec) / base_reward_quotient / 5 } + pub fn get_effective_balances(&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) -> u64 { std::cmp::min(self.validator_balances[validator_index], spec.max_deposit) } @@ -773,13 +741,34 @@ impl BeaconState { None } } +} - pub fn get_attestation_participants( - &self, - _attestation_data: &AttestationData, - _aggregation_bitfield: &Bitfield, - ) -> Vec { - // TODO: stubbed out. - vec![0, 1] +impl From for Error { + fn from(e: CommitteesError) -> Error { + Error::CommitteesError(e) + } +} + +impl From for Error { + fn from(e: AttestationParticipantsError) -> Error { + Error::AttestationParticipantsError(e) + } +} + +impl From for Error { + fn from(e: InclusionError) -> Error { + Error::InclusionError(e) + } +} + +impl From for InclusionError { + fn from(e: AttestationParticipantsError) -> InclusionError { + InclusionError::AttestationParticipantsError(e) + } +} + +impl From for Error { + fn from(e: WinningRootError) -> Error { + Error::WinningRootError(e) } } diff --git a/eth2/types/src/beacon_state/mod.rs b/eth2/types/src/beacon_state/mod.rs index 85397ee11..4ccb3322a 100644 --- a/eth2/types/src/beacon_state/mod.rs +++ b/eth2/types/src/beacon_state/mod.rs @@ -10,21 +10,18 @@ use rand::RngCore; use serde_derive::Serialize; use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash}; +mod attestation_participants; mod attestation_validation; +mod committees; mod epoch_processing; mod shuffling; mod slot_processing; mod winning_root; +pub use self::attestation_participants::Error as AttestationParticipantsError; pub use self::attestation_validation::Error as AttestationValidationError; +pub use self::committees::Error as CommitteesError; 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; diff --git a/eth2/types/src/beacon_state/shuffling.rs b/eth2/types/src/beacon_state/shuffling.rs index 95b402f8a..bef3abe71 100644 --- a/eth2/types/src/beacon_state/shuffling.rs +++ b/eth2/types/src/beacon_state/shuffling.rs @@ -1,16 +1,8 @@ -use super::Error; +use super::CommitteesError; use crate::{validator_registry::get_active_validator_indices, BeaconState, ChainSpec, Hash256}; use honey_badger_split::SplitExt; -use std::ops::Range; use vec_shuffle::shuffle; -// 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> { let slot = slot - (slot % spec.epoch_length); @@ -31,112 +23,21 @@ impl BeaconState { .collect() } - pub fn get_committee_count_per_slot( - &self, - active_validator_count: usize, - spec: &ChainSpec, - ) -> u64 { - std::cmp::max( - 1, - std::cmp::min( - spec.shard_count / spec.epoch_length, - active_validator_count as u64 / spec.epoch_length / spec.target_committee_size, - ), - ) - } - - /// 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( + /// 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, - /* - 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 { + ) -> Result { let committees = self.get_crosslink_committees_at_slot(slot, spec)?; committees .first() - .ok_or(Error::InsufficientNumberOfValidators) + .ok_or(CommitteesError::InsufficientNumberOfValidators) .and_then(|(first_committee, _)| { let index = (slot as usize) .checked_rem(first_committee.len()) - .ok_or(Error::InsufficientNumberOfValidators)?; + .ok_or(CommitteesError::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 5dc351dae..5974596b6 100644 --- a/eth2/types/src/beacon_state/slot_processing.rs +++ b/eth2/types/src/beacon_state/slot_processing.rs @@ -1,20 +1,14 @@ -use crate::{BeaconState, ChainSpec, Hash256}; - -pub enum Error { - UnableToDetermineProducer, -} +use crate::{beacon_state::CommitteesError, BeaconState, ChainSpec, Hash256}; impl BeaconState { pub fn per_slot_processing( &mut self, previous_block_root: Hash256, spec: &ChainSpec, - ) -> Result<(), Error> { + ) -> Result<(), CommitteesError> { self.slot += 1; - let block_proposer = self - .get_beacon_proposer_index(self.slot, spec) - .map_err(|_| Error::UnableToDetermineProducer)?; + let block_proposer = self.get_beacon_proposer_index(self.slot, spec)?; self.validator_registry[block_proposer].proposer_slots += 1; self.latest_randao_mixes[(self.slot % spec.latest_randao_mixes_length) as usize] = @@ -35,7 +29,18 @@ impl BeaconState { &self, validator_index: usize, spec: &ChainSpec, - ) -> (u64, u64) { + ) -> Result<(u64, u64), CommitteesError> { + let mut result = None; + for slot in self.get_current_epoch_boundaries(spec.epoch_length) { + for (committee, shard) in self.get_crosslink_committees_at_slot(slot, spec)? { + if committee.iter().find(|i| **i == validator_index).is_some() { + result = Some(Ok((slot, shard))); + } + + } + } + result.unwrap() + /* // TODO: this is a stub; implement it properly. let validator_index = validator_index as u64; @@ -43,6 +48,7 @@ impl BeaconState { let shard = validator_index % spec.shard_count; (slot, shard) + */ } } diff --git a/eth2/types/src/beacon_state/winning_root.rs b/eth2/types/src/beacon_state/winning_root.rs index 04adcb007..20fdca09b 100644 --- a/eth2/types/src/beacon_state/winning_root.rs +++ b/eth2/types/src/beacon_state/winning_root.rs @@ -1,11 +1,12 @@ -use crate::{BeaconState, ChainSpec, Hash256, PendingAttestation}; +use crate::{ + beacon_state::AttestationParticipantsError, BeaconState, ChainSpec, Hash256, PendingAttestation, +}; use std::collections::HashMap; +#[derive(Debug, PartialEq)] pub enum Error { - UnableToDetermineProducer, - NoBlockRoots, - UnableToGetCrosslinkCommittees, - BaseRewardQuotientIsZero, + NoWinningRoot, + AttestationParticipantsError(AttestationParticipantsError), } #[derive(Clone)] @@ -23,7 +24,7 @@ impl BeaconState { current_epoch_attestations: &[&PendingAttestation], previous_epoch_attestations: &[&PendingAttestation], spec: &ChainSpec, - ) -> Option { + ) -> Result { let mut attestations = current_epoch_attestations.to_vec(); attestations.append(&mut previous_epoch_attestations.to_vec()); @@ -42,14 +43,23 @@ impl BeaconState { continue; } - let attesting_validator_indices = attestations.iter().fold(vec![], |mut acc, a| { - if (a.data.shard == shard) && (a.data.shard_block_root == *shard_block_root) { - acc.append( - &mut self.get_attestation_participants(&a.data, &a.aggregation_bitfield), - ); - } - acc - }); + // TODO: `cargo fmt` makes this rather ugly; tidy up. + let attesting_validator_indices = attestations.iter().try_fold::<_, _, Result< + _, + AttestationParticipantsError, + >>( + vec![], + |mut acc, a| { + if (a.data.shard == shard) && (a.data.shard_block_root == *shard_block_root) { + acc.append(&mut self.get_attestation_participants( + &a.data, + &a.aggregation_bitfield, + spec, + )?); + } + Ok(acc) + }, + )?; let total_balance: u64 = attesting_validator_indices .iter() @@ -73,7 +83,7 @@ impl BeaconState { candidates.insert(*shard_block_root, candidate_root); } - let winner = candidates + Ok(candidates .iter() .filter_map(|(_hash, candidate)| { if candidate.total_attesting_balance == highest_seen_balance { @@ -82,11 +92,15 @@ impl BeaconState { None } }) - .min_by_key(|candidate| candidate.shard_block_root); - - match winner { - Some(winner) => Some(winner.clone()), - None => None, - } + .min_by_key(|candidate| candidate.shard_block_root) + .ok_or_else(|| Error::NoWinningRoot)? + // TODO: avoid clone. + .clone()) + } +} + +impl From for Error { + fn from (e: AttestationParticipantsError) -> Error { + Error::AttestationParticipantsError(e) } }