diff --git a/beacon_node/beacon_chain/src/epoch_processing.rs b/beacon_node/beacon_chain/src/epoch_processing.rs new file mode 100644 index 0000000000..405ba7df2d --- /dev/null +++ b/beacon_node/beacon_chain/src/epoch_processing.rs @@ -0,0 +1,9 @@ +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/lib.rs b/beacon_node/beacon_chain/src/lib.rs index d6058eab76..2a6bc9a6bb 100644 --- a/beacon_node/beacon_chain/src/lib.rs +++ b/beacon_node/beacon_chain/src/lib.rs @@ -4,6 +4,7 @@ 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 3e694d3313..4b39c12dea 100644 --- a/beacon_node/beacon_chain/src/state_transition.rs +++ b/beacon_node/beacon_chain/src/state_transition.rs @@ -239,12 +239,13 @@ where } ensure!( attestation.data.justified_block_root - == *get_block_root( - &state, - attestation.data.justified_slot, - self.spec.latest_block_roots_length - ) - .ok_or(Error::NoBlockRoot)?, + == *state + .get_block_root( + &state, + attestation.data.justified_slot, + self.spec.latest_block_roots_length + ) + .ok_or(Error::NoBlockRoot)?, Error::BadAttestation ); ensure!( @@ -376,24 +377,10 @@ fn get_attestation_participants( _attestation_data: &AttestationData, _aggregation_bitfield: &BooleanBitfield, ) -> Vec { + // TODO: stubbed out. vec![0, 1] } -fn get_block_root( - state: &BeaconState, - slot: u64, - latest_block_roots_length: u64, -) -> Option<&Hash256> { - // TODO: test - if state.slot <= slot + latest_block_roots_length && slot <= state.slot { - state - .latest_block_roots - .get((slot % latest_block_roots_length) as usize) - } else { - None - } -} - fn penalize_validator(_state: &BeaconState, _proposer_index: usize) { // TODO: stubbed out. } diff --git a/eth2/types/Cargo.toml b/eth2/types/Cargo.toml index b3c3f7462b..a553e42404 100644 --- a/eth2/types/Cargo.toml +++ b/eth2/types/Cargo.toml @@ -9,6 +9,7 @@ bls = { path = "../utils/bls" } boolean-bitfield = { path = "../utils/boolean-bitfield" } ethereum-types = "0.4.0" hashing = { path = "../utils/hashing" } +integer-sqrt = "0.1" rand = "0.5.5" serde = "1.0" serde_derive = "1.0" diff --git a/eth2/types/src/beacon_state/epoch_processing.rs b/eth2/types/src/beacon_state/epoch_processing.rs new file mode 100644 index 0000000000..cd980af3df --- /dev/null +++ b/eth2/types/src/beacon_state/epoch_processing.rs @@ -0,0 +1,837 @@ +use super::winning_root::WinningRoot; +use crate::{ + validator::StatusFlags, validator_registry::get_active_validator_indices, Attestation, + AttestationData, BeaconState, Bitfield, ChainSpec, Crosslink, Hash256, PendingAttestation, +}; +use integer_sqrt::IntegerSquareRoot; +use std::collections::{HashMap, HashSet}; +use std::iter::FromIterator; + +pub enum Error { + UnableToDetermineProducer, + NoBlockRoots, + UnableToGetCrosslinkCommittees, + BaseRewardQuotientIsZero, +} + +macro_rules! safe_add_assign { + ($a: expr, $b: expr) => { + $a = $a.saturating_add($b); + }; +} +macro_rules! safe_sub_assign { + ($a: expr, $b: expr) => { + $a = $a.saturating_sub($b); + }; +} + +impl BeaconState { + pub fn per_epoch_processing(&mut self, spec: &ChainSpec) -> Result<(), Error> { + /* + * 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| self.get_effective_balance(*i, spec)); + + let current_epoch_attestations: Vec<&PendingAttestation> = self + .latest_attestations + .iter() + .filter(|a| (self.slot - spec.epoch_length <= a.data.slot) && (a.data.slot < self.slot)) + .collect(); + + /* + * Validators attesting during the current epoch. + */ + if self.latest_block_roots.is_empty() { + return Err(Error::NoBlockRoots); + } + + let current_epoch_boundary_attestations: Vec<&PendingAttestation> = + current_epoch_attestations + .iter() + // `filter_map` is used to avoid a double borrow (`&&..`). + .filter_map(|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, + // 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 + } + }) + .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)); + + /* + * Validators attesting during the previous epoch + */ + + /* + * Validators that made an attestation during the previous epoch + */ + let previous_epoch_attestations: Vec<&PendingAttestation> = self + .latest_attestations + .iter() + .filter(|a| { + //TODO: ensure these saturating subs are correct. + (self.slot.saturating_sub(2 * spec.epoch_length) <= a.data.slot) + && (a.data.slot < self.slot.saturating_sub(spec.epoch_length)) + }) + .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 + }); + + /* + * Validators targetting the previous justified slot + */ + 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 + } + }) + .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 + } + }) + .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)); + + /* + * 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_map` is used to avoid a double borrow (`&&..`). + .filter_map(|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) + { + Some(root) => 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 + } + }) + .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)); + + /* + * Validators attesting to the expected beacon chain head during the previous epoch. + */ + 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) + { + Some(root) => 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 + } + }) + .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 all_crosslink_committees: Vec = { + // TODO: check staturating sub is correct + let start_slot = self.slot.saturating_sub(2 * spec.epoch_length); + + // Sub is safe due to previous line. + // + // TODO: provide detailed reasoning. + let mut committees = Vec::with_capacity((self.slot - start_slot) as usize); + for slot in start_slot..self.slot { + match self.get_crosslink_committees_at_slot(slot) { + Some(c) => committees.push(c), + None => return Err(Error::UnableToGetCrosslinkCommittees), + } + } + committees + }; + + // TODO: I didn't include the `winning_balance` stuff.. Not sure why it's there. + + /* + * Eth1 Data + */ + if self.slot % 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 + */ + self.previous_justified_slot = self.justified_slot; + self.justification_bitfield = (self.justification_bitfield * 2) % u64::pow(2, 64); + + // If >= 2/3 of validators voted for the previous epoch boundary + if (3 * previous_epoch_boundary_attesting_balance) >= (2 * total_balance) { + // TODO: check saturating_sub is correct. + self.justification_bitfield |= 2; + self.justified_slot = self.slot.saturating_sub(2 * spec.epoch_length); + } + + // If >= 2/3 of validators voted for the current epoch boundary + if (3 * current_epoch_boundary_attesting_balance) >= (2 * total_balance) { + // TODO: check saturating_sub is correct. + self.justification_bitfield |= 1; + self.justified_slot = self.slot.saturating_sub(1 * spec.epoch_length); + } + + if (self.previous_justified_slot == self.slot.saturating_sub(2 * spec.epoch_length)) + && (self.justification_bitfield % 4 == 3) + { + self.finalized_slot = self.previous_justified_slot; + } + if (self.previous_justified_slot == self.slot.saturating_sub(3 * spec.epoch_length)) + && (self.justification_bitfield % 8 == 7) + { + self.finalized_slot = self.previous_justified_slot; + } + if (self.previous_justified_slot == self.slot.saturating_sub(4 * spec.epoch_length)) + && (self.justification_bitfield % 16 == 14) + { + self.finalized_slot = self.previous_justified_slot; + } + if (self.previous_justified_slot == self.slot.saturating_sub(4 * spec.epoch_length)) + && (self.justification_bitfield % 16 == 15) + { + self.finalized_slot = self.previous_justified_slot; + } + + /* + * Crosslinks + */ + + // Cached for later lookups. + 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) + .ok_or_else(|| Error::UnableToGetCrosslinkCommittees)?; + + for (crosslink_committee, shard) in crosslink_committees_at_slot { + let shard = shard as u64; + + let winning_root = self.winning_root( + shard, + ¤t_epoch_attestations, + &previous_epoch_attestations, + spec, + ); + + if let Some(winning_root) = winning_root { + let total_committee_balance: u64 = crosslink_committee + .iter() + .fold(0, |acc, i| self.get_effective_balance(*i, spec)); + + winning_root_for_shards.insert(shard, winning_root.clone()); + + if (3 * winning_root.total_attesting_balance) >= (2 * total_committee_balance) { + self.latest_crosslinks[shard as usize] = Crosslink { + slot: self.slot, + shard_block_root: winning_root.shard_block_root, + } + } + } + } + } + + /* + * Rewards and Penalities + */ + let base_reward_quotient = total_balance.integer_sqrt(); + if base_reward_quotient == 0 { + 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 + */ + let epochs_since_finality = (self.slot - self.finalized_slot) / spec.epoch_length; + + // TODO: fix this extra map + let previous_epoch_justified_attester_indices_hashset: HashSet = + HashSet::from_iter(previous_epoch_justified_attester_indices.iter().map(|i| *i)); + let previous_epoch_boundary_attester_indices_hashset: HashSet = + HashSet::from_iter(previous_epoch_boundary_attester_indices.iter().map(|i| *i)); + let previous_epoch_head_attester_indices_hashset: HashSet = + HashSet::from_iter(previous_epoch_head_attester_indices.iter().map(|i| *i)); + let previous_epoch_attester_indices_hashset: HashSet = + HashSet::from_iter(previous_epoch_attester_indices.iter().map(|i| *i)); + + 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 / total_balance + ); + } else { + 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 / total_balance + ); + } else { + 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 / total_balance + ); + } else { + 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 = match self.inclusion_distance(index) { + Some(distance) => distance, + None => unreachable!(), + }; + + 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 !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); + } + } + + 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!(), + }; + + safe_sub_assign!( + self.validator_balances[index], + base_reward + - base_reward * spec.min_attestation_inclusion_delay / inclusion_distance + ); + } + } + + /* + * Attestation inclusion + */ + for index in previous_epoch_attester_indices_hashset { + let inclusion_slot = match self.inclusion_slot(index) { + Some(slot) => slot, + None => unreachable!(), + }; + let proposer_index = self + .get_beacon_proposer_index(inclusion_slot, spec) + .ok_or_else(|| 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 + ); + } + + /* + * 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) + .ok_or_else(|| Error::UnableToGetCrosslinkCommittees)?; + + 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"); + + // 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); + + 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(); + + /* + * Validator Registry + */ + self.previous_epoch_calculation_slot = self.current_epoch_calculation_slot; + self.previous_epoch_start_shard = self.current_epoch_start_shard; + self.previous_epoch_randao_mix = self.current_epoch_randao_mix; + + let should_update_validator_registy = if self.finalized_slot + > self.validator_registry_update_slot + { + (0..self.get_current_epoch_committee_count_per_slot(spec)).all(|i| { + let shard = (self.current_epoch_start_shard + i as u64) % spec.shard_count; + self.latest_crosslinks[shard as usize].slot > self.validator_registry_update_slot + }) + } else { + false + }; + + if should_update_validator_registy { + self.update_validator_registry(total_balance, spec); + + self.current_epoch_calculation_slot = self.slot; + self.current_epoch_start_shard = (self.current_epoch_start_shard + + self.get_current_epoch_committee_count_per_slot(spec) as u64 * spec.epoch_length) + % spec.shard_count; + self.current_epoch_randao_mix = self.get_randao_mix( + self.current_epoch_calculation_slot + .saturating_sub(spec.seed_lookahead), + spec, + ); + } else { + let epochs_since_last_registry_change = + (self.slot - self.validator_registry_update_slot) / spec.epoch_length; + if epochs_since_last_registry_change.is_power_of_two() { + self.current_epoch_calculation_slot = self.slot; + self.current_epoch_randao_mix = self.get_randao_mix( + self.current_epoch_calculation_slot + .saturating_sub(spec.seed_lookahead), + spec, + ); + } + } + + self.process_penalties_and_exits(spec); + + let e = self.slot / spec.epoch_length; + self.latest_penalized_balances[((e + 1) % spec.latest_penalized_exit_length) as usize] = + self.latest_penalized_balances[(e % spec.latest_penalized_exit_length) as usize]; + + self.latest_attestations = self + .latest_attestations + .iter() + .filter_map(|a| { + if a.data.slot < self.slot - spec.epoch_length { + Some(a.clone()) + } else { + None + } + }) + .collect(); + + Ok(()) + } + + 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| self.get_effective_balance(*i, spec)); + + for index in 0..self.validator_balances.len() { + let validator = &self.validator_registry[index]; + + if (self.slot / spec.epoch_length) + == (validator.penalized_slot / spec.epoch_length) + + spec.latest_penalized_exit_length / 2 + { + let e = (self.slot / spec.epoch_length) % spec.latest_penalized_exit_length; + let total_at_start = self.latest_penalized_balances + [((e + 1) % spec.latest_penalized_exit_length) as usize]; + let total_at_end = self.latest_penalized_balances[e as usize]; + let total_penalities = total_at_end.saturating_sub(total_at_end); + let penalty = self.get_effective_balance(index, spec) + * std::cmp::min(total_penalities * 3, total_balance) + / total_balance; + safe_sub_assign!(self.validator_balances[index], penalty); + } + } + + let eligible = |index: usize| { + let validator = &self.validator_registry[index]; + + if validator.penalized_slot <= self.slot { + let penalized_withdrawal_time = + spec.latest_penalized_exit_length * spec.epoch_length / 2; + self.slot >= validator.penalized_slot + penalized_withdrawal_time + } else { + self.slot >= validator.exit_slot + spec.min_validator_withdrawal_time + } + }; + + let mut eligable_indices: Vec = (0..self.validator_registry.len()) + .filter(|i| eligible(*i)) + .collect(); + eligable_indices.sort_by_key(|i| self.validator_registry[*i].exit_count); + let mut withdrawn_so_far = 0; + for index in eligable_indices { + self.prepare_validator_for_withdrawal(index); + withdrawn_so_far += 1; + if withdrawn_so_far >= spec.max_withdrawals_per_epoch { + break; + } + } + } + + fn prepare_validator_for_withdrawal(&mut self, index: usize) { + //TODO: we're not ANDing here, we're setting. Potentially wrong. + self.validator_registry[index].status_flags = Some(StatusFlags::Withdrawable); + } + + fn get_randao_mix(&mut self, slot: u64, spec: &ChainSpec) -> Hash256 { + assert!(self.slot < slot + spec.latest_randao_mixes_length); + assert!(slot <= self.slot); + self.latest_randao_mixes[(slot & spec.latest_randao_mixes_length) as usize] + } + + fn update_validator_registry(&mut self, total_balance: u64, 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| self.get_effective_balance(*i, spec)); + + let max_balance_churn = std::cmp::max( + spec.max_deposit, + total_balance / (2 * spec.max_balance_churn_quotient), + ); + + let mut balance_churn = 0; + for index in 0..self.validator_registry.len() { + let validator = &self.validator_registry[index]; + + if (validator.activation_slot > self.slot + spec.entry_exit_delay) + && self.validator_balances[index] >= spec.max_deposit + { + balance_churn += self.get_effective_balance(index, spec); + if balance_churn > max_balance_churn { + break; + } + + self.activate_validator(index, false, spec); + } + } + + let mut balance_churn = 0; + for index in 0..self.validator_registry.len() { + let validator = &self.validator_registry[index]; + + if (validator.exit_slot > self.slot + spec.entry_exit_delay) + && validator.status_flags == Some(StatusFlags::InitiatedExit) + { + balance_churn += self.get_effective_balance(index, spec); + if balance_churn > max_balance_churn { + break; + } + + self.exit_validator(index, spec); + } + } + + self.validator_registry_update_slot = self.slot; + } + + fn exit_validator(&mut self, validator_index: usize, spec: &ChainSpec) { + if self.validator_registry[validator_index].exit_slot + <= self.entry_exit_effect_slot(self.slot, spec) + { + return; + } + + self.validator_registry[validator_index].exit_slot = + self.entry_exit_effect_slot(self.slot, spec); + + self.validator_registry_exit_count += 1; + self.validator_registry[validator_index].exit_count = self.validator_registry_exit_count; + } + + fn activate_validator(&mut self, validator_index: usize, is_genesis: bool, spec: &ChainSpec) { + self.validator_registry[validator_index].activation_slot = if is_genesis { + spec.genesis_slot + } else { + self.entry_exit_effect_slot(self.slot, spec) + } + } + + fn entry_exit_effect_slot(&self, slot: u64, spec: &ChainSpec) -> u64 { + (slot - slot % spec.epoch_length) + spec.epoch_length + spec.entry_exit_delay + } + + fn get_current_epoch_committee_count_per_slot(&self, spec: &ChainSpec) -> usize { + let current_active_validators = get_active_validator_indices( + &self.validator_registry, + self.current_epoch_calculation_slot, + ); + self.get_committee_count_per_slot(current_active_validators.len(), spec) + } + + fn get_committee_count_per_slot( + &self, + active_validator_count: usize, + spec: &ChainSpec, + ) -> usize { + std::cmp::max( + 1, + std::cmp::min( + spec.shard_count as usize / spec.epoch_length as usize, + active_validator_count + / spec.epoch_length as usize + / spec.target_committee_size as usize, + ), + ) + } + + fn process_ejections(&self) { + //TODO: stubbed out. + } + + fn inactivity_penalty( + &self, + validator_index: usize, + epochs_since_finality: u64, + base_reward_quotient: u64, + spec: &ChainSpec, + ) -> u64 { + let effective_balance = self.get_effective_balance(validator_index, spec); + self.base_reward(validator_index, base_reward_quotient, spec) + + 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_slot(&self, validator_index: usize) -> Option { + let attestation = self.earliest_included_attestation(validator_index)?; + Some(attestation.slot_included) + } + + fn earliest_included_attestation(&self, validator_index: usize) -> Option<&PendingAttestation> { + self.latest_attestations + .iter() + .filter(|a| { + self.get_attestation_participants(&a.data, &a.aggregation_bitfield) + .iter() + .find(|i| **i == validator_index) + .is_some() + }) + .min_by_key(|a| a.slot_included) + } + + fn base_reward( + &self, + validator_index: usize, + base_reward_quotient: u64, + spec: &ChainSpec, + ) -> u64 { + self.get_effective_balance(validator_index, spec) / base_reward_quotient / 5 + } + + pub fn get_crosslink_committees_at_slot(&self, slot: u64) -> Option { + Some(vec![(vec![0], 0)]) + } + + pub fn get_effective_balance(&self, validator_index: usize, spec: &ChainSpec) -> u64 { + std::cmp::min(self.validator_balances[validator_index], spec.max_deposit) + } + + pub fn get_block_root(&self, slot: u64, spec: &ChainSpec) -> Option<&Hash256> { + if self.slot <= slot + spec.latest_block_roots_length && slot <= self.slot { + self.latest_block_roots + .get((slot % spec.latest_block_roots_length) as usize) + } else { + None + } + } + + pub fn get_attestation_participants( + &self, + _attestation_data: &AttestationData, + _aggregation_bitfield: &Bitfield, + ) -> Vec { + // TODO: stubbed out. + vec![0, 1] + } +} + +type CrosslinkCommittee = (Vec, usize); +type CrosslinkCommittees = Vec; + +fn merkle_root(_input: &[Hash256]) -> Hash256 { + Hash256::zero() +} diff --git a/eth2/types/src/beacon_state/mod.rs b/eth2/types/src/beacon_state/mod.rs index 71c4d1e877..46f1396912 100644 --- a/eth2/types/src/beacon_state/mod.rs +++ b/eth2/types/src/beacon_state/mod.rs @@ -10,9 +10,12 @@ use rand::RngCore; use serde_derive::Serialize; use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash}; -mod slot_advance; +mod epoch_processing; +mod slot_processing; +mod winning_root; -pub use self::slot_advance::Error as SlotProcessingError; +pub use self::epoch_processing::Error as EpochProcessingError; +pub use self::slot_processing::Error as SlotProcessingError; // Custody will not be added to the specs until Phase 1 (Sharding Phase) so dummy class used. type CustodyChallenge = usize; @@ -53,7 +56,7 @@ pub struct BeaconState { // Recent state pub latest_crosslinks: Vec, pub latest_block_roots: Vec, - pub latest_penalized_exit_balances: Vec, + pub latest_penalized_balances: Vec, pub latest_attestations: Vec, pub batched_block_roots: Vec, @@ -93,7 +96,7 @@ impl Encodable for BeaconState { s.append(&self.finalized_slot); s.append(&self.latest_crosslinks); s.append(&self.latest_block_roots); - s.append(&self.latest_penalized_exit_balances); + s.append(&self.latest_penalized_balances); s.append(&self.latest_attestations); s.append(&self.batched_block_roots); s.append(&self.latest_eth1_data); @@ -126,7 +129,7 @@ impl Decodable for BeaconState { let (finalized_slot, i) = <_>::ssz_decode(bytes, i)?; let (latest_crosslinks, i) = <_>::ssz_decode(bytes, i)?; let (latest_block_roots, i) = <_>::ssz_decode(bytes, i)?; - let (latest_penalized_exit_balances, i) = <_>::ssz_decode(bytes, i)?; + let (latest_penalized_balances, i) = <_>::ssz_decode(bytes, i)?; let (latest_attestations, i) = <_>::ssz_decode(bytes, i)?; let (batched_block_roots, i) = <_>::ssz_decode(bytes, i)?; let (latest_eth1_data, i) = <_>::ssz_decode(bytes, i)?; @@ -157,7 +160,7 @@ impl Decodable for BeaconState { finalized_slot, latest_crosslinks, latest_block_roots, - latest_penalized_exit_balances, + latest_penalized_balances, latest_attestations, batched_block_roots, latest_eth1_data, @@ -194,7 +197,7 @@ impl TreeHash for BeaconState { result.append(&mut self.finalized_slot.hash_tree_root()); result.append(&mut self.latest_crosslinks.hash_tree_root()); result.append(&mut self.latest_block_roots.hash_tree_root()); - result.append(&mut self.latest_penalized_exit_balances.hash_tree_root()); + result.append(&mut self.latest_penalized_balances.hash_tree_root()); result.append(&mut self.latest_attestations.hash_tree_root()); result.append(&mut self.batched_block_roots.hash_tree_root()); result.append(&mut self.latest_eth1_data.hash_tree_root()); @@ -229,7 +232,7 @@ impl TestRandom for BeaconState { finalized_slot: <_>::random_for_test(rng), latest_crosslinks: <_>::random_for_test(rng), latest_block_roots: <_>::random_for_test(rng), - latest_penalized_exit_balances: <_>::random_for_test(rng), + latest_penalized_balances: <_>::random_for_test(rng), latest_attestations: <_>::random_for_test(rng), batched_block_roots: <_>::random_for_test(rng), latest_eth1_data: <_>::random_for_test(rng), diff --git a/eth2/types/src/beacon_state/slot_advance.rs b/eth2/types/src/beacon_state/slot_processing.rs similarity index 100% rename from eth2/types/src/beacon_state/slot_advance.rs rename to eth2/types/src/beacon_state/slot_processing.rs diff --git a/eth2/types/src/beacon_state/winning_root.rs b/eth2/types/src/beacon_state/winning_root.rs new file mode 100644 index 0000000000..04adcb0078 --- /dev/null +++ b/eth2/types/src/beacon_state/winning_root.rs @@ -0,0 +1,92 @@ +use crate::{BeaconState, ChainSpec, Hash256, PendingAttestation}; +use std::collections::HashMap; + +pub enum Error { + UnableToDetermineProducer, + NoBlockRoots, + UnableToGetCrosslinkCommittees, + BaseRewardQuotientIsZero, +} + +#[derive(Clone)] +pub struct WinningRoot { + pub shard_block_root: Hash256, + pub attesting_validator_indices: Vec, + pub total_balance: u64, + pub total_attesting_balance: u64, +} + +impl BeaconState { + pub(crate) fn winning_root( + &self, + shard: u64, + current_epoch_attestations: &[&PendingAttestation], + previous_epoch_attestations: &[&PendingAttestation], + spec: &ChainSpec, + ) -> Option { + let mut attestations = current_epoch_attestations.to_vec(); + attestations.append(&mut previous_epoch_attestations.to_vec()); + + let mut candidates: HashMap = HashMap::new(); + + let mut highest_seen_balance = 0; + + for a in &attestations { + if a.data.shard != shard { + continue; + } + + let shard_block_root = &a.data.shard_block_root; + + if candidates.contains_key(shard_block_root) { + 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 + }); + + let total_balance: u64 = attesting_validator_indices + .iter() + .fold(0, |acc, i| acc + self.get_effective_balance(*i, spec)); + + let total_attesting_balance: u64 = attesting_validator_indices + .iter() + .fold(0, |acc, i| acc + self.get_effective_balance(*i, spec)); + + if total_attesting_balance > highest_seen_balance { + highest_seen_balance = total_attesting_balance; + } + + let candidate_root = WinningRoot { + shard_block_root: shard_block_root.clone(), + attesting_validator_indices, + total_attesting_balance, + total_balance, + }; + + candidates.insert(*shard_block_root, candidate_root); + } + + let winner = candidates + .iter() + .filter_map(|(_hash, candidate)| { + if candidate.total_attesting_balance == highest_seen_balance { + Some(candidate) + } else { + None + } + }) + .min_by_key(|candidate| candidate.shard_block_root); + + match winner { + Some(winner) => Some(winner.clone()), + None => None, + } + } +}