diff --git a/eth2/state_processing/src/lib.rs b/eth2/state_processing/src/lib.rs index a5cc4f26e..44c0e28a0 100644 --- a/eth2/state_processing/src/lib.rs +++ b/eth2/state_processing/src/lib.rs @@ -9,5 +9,6 @@ pub use per_block_processing::{ errors::{BlockInvalid, BlockProcessingError}, per_block_processing, per_block_processing_without_verifying_block_signature, }; +pub use per_epoch_processing::{errors::EpochProcessingError, per_epoch_processing}; // pub use epoch_processable::{EpochProcessable, Error as EpochProcessingError}; // pub use slot_processable::{Error as SlotProcessingError, SlotProcessable}; diff --git a/eth2/state_processing/src/per_epoch_processing.rs b/eth2/state_processing/src/per_epoch_processing.rs index 4c66d8ddc..a54518364 100644 --- a/eth2/state_processing/src/per_epoch_processing.rs +++ b/eth2/state_processing/src/per_epoch_processing.rs @@ -1,16 +1,16 @@ -use errors::EpochProcessingError as Error; +use errors::{EpochProcessingError as Error, WinningRootError}; +use grouped_attesters::GroupedAttesters; use integer_sqrt::IntegerSquareRoot; use log::{debug, trace}; use rayon::prelude::*; use ssz::TreeHash; use std::collections::{HashMap, HashSet}; use std::iter::FromIterator; -use types::{ - validator_registry::get_active_validator_indices, BeaconState, BeaconStateError, ChainSpec, - Crosslink, Epoch, Hash256, InclusionError, PendingAttestation, RelativeEpoch, -}; +use types::{validator_registry::get_active_validator_indices, *}; +use winning_root::{winning_root, WinningRoot}; -mod errors; +pub mod errors; +mod grouped_attesters; mod tests; mod winning_root; @@ -25,584 +25,483 @@ macro_rules! safe_sub_assign { }; } -pub trait EpochProcessable { - fn per_epoch_processing(&mut self, spec: &ChainSpec) -> Result<(), Error>; -} - -impl EpochProcessable for BeaconState { - // Cyclomatic complexity is ignored. It would be ideal to split this function apart, however it - // remains monolithic to allow for easier spec updates. Once the spec is more stable we can - // optimise. - #[allow(clippy::cyclomatic_complexity)] - fn per_epoch_processing(&mut self, spec: &ChainSpec) -> Result<(), Error> { - let current_epoch = self.current_epoch(spec); - let previous_epoch = self.previous_epoch(spec); - let next_epoch = self.next_epoch(spec); - - debug!( - "Starting per-epoch processing on epoch {}...", - self.current_epoch(spec) - ); - - // Ensure all of the caches are built. - self.build_epoch_cache(RelativeEpoch::Previous, spec)?; - self.build_epoch_cache(RelativeEpoch::Current, spec)?; - self.build_epoch_cache(RelativeEpoch::Next, spec)?; - - /* - * Validators attesting during the current epoch. - */ - let active_validator_indices = get_active_validator_indices( - &self.validator_registry, - self.slot.epoch(spec.slots_per_epoch), - ); - let current_total_balance = self.get_total_balance(&active_validator_indices[..], spec); - - trace!( - "{} validators with a total balance of {} wei.", - active_validator_indices.len(), - current_total_balance - ); - - let current_epoch_attestations: Vec<&PendingAttestation> = self - .latest_attestations - .par_iter() - .filter(|a| { - (a.data.slot / spec.slots_per_epoch).epoch(spec.slots_per_epoch) - == self.current_epoch(spec) - }) - .collect(); - - trace!( - "Current epoch attestations: {}", - current_epoch_attestations.len() - ); - - let current_epoch_boundary_attestations: Vec<&PendingAttestation> = - current_epoch_attestations - .par_iter() - .filter( - |a| match self.get_block_root(self.current_epoch_start_slot(spec), spec) { - Some(block_root) => { - (a.data.epoch_boundary_root == *block_root) - && (a.data.justified_epoch == self.justified_epoch) - } - None => unreachable!(), - }, - ) - .cloned() - .collect(); - - let current_epoch_boundary_attester_indices = self - .get_attestation_participants_union(¤t_epoch_boundary_attestations[..], spec)?; - let current_epoch_boundary_attesting_balance = - self.get_total_balance(¤t_epoch_boundary_attester_indices[..], spec); - - trace!( - "Current epoch boundary attesters: {}", - current_epoch_boundary_attester_indices.len() - ); - - /* - * Validators attesting during the previous epoch - */ - - /* - * Validators that made an attestation during the previous epoch - */ - let previous_epoch_attestations: Vec<&PendingAttestation> = self - .latest_attestations - .par_iter() - .filter(|a| { - //TODO: ensure these saturating subs are correct. - (a.data.slot / spec.slots_per_epoch).epoch(spec.slots_per_epoch) - == self.previous_epoch(spec) - }) - .collect(); - - debug!( - "previous epoch attestations: {}", - previous_epoch_attestations.len() - ); - - let previous_epoch_attester_indices = - self.get_attestation_participants_union(&previous_epoch_attestations[..], spec)?; - let previous_total_balance = self.get_total_balance( - &get_active_validator_indices(&self.validator_registry, previous_epoch), - spec, - ); - - /* - * Validators targetting the previous justified slot - */ - let previous_epoch_justified_attestations: Vec<&PendingAttestation> = { - let mut a: Vec<&PendingAttestation> = current_epoch_attestations - .iter() - .filter(|a| a.data.justified_epoch == self.previous_justified_epoch) - .cloned() - .collect(); - let mut b: Vec<&PendingAttestation> = previous_epoch_attestations - .iter() - .filter(|a| a.data.justified_epoch == self.previous_justified_epoch) - .cloned() - .collect(); - a.append(&mut b); - a - }; - - let previous_epoch_justified_attester_indices = self - .get_attestation_participants_union(&previous_epoch_justified_attestations[..], spec)?; - let previous_epoch_justified_attesting_balance = - self.get_total_balance(&previous_epoch_justified_attester_indices[..], spec); - - /* - * Validators justifying the epoch boundary block at the start of the previous epoch - */ - let previous_epoch_boundary_attestations: Vec<&PendingAttestation> = - previous_epoch_justified_attestations - .iter() - .filter( - |a| match self.get_block_root(self.previous_epoch_start_slot(spec), spec) { - Some(block_root) => a.data.epoch_boundary_root == *block_root, - None => unreachable!(), - }, - ) - .cloned() - .collect(); - - let previous_epoch_boundary_attester_indices = self - .get_attestation_participants_union(&previous_epoch_boundary_attestations[..], spec)?; - let previous_epoch_boundary_attesting_balance = - self.get_total_balance(&previous_epoch_boundary_attester_indices[..], spec); - - /* - * Validators attesting to the expected beacon chain head during the previous epoch. - */ - let previous_epoch_head_attestations: Vec<&PendingAttestation> = - previous_epoch_attestations - .iter() - .filter(|a| match self.get_block_root(a.data.slot, spec) { - Some(block_root) => a.data.beacon_block_root == *block_root, - None => unreachable!(), - }) - .cloned() - .collect(); - - let previous_epoch_head_attester_indices = - self.get_attestation_participants_union(&previous_epoch_head_attestations[..], spec)?; - let previous_epoch_head_attesting_balance = - self.get_total_balance(&previous_epoch_head_attester_indices[..], spec); - - debug!( - "previous_epoch_head_attester_balance of {} wei.", - previous_epoch_head_attesting_balance - ); - - /* - * Eth1 Data - */ - if self.next_epoch(spec) % spec.eth1_data_voting_period == 0 { - for eth1_data_vote in &self.eth1_data_votes { - if eth1_data_vote.vote_count * 2 > spec.eth1_data_voting_period { - self.latest_eth1_data = eth1_data_vote.eth1_data.clone(); - } - } - self.eth1_data_votes = vec![]; - } - - /* - * Justification - */ - - let mut new_justified_epoch = self.justified_epoch; - self.justification_bitfield <<= 1; - - // If > 2/3 of the total balance attested to the previous epoch boundary - // - // - Set the 2nd bit of the bitfield. - // - Set the previous epoch to be justified. - if (3 * previous_epoch_boundary_attesting_balance) >= (2 * current_total_balance) { - self.justification_bitfield |= 2; - new_justified_epoch = previous_epoch; - trace!(">= 2/3 voted for previous epoch boundary"); - } - // If > 2/3 of the total balance attested to the previous epoch boundary - // - // - Set the 1st bit of the bitfield. - // - Set the current epoch to be justified. - if (3 * current_epoch_boundary_attesting_balance) >= (2 * current_total_balance) { - self.justification_bitfield |= 1; - new_justified_epoch = current_epoch; - trace!(">= 2/3 voted for current epoch boundary"); - } - - // If: - // - // - All three epochs prior to this epoch have been justified. - // - The previous justified justified epoch was three epochs ago. - // - // Then, set the finalized epoch to be three epochs ago. - if ((self.justification_bitfield >> 1) % 8 == 0b111) - & (self.previous_justified_epoch == previous_epoch - 2) - { - self.finalized_epoch = self.previous_justified_epoch; - trace!("epoch - 3 was finalized (1st condition)."); - } - // If: - // - // - Both two epochs prior to this epoch have been justified. - // - The previous justified epoch was two epochs ago. - // - // Then, set the finalized epoch to two epochs ago. - if ((self.justification_bitfield >> 1) % 4 == 0b11) - & (self.previous_justified_epoch == previous_epoch - 1) - { - self.finalized_epoch = self.previous_justified_epoch; - trace!("epoch - 2 was finalized (2nd condition)."); - } - // If: - // - // - This epoch and the two prior have been justified. - // - The presently justified epoch was two epochs ago. - // - // Then, set the finalized epoch to two epochs ago. - if (self.justification_bitfield % 8 == 0b111) & (self.justified_epoch == previous_epoch - 1) - { - self.finalized_epoch = self.justified_epoch; - trace!("epoch - 2 was finalized (3rd condition)."); - } - // If: - // - // - This epoch and the epoch prior to it have been justified. - // - Set the previous epoch to be justified. - // - // Then, set the finalized epoch to be the previous epoch. - if (self.justification_bitfield % 4 == 0b11) & (self.justified_epoch == previous_epoch) { - self.finalized_epoch = self.justified_epoch; - trace!("epoch - 1 was finalized (4th condition)."); - } - - self.previous_justified_epoch = self.justified_epoch; - self.justified_epoch = new_justified_epoch; - - debug!( - "Finalized epoch {}, justified epoch {}.", - self.finalized_epoch, self.justified_epoch - ); - - /* - * Crosslinks - */ - - // Cached for later lookups. - let mut winning_root_for_shards: HashMap> = - HashMap::new(); - - // for slot in self.slot.saturating_sub(2 * spec.slots_per_epoch)..self.slot { - for slot in self.previous_epoch(spec).slot_iter(spec.slots_per_epoch) { - trace!( - "Finding winning root for slot: {} (epoch: {})", - slot, - slot.epoch(spec.slots_per_epoch) - ); - - // Clone is used to remove the borrow. It becomes an issue later when trying to mutate - // `self.balances`. - let crosslink_committees_at_slot = - self.get_crosslink_committees_at_slot(slot, spec)?.clone(); - - for (crosslink_committee, shard) in crosslink_committees_at_slot { - let shard = shard as u64; - - let winning_root = winning_root( - self, - shard, - ¤t_epoch_attestations, - &previous_epoch_attestations, - spec, - ); - - if let Ok(winning_root) = &winning_root { - let total_committee_balance = - self.get_total_balance(&crosslink_committee[..], spec); - - if (3 * winning_root.total_attesting_balance) >= (2 * total_committee_balance) { - self.latest_crosslinks[shard as usize] = Crosslink { - epoch: current_epoch, - shard_block_root: winning_root.shard_block_root, - } - } - } - winning_root_for_shards.insert(shard, winning_root); - } - } - - trace!( - "Found {} winning shard roots.", - winning_root_for_shards.len() - ); - - /* - * Rewards and Penalities - */ - let base_reward_quotient = - previous_total_balance.integer_sqrt() / spec.base_reward_quotient; - if base_reward_quotient == 0 { - return Err(Error::BaseRewardQuotientIsZero); - } - - /* - * Justification and finalization - */ - let epochs_since_finality = next_epoch - self.finalized_epoch; - - let previous_epoch_justified_attester_indices_hashset: HashSet = - HashSet::from_iter(previous_epoch_justified_attester_indices.iter().cloned()); - let previous_epoch_boundary_attester_indices_hashset: HashSet = - HashSet::from_iter(previous_epoch_boundary_attester_indices.iter().cloned()); - let previous_epoch_head_attester_indices_hashset: HashSet = - HashSet::from_iter(previous_epoch_head_attester_indices.iter().cloned()); - let previous_epoch_attester_indices_hashset: HashSet = - HashSet::from_iter(previous_epoch_attester_indices.iter().cloned()); - let active_validator_indices_hashset: HashSet = - HashSet::from_iter(active_validator_indices.iter().cloned()); - - debug!("previous epoch justified attesters: {}, previous epoch boundary attesters: {}, previous epoch head attesters: {}, previous epoch attesters: {}", previous_epoch_justified_attester_indices.len(), previous_epoch_boundary_attester_indices.len(), previous_epoch_head_attester_indices.len(), previous_epoch_attester_indices.len()); - - debug!("{} epochs since finality.", epochs_since_finality); - - if epochs_since_finality <= 4 { - for index in 0..self.validator_balances.len() { - let base_reward = self.base_reward(index, base_reward_quotient, spec); - - if previous_epoch_justified_attester_indices_hashset.contains(&index) { - safe_add_assign!( - self.validator_balances[index], - base_reward * previous_epoch_justified_attesting_balance - / previous_total_balance - ); - } else if active_validator_indices_hashset.contains(&index) { - safe_sub_assign!(self.validator_balances[index], base_reward); - } - - if previous_epoch_boundary_attester_indices_hashset.contains(&index) { - safe_add_assign!( - self.validator_balances[index], - base_reward * previous_epoch_boundary_attesting_balance - / previous_total_balance - ); - } else if active_validator_indices_hashset.contains(&index) { - safe_sub_assign!(self.validator_balances[index], base_reward); - } - - if previous_epoch_head_attester_indices_hashset.contains(&index) { - safe_add_assign!( - self.validator_balances[index], - base_reward * previous_epoch_head_attesting_balance - / previous_total_balance - ); - } else if active_validator_indices_hashset.contains(&index) { - safe_sub_assign!(self.validator_balances[index], base_reward); - } - } - - for index in previous_epoch_attester_indices { - let base_reward = self.base_reward(index, base_reward_quotient, spec); - let inclusion_distance = - self.inclusion_distance(&previous_epoch_attestations, index, spec)?; - - safe_add_assign!( - self.validator_balances[index], - base_reward * spec.min_attestation_inclusion_delay / inclusion_distance - ) - } - } else { - for index in 0..self.validator_balances.len() { - let inactivity_penalty = self.inactivity_penalty( - index, - epochs_since_finality, - base_reward_quotient, - spec, - ); - if active_validator_indices_hashset.contains(&index) { - if !previous_epoch_justified_attester_indices_hashset.contains(&index) { - safe_sub_assign!(self.validator_balances[index], inactivity_penalty); - } - if !previous_epoch_boundary_attester_indices_hashset.contains(&index) { - safe_sub_assign!(self.validator_balances[index], inactivity_penalty); - } - if !previous_epoch_head_attester_indices_hashset.contains(&index) { - safe_sub_assign!(self.validator_balances[index], inactivity_penalty); - } - - if self.validator_registry[index].penalized_epoch <= current_epoch { - let base_reward = self.base_reward(index, base_reward_quotient, spec); - safe_sub_assign!( - self.validator_balances[index], - 2 * inactivity_penalty + base_reward - ); - } - } - } - - for index in previous_epoch_attester_indices { - let base_reward = self.base_reward(index, base_reward_quotient, spec); - let inclusion_distance = - self.inclusion_distance(&previous_epoch_attestations, index, spec)?; - - safe_sub_assign!( - self.validator_balances[index], - base_reward - - base_reward * spec.min_attestation_inclusion_delay / inclusion_distance - ); - } - } - - trace!("Processed validator justification and finalization rewards/penalities."); - - /* - * Attestation inclusion - */ - for &index in &previous_epoch_attester_indices_hashset { - let inclusion_slot = - self.inclusion_slot(&previous_epoch_attestations[..], index, spec)?; - let proposer_index = self - .get_beacon_proposer_index(inclusion_slot, spec) - .map_err(|_| Error::UnableToDetermineProducer)?; - let base_reward = self.base_reward(proposer_index, base_reward_quotient, spec); - safe_add_assign!( - self.validator_balances[proposer_index], - base_reward / spec.includer_reward_quotient - ); - } - - trace!( - "Previous epoch attesters: {}.", - previous_epoch_attester_indices_hashset.len() - ); - - /* - * Crosslinks - */ - for slot in self.previous_epoch(spec).slot_iter(spec.slots_per_epoch) { - // Clone is used to remove the borrow. It becomes an issue later when trying to mutate - // `self.balances`. - let crosslink_committees_at_slot = - self.get_crosslink_committees_at_slot(slot, spec)?.clone(); - - for (_crosslink_committee, shard) in crosslink_committees_at_slot { - let shard = shard as u64; - - 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().cloned(), - ); - - 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); - } - } - - 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 - ); - } - } - } - } - - /* - * Ejections - */ - self.process_ejections(spec); - - /* - * Validator Registry - */ - self.previous_shuffling_epoch = self.current_shuffling_epoch; - self.previous_epoch_start_shard = self.current_epoch_start_shard; - - debug!( - "setting previous_shuffling_seed to : {}", - self.current_shuffling_seed - ); - - self.previous_shuffling_seed = self.current_shuffling_seed; - - let should_update_validator_registy = if self.finalized_epoch - > self.validator_registry_update_epoch - { - (0..self.get_current_epoch_committee_count(spec)).all(|i| { - let shard = (self.current_epoch_start_shard + i as u64) % spec.shard_count; - self.latest_crosslinks[shard as usize].epoch > self.validator_registry_update_epoch - }) - } else { - false - }; - - if should_update_validator_registy { - trace!("updating validator registry."); - self.update_validator_registry(spec); - - self.current_shuffling_epoch = next_epoch; - self.current_epoch_start_shard = (self.current_epoch_start_shard - + self.get_current_epoch_committee_count(spec) as u64) - % spec.shard_count; - self.current_shuffling_seed = self.generate_seed(self.current_shuffling_epoch, spec)? - } else { - trace!("not updating validator registry."); - let epochs_since_last_registry_update = - current_epoch - self.validator_registry_update_epoch; - if (epochs_since_last_registry_update > 1) - & epochs_since_last_registry_update.is_power_of_two() - { - self.current_shuffling_epoch = next_epoch; - self.current_shuffling_seed = - self.generate_seed(self.current_shuffling_epoch, spec)? - } - } - - self.process_penalties_and_exits(spec); - - self.latest_index_roots[(next_epoch.as_usize() + spec.entry_exit_delay as usize) - % spec.latest_index_roots_length] = hash_tree_root(get_active_validator_indices( - &self.validator_registry, - next_epoch + Epoch::from(spec.entry_exit_delay), - )); - self.latest_penalized_balances[next_epoch.as_usize() % spec.latest_penalized_exit_length] = - self.latest_penalized_balances - [current_epoch.as_usize() % spec.latest_penalized_exit_length]; - self.latest_randao_mixes[next_epoch.as_usize() % spec.latest_randao_mixes_length] = self - .get_randao_mix(current_epoch, spec) - .and_then(|x| Some(*x)) - .ok_or_else(|| Error::NoRandaoSeed)?; - self.latest_attestations = self - .latest_attestations - .iter() - .filter(|a| a.data.slot.epoch(spec.slots_per_epoch) >= current_epoch) - .cloned() - .collect(); - - debug!("Epoch transition complete."); - - Ok(()) - } +pub fn per_epoch_processing(state: &mut BeaconState, spec: &ChainSpec) -> Result<(), Error> { + let current_epoch = state.current_epoch(spec); + let previous_epoch = state.previous_epoch(spec); + let next_epoch = state.next_epoch(spec); + + debug!( + "Starting per-epoch processing on epoch {}...", + state.current_epoch(spec) + ); + + // Ensure all of the caches are built. + state.build_epoch_cache(RelativeEpoch::Previous, spec)?; + state.build_epoch_cache(RelativeEpoch::Current, spec)?; + state.build_epoch_cache(RelativeEpoch::Next, spec)?; + + let attesters = GroupedAttesters::new(&state, spec)?; + + let active_validator_indices = get_active_validator_indices( + &state.validator_registry, + state.slot.epoch(spec.slots_per_epoch), + ); + + let current_total_balance = state.get_total_balance(&active_validator_indices[..], spec); + let previous_total_balance = state.get_total_balance( + &get_active_validator_indices(&state.validator_registry, previous_epoch)[..], + spec, + ); + + process_eth1_data(state, spec); + + process_justification( + state, + current_total_balance, + previous_total_balance, + attesters.previous_epoch_boundary.balance, + attesters.current_epoch_boundary.balance, + spec, + ); + + // Crosslinks + let winning_root_for_shards = process_crosslinks(state, spec)?; + + // Rewards and Penalities + let active_validator_indices_hashset: HashSet = + HashSet::from_iter(active_validator_indices.iter().cloned()); + process_rewards_and_penalities( + state, + active_validator_indices_hashset, + &attesters, + previous_total_balance, + &winning_root_for_shards, + spec, + )?; + + // Ejections + state.process_ejections(spec); + + // Validator Registry + process_validator_registry(state, spec)?; + + // Final updates + state.latest_active_index_roots[(next_epoch.as_usize() + + spec.activation_exit_delay as usize) + % spec.latest_active_index_roots_length] = hash_tree_root(get_active_validator_indices( + &state.validator_registry, + next_epoch + Epoch::from(spec.activation_exit_delay), + )); + + state.latest_slashed_balances[next_epoch.as_usize() % spec.latest_slashed_exit_length] = + state.latest_slashed_balances[current_epoch.as_usize() % spec.latest_slashed_exit_length]; + state.latest_randao_mixes[next_epoch.as_usize() % spec.latest_randao_mixes_length] = state + .get_randao_mix(current_epoch, spec) + .and_then(|x| Some(*x)) + .ok_or_else(|| Error::NoRandaoSeed)?; + state.latest_attestations = state + .latest_attestations + .iter() + .filter(|a| a.data.slot.epoch(spec.slots_per_epoch) >= current_epoch) + .cloned() + .collect(); + + // Rotate the epoch caches to suit the epoch transition. + state.advance_caches(); + + debug!("Epoch transition complete."); + + Ok(()) } fn hash_tree_root(input: Vec) -> Hash256 { Hash256::from(&input.hash_tree_root()[..]) } + +/// Spec v0.4.0 +fn process_eth1_data(state: &mut BeaconState, spec: &ChainSpec) { + let next_epoch = state.next_epoch(spec); + let voting_period = spec.epochs_per_eth1_voting_period; + + if next_epoch % voting_period == 0 { + for eth1_data_vote in &state.eth1_data_votes { + if eth1_data_vote.vote_count * 2 > voting_period { + state.latest_eth1_data = eth1_data_vote.eth1_data.clone(); + } + } + state.eth1_data_votes = vec![]; + } +} + +/// Spec v0.4.0 +fn process_justification( + state: &mut BeaconState, + current_total_balance: u64, + previous_total_balance: u64, + previous_epoch_boundary_attesting_balance: u64, + current_epoch_boundary_attesting_balance: u64, + spec: &ChainSpec, +) { + let previous_epoch = state.previous_epoch(spec); + let current_epoch = state.current_epoch(spec); + + let mut new_justified_epoch = state.justified_epoch; + state.justification_bitfield <<= 1; + + // If > 2/3 of the total balance attested to the previous epoch boundary + // + // - Set the 2nd bit of the bitfield. + // - Set the previous epoch to be justified. + if (3 * previous_epoch_boundary_attesting_balance) >= (2 * previous_total_balance) { + state.justification_bitfield |= 2; + new_justified_epoch = previous_epoch; + } + // If > 2/3 of the total balance attested to the previous epoch boundary + // + // - Set the 1st bit of the bitfield. + // - Set the current epoch to be justified. + if (3 * current_epoch_boundary_attesting_balance) >= (2 * current_total_balance) { + state.justification_bitfield |= 1; + new_justified_epoch = current_epoch; + } + + // If: + // + // - All three epochs prior to this epoch have been justified. + // - The previous justified justified epoch was three epochs ago. + // + // Then, set the finalized epoch to be three epochs ago. + if ((state.justification_bitfield >> 1) % 8 == 0b111) + & (state.previous_justified_epoch == previous_epoch - 2) + { + state.finalized_epoch = state.previous_justified_epoch; + } + // If: + // + // - Both two epochs prior to this epoch have been justified. + // - The previous justified epoch was two epochs ago. + // + // Then, set the finalized epoch to two epochs ago. + if ((state.justification_bitfield >> 1) % 4 == 0b11) + & (state.previous_justified_epoch == previous_epoch - 1) + { + state.finalized_epoch = state.previous_justified_epoch; + } + // If: + // + // - This epoch and the two prior have been justified. + // - The presently justified epoch was two epochs ago. + // + // Then, set the finalized epoch to two epochs ago. + if (state.justification_bitfield % 8 == 0b111) & (state.justified_epoch == previous_epoch - 1) { + state.finalized_epoch = state.justified_epoch; + } + // If: + // + // - This epoch and the epoch prior to it have been justified. + // - Set the previous epoch to be justified. + // + // Then, set the finalized epoch to be the previous epoch. + if (state.justification_bitfield % 4 == 0b11) & (state.justified_epoch == previous_epoch) { + state.finalized_epoch = state.justified_epoch; + } + + state.previous_justified_epoch = state.justified_epoch; + state.justified_epoch = new_justified_epoch; +} + +pub type WinningRootHashSet = HashMap>; + +fn process_crosslinks( + state: &mut BeaconState, + spec: &ChainSpec, +) -> Result { + let current_epoch_attestations: Vec<&PendingAttestation> = state + .latest_attestations + .par_iter() + .filter(|a| { + (a.data.slot / spec.slots_per_epoch).epoch(spec.slots_per_epoch) + == state.current_epoch(spec) + }) + .collect(); + + let previous_epoch_attestations: Vec<&PendingAttestation> = state + .latest_attestations + .par_iter() + .filter(|a| { + (a.data.slot / spec.slots_per_epoch).epoch(spec.slots_per_epoch) + == state.previous_epoch(spec) + }) + .collect(); + + let mut winning_root_for_shards: HashMap> = + HashMap::new(); + // for slot in state.slot.saturating_sub(2 * spec.slots_per_epoch)..state.slot { + for slot in state.previous_epoch(spec).slot_iter(spec.slots_per_epoch) { + trace!( + "Finding winning root for slot: {} (epoch: {})", + slot, + slot.epoch(spec.slots_per_epoch) + ); + + // Clone is used to remove the borrow. It becomes an issue later when trying to mutate + // `state.balances`. + let crosslink_committees_at_slot = + state.get_crosslink_committees_at_slot(slot, spec)?.clone(); + + for (crosslink_committee, shard) in crosslink_committees_at_slot { + let shard = shard as u64; + + let winning_root = winning_root( + state, + shard, + ¤t_epoch_attestations[..], + &previous_epoch_attestations[..], + spec, + ); + + if let Ok(winning_root) = &winning_root { + let total_committee_balance = state.get_total_balance(&crosslink_committee, spec); + + if (3 * winning_root.total_attesting_balance) >= (2 * total_committee_balance) { + state.latest_crosslinks[shard as usize] = Crosslink { + epoch: state.current_epoch(spec), + crosslink_data_root: winning_root.crosslink_data_root, + } + } + } + winning_root_for_shards.insert(shard, winning_root); + } + } + + Ok(winning_root_for_shards) +} + +fn process_rewards_and_penalities( + state: &mut BeaconState, + active_validator_indices: HashSet, + attesters: &GroupedAttesters, + previous_total_balance: u64, + winning_root_for_shards: &HashMap>, + spec: &ChainSpec, +) -> Result<(), Error> { + let next_epoch = state.next_epoch(spec); + + let previous_epoch_attestations: Vec<&PendingAttestation> = state + .latest_attestations + .par_iter() + .filter(|a| { + (a.data.slot / spec.slots_per_epoch).epoch(spec.slots_per_epoch) + == state.previous_epoch(spec) + }) + .collect(); + + let base_reward_quotient = previous_total_balance.integer_sqrt() / spec.base_reward_quotient; + if base_reward_quotient == 0 { + return Err(Error::BaseRewardQuotientIsZero); + } + + /* + * Justification and finalization + */ + let epochs_since_finality = next_epoch - state.finalized_epoch; + + let active_validator_indices_hashset: HashSet = + HashSet::from_iter(active_validator_indices.iter().cloned()); + + if epochs_since_finality <= 4 { + for index in 0..state.validator_balances.len() { + let base_reward = state.base_reward(index, base_reward_quotient, spec); + + if attesters.previous_epoch.indices.contains(&index) { + safe_add_assign!( + state.validator_balances[index], + base_reward * attesters.previous_epoch.balance / previous_total_balance + ); + } else if active_validator_indices_hashset.contains(&index) { + safe_sub_assign!(state.validator_balances[index], base_reward); + } + + if attesters.previous_epoch_boundary.indices.contains(&index) { + safe_add_assign!( + state.validator_balances[index], + base_reward * attesters.previous_epoch_boundary.balance + / previous_total_balance + ); + } else if active_validator_indices_hashset.contains(&index) { + safe_sub_assign!(state.validator_balances[index], base_reward); + } + + if attesters.previous_epoch_head.indices.contains(&index) { + safe_add_assign!( + state.validator_balances[index], + base_reward * attesters.previous_epoch_head.balance / previous_total_balance + ); + } else if active_validator_indices_hashset.contains(&index) { + safe_sub_assign!(state.validator_balances[index], base_reward); + } + } + + for &index in &attesters.previous_epoch.indices { + let base_reward = state.base_reward(index, base_reward_quotient, spec); + let inclusion_distance = + state.inclusion_distance(&previous_epoch_attestations, index, spec)?; + + safe_add_assign!( + state.validator_balances[index], + base_reward * spec.min_attestation_inclusion_delay / inclusion_distance + ) + } + } else { + for index in 0..state.validator_balances.len() { + let inactivity_penalty = + state.inactivity_penalty(index, epochs_since_finality, base_reward_quotient, spec); + if active_validator_indices_hashset.contains(&index) { + if !attesters.previous_epoch.indices.contains(&index) { + safe_sub_assign!(state.validator_balances[index], inactivity_penalty); + } + if !attesters.previous_epoch_boundary.indices.contains(&index) { + safe_sub_assign!(state.validator_balances[index], inactivity_penalty); + } + if !attesters.previous_epoch_head.indices.contains(&index) { + safe_sub_assign!(state.validator_balances[index], inactivity_penalty); + } + + if state.validator_registry[index].slashed { + let base_reward = state.base_reward(index, base_reward_quotient, spec); + safe_sub_assign!( + state.validator_balances[index], + 2 * inactivity_penalty + base_reward + ); + } + } + } + + for &index in &attesters.previous_epoch.indices { + let base_reward = state.base_reward(index, base_reward_quotient, spec); + let inclusion_distance = + state.inclusion_distance(&previous_epoch_attestations, index, spec)?; + + safe_sub_assign!( + state.validator_balances[index], + base_reward + - base_reward * spec.min_attestation_inclusion_delay / inclusion_distance + ); + } + } + + trace!("Processed validator justification and finalization rewards/penalities."); + + /* + * Attestation inclusion + */ + for &index in &attesters.previous_epoch.indices { + let inclusion_slot = state.inclusion_slot(&previous_epoch_attestations[..], index, spec)?; + let proposer_index = state + .get_beacon_proposer_index(inclusion_slot, spec) + .map_err(|_| Error::UnableToDetermineProducer)?; + let base_reward = state.base_reward(proposer_index, base_reward_quotient, spec); + safe_add_assign!( + state.validator_balances[proposer_index], + base_reward / spec.attestation_inclusion_reward_quotient + ); + } + + /* + * Crosslinks + */ + for slot in state.previous_epoch(spec).slot_iter(spec.slots_per_epoch) { + // Clone is used to remove the borrow. It becomes an issue later when trying to mutate + // `state.balances`. + let crosslink_committees_at_slot = + state.get_crosslink_committees_at_slot(slot, spec)?.clone(); + + for (_crosslink_committee, shard) in crosslink_committees_at_slot { + let shard = shard as u64; + + 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().cloned()); + + for index in 0..state.validator_balances.len() { + let base_reward = state.base_reward(index, base_reward_quotient, spec); + + if attesting_validator_indices.contains(&index) { + safe_add_assign!( + state.validator_balances[index], + base_reward * winning_root.total_attesting_balance + / winning_root.total_balance + ); + } else { + safe_sub_assign!(state.validator_balances[index], base_reward); + } + } + + for index in &winning_root.attesting_validator_indices { + let base_reward = state.base_reward(*index, base_reward_quotient, spec); + safe_add_assign!( + state.validator_balances[*index], + base_reward * winning_root.total_attesting_balance + / winning_root.total_balance + ); + } + } + } + } + + Ok(()) +} + +fn process_validator_registry(state: &mut BeaconState, spec: &ChainSpec) -> Result<(), Error> { + let current_epoch = state.current_epoch(spec); + let next_epoch = state.next_epoch(spec); + + state.previous_shuffling_epoch = state.current_shuffling_epoch; + state.previous_shuffling_start_shard = state.current_shuffling_start_shard; + + debug!( + "setting previous_shuffling_seed to : {}", + state.current_shuffling_seed + ); + + state.previous_shuffling_seed = state.current_shuffling_seed; + + let should_update_validator_registy = if state.finalized_epoch + > state.validator_registry_update_epoch + { + (0..state.get_current_epoch_committee_count(spec)).all(|i| { + let shard = (state.current_shuffling_start_shard + i as u64) % spec.shard_count; + state.latest_crosslinks[shard as usize].epoch > state.validator_registry_update_epoch + }) + } else { + false + }; + + if should_update_validator_registy { + trace!("updating validator registry."); + state.update_validator_registry(spec); + + state.current_shuffling_epoch = next_epoch; + state.current_shuffling_start_shard = (state.current_shuffling_start_shard + + state.get_current_epoch_committee_count(spec) as u64) + % spec.shard_count; + state.current_shuffling_seed = state.generate_seed(state.current_shuffling_epoch, spec)? + } else { + trace!("not updating validator registry."); + let epochs_since_last_registry_update = + current_epoch - state.validator_registry_update_epoch; + if (epochs_since_last_registry_update > 1) + & epochs_since_last_registry_update.is_power_of_two() + { + state.current_shuffling_epoch = next_epoch; + state.current_shuffling_seed = + state.generate_seed(state.current_shuffling_epoch, spec)? + } + } + + state.process_slashings(spec); + state.process_exit_queue(spec); + + Ok(()) +} diff --git a/eth2/state_processing/src/per_epoch_processing/grouped_attesters.rs b/eth2/state_processing/src/per_epoch_processing/grouped_attesters.rs new file mode 100644 index 000000000..02dbc4050 --- /dev/null +++ b/eth2/state_processing/src/per_epoch_processing/grouped_attesters.rs @@ -0,0 +1,98 @@ +use std::collections::HashSet; +use types::*; + +#[derive(Default)] +pub struct Attesters { + pub indices: HashSet, + pub balance: u64, +} + +impl Attesters { + fn add(&mut self, additional_indices: &[usize], additional_balance: u64) { + self.indices.reserve(additional_indices.len()); + for i in additional_indices { + self.indices.insert(*i); + } + self.balance.saturating_add(additional_balance); + } +} + +pub struct GroupedAttesters { + pub current_epoch: Attesters, + pub current_epoch_boundary: Attesters, + pub previous_epoch: Attesters, + pub previous_epoch_boundary: Attesters, + pub previous_epoch_head: Attesters, +} + +impl GroupedAttesters { + pub fn new(state: &BeaconState, spec: &ChainSpec) -> Result { + let mut current_epoch = Attesters::default(); + let mut current_epoch_boundary = Attesters::default(); + let mut previous_epoch = Attesters::default(); + let mut previous_epoch_boundary = Attesters::default(); + let mut previous_epoch_head = Attesters::default(); + + for a in &state.latest_attestations { + let attesting_indices = + state.get_attestation_participants(&a.data, &a.aggregation_bitfield, spec)?; + let attesting_balance = state.get_total_balance(&attesting_indices, spec); + + if is_from_epoch(a, state.current_epoch(spec), spec) { + current_epoch.add(&attesting_indices, attesting_balance); + + if has_common_epoch_boundary_root(a, state, state.current_epoch(spec), spec)? { + current_epoch_boundary.add(&attesting_indices, attesting_balance); + } + } else if is_from_epoch(a, state.previous_epoch(spec), spec) { + previous_epoch.add(&attesting_indices, attesting_balance); + + if has_common_epoch_boundary_root(a, state, state.previous_epoch(spec), spec)? { + previous_epoch_boundary.add(&attesting_indices, attesting_balance); + } + + if has_common_beacon_block_root(a, state, spec)? { + previous_epoch_head.add(&attesting_indices, attesting_balance); + } + } + } + + Ok(Self { + current_epoch, + current_epoch_boundary, + previous_epoch, + previous_epoch_boundary, + previous_epoch_head, + }) + } +} + +fn is_from_epoch(a: &PendingAttestation, epoch: Epoch, spec: &ChainSpec) -> bool { + a.data.slot.epoch(spec.slots_per_epoch) == epoch +} + +fn has_common_epoch_boundary_root( + a: &PendingAttestation, + state: &BeaconState, + epoch: Epoch, + spec: &ChainSpec, +) -> Result { + let slot = epoch.start_slot(spec.slots_per_epoch); + let state_boundary_root = *state + .get_block_root(slot, spec) + .ok_or_else(|| BeaconStateError::InsufficientBlockRoots)?; + + Ok(a.data.epoch_boundary_root == state_boundary_root) +} + +fn has_common_beacon_block_root( + a: &PendingAttestation, + state: &BeaconState, + spec: &ChainSpec, +) -> Result { + let state_block_root = *state + .get_block_root(a.data.slot, spec) + .ok_or_else(|| BeaconStateError::InsufficientBlockRoots)?; + + Ok(a.data.beacon_block_root == state_block_root) +} diff --git a/eth2/state_processing/src/per_epoch_processing/tests.rs b/eth2/state_processing/src/per_epoch_processing/tests.rs index d683d3971..627df858b 100644 --- a/eth2/state_processing/src/per_epoch_processing/tests.rs +++ b/eth2/state_processing/src/per_epoch_processing/tests.rs @@ -1,5 +1,5 @@ #![cfg(test)] -use crate::EpochProcessable; +use crate::per_epoch_processing; use env_logger::{Builder, Env}; use types::beacon_state::BeaconStateBuilder; use types::*; @@ -17,5 +17,5 @@ fn runs_without_error() { let mut state = builder.cloned_state(); let spec = &builder.spec; - state.per_epoch_processing(spec).unwrap(); + per_epoch_processing(&mut state, spec).unwrap(); } diff --git a/eth2/state_processing/src/per_epoch_processing/winning_root.rs b/eth2/state_processing/src/per_epoch_processing/winning_root.rs index 5b1e5925f..c3b650c3d 100644 --- a/eth2/state_processing/src/per_epoch_processing/winning_root.rs +++ b/eth2/state_processing/src/per_epoch_processing/winning_root.rs @@ -1,15 +1,16 @@ -use super::WinningRootError; +use super::errors::WinningRootError; +use std::collections::HashMap; use types::*; #[derive(Clone)] pub struct WinningRoot { - pub shard_block_root: Hash256, + pub crosslink_data_root: Hash256, pub attesting_validator_indices: Vec, pub total_balance: u64, pub total_attesting_balance: u64, } -fn winning_root( +pub fn winning_root( state: &BeaconState, shard: u64, current_epoch_attestations: &[&PendingAttestation], @@ -28,16 +29,16 @@ fn winning_root( continue; } - let shard_block_root = &a.data.shard_block_root; + let crosslink_data_root = &a.data.crosslink_data_root; - if candidates.contains_key(shard_block_root) { + if candidates.contains_key(crosslink_data_root) { continue; } let attesting_validator_indices = attestations .iter() .try_fold::<_, _, Result<_, BeaconStateError>>(vec![], |mut acc, a| { - if (a.data.shard == shard) && (a.data.shard_block_root == *shard_block_root) { + if (a.data.shard == shard) && (a.data.crosslink_data_root == *crosslink_data_root) { acc.append(&mut state.get_attestation_participants( &a.data, &a.aggregation_bitfield, @@ -60,13 +61,13 @@ fn winning_root( } let candidate_root = WinningRoot { - shard_block_root: *shard_block_root, + crosslink_data_root: *crosslink_data_root, attesting_validator_indices, total_attesting_balance, total_balance, }; - candidates.insert(*shard_block_root, candidate_root); + candidates.insert(*crosslink_data_root, candidate_root); } Ok(candidates @@ -78,7 +79,7 @@ fn winning_root( None } }) - .min_by_key(|candidate| candidate.shard_block_root) + .min_by_key(|candidate| candidate.crosslink_data_root) .ok_or_else(|| WinningRootError::NoWinningRoot)? // TODO: avoid clone. .clone())