From 71d95ee9db698aa558dd78780202db7128de8ac1 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 18 Mar 2019 18:08:53 +1100 Subject: [PATCH 01/10] Add new field to test_harness YAML, remove prints --- beacon_node/beacon_chain/src/beacon_chain.rs | 5 ----- .../beacon_chain/test_harness/specs/validator_registry.yaml | 1 + beacon_node/beacon_chain/test_harness/src/test_case.rs | 4 ++++ .../beacon_chain/test_harness/src/test_case/config.rs | 3 +++ 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index bf87adf10..01787f95b 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -596,7 +596,6 @@ where // Transition the parent state to the present slot. let mut state = parent_state; - println!("parent process state: {:?}", state.latest_block_header); let previous_block_header = parent_block.into_header(); for _ in state.slot.as_u64()..present_slot.as_u64() { if let Err(e) = per_slot_processing(&mut state, &previous_block_header, &self.spec) { @@ -614,8 +613,6 @@ where )); } - println!("process state: {:?}", state.latest_block_header); - let state_root = state.canonical_root(); if block.state_root != state_root { @@ -706,8 +703,6 @@ where per_block_processing_without_verifying_block_signature(&mut state, &block, &self.spec)?; - println!("produce state: {:?}", state.latest_block_header); - let state_root = state.canonical_root(); block.state_root = state_root; diff --git a/beacon_node/beacon_chain/test_harness/specs/validator_registry.yaml b/beacon_node/beacon_chain/test_harness/specs/validator_registry.yaml index aea7dcf31..0c4f5004b 100644 --- a/beacon_node/beacon_chain/test_harness/specs/validator_registry.yaml +++ b/beacon_node/beacon_chain/test_harness/specs/validator_registry.yaml @@ -9,6 +9,7 @@ test_cases: deposits_for_chain_start: 1000 num_slots: 64 skip_slots: [2, 3] + persistent_committee_period: 0 deposits: # At slot 1, create a new validator deposit of 5 ETH. - slot: 1 diff --git a/beacon_node/beacon_chain/test_harness/src/test_case.rs b/beacon_node/beacon_chain/test_harness/src/test_case.rs index 1361127a1..f65b45505 100644 --- a/beacon_node/beacon_chain/test_harness/src/test_case.rs +++ b/beacon_node/beacon_chain/test_harness/src/test_case.rs @@ -62,6 +62,10 @@ impl TestCase { spec.slots_per_epoch = n; } + if let Some(n) = self.config.persistent_committee_period { + spec.persistent_committee_period = n; + } + spec } diff --git a/beacon_node/beacon_chain/test_harness/src/test_case/config.rs b/beacon_node/beacon_chain/test_harness/src/test_case/config.rs index f336b9d53..12d5da2d7 100644 --- a/beacon_node/beacon_chain/test_harness/src/test_case/config.rs +++ b/beacon_node/beacon_chain/test_harness/src/test_case/config.rs @@ -20,6 +20,8 @@ pub struct Config { pub deposits_for_chain_start: usize, /// Number of slots in an epoch. pub slots_per_epoch: Option, + /// Affects the number of epochs a validator must be active before they can withdraw. + pub persistent_committee_period: Option, /// Number of slots to build before ending execution. pub num_slots: u64, /// Number of slots that should be skipped due to inactive validator. @@ -45,6 +47,7 @@ impl Config { deposits_for_chain_start: as_usize(&yaml, "deposits_for_chain_start") .expect("Must specify validator count"), slots_per_epoch: as_u64(&yaml, "slots_per_epoch"), + persistent_committee_period: as_u64(&yaml, "persistent_committee_period"), num_slots: as_u64(&yaml, "num_slots").expect("Must specify `config.num_slots`"), skip_slots: as_vec_u64(yaml, "skip_slots"), deposits: parse_deposits(&yaml), From 7503f31ddc117b91debcfacd8d7b3e517a86899e Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 18 Mar 2019 18:09:31 +1100 Subject: [PATCH 02/10] Fix bug with per-block processing --- eth2/state_processing/src/per_block_processing.rs | 5 ++++- eth2/state_processing/src/per_slot_processing.rs | 3 --- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/eth2/state_processing/src/per_block_processing.rs b/eth2/state_processing/src/per_block_processing.rs index 78cf927f5..14c72c53b 100644 --- a/eth2/state_processing/src/per_block_processing.rs +++ b/eth2/state_processing/src/per_block_processing.rs @@ -100,8 +100,11 @@ pub fn process_block_header( ) -> Result<(), Error> { verify!(block.slot == state.slot, Invalid::StateSlotMismatch); + // NOTE: this is not to spec. I think spec is broken. See: + // + // https://github.com/ethereum/eth2.0-specs/issues/797 verify!( - block.previous_block_root.as_bytes() == &state.latest_block_header.hash_tree_root()[..], + block.previous_block_root == *state.get_block_root(state.slot - 1, spec)?, Invalid::ParentBlockRootMismatch ); diff --git a/eth2/state_processing/src/per_slot_processing.rs b/eth2/state_processing/src/per_slot_processing.rs index a90c5b408..8f02b70e3 100644 --- a/eth2/state_processing/src/per_slot_processing.rs +++ b/eth2/state_processing/src/per_slot_processing.rs @@ -25,9 +25,6 @@ pub fn per_slot_processing( state.slot += 1; - let latest_block_root = Hash256::from_slice(&state.latest_block_header.hash_tree_root()[..]); - state.set_block_root(state.slot - 1, latest_block_root, spec)?; - Ok(()) } From 1028acf3f184bdf4982557ec65f0fa52d9eb92b6 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 18 Mar 2019 21:34:42 +1100 Subject: [PATCH 03/10] Move state trans fns into state_processing --- .../src/common/exit_validator.rs | 22 + eth2/state_processing/src/common/mod.rs | 7 + .../src/common/slash_validator.rs | 62 + .../src/common/verify_bitfield.rs} | 2 +- .../state_processing/src/get_genesis_state.rs | 3 +- eth2/state_processing/src/lib.rs | 1 + .../src/per_block_processing.rs | 5 +- .../validate_attestation.rs | 2 +- .../verify_slashable_attestation.rs | 2 +- .../src/per_epoch_processing.rs | 31 +- .../get_attestation_participants.rs | 3 +- .../per_epoch_processing/process_ejections.rs | 28 + .../process_exit_queue.rs | 42 + .../per_epoch_processing/process_slashings.rs | 36 + .../process_validator_registry.rs | 6 +- .../update_validator_registry.rs | 51 + eth2/types/src/beacon_state.rs | 1107 +++++++---------- 17 files changed, 741 insertions(+), 669 deletions(-) create mode 100644 eth2/state_processing/src/common/exit_validator.rs create mode 100644 eth2/state_processing/src/common/mod.rs create mode 100644 eth2/state_processing/src/common/slash_validator.rs rename eth2/{types/src/beacon_state/helpers.rs => state_processing/src/common/verify_bitfield.rs} (96%) create mode 100644 eth2/state_processing/src/per_epoch_processing/process_ejections.rs create mode 100644 eth2/state_processing/src/per_epoch_processing/process_exit_queue.rs create mode 100644 eth2/state_processing/src/per_epoch_processing/process_slashings.rs create mode 100644 eth2/state_processing/src/per_epoch_processing/update_validator_registry.rs diff --git a/eth2/state_processing/src/common/exit_validator.rs b/eth2/state_processing/src/common/exit_validator.rs new file mode 100644 index 000000000..8ab530b18 --- /dev/null +++ b/eth2/state_processing/src/common/exit_validator.rs @@ -0,0 +1,22 @@ +use types::{BeaconStateError as Error, *}; + +/// Exit the validator of the given `index`. +/// +/// Spec v0.5.0 +pub fn exit_validator( + state: &mut BeaconState, + validator_index: usize, + spec: &ChainSpec, +) -> Result<(), Error> { + if validator_index >= state.validator_registry.len() { + return Err(Error::UnknownValidator); + } + + let delayed_epoch = state.get_delayed_activation_exit_epoch(state.current_epoch(spec), spec); + + if state.validator_registry[validator_index].exit_epoch > delayed_epoch { + state.validator_registry[validator_index].exit_epoch = delayed_epoch; + } + + Ok(()) +} diff --git a/eth2/state_processing/src/common/mod.rs b/eth2/state_processing/src/common/mod.rs new file mode 100644 index 000000000..49898d10f --- /dev/null +++ b/eth2/state_processing/src/common/mod.rs @@ -0,0 +1,7 @@ +mod exit_validator; +mod slash_validator; +mod verify_bitfield; + +pub use exit_validator::exit_validator; +pub use slash_validator::slash_validator; +pub use verify_bitfield::verify_bitfield_length; diff --git a/eth2/state_processing/src/common/slash_validator.rs b/eth2/state_processing/src/common/slash_validator.rs new file mode 100644 index 000000000..9be87b978 --- /dev/null +++ b/eth2/state_processing/src/common/slash_validator.rs @@ -0,0 +1,62 @@ +use crate::common::exit_validator; +use types::{BeaconStateError as Error, *}; + +/// Slash the validator with index ``index``. +/// +/// Spec v0.5.0 +pub fn slash_validator( + state: &mut BeaconState, + validator_index: usize, + spec: &ChainSpec, +) -> Result<(), Error> { + let current_epoch = state.current_epoch(spec); + + if (validator_index >= state.validator_registry.len()) + | (validator_index >= state.validator_balances.len()) + { + return Err(BeaconStateError::UnknownValidator); + } + + let validator = &state.validator_registry[validator_index]; + + let effective_balance = state.get_effective_balance(validator_index, spec)?; + + // A validator that is withdrawn cannot be slashed. + // + // This constraint will be lifted in Phase 0. + if state.slot + >= validator + .withdrawable_epoch + .start_slot(spec.slots_per_epoch) + { + return Err(Error::ValidatorIsWithdrawable); + } + + exit_validator(state, validator_index, spec)?; + + state.set_slashed_balance( + current_epoch, + state.get_slashed_balance(current_epoch, spec)? + effective_balance, + spec, + )?; + + let whistleblower_index = + state.get_beacon_proposer_index(state.slot, RelativeEpoch::Current, spec)?; + let whistleblower_reward = effective_balance / spec.whistleblower_reward_quotient; + + safe_add_assign!( + state.validator_balances[whistleblower_index as usize], + whistleblower_reward + ); + safe_sub_assign!( + state.validator_balances[validator_index], + whistleblower_reward + ); + + state.validator_registry[validator_index].slashed = true; + + state.validator_registry[validator_index].withdrawable_epoch = + current_epoch + Epoch::from(spec.latest_slashed_exit_length); + + Ok(()) +} diff --git a/eth2/types/src/beacon_state/helpers.rs b/eth2/state_processing/src/common/verify_bitfield.rs similarity index 96% rename from eth2/types/src/beacon_state/helpers.rs rename to eth2/state_processing/src/common/verify_bitfield.rs index adae7bab4..8ff5c96ca 100644 --- a/eth2/types/src/beacon_state/helpers.rs +++ b/eth2/state_processing/src/common/verify_bitfield.rs @@ -1,4 +1,4 @@ -use crate::*; +use types::*; /// Verify ``bitfield`` against the ``committee_size``. /// diff --git a/eth2/state_processing/src/get_genesis_state.rs b/eth2/state_processing/src/get_genesis_state.rs index 3c6612349..bfcf82216 100644 --- a/eth2/state_processing/src/get_genesis_state.rs +++ b/eth2/state_processing/src/get_genesis_state.rs @@ -37,8 +37,7 @@ pub fn get_genesis_state( .get_active_validator_indices(spec.genesis_epoch, spec)? .to_vec(); let genesis_active_index_root = Hash256::from_slice(&active_validator_indices.hash_tree_root()); - state.latest_active_index_roots = - vec![genesis_active_index_root; spec.latest_active_index_roots_length as usize]; + state.fill_active_index_roots_with(genesis_active_index_root, spec); // Generate the current shuffling seed. state.current_shuffling_seed = state.generate_seed(spec.genesis_epoch, spec)?; diff --git a/eth2/state_processing/src/lib.rs b/eth2/state_processing/src/lib.rs index 78dc7270d..6757b5dbd 100644 --- a/eth2/state_processing/src/lib.rs +++ b/eth2/state_processing/src/lib.rs @@ -1,6 +1,7 @@ #[macro_use] mod macros; +pub mod common; pub mod get_genesis_state; pub mod per_block_processing; pub mod per_epoch_processing; diff --git a/eth2/state_processing/src/per_block_processing.rs b/eth2/state_processing/src/per_block_processing.rs index 14c72c53b..33f953b71 100644 --- a/eth2/state_processing/src/per_block_processing.rs +++ b/eth2/state_processing/src/per_block_processing.rs @@ -1,4 +1,5 @@ use self::verify_proposer_slashing::verify_proposer_slashing; +use crate::common::slash_validator; use errors::{BlockInvalid as Invalid, BlockProcessingError as Error, IntoWithIndex}; use rayon::prelude::*; use ssz::{SignedRoot, TreeHash}; @@ -222,7 +223,7 @@ pub fn process_proposer_slashings( // Update the state. for proposer_slashing in proposer_slashings { - state.slash_validator(proposer_slashing.proposer_index as usize, spec)?; + slash_validator(state, proposer_slashing.proposer_index as usize, spec)?; } Ok(()) @@ -279,7 +280,7 @@ pub fn process_attester_slashings( .map_err(|e| e.into_with_index(i))?; for i in slashable_indices { - state.slash_validator(i as usize, spec)?; + slash_validator(state, i as usize, spec)?; } } diff --git a/eth2/state_processing/src/per_block_processing/validate_attestation.rs b/eth2/state_processing/src/per_block_processing/validate_attestation.rs index 272eeb18b..113dbc4ce 100644 --- a/eth2/state_processing/src/per_block_processing/validate_attestation.rs +++ b/eth2/state_processing/src/per_block_processing/validate_attestation.rs @@ -1,6 +1,6 @@ use super::errors::{AttestationInvalid as Invalid, AttestationValidationError as Error}; +use crate::common::verify_bitfield_length; use ssz::TreeHash; -use types::beacon_state::helpers::*; use types::*; /// Indicates if an `Attestation` is valid to be included in a block in the current epoch of the diff --git a/eth2/state_processing/src/per_block_processing/verify_slashable_attestation.rs b/eth2/state_processing/src/per_block_processing/verify_slashable_attestation.rs index aa9a32196..d3ab5e398 100644 --- a/eth2/state_processing/src/per_block_processing/verify_slashable_attestation.rs +++ b/eth2/state_processing/src/per_block_processing/verify_slashable_attestation.rs @@ -1,8 +1,8 @@ use super::errors::{ SlashableAttestationInvalid as Invalid, SlashableAttestationValidationError as Error, }; +use crate::common::verify_bitfield_length; use ssz::TreeHash; -use types::beacon_state::helpers::verify_bitfield_length; use types::*; /// Indicates if a `SlashableAttestation` is valid to be included in a block in the current epoch of the given diff --git a/eth2/state_processing/src/per_epoch_processing.rs b/eth2/state_processing/src/per_epoch_processing.rs index d1bb4269a..97a0e9987 100644 --- a/eth2/state_processing/src/per_epoch_processing.rs +++ b/eth2/state_processing/src/per_epoch_processing.rs @@ -1,5 +1,8 @@ use errors::EpochProcessingError as Error; use integer_sqrt::IntegerSquareRoot; +use process_ejections::process_ejections; +use process_exit_queue::process_exit_queue; +use process_slashings::process_slashings; use process_validator_registry::process_validator_registry; use rayon::prelude::*; use ssz::TreeHash; @@ -11,8 +14,12 @@ use winning_root::{winning_root, WinningRoot}; pub mod errors; pub mod get_attestation_participants; pub mod inclusion_distance; +pub mod process_ejections; +pub mod process_exit_queue; +pub mod process_slashings; pub mod process_validator_registry; pub mod tests; +pub mod update_validator_registry; pub mod validator_statuses; pub mod winning_root; @@ -45,14 +52,16 @@ pub fn per_epoch_processing(state: &mut BeaconState, spec: &ChainSpec) -> Result process_rewards_and_penalities(state, &mut statuses, &winning_root_for_shards, spec)?; // Ejections - state.process_ejections(spec)?; + process_ejections(state, spec)?; // Validator Registry process_validator_registry(state, spec)?; + process_slashings(state, spec)?; + process_exit_queue(state, spec); // Final updates update_active_tree_index_roots(state, spec)?; - update_latest_slashed_balances(state, spec); + update_latest_slashed_balances(state, spec)?; clean_attestations(state); // Rotate the epoch caches to suit the epoch transition. @@ -451,9 +460,7 @@ pub fn update_active_tree_index_roots( ) .hash_tree_root(); - state.latest_active_index_roots[(next_epoch.as_usize() - + spec.activation_exit_delay as usize) - % spec.latest_active_index_roots_length] = Hash256::from_slice(&active_tree_root[..]); + state.set_active_index_root(next_epoch, Hash256::from_slice(&active_tree_root[..]), spec)?; Ok(()) } @@ -461,12 +468,20 @@ pub fn update_active_tree_index_roots( /// Advances the state's `latest_slashed_balances` field. /// /// Spec v0.4.0 -pub fn update_latest_slashed_balances(state: &mut BeaconState, spec: &ChainSpec) { +pub fn update_latest_slashed_balances( + state: &mut BeaconState, + spec: &ChainSpec, +) -> Result<(), Error> { let current_epoch = state.current_epoch(spec); let next_epoch = state.next_epoch(spec); - 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.set_slashed_balance( + next_epoch, + state.get_slashed_balance(current_epoch, spec)?, + spec, + )?; + + Ok(()) } /// Removes all pending attestations from the previous epoch. diff --git a/eth2/state_processing/src/per_epoch_processing/get_attestation_participants.rs b/eth2/state_processing/src/per_epoch_processing/get_attestation_participants.rs index d822e434d..3e52776b1 100644 --- a/eth2/state_processing/src/per_epoch_processing/get_attestation_participants.rs +++ b/eth2/state_processing/src/per_epoch_processing/get_attestation_participants.rs @@ -1,4 +1,5 @@ -use types::{beacon_state::helpers::verify_bitfield_length, *}; +use crate::common::verify_bitfield_length; +use types::*; /// Returns validator indices which participated in the attestation. /// diff --git a/eth2/state_processing/src/per_epoch_processing/process_ejections.rs b/eth2/state_processing/src/per_epoch_processing/process_ejections.rs new file mode 100644 index 000000000..27dd37479 --- /dev/null +++ b/eth2/state_processing/src/per_epoch_processing/process_ejections.rs @@ -0,0 +1,28 @@ +use crate::common::exit_validator; +use types::{BeaconStateError as Error, *}; + +/// Iterate through the validator registry and eject active validators with balance below +/// ``EJECTION_BALANCE``. +/// +/// Spec v0.5.0 +pub fn process_ejections(state: &mut BeaconState, spec: &ChainSpec) -> Result<(), Error> { + // There is an awkward double (triple?) loop here because we can't loop across the borrowed + // active validator indices and mutate state in the one loop. + let exitable: Vec = state + .get_active_validator_indices(state.current_epoch(spec), spec)? + .iter() + .filter_map(|&i| { + if state.validator_balances[i as usize] < spec.ejection_balance { + Some(i) + } else { + None + } + }) + .collect(); + + for validator_index in exitable { + exit_validator(state, validator_index, spec)? + } + + Ok(()) +} diff --git a/eth2/state_processing/src/per_epoch_processing/process_exit_queue.rs b/eth2/state_processing/src/per_epoch_processing/process_exit_queue.rs new file mode 100644 index 000000000..f672c97be --- /dev/null +++ b/eth2/state_processing/src/per_epoch_processing/process_exit_queue.rs @@ -0,0 +1,42 @@ +use types::*; + +/// Process the exit queue. +/// +/// Spec v0.5.0 +pub fn process_exit_queue(state: &mut BeaconState, spec: &ChainSpec) { + let current_epoch = state.current_epoch(spec); + + let eligible = |index: usize| { + let validator = &state.validator_registry[index]; + + if validator.withdrawable_epoch != spec.far_future_epoch { + false + } else { + current_epoch >= validator.exit_epoch + spec.min_validator_withdrawability_delay + } + }; + + let mut eligable_indices: Vec = (0..state.validator_registry.len()) + .filter(|i| eligible(*i)) + .collect(); + eligable_indices.sort_by_key(|i| state.validator_registry[*i].exit_epoch); + + for (withdrawn_so_far, index) in eligable_indices.iter().enumerate() { + if withdrawn_so_far as u64 >= spec.max_exit_dequeues_per_epoch { + break; + } + prepare_validator_for_withdrawal(state, *index, spec); + } +} + +/// Initiate an exit for the validator of the given `index`. +/// +/// Spec v0.5.0 +fn prepare_validator_for_withdrawal( + state: &mut BeaconState, + validator_index: usize, + spec: &ChainSpec, +) { + state.validator_registry[validator_index].withdrawable_epoch = + state.current_epoch(spec) + spec.min_validator_withdrawability_delay; +} diff --git a/eth2/state_processing/src/per_epoch_processing/process_slashings.rs b/eth2/state_processing/src/per_epoch_processing/process_slashings.rs new file mode 100644 index 000000000..b14a9ee37 --- /dev/null +++ b/eth2/state_processing/src/per_epoch_processing/process_slashings.rs @@ -0,0 +1,36 @@ +use types::{BeaconStateError as Error, *}; + +/// Process slashings. +/// +/// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. +/// +/// Spec v0.4.0 +pub fn process_slashings(state: &mut BeaconState, spec: &ChainSpec) -> Result<(), Error> { + let current_epoch = state.current_epoch(spec); + let active_validator_indices = state.get_active_validator_indices(current_epoch, spec)?; + let total_balance = state.get_total_balance(&active_validator_indices[..], spec)?; + + for (index, validator) in state.validator_registry.iter().enumerate() { + if validator.slashed + && (current_epoch + == validator.withdrawable_epoch - Epoch::from(spec.latest_slashed_exit_length / 2)) + { + // TODO: check the following two lines are correct. + let total_at_start = state.get_slashed_balance(current_epoch + 1, spec)?; + let total_at_end = state.get_slashed_balance(current_epoch, spec)?; + + let total_penalities = total_at_end.saturating_sub(total_at_start); + + let effective_balance = state.get_effective_balance(index, spec)?; + let penalty = std::cmp::max( + effective_balance * std::cmp::min(total_penalities * 3, total_balance) + / total_balance, + effective_balance / spec.min_penalty_quotient, + ); + + safe_sub_assign!(state.validator_balances[index], penalty); + } + } + + Ok(()) +} diff --git a/eth2/state_processing/src/per_epoch_processing/process_validator_registry.rs b/eth2/state_processing/src/per_epoch_processing/process_validator_registry.rs index 26ebd60b3..2eb39711d 100644 --- a/eth2/state_processing/src/per_epoch_processing/process_validator_registry.rs +++ b/eth2/state_processing/src/per_epoch_processing/process_validator_registry.rs @@ -1,3 +1,4 @@ +use super::update_validator_registry::update_validator_registry; use super::Error; use types::*; @@ -14,7 +15,7 @@ pub fn process_validator_registry(state: &mut BeaconState, spec: &ChainSpec) -> state.previous_shuffling_seed = state.current_shuffling_seed; if should_update_validator_registry(state, spec)? { - state.update_validator_registry(spec)?; + update_validator_registry(state, spec)?; state.current_shuffling_epoch = next_epoch; state.current_shuffling_start_shard = (state.current_shuffling_start_shard @@ -37,9 +38,6 @@ pub fn process_validator_registry(state: &mut BeaconState, spec: &ChainSpec) -> } } - state.process_slashings(spec)?; - state.process_exit_queue(spec); - Ok(()) } diff --git a/eth2/state_processing/src/per_epoch_processing/update_validator_registry.rs b/eth2/state_processing/src/per_epoch_processing/update_validator_registry.rs new file mode 100644 index 000000000..8b612c346 --- /dev/null +++ b/eth2/state_processing/src/per_epoch_processing/update_validator_registry.rs @@ -0,0 +1,51 @@ +use crate::common::exit_validator; +use types::{BeaconStateError as Error, *}; + +/// Update validator registry, activating/exiting validators if possible. +/// +/// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. +/// +/// Spec v0.4.0 +pub fn update_validator_registry(state: &mut BeaconState, spec: &ChainSpec) -> Result<(), Error> { + let current_epoch = state.current_epoch(spec); + let active_validator_indices = state.get_active_validator_indices(current_epoch, spec)?; + let total_balance = state.get_total_balance(&active_validator_indices[..], spec)?; + + let max_balance_churn = std::cmp::max( + spec.max_deposit_amount, + total_balance / (2 * spec.max_balance_churn_quotient), + ); + + let mut balance_churn = 0; + for index in 0..state.validator_registry.len() { + let validator = &state.validator_registry[index]; + + if (validator.activation_epoch == spec.far_future_epoch) + & (state.validator_balances[index] == spec.max_deposit_amount) + { + balance_churn += state.get_effective_balance(index, spec)?; + if balance_churn > max_balance_churn { + break; + } + state.activate_validator(index, false, spec); + } + } + + let mut balance_churn = 0; + for index in 0..state.validator_registry.len() { + let validator = &state.validator_registry[index]; + + if (validator.exit_epoch == spec.far_future_epoch) & (validator.initiated_exit) { + balance_churn += state.get_effective_balance(index, spec)?; + if balance_churn > max_balance_churn { + break; + } + + exit_validator(state, index, spec)?; + } + } + + state.validator_registry_update_epoch = current_epoch; + + Ok(()) +} diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index 1b2424774..30f95c02c 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -10,7 +10,6 @@ use ssz_derive::{Decode, Encode, TreeHash}; use test_random_derive::TestRandom; mod epoch_cache; -pub mod helpers; mod pubkey_cache; mod tests; @@ -19,17 +18,13 @@ pub const CACHED_EPOCHS: usize = 4; #[derive(Debug, PartialEq)] pub enum Error { EpochOutOfBounds, - /// The supplied shard is unknown. It may be larger than the maximum shard count, or not in a - /// committee for the given slot. SlotOutOfBounds, ShardOutOfBounds, - UnableToShuffle, UnknownValidator, + UnableToDetermineProducer, InvalidBitfield, ValidatorIsWithdrawable, InsufficientRandaoMixes, - NoValidators, - UnableToDetermineProducer, InsufficientBlockRoots, InsufficientIndexRoots, InsufficientAttestations, @@ -37,27 +32,16 @@ pub enum Error { InsufficientSlashedBalances, InsufficientStateRoots, NoCommitteeForShard, - EpochCacheUninitialized(RelativeEpoch), PubkeyCacheInconsistent, PubkeyCacheIncomplete { cache_len: usize, registry_len: usize, }, + EpochCacheUninitialized(RelativeEpoch), RelativeEpochError(RelativeEpochError), EpochCacheError(EpochCacheError), } -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); - }; -} - /// The state of the `BeaconChain` at some slot. /// /// Spec v0.5.0 @@ -95,10 +79,10 @@ pub struct BeaconState { // Recent state pub latest_crosslinks: Vec, - pub latest_block_roots: Vec, - pub latest_state_roots: Vec, - pub latest_active_index_roots: Vec, - pub latest_slashed_balances: Vec, + latest_block_roots: Vec, + latest_state_roots: Vec, + latest_active_index_roots: Vec, + latest_slashed_balances: Vec, pub latest_block_header: BeaconBlockHeader, pub historical_roots: Vec, @@ -209,6 +193,458 @@ impl BeaconState { Hash256::from_slice(&self.hash_tree_root()[..]) } + /// If a validator pubkey exists in the validator registry, returns `Some(i)`, otherwise + /// returns `None`. + /// + /// Requires a fully up-to-date `pubkey_cache`, returns an error if this is not the case. + pub fn get_validator_index(&self, pubkey: &PublicKey) -> Result, Error> { + if self.pubkey_cache.len() == self.validator_registry.len() { + Ok(self.pubkey_cache.get(pubkey)) + } else { + Err(Error::PubkeyCacheIncomplete { + cache_len: self.pubkey_cache.len(), + registry_len: self.validator_registry.len(), + }) + } + } + + /// The epoch corresponding to `self.slot`. + /// + /// Spec v0.5.0 + pub fn current_epoch(&self, spec: &ChainSpec) -> Epoch { + self.slot.epoch(spec.slots_per_epoch) + } + + /// The epoch prior to `self.current_epoch()`. + /// + /// If the current epoch is the genesis epoch, the genesis_epoch is returned. + /// + /// Spec v0.5.0 + pub fn previous_epoch(&self, spec: &ChainSpec) -> Epoch { + self.current_epoch(&spec) - 1 + } + + /// The epoch following `self.current_epoch()`. + /// + /// Spec v0.5.0 + pub fn next_epoch(&self, spec: &ChainSpec) -> Epoch { + self.current_epoch(spec) + 1 + } + + /// Returns the active validator indices for the given epoch, assuming there is no validator + /// registry update in the next epoch. + /// + /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. + /// + /// Spec v0.5.0 + pub fn get_active_validator_indices( + &self, + epoch: Epoch, + spec: &ChainSpec, + ) -> Result<&[usize], Error> { + // If the slot is in the next epoch, assume there was no validator registry update. + let relative_epoch = + match RelativeEpoch::from_epoch(self.slot.epoch(spec.slots_per_epoch), epoch) { + Err(RelativeEpochError::AmbiguiousNextEpoch) => { + Ok(RelativeEpoch::NextWithoutRegistryChange) + } + e => e, + }?; + + let cache = self.cache(relative_epoch, spec)?; + + Ok(&cache.active_validator_indices) + } + + /// Returns the crosslink committees for some slot. + /// + /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. + /// + /// Spec v0.5.0 + pub fn get_crosslink_committees_at_slot( + &self, + slot: Slot, + spec: &ChainSpec, + ) -> Result<&Vec, Error> { + // If the slot is in the next epoch, assume there was no validator registry update. + let relative_epoch = match RelativeEpoch::from_slot(self.slot, slot, spec) { + Err(RelativeEpochError::AmbiguiousNextEpoch) => { + Ok(RelativeEpoch::NextWithoutRegistryChange) + } + e => e, + }?; + + let cache = self.cache(relative_epoch, spec)?; + + Ok(cache + .get_crosslink_committees_at_slot(slot, spec) + .ok_or_else(|| Error::SlotOutOfBounds)?) + } + + /// Returns the crosslink committees for some shard in an epoch. + /// + /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. + /// + /// Spec v0.5.0 + pub fn get_crosslink_committee_for_shard( + &self, + epoch: Epoch, + shard: Shard, + spec: &ChainSpec, + ) -> Result<&CrosslinkCommittee, Error> { + // If the slot is in the next epoch, assume there was no validator registry update. + let relative_epoch = match RelativeEpoch::from_epoch(self.current_epoch(spec), epoch) { + Err(RelativeEpochError::AmbiguiousNextEpoch) => { + Ok(RelativeEpoch::NextWithoutRegistryChange) + } + e => e, + }?; + + let cache = self.cache(relative_epoch, spec)?; + + Ok(cache + .get_crosslink_committee_for_shard(shard, spec) + .ok_or_else(|| Error::NoCommitteeForShard)?) + } + + /// 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. + /// + /// Spec v0.5.0 + pub fn get_beacon_proposer_index( + &self, + slot: Slot, + relative_epoch: RelativeEpoch, + spec: &ChainSpec, + ) -> Result { + let cache = self.cache(relative_epoch, spec)?; + + let committees = cache + .get_crosslink_committees_at_slot(slot, spec) + .ok_or_else(|| Error::SlotOutOfBounds)?; + + let epoch = slot.epoch(spec.slots_per_epoch); + + committees + .first() + .ok_or(Error::UnableToDetermineProducer) + .and_then(|first| { + let index = epoch + .as_usize() + .checked_rem(first.committee.len()) + .ok_or(Error::UnableToDetermineProducer)?; + Ok(first.committee[index]) + }) + } + + /// Safely obtains the index for latest block roots, given some `slot`. + /// + /// Spec v0.5.0 + fn get_latest_block_roots_index(&self, slot: Slot, spec: &ChainSpec) -> Result { + if (slot < self.slot) && (self.slot <= slot + spec.slots_per_historical_root as u64) { + let i = slot.as_usize() % spec.slots_per_historical_root; + if i >= self.latest_block_roots.len() { + Err(Error::InsufficientStateRoots) + } else { + Ok(i) + } + } else { + Err(BeaconStateError::SlotOutOfBounds) + } + } + + /// Return the block root at a recent `slot`. + /// + /// Spec v0.5.0 + pub fn get_block_root( + &self, + slot: Slot, + spec: &ChainSpec, + ) -> Result<&Hash256, BeaconStateError> { + let i = self.get_latest_block_roots_index(slot, spec)?; + Ok(&self.latest_block_roots[i]) + } + + /// Sets the block root for some given slot. + /// + /// Spec v0.5.0 + pub fn set_block_root( + &mut self, + slot: Slot, + block_root: Hash256, + spec: &ChainSpec, + ) -> Result<(), BeaconStateError> { + let i = self.get_latest_block_roots_index(slot, spec)?; + Ok(self.latest_block_roots[i] = block_root) + } + + /// XOR-assigns the existing `epoch` randao mix with the hash of the `signature`. + /// + /// # Errors: + /// + /// See `Self::get_randao_mix`. + /// + /// Spec v0.5.0 + pub fn update_randao_mix( + &mut self, + epoch: Epoch, + signature: &Signature, + spec: &ChainSpec, + ) -> Result<(), Error> { + let i = epoch.as_usize() % spec.latest_randao_mixes_length; + + let signature_hash = Hash256::from_slice(&hash(&ssz_encode(signature))); + + self.latest_randao_mixes[i] = *self.get_randao_mix(epoch, spec)? ^ signature_hash; + + Ok(()) + } + + /// Return the randao mix at a recent ``epoch``. + /// + /// # Errors: + /// - `InsufficientRandaoMixes` if `self.latest_randao_mixes` is shorter than + /// `spec.latest_randao_mixes_length`. + /// - `EpochOutOfBounds` if the state no longer stores randao mixes for the given `epoch`. + /// + /// Spec v0.5.0 + pub fn get_randao_mix(&self, epoch: Epoch, spec: &ChainSpec) -> Result<&Hash256, Error> { + let current_epoch = self.current_epoch(spec); + + if (current_epoch - (spec.latest_randao_mixes_length as u64) < epoch) + & (epoch <= current_epoch) + { + self.latest_randao_mixes + .get(epoch.as_usize() % spec.latest_randao_mixes_length) + .ok_or_else(|| Error::InsufficientRandaoMixes) + } else { + Err(Error::EpochOutOfBounds) + } + } + + /// Safely obtains the index for `latest_active_index_roots`, given some `epoch`. + /// + /// Spec v0.5.0 + fn get_active_index_root_index(&self, epoch: Epoch, spec: &ChainSpec) -> Result { + let current_epoch = self.current_epoch(spec); + + if (current_epoch - spec.latest_active_index_roots_length as u64 + + spec.activation_exit_delay + < epoch) + & (epoch <= current_epoch + spec.activation_exit_delay) + { + let i = epoch.as_usize() % spec.latest_active_index_roots_length; + if i < self.latest_active_index_roots.len() { + Ok(i) + } else { + Err(Error::InsufficientIndexRoots) + } + } else { + Err(Error::EpochOutOfBounds) + } + } + + /// Return the `active_index_root` at a recent `epoch`. + /// + /// Spec v0.5.0 + pub fn get_active_index_root(&self, epoch: Epoch, spec: &ChainSpec) -> Result { + let i = self.get_active_index_root_index(epoch, spec)?; + Ok(self.latest_active_index_roots[i]) + } + + /// Set the `active_index_root` at a recent `epoch`. + /// + /// Spec v0.5.0 + pub fn set_active_index_root( + &mut self, + epoch: Epoch, + index_root: Hash256, + spec: &ChainSpec, + ) -> Result<(), Error> { + let i = self.get_active_index_root_index(epoch, spec)?; + Ok(self.latest_active_index_roots[i] = index_root) + } + + /// Replace `active_index_roots` with clones of `index_root`. + /// + /// Spec v0.5.0 + pub fn fill_active_index_roots_with(&mut self, index_root: Hash256, spec: &ChainSpec) { + self.latest_active_index_roots = + vec![index_root; spec.latest_active_index_roots_length as usize] + } + + /// Safely obtains the index for latest state roots, given some `slot`. + /// + /// Spec v0.5.0 + fn get_latest_state_roots_index(&self, slot: Slot, spec: &ChainSpec) -> Result { + if (slot < self.slot) && (self.slot <= slot + spec.slots_per_historical_root as u64) { + let i = slot.as_usize() % spec.slots_per_historical_root; + if i >= self.latest_state_roots.len() { + Err(Error::InsufficientStateRoots) + } else { + Ok(i) + } + } else { + Err(BeaconStateError::SlotOutOfBounds) + } + } + + /// Gets the state root for some slot. + /// + /// Spec v0.5.0 + pub fn get_state_root(&mut self, slot: Slot, spec: &ChainSpec) -> Result<&Hash256, Error> { + let i = self.get_latest_state_roots_index(slot, spec)?; + Ok(&self.latest_state_roots[i]) + } + + /// Sets the latest state root for slot. + /// + /// Spec v0.5.0 + pub fn set_state_root( + &mut self, + slot: Slot, + state_root: Hash256, + spec: &ChainSpec, + ) -> Result<(), Error> { + let i = self.get_latest_state_roots_index(slot, spec)?; + Ok(self.latest_state_roots[i] = state_root) + } + + /// Safely obtains the index for `latest_slashed_balances`, given some `epoch`. + /// + /// Spec v0.5.0 + fn get_slashed_balance_index(&self, epoch: Epoch, spec: &ChainSpec) -> Result { + let i = epoch.as_usize() % spec.latest_slashed_exit_length; + + // NOTE: the validity of the epoch is not checked. It is not in the spec but it's probably + // useful to have. + if i < self.latest_slashed_balances.len() { + Ok(i) + } else { + Err(Error::InsufficientSlashedBalances) + } + } + + /// Gets the total slashed balances for some epoch. + /// + /// Spec v0.5.0 + pub fn get_slashed_balance(&self, epoch: Epoch, spec: &ChainSpec) -> Result { + let i = self.get_slashed_balance_index(epoch, spec)?; + Ok(self.latest_slashed_balances[i]) + } + + /// Sets the total slashed balances for some epoch. + /// + /// Spec v0.5.0 + pub fn set_slashed_balance( + &mut self, + epoch: Epoch, + balance: u64, + spec: &ChainSpec, + ) -> Result<(), Error> { + let i = self.get_slashed_balance_index(epoch, spec)?; + Ok(self.latest_slashed_balances[i] = balance) + } + + /// Generate a seed for the given `epoch`. + /// + /// Spec v0.5.0 + pub fn generate_seed(&self, epoch: Epoch, spec: &ChainSpec) -> Result { + let mut input = self + .get_randao_mix(epoch - spec.min_seed_lookahead, spec)? + .as_bytes() + .to_vec(); + + input.append(&mut self.get_active_index_root(epoch, spec)?.as_bytes().to_vec()); + + input.append(&mut int_to_bytes32(epoch.as_u64())); + + Ok(Hash256::from_slice(&hash(&input[..])[..])) + } + + /// Return the effective balance (also known as "balance at stake") for a validator with the given ``index``. + /// + /// Spec v0.5.0 + pub fn get_effective_balance( + &self, + validator_index: usize, + spec: &ChainSpec, + ) -> Result { + let balance = self + .validator_balances + .get(validator_index) + .ok_or_else(|| Error::UnknownValidator)?; + Ok(std::cmp::min(*balance, spec.max_deposit_amount)) + } + + /// Return the epoch at which an activation or exit triggered in ``epoch`` takes effect. + /// + /// Spec v0.5.0 + pub fn get_delayed_activation_exit_epoch(&self, epoch: Epoch, spec: &ChainSpec) -> Epoch { + epoch + 1 + spec.activation_exit_delay + } + + /// Activate the validator of the given ``index``. + /// + /// Spec v0.5.0 + pub fn activate_validator( + &mut self, + validator_index: usize, + is_genesis: bool, + spec: &ChainSpec, + ) { + let current_epoch = self.current_epoch(spec); + + self.validator_registry[validator_index].activation_epoch = if is_genesis { + spec.genesis_epoch + } else { + self.get_delayed_activation_exit_epoch(current_epoch, spec) + } + } + + /// Initiate an exit for the validator of the given `index`. + /// + /// Spec v0.5.0 + pub fn initiate_validator_exit(&mut self, validator_index: usize) { + self.validator_registry[validator_index].initiated_exit = true; + } + + /// Returns the `slot`, `shard` and `committee_index` for which a validator must produce an + /// attestation. + /// + /// Only reads the current epoch. + /// + /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. + /// + /// Spec v0.5.0 + pub fn get_attestation_duties( + &self, + validator_index: usize, + spec: &ChainSpec, + ) -> Result<&Option, Error> { + let cache = self.cache(RelativeEpoch::Current, spec)?; + + Ok(cache + .attestation_duties + .get(validator_index) + .ok_or_else(|| Error::UnknownValidator)?) + } + + /// Return the combined effective balance of an array of validators. + /// + /// Spec v0.5.0 + pub fn get_total_balance( + &self, + validator_indices: &[usize], + spec: &ChainSpec, + ) -> Result { + validator_indices.iter().try_fold(0_u64, |acc, i| { + self.get_effective_balance(*i, spec) + .and_then(|bal| Ok(bal + acc)) + }) + } + /// Build an epoch cache, unless it is has already been built. pub fn build_epoch_cache( &mut self, @@ -311,633 +747,6 @@ impl BeaconState { pub fn drop_pubkey_cache(&mut self) { self.pubkey_cache = PubkeyCache::default() } - - /// If a validator pubkey exists in the validator registry, returns `Some(i)`, otherwise - /// returns `None`. - /// - /// Requires a fully up-to-date `pubkey_cache`, returns an error if this is not the case. - pub fn get_validator_index(&self, pubkey: &PublicKey) -> Result, Error> { - if self.pubkey_cache.len() == self.validator_registry.len() { - Ok(self.pubkey_cache.get(pubkey)) - } else { - Err(Error::PubkeyCacheIncomplete { - cache_len: self.pubkey_cache.len(), - registry_len: self.validator_registry.len(), - }) - } - } - - /// The epoch corresponding to `self.slot`. - /// - /// Spec v0.5.0 - pub fn current_epoch(&self, spec: &ChainSpec) -> Epoch { - self.slot.epoch(spec.slots_per_epoch) - } - - /// The epoch prior to `self.current_epoch()`. - /// - /// If the current epoch is the genesis epoch, the genesis_epoch is returned. - /// - /// Spec v0.5.0 - pub fn previous_epoch(&self, spec: &ChainSpec) -> Epoch { - self.current_epoch(&spec) - 1 - } - - /// The epoch following `self.current_epoch()`. - /// - /// Spec v0.5.0 - pub fn next_epoch(&self, spec: &ChainSpec) -> Epoch { - self.current_epoch(spec) + 1 - } - - /// Returns the active validator indices for the given epoch, assuming there is no validator - /// registry update in the next epoch. - /// - /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. - /// - /// Spec v0.5.0 - pub fn get_active_validator_indices( - &self, - epoch: Epoch, - spec: &ChainSpec, - ) -> Result<&[usize], Error> { - // If the slot is in the next epoch, assume there was no validator registry update. - let relative_epoch = - match RelativeEpoch::from_epoch(self.slot.epoch(spec.slots_per_epoch), epoch) { - Err(RelativeEpochError::AmbiguiousNextEpoch) => { - Ok(RelativeEpoch::NextWithoutRegistryChange) - } - e => e, - }?; - - let cache = self.cache(relative_epoch, spec)?; - - Ok(&cache.active_validator_indices) - } - - /// Returns the crosslink committees for some slot. - /// - /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. - /// - /// Spec v0.5.0 - pub fn get_crosslink_committees_at_slot( - &self, - slot: Slot, - spec: &ChainSpec, - ) -> Result<&Vec, Error> { - // If the slot is in the next epoch, assume there was no validator registry update. - let relative_epoch = match RelativeEpoch::from_slot(self.slot, slot, spec) { - Err(RelativeEpochError::AmbiguiousNextEpoch) => { - Ok(RelativeEpoch::NextWithoutRegistryChange) - } - e => e, - }?; - - let cache = self.cache(relative_epoch, spec)?; - - Ok(cache - .get_crosslink_committees_at_slot(slot, spec) - .ok_or_else(|| Error::SlotOutOfBounds)?) - } - - /// Returns the crosslink committees for some shard in an epoch. - /// - /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. - /// - /// Spec v0.4.0 - pub fn get_crosslink_committee_for_shard( - &self, - epoch: Epoch, - shard: Shard, - spec: &ChainSpec, - ) -> Result<&CrosslinkCommittee, Error> { - // If the slot is in the next epoch, assume there was no validator registry update. - let relative_epoch = match RelativeEpoch::from_epoch(self.current_epoch(spec), epoch) { - Err(RelativeEpochError::AmbiguiousNextEpoch) => { - Ok(RelativeEpoch::NextWithoutRegistryChange) - } - e => e, - }?; - - let cache = self.cache(relative_epoch, spec)?; - - Ok(cache - .get_crosslink_committee_for_shard(shard, spec) - .ok_or_else(|| Error::NoCommitteeForShard)?) - } - - /// Safely obtains the index for latest block roots, given some `slot`. - /// - /// Spec v0.5.0 - fn get_latest_block_roots_index(&self, slot: Slot, spec: &ChainSpec) -> Result { - if (slot < self.slot) && (self.slot <= slot + spec.slots_per_historical_root as u64) { - let i = slot.as_usize() % spec.slots_per_historical_root; - if i >= self.latest_block_roots.len() { - Err(Error::InsufficientStateRoots) - } else { - Ok(i) - } - } else { - Err(BeaconStateError::SlotOutOfBounds) - } - } - - /// Return the block root at a recent `slot`. - /// - /// Spec v0.5.0 - pub fn get_block_root( - &self, - slot: Slot, - spec: &ChainSpec, - ) -> Result<&Hash256, BeaconStateError> { - let i = self.get_latest_block_roots_index(slot, spec)?; - Ok(&self.latest_block_roots[i]) - } - - /// Sets the block root for some given slot. - /// - /// Spec v0.5.0 - pub fn set_block_root( - &mut self, - slot: Slot, - block_root: Hash256, - spec: &ChainSpec, - ) -> Result<(), BeaconStateError> { - let i = self.get_latest_block_roots_index(slot, spec)?; - Ok(self.latest_block_roots[i] = block_root) - } - - /// XOR-assigns the existing `epoch` randao mix with the hash of the `signature`. - /// - /// # Errors: - /// - /// See `Self::get_randao_mix`. - /// - /// Spec v0.5.0 - pub fn update_randao_mix( - &mut self, - epoch: Epoch, - signature: &Signature, - spec: &ChainSpec, - ) -> Result<(), Error> { - let i = epoch.as_usize() % spec.latest_randao_mixes_length; - - let signature_hash = Hash256::from_slice(&hash(&ssz_encode(signature))); - - self.latest_randao_mixes[i] = *self.get_randao_mix(epoch, spec)? ^ signature_hash; - - Ok(()) - } - - /// Return the randao mix at a recent ``epoch``. - /// - /// # Errors: - /// - `InsufficientRandaoMixes` if `self.latest_randao_mixes` is shorter than - /// `spec.latest_randao_mixes_length`. - /// - `EpochOutOfBounds` if the state no longer stores randao mixes for the given `epoch`. - /// - /// Spec v0.5.0 - pub fn get_randao_mix(&self, epoch: Epoch, spec: &ChainSpec) -> Result<&Hash256, Error> { - let current_epoch = self.current_epoch(spec); - - if (current_epoch - (spec.latest_randao_mixes_length as u64) < epoch) - & (epoch <= current_epoch) - { - self.latest_randao_mixes - .get(epoch.as_usize() % spec.latest_randao_mixes_length) - .ok_or_else(|| Error::InsufficientRandaoMixes) - } else { - Err(Error::EpochOutOfBounds) - } - } - - /// Return the index root at a recent `epoch`. - /// - /// Spec v0.4.0 - pub fn get_active_index_root(&self, epoch: Epoch, spec: &ChainSpec) -> Option { - let current_epoch = self.current_epoch(spec); - - if (current_epoch - spec.latest_active_index_roots_length as u64 - + spec.activation_exit_delay - < epoch) - & (epoch <= current_epoch + spec.activation_exit_delay) - { - Some( - self.latest_active_index_roots - [epoch.as_usize() % spec.latest_active_index_roots_length], - ) - } else { - None - } - } - - /// Safely obtains the index for latest state roots, given some `slot`. - /// - /// Spec v0.5.0 - fn get_latest_state_roots_index(&self, slot: Slot, spec: &ChainSpec) -> Result { - if (slot < self.slot) && (self.slot <= slot + spec.slots_per_historical_root as u64) { - let i = slot.as_usize() % spec.slots_per_historical_root; - if i >= self.latest_state_roots.len() { - Err(Error::InsufficientStateRoots) - } else { - Ok(i) - } - } else { - Err(BeaconStateError::SlotOutOfBounds) - } - } - - /// Gets the state root for some slot. - /// - /// Spec v0.5.0 - pub fn get_state_root(&mut self, slot: Slot, spec: &ChainSpec) -> Result<&Hash256, Error> { - let i = self.get_latest_state_roots_index(slot, spec)?; - Ok(&self.latest_state_roots[i]) - } - - /// Sets the latest state root for slot. - /// - /// Spec v0.5.0 - pub fn set_state_root( - &mut self, - slot: Slot, - state_root: Hash256, - spec: &ChainSpec, - ) -> Result<(), Error> { - let i = self.get_latest_state_roots_index(slot, spec)?; - Ok(self.latest_state_roots[i] = state_root) - } - - /// Generate a seed for the given `epoch`. - /// - /// Spec v0.4.0 - pub fn generate_seed(&self, epoch: Epoch, spec: &ChainSpec) -> Result { - let mut input = self - .get_randao_mix(epoch - spec.min_seed_lookahead, spec)? - .as_bytes() - .to_vec(); - - input.append( - &mut self - .get_active_index_root(epoch, spec) - .ok_or_else(|| Error::InsufficientIndexRoots)? - .as_bytes() - .to_vec(), - ); - - input.append(&mut int_to_bytes32(epoch.as_u64())); - - Ok(Hash256::from_slice(&hash(&input[..])[..])) - } - - /// 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. - /// - /// Spec v0.5.0 - pub fn get_beacon_proposer_index( - &self, - slot: Slot, - relative_epoch: RelativeEpoch, - spec: &ChainSpec, - ) -> Result { - let cache = self.cache(relative_epoch, spec)?; - - let committees = cache - .get_crosslink_committees_at_slot(slot, spec) - .ok_or_else(|| Error::SlotOutOfBounds)?; - - let epoch = slot.epoch(spec.slots_per_epoch); - - committees - .first() - .ok_or(Error::UnableToDetermineProducer) - .and_then(|first| { - let index = epoch - .as_usize() - .checked_rem(first.committee.len()) - .ok_or(Error::UnableToDetermineProducer)?; - Ok(first.committee[index]) - }) - } - - /// Return the effective balance (also known as "balance at stake") for a validator with the given ``index``. - /// - /// Spec v0.4.0 - pub fn get_effective_balance( - &self, - validator_index: usize, - spec: &ChainSpec, - ) -> Result { - let balance = self - .validator_balances - .get(validator_index) - .ok_or_else(|| Error::UnknownValidator)?; - Ok(std::cmp::min(*balance, spec.max_deposit_amount)) - } - - /// Return the epoch at which an activation or exit triggered in ``epoch`` takes effect. - /// - /// Spec v0.4.0 - pub fn get_delayed_activation_exit_epoch(&self, epoch: Epoch, spec: &ChainSpec) -> Epoch { - epoch + 1 + spec.activation_exit_delay - } - - /// Activate the validator of the given ``index``. - /// - /// Spec v0.5.0 - pub fn activate_validator( - &mut self, - validator_index: usize, - is_genesis: bool, - spec: &ChainSpec, - ) { - let current_epoch = self.current_epoch(spec); - - self.validator_registry[validator_index].activation_epoch = if is_genesis { - spec.genesis_epoch - } else { - self.get_delayed_activation_exit_epoch(current_epoch, spec) - } - } - - /// Initiate an exit for the validator of the given `index`. - /// - /// Spec v0.5.0 - pub fn initiate_validator_exit(&mut self, validator_index: usize) { - self.validator_registry[validator_index].initiated_exit = true; - } - - /// Exit the validator of the given `index`. - /// - /// Spec v0.4.0 - fn exit_validator(&mut self, validator_index: usize, spec: &ChainSpec) { - let current_epoch = self.current_epoch(spec); - let delayed_epoch = self.get_delayed_activation_exit_epoch(current_epoch, spec); - - if self.validator_registry[validator_index].exit_epoch <= delayed_epoch { - return; - } - - self.validator_registry[validator_index].exit_epoch = delayed_epoch; - } - - /// Slash the validator with index ``index``. - /// - /// Spec v0.5.0 - pub fn slash_validator( - &mut self, - validator_index: usize, - spec: &ChainSpec, - ) -> Result<(), Error> { - let current_epoch = self.current_epoch(spec); - - let validator = &self - .validator_registry - .get(validator_index) - .ok_or_else(|| Error::UnknownValidator)?; - let effective_balance = self.get_effective_balance(validator_index, spec)?; - - // A validator that is withdrawn cannot be slashed. - // - // This constraint will be lifted in Phase 0. - if self.slot - >= validator - .withdrawable_epoch - .start_slot(spec.slots_per_epoch) - { - return Err(Error::ValidatorIsWithdrawable); - } - - self.exit_validator(validator_index, spec); - - self.increment_current_epoch_slashed_balances(effective_balance, spec)?; - - let whistleblower_index = - self.get_beacon_proposer_index(self.slot, RelativeEpoch::Current, spec)?; - let whistleblower_reward = effective_balance / spec.whistleblower_reward_quotient; - - safe_add_assign!( - self.validator_balances[whistleblower_index as usize], - whistleblower_reward - ); - safe_sub_assign!( - self.validator_balances[validator_index], - whistleblower_reward - ); - - self.validator_registry[validator_index].slashed = true; - - self.validator_registry[validator_index].withdrawable_epoch = - current_epoch + Epoch::from(spec.latest_slashed_exit_length); - - Ok(()) - } - - /// Increment `self.latest_slashed_balances` with a slashing from the current epoch. - /// - /// Spec v0.5.0. - fn increment_current_epoch_slashed_balances( - &mut self, - increment: u64, - spec: &ChainSpec, - ) -> Result<(), Error> { - let current_epoch = self.current_epoch(spec); - - let slashed_balances_index = current_epoch.as_usize() % spec.latest_slashed_exit_length; - if slashed_balances_index >= self.latest_slashed_balances.len() { - return Err(Error::InsufficientSlashedBalances); - } - - self.latest_slashed_balances[slashed_balances_index] += increment; - - Ok(()) - } - - /// Initiate an exit for the validator of the given `index`. - /// - /// Spec v0.4.0 - pub fn prepare_validator_for_withdrawal(&mut self, validator_index: usize, spec: &ChainSpec) { - //TODO: we're not ANDing here, we're setting. Potentially wrong. - self.validator_registry[validator_index].withdrawable_epoch = - self.current_epoch(spec) + spec.min_validator_withdrawability_delay; - } - - /// Returns the `slot`, `shard` and `committee_index` for which a validator must produce an - /// attestation. - /// - /// Only reads the current epoch. - /// - /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. - /// - /// Spec v0.4.0 - pub fn get_attestation_duties( - &self, - validator_index: usize, - spec: &ChainSpec, - ) -> Result<&Option, Error> { - let cache = self.cache(RelativeEpoch::Current, spec)?; - - Ok(cache - .attestation_duties - .get(validator_index) - .ok_or_else(|| Error::UnknownValidator)?) - } - - /// Process slashings. - /// - /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. - /// - /// Spec v0.4.0 - pub fn process_slashings(&mut self, spec: &ChainSpec) -> Result<(), Error> { - let current_epoch = self.current_epoch(spec); - let active_validator_indices = self.get_active_validator_indices(current_epoch, spec)?; - let total_balance = self.get_total_balance(&active_validator_indices[..], spec)?; - - for (index, validator) in self.validator_registry.iter().enumerate() { - if validator.slashed - && (current_epoch - == validator.withdrawable_epoch - - Epoch::from(spec.latest_slashed_exit_length / 2)) - { - let epoch_index: usize = current_epoch.as_usize() % spec.latest_slashed_exit_length; - - let total_at_start = self.latest_slashed_balances - [(epoch_index + 1) % spec.latest_slashed_exit_length]; - let total_at_end = self.latest_slashed_balances[epoch_index]; - let total_penalities = total_at_end.saturating_sub(total_at_start); - - let effective_balance = self.get_effective_balance(index, spec)?; - let penalty = std::cmp::max( - effective_balance * std::cmp::min(total_penalities * 3, total_balance) - / total_balance, - effective_balance / spec.min_penalty_quotient, - ); - - safe_sub_assign!(self.validator_balances[index], penalty); - } - } - - Ok(()) - } - - /// Process the exit queue. - /// - /// Spec v0.4.0 - pub fn process_exit_queue(&mut self, spec: &ChainSpec) { - let current_epoch = self.current_epoch(spec); - - let eligible = |index: usize| { - let validator = &self.validator_registry[index]; - - if validator.withdrawable_epoch != spec.far_future_epoch { - false - } else { - current_epoch >= validator.exit_epoch + spec.min_validator_withdrawability_delay - } - }; - - 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_epoch); - - for (withdrawn_so_far, index) in eligable_indices.iter().enumerate() { - if withdrawn_so_far as u64 >= spec.max_exit_dequeues_per_epoch { - break; - } - self.prepare_validator_for_withdrawal(*index, spec); - } - } - - /// Update validator registry, activating/exiting validators if possible. - /// - /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. - /// - /// Spec v0.4.0 - pub fn update_validator_registry(&mut self, spec: &ChainSpec) -> Result<(), Error> { - let current_epoch = self.current_epoch(spec); - let active_validator_indices = self.get_active_validator_indices(current_epoch, spec)?; - let total_balance = self.get_total_balance(&active_validator_indices[..], spec)?; - - let max_balance_churn = std::cmp::max( - spec.max_deposit_amount, - 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_epoch == spec.far_future_epoch) - & (self.validator_balances[index] == spec.max_deposit_amount) - { - 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_epoch == spec.far_future_epoch) & (validator.initiated_exit) { - balance_churn += self.get_effective_balance(index, spec)?; - if balance_churn > max_balance_churn { - break; - } - - self.exit_validator(index, spec); - } - } - - self.validator_registry_update_epoch = current_epoch; - - Ok(()) - } - - /// Iterate through the validator registry and eject active validators with balance below - /// ``EJECTION_BALANCE``. - /// - /// Spec v0.5.0 - pub fn process_ejections(&mut self, spec: &ChainSpec) -> Result<(), Error> { - // There is an awkward double (triple?) loop here because we can't loop across the borrowed - // active validator indices and mutate state in the one loop. - let exitable: Vec = self - .get_active_validator_indices(self.current_epoch(spec), spec)? - .iter() - .filter_map(|&i| { - if self.validator_balances[i as usize] < spec.ejection_balance { - Some(i) - } else { - None - } - }) - .collect(); - - for validator_index in exitable { - self.exit_validator(validator_index, spec) - } - - Ok(()) - } - - /// Return the combined effective balance of an array of validators. - /// - /// Spec v0.5.0 - pub fn get_total_balance( - &self, - validator_indices: &[usize], - spec: &ChainSpec, - ) -> Result { - validator_indices.iter().try_fold(0_u64, |acc, i| { - self.get_effective_balance(*i, spec) - .and_then(|bal| Ok(bal + acc)) - }) - } } impl From for Error { From 37b8e9f39a50737bd7d74f984fc614d6bf6e5f49 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 19 Mar 2019 09:09:57 +1100 Subject: [PATCH 04/10] Move `get_active_validator_indices` to state --- eth2/fork_choice/src/bitwise_lmd_ghost.rs | 11 +- eth2/fork_choice/src/optimized_lmd_ghost.rs | 11 +- eth2/fork_choice/src/slow_lmd_ghost.rs | 10 +- eth2/fork_choice/tests/tests.rs | 3 +- .../state_processing/src/get_genesis_state.rs | 2 +- .../src/per_epoch_processing.rs | 21 +-- .../per_epoch_processing/process_ejections.rs | 2 +- .../per_epoch_processing/process_slashings.rs | 3 +- .../process_validator_registry.rs | 4 +- .../update_validator_registry.rs | 3 +- eth2/types/src/beacon_state.rs | 27 +-- .../src/beacon_state/epoch_cache/tests.rs | 9 +- eth2/types/src/beacon_state/pubkey_cache.rs | 11 +- eth2/types/src/chain_spec.rs | 2 +- eth2/types/src/lib.rs | 1 - eth2/types/src/transfer.rs | 2 +- eth2/types/src/validator_registry.rs | 174 ------------------ 17 files changed, 57 insertions(+), 239 deletions(-) delete mode 100644 eth2/types/src/validator_registry.rs diff --git a/eth2/fork_choice/src/bitwise_lmd_ghost.rs b/eth2/fork_choice/src/bitwise_lmd_ghost.rs index 9410fd203..8ae0251d2 100644 --- a/eth2/fork_choice/src/bitwise_lmd_ghost.rs +++ b/eth2/fork_choice/src/bitwise_lmd_ghost.rs @@ -10,10 +10,7 @@ use db::{ use log::{debug, trace}; use std::collections::HashMap; use std::sync::Arc; -use types::{ - validator_registry::get_active_validator_indices, BeaconBlock, ChainSpec, Hash256, Slot, - SlotHeight, -}; +use types::{BeaconBlock, ChainSpec, Hash256, Slot, SlotHeight}; //TODO: Pruning - Children //TODO: Handle Syncing @@ -93,10 +90,8 @@ where .get_deserialized(&state_root)? .ok_or_else(|| ForkChoiceError::MissingBeaconState(*state_root))?; - let active_validator_indices = get_active_validator_indices( - ¤t_state.validator_registry[..], - block_slot.epoch(spec.slots_per_epoch), - ); + let active_validator_indices = + current_state.get_active_validator_indices(block_slot.epoch(spec.slots_per_epoch)); for index in active_validator_indices { let balance = std::cmp::min( diff --git a/eth2/fork_choice/src/optimized_lmd_ghost.rs b/eth2/fork_choice/src/optimized_lmd_ghost.rs index e1b8914a6..ee2919e85 100644 --- a/eth2/fork_choice/src/optimized_lmd_ghost.rs +++ b/eth2/fork_choice/src/optimized_lmd_ghost.rs @@ -10,10 +10,7 @@ use log::{debug, trace}; use std::cmp::Ordering; use std::collections::HashMap; use std::sync::Arc; -use types::{ - validator_registry::get_active_validator_indices, BeaconBlock, ChainSpec, Hash256, Slot, - SlotHeight, -}; +use types::{BeaconBlock, ChainSpec, Hash256, Slot, SlotHeight}; //TODO: Pruning - Children //TODO: Handle Syncing @@ -93,10 +90,8 @@ where .get_deserialized(&state_root)? .ok_or_else(|| ForkChoiceError::MissingBeaconState(*state_root))?; - let active_validator_indices = get_active_validator_indices( - ¤t_state.validator_registry[..], - block_slot.epoch(spec.slots_per_epoch), - ); + let active_validator_indices = + current_state.get_active_validator_indices(block_slot.epoch(spec.slots_per_epoch)); for index in active_validator_indices { let balance = std::cmp::min( diff --git a/eth2/fork_choice/src/slow_lmd_ghost.rs b/eth2/fork_choice/src/slow_lmd_ghost.rs index af58aa7b8..25d137089 100644 --- a/eth2/fork_choice/src/slow_lmd_ghost.rs +++ b/eth2/fork_choice/src/slow_lmd_ghost.rs @@ -8,9 +8,7 @@ use db::{ use log::{debug, trace}; use std::collections::HashMap; use std::sync::Arc; -use types::{ - validator_registry::get_active_validator_indices, BeaconBlock, ChainSpec, Hash256, Slot, -}; +use types::{BeaconBlock, ChainSpec, Hash256, Slot}; //TODO: Pruning and syncing @@ -61,10 +59,8 @@ where .get_deserialized(&state_root)? .ok_or_else(|| ForkChoiceError::MissingBeaconState(*state_root))?; - let active_validator_indices = get_active_validator_indices( - ¤t_state.validator_registry[..], - block_slot.epoch(spec.slots_per_epoch), - ); + let active_validator_indices = + current_state.get_active_validator_indices(block_slot.epoch(spec.slots_per_epoch)); for index in active_validator_indices { let balance = std::cmp::min( diff --git a/eth2/fork_choice/tests/tests.rs b/eth2/fork_choice/tests/tests.rs index 80fbbbe20..3ce63eeb7 100644 --- a/eth2/fork_choice/tests/tests.rs +++ b/eth2/fork_choice/tests/tests.rs @@ -242,8 +242,9 @@ fn setup_inital_state( let spec = ChainSpec::foundation(); - let state_builder = + let mut state_builder = TestingBeaconStateBuilder::from_single_keypair(num_validators, &Keypair::random(), &spec); + state_builder.build_caches(&spec).unwrap(); let (state, _keypairs) = state_builder.build(); let state_root = state.canonical_root(); diff --git a/eth2/state_processing/src/get_genesis_state.rs b/eth2/state_processing/src/get_genesis_state.rs index bfcf82216..7c4d4cafd 100644 --- a/eth2/state_processing/src/get_genesis_state.rs +++ b/eth2/state_processing/src/get_genesis_state.rs @@ -34,7 +34,7 @@ pub fn get_genesis_state( // Set all the active index roots to be the genesis active index root. let active_validator_indices = state - .get_active_validator_indices(spec.genesis_epoch, spec)? + .get_cached_active_validator_indices(RelativeEpoch::Current, spec)? .to_vec(); let genesis_active_index_root = Hash256::from_slice(&active_validator_indices.hash_tree_root()); state.fill_active_index_roots_with(genesis_active_index_root, spec); diff --git a/eth2/state_processing/src/per_epoch_processing.rs b/eth2/state_processing/src/per_epoch_processing.rs index 97a0e9987..8e03457d3 100644 --- a/eth2/state_processing/src/per_epoch_processing.rs +++ b/eth2/state_processing/src/per_epoch_processing.rs @@ -7,7 +7,7 @@ use process_validator_registry::process_validator_registry; use rayon::prelude::*; use ssz::TreeHash; use std::collections::HashMap; -use types::{validator_registry::get_active_validator_indices, *}; +use types::*; use validator_statuses::{TotalBalances, ValidatorStatuses}; use winning_root::{winning_root, WinningRoot}; @@ -70,16 +70,6 @@ pub fn per_epoch_processing(state: &mut BeaconState, spec: &ChainSpec) -> Result Ok(()) } -/// Returns a list of active validator indices for the state's current epoch. -/// -/// Spec v0.5.0 -pub fn calculate_active_validator_indices(state: &BeaconState, spec: &ChainSpec) -> Vec { - get_active_validator_indices( - &state.validator_registry, - state.slot.epoch(spec.slots_per_epoch), - ) -} - /// Calculates various sets of attesters, including: /// /// - current epoch attesters @@ -454,11 +444,10 @@ pub fn update_active_tree_index_roots( ) -> Result<(), Error> { let next_epoch = state.next_epoch(spec); - let active_tree_root = get_active_validator_indices( - &state.validator_registry, - next_epoch + Epoch::from(spec.activation_exit_delay), - ) - .hash_tree_root(); + let active_tree_root = state + .get_active_validator_indices(next_epoch + Epoch::from(spec.activation_exit_delay)) + .to_vec() + .hash_tree_root(); state.set_active_index_root(next_epoch, Hash256::from_slice(&active_tree_root[..]), spec)?; diff --git a/eth2/state_processing/src/per_epoch_processing/process_ejections.rs b/eth2/state_processing/src/per_epoch_processing/process_ejections.rs index 27dd37479..a60d92187 100644 --- a/eth2/state_processing/src/per_epoch_processing/process_ejections.rs +++ b/eth2/state_processing/src/per_epoch_processing/process_ejections.rs @@ -9,7 +9,7 @@ pub fn process_ejections(state: &mut BeaconState, spec: &ChainSpec) -> Result<() // There is an awkward double (triple?) loop here because we can't loop across the borrowed // active validator indices and mutate state in the one loop. let exitable: Vec = state - .get_active_validator_indices(state.current_epoch(spec), spec)? + .get_cached_active_validator_indices(RelativeEpoch::Current, spec)? .iter() .filter_map(|&i| { if state.validator_balances[i as usize] < spec.ejection_balance { diff --git a/eth2/state_processing/src/per_epoch_processing/process_slashings.rs b/eth2/state_processing/src/per_epoch_processing/process_slashings.rs index b14a9ee37..19c1e519b 100644 --- a/eth2/state_processing/src/per_epoch_processing/process_slashings.rs +++ b/eth2/state_processing/src/per_epoch_processing/process_slashings.rs @@ -7,7 +7,8 @@ use types::{BeaconStateError as Error, *}; /// Spec v0.4.0 pub fn process_slashings(state: &mut BeaconState, spec: &ChainSpec) -> Result<(), Error> { let current_epoch = state.current_epoch(spec); - let active_validator_indices = state.get_active_validator_indices(current_epoch, spec)?; + let active_validator_indices = + state.get_cached_active_validator_indices(RelativeEpoch::Current, spec)?; let total_balance = state.get_total_balance(&active_validator_indices[..], spec)?; for (index, validator) in state.validator_registry.iter().enumerate() { diff --git a/eth2/state_processing/src/per_epoch_processing/process_validator_registry.rs b/eth2/state_processing/src/per_epoch_processing/process_validator_registry.rs index 2eb39711d..85d6c37f6 100644 --- a/eth2/state_processing/src/per_epoch_processing/process_validator_registry.rs +++ b/eth2/state_processing/src/per_epoch_processing/process_validator_registry.rs @@ -21,7 +21,7 @@ pub fn process_validator_registry(state: &mut BeaconState, spec: &ChainSpec) -> state.current_shuffling_start_shard = (state.current_shuffling_start_shard + spec.get_epoch_committee_count( state - .get_active_validator_indices(current_epoch, spec)? + .get_cached_active_validator_indices(RelativeEpoch::Current, spec)? .len(), ) as u64) % spec.shard_count; @@ -53,7 +53,7 @@ pub fn should_update_validator_registry( } let num_active_validators = state - .get_active_validator_indices(state.current_epoch(spec), spec)? + .get_cached_active_validator_indices(RelativeEpoch::Current, spec)? .len(); let current_epoch_committee_count = spec.get_epoch_committee_count(num_active_validators); diff --git a/eth2/state_processing/src/per_epoch_processing/update_validator_registry.rs b/eth2/state_processing/src/per_epoch_processing/update_validator_registry.rs index 8b612c346..ecf05ce6f 100644 --- a/eth2/state_processing/src/per_epoch_processing/update_validator_registry.rs +++ b/eth2/state_processing/src/per_epoch_processing/update_validator_registry.rs @@ -8,7 +8,8 @@ use types::{BeaconStateError as Error, *}; /// Spec v0.4.0 pub fn update_validator_registry(state: &mut BeaconState, spec: &ChainSpec) -> Result<(), Error> { let current_epoch = state.current_epoch(spec); - let active_validator_indices = state.get_active_validator_indices(current_epoch, spec)?; + let active_validator_indices = + state.get_cached_active_validator_indices(RelativeEpoch::Current, spec)?; let total_balance = state.get_total_balance(&active_validator_indices[..], spec)?; let max_balance_churn = std::cmp::max( diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index 30f95c02c..22e7c6ecf 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -1,4 +1,4 @@ -use self::epoch_cache::{EpochCache, Error as EpochCacheError}; +use self::epoch_cache::{get_active_validator_indices, EpochCache, Error as EpochCacheError}; use crate::test_utils::TestRandom; use crate::*; use int_to_bytes::int_to_bytes32; @@ -234,28 +234,31 @@ impl BeaconState { /// Returns the active validator indices for the given epoch, assuming there is no validator /// registry update in the next epoch. /// + /// This uses the cache, so it saves an iteration over the validator registry, however it can + /// not return a result for any epoch before the previous epoch. + /// /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. /// /// Spec v0.5.0 - pub fn get_active_validator_indices( + pub fn get_cached_active_validator_indices( &self, - epoch: Epoch, + relative_epoch: RelativeEpoch, spec: &ChainSpec, ) -> Result<&[usize], Error> { - // If the slot is in the next epoch, assume there was no validator registry update. - let relative_epoch = - match RelativeEpoch::from_epoch(self.slot.epoch(spec.slots_per_epoch), epoch) { - Err(RelativeEpochError::AmbiguiousNextEpoch) => { - Ok(RelativeEpoch::NextWithoutRegistryChange) - } - e => e, - }?; - let cache = self.cache(relative_epoch, spec)?; Ok(&cache.active_validator_indices) } + /// Returns the active validator indices for the given epoch. + /// + /// Does not utilize the cache, performs a full iteration over the validator registry. + /// + /// Spec v0.5.0 + pub fn get_active_validator_indices(&self, epoch: Epoch) -> Vec { + get_active_validator_indices(&self.validator_registry, epoch) + } + /// Returns the crosslink committees for some slot. /// /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. diff --git a/eth2/types/src/beacon_state/epoch_cache/tests.rs b/eth2/types/src/beacon_state/epoch_cache/tests.rs index 10df635f2..5643776e2 100644 --- a/eth2/types/src/beacon_state/epoch_cache/tests.rs +++ b/eth2/types/src/beacon_state/epoch_cache/tests.rs @@ -7,15 +7,19 @@ use swap_or_not_shuffle::shuffle_list; fn do_sane_cache_test( state: BeaconState, epoch: Epoch, + relative_epoch: RelativeEpoch, validator_count: usize, expected_seed: Hash256, expected_shuffling_start: u64, spec: &ChainSpec, ) { let active_indices: Vec = (0..validator_count).collect(); + assert_eq!( &active_indices[..], - state.get_active_validator_indices(epoch, &spec).unwrap(), + state + .get_cached_active_validator_indices(relative_epoch, &spec) + .unwrap(), "Validator indices mismatch" ); @@ -101,6 +105,7 @@ fn builds_sane_current_epoch_cache() { do_sane_cache_test( state.clone(), state.current_epoch(&spec), + RelativeEpoch::Current, validator_count as usize, state.current_shuffling_seed, state.current_shuffling_start_shard, @@ -117,6 +122,7 @@ fn builds_sane_previous_epoch_cache() { do_sane_cache_test( state.clone(), state.previous_epoch(&spec), + RelativeEpoch::Previous, validator_count as usize, state.previous_shuffling_seed, state.previous_shuffling_start_shard, @@ -134,6 +140,7 @@ fn builds_sane_next_without_update_epoch_cache() { do_sane_cache_test( state.clone(), state.next_epoch(&spec), + RelativeEpoch::NextWithoutRegistryChange, validator_count as usize, state.current_shuffling_seed, state.current_shuffling_start_shard, diff --git a/eth2/types/src/beacon_state/pubkey_cache.rs b/eth2/types/src/beacon_state/pubkey_cache.rs index 340bdb311..4632a2d9c 100644 --- a/eth2/types/src/beacon_state/pubkey_cache.rs +++ b/eth2/types/src/beacon_state/pubkey_cache.rs @@ -6,13 +6,17 @@ type ValidatorIndex = usize; #[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize)] pub struct PubkeyCache { + /// Maintain the number of keys added to the map. It is not sufficient to just use the HashMap + /// len, as it does not increase when duplicate keys are added. Duplicate keys are used during + /// testing. + len: usize, map: HashMap, } impl PubkeyCache { - /// Returns the number of validator indices already in the map. + /// Returns the number of validator indices added to the map so far. pub fn len(&self) -> ValidatorIndex { - self.map.len() + self.len } /// Inserts a validator index into the map. @@ -20,8 +24,9 @@ impl PubkeyCache { /// The added index must equal the number of validators already added to the map. This ensures /// that an index is never skipped. pub fn insert(&mut self, pubkey: PublicKey, index: ValidatorIndex) -> bool { - if index == self.map.len() { + if index == self.len { self.map.insert(pubkey, index); + self.len += 1; true } else { false diff --git a/eth2/types/src/chain_spec.rs b/eth2/types/src/chain_spec.rs index e9ade2c91..cfb88bcb8 100644 --- a/eth2/types/src/chain_spec.rs +++ b/eth2/types/src/chain_spec.rs @@ -117,7 +117,7 @@ pub struct ChainSpec { impl ChainSpec { /// Return the number of committees in one epoch. /// - /// Spec v0.4.0 + /// Spec v0.5.0 pub fn get_epoch_committee_count(&self, active_validator_count: usize) -> u64 { std::cmp::max( 1, diff --git a/eth2/types/src/lib.rs b/eth2/types/src/lib.rs index 05f8254d5..30b0e4a13 100644 --- a/eth2/types/src/lib.rs +++ b/eth2/types/src/lib.rs @@ -34,7 +34,6 @@ pub mod relative_epoch; pub mod slot_epoch; pub mod slot_height; pub mod validator; -pub mod validator_registry; use ethereum_types::{H160, H256, U256}; use std::collections::HashMap; diff --git a/eth2/types/src/transfer.rs b/eth2/types/src/transfer.rs index 1c9968702..2570d7b3f 100644 --- a/eth2/types/src/transfer.rs +++ b/eth2/types/src/transfer.rs @@ -9,7 +9,7 @@ use test_random_derive::TestRandom; /// The data submitted to the deposit contract. /// -/// Spec v0.4.0 +/// Spec v0.5.0 #[derive( Debug, PartialEq, diff --git a/eth2/types/src/validator_registry.rs b/eth2/types/src/validator_registry.rs deleted file mode 100644 index db35ae993..000000000 --- a/eth2/types/src/validator_registry.rs +++ /dev/null @@ -1,174 +0,0 @@ -/// Contains logic to manipulate a `&[Validator]`. -/// For now, we avoid defining a newtype and just have flat functions here. -use super::validator::*; -use crate::Epoch; - -/// Given an indexed sequence of `validators`, return the indices corresponding to validators that are active at `epoch`. -/// -/// Spec v0.4.0 -pub fn get_active_validator_indices(validators: &[Validator], epoch: Epoch) -> Vec { - let mut active = Vec::with_capacity(validators.len()); - - for (index, validator) in validators.iter().enumerate() { - if validator.is_active_at(epoch) { - active.push(index) - } - } - - active.shrink_to_fit(); - - active -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng}; - - #[test] - fn can_get_empty_active_validator_indices() { - let mut rng = XorShiftRng::from_seed([42; 16]); - - let validators = vec![]; - let some_epoch = Epoch::random_for_test(&mut rng); - let indices = get_active_validator_indices(&validators, some_epoch); - assert_eq!(indices, vec![]); - } - - #[test] - fn can_get_no_active_validator_indices() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let mut validators = vec![]; - let count_validators = 10; - for _ in 0..count_validators { - validators.push(Validator::default()) - } - - let some_epoch = Epoch::random_for_test(&mut rng); - let indices = get_active_validator_indices(&validators, some_epoch); - assert_eq!(indices, vec![]); - } - - #[test] - fn can_get_all_active_validator_indices() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let count_validators = 10; - let some_epoch = Epoch::random_for_test(&mut rng); - - let mut validators = (0..count_validators) - .into_iter() - .map(|_| { - let mut validator = Validator::default(); - - let activation_offset = u64::random_for_test(&mut rng); - let exit_offset = u64::random_for_test(&mut rng); - - validator.activation_epoch = some_epoch - activation_offset; - validator.exit_epoch = some_epoch + exit_offset; - - validator - }) - .collect::>(); - - // test boundary condition by ensuring that at least one validator in the list just activated - if let Some(validator) = validators.get_mut(0) { - validator.activation_epoch = some_epoch; - } - - let indices = get_active_validator_indices(&validators, some_epoch); - assert_eq!( - indices, - (0..count_validators).into_iter().collect::>() - ); - } - - fn set_validators_to_default_entry_exit(validators: &mut [Validator]) { - for validator in validators.iter_mut() { - validator.activation_epoch = Epoch::max_value(); - validator.exit_epoch = Epoch::max_value(); - } - } - - // sets all `validators` to be active as of some epoch prior to `epoch`. returns the activation epoch. - fn set_validators_to_activated(validators: &mut [Validator], epoch: Epoch) -> Epoch { - let activation_epoch = epoch - 10; - for validator in validators.iter_mut() { - validator.activation_epoch = activation_epoch; - } - activation_epoch - } - - // sets all `validators` to be exited as of some epoch before `epoch`. - fn set_validators_to_exited( - validators: &mut [Validator], - epoch: Epoch, - activation_epoch: Epoch, - ) { - assert!(activation_epoch < epoch); - let mut exit_epoch = activation_epoch + 10; - while exit_epoch >= epoch { - exit_epoch -= 1; - } - assert!(activation_epoch < exit_epoch && exit_epoch < epoch); - - for validator in validators.iter_mut() { - validator.exit_epoch = exit_epoch; - } - } - - #[test] - fn can_get_some_active_validator_indices() { - let mut rng = XorShiftRng::from_seed([42; 16]); - const COUNT_PARTITIONS: usize = 3; - const COUNT_VALIDATORS: usize = 3 * COUNT_PARTITIONS; - let some_epoch: Epoch = Epoch::random_for_test(&mut rng); - - let mut validators = (0..COUNT_VALIDATORS) - .into_iter() - .map(|_| { - let mut validator = Validator::default(); - - let activation_offset = Epoch::random_for_test(&mut rng); - let exit_offset = Epoch::random_for_test(&mut rng); - - validator.activation_epoch = some_epoch - activation_offset; - validator.exit_epoch = some_epoch + exit_offset; - - validator - }) - .collect::>(); - - // we partition the set into partitions based on lifecycle: - for (i, chunk) in validators.chunks_exact_mut(COUNT_PARTITIONS).enumerate() { - match i { - 0 => { - // 1. not activated (Default::default()) - set_validators_to_default_entry_exit(chunk); - } - 1 => { - // 2. activated, but not exited - set_validators_to_activated(chunk, some_epoch); - // test boundary condition by ensuring that at least one validator in the list just activated - if let Some(validator) = chunk.get_mut(0) { - validator.activation_epoch = some_epoch; - } - } - 2 => { - // 3. exited - let activation_epoch = set_validators_to_activated(chunk, some_epoch); - set_validators_to_exited(chunk, some_epoch, activation_epoch); - // test boundary condition by ensuring that at least one validator in the list just exited - if let Some(validator) = chunk.get_mut(0) { - validator.exit_epoch = some_epoch; - } - } - _ => unreachable!( - "constants local to this test not in sync with generation of test case" - ), - } - } - - let indices = get_active_validator_indices(&validators, some_epoch); - assert_eq!(indices, vec![3, 4, 5]); - } -} From d20fb93f0cd927526881dbaad439c3d827e2d644 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 19 Mar 2019 17:16:33 +1100 Subject: [PATCH 05/10] Update rewards processing to v0.5.0 --- .../src/per_epoch_processing.rs | 309 +++-------------- .../src/per_epoch_processing/apply_rewards.rs | 317 ++++++++++++++++++ .../src/per_epoch_processing/errors.rs | 1 + .../validator_statuses.rs | 41 ++- 4 files changed, 393 insertions(+), 275 deletions(-) create mode 100644 eth2/state_processing/src/per_epoch_processing/apply_rewards.rs diff --git a/eth2/state_processing/src/per_epoch_processing.rs b/eth2/state_processing/src/per_epoch_processing.rs index 8e03457d3..24f4a1e1f 100644 --- a/eth2/state_processing/src/per_epoch_processing.rs +++ b/eth2/state_processing/src/per_epoch_processing.rs @@ -1,16 +1,16 @@ +use apply_rewards::apply_rewards; use errors::EpochProcessingError as Error; -use integer_sqrt::IntegerSquareRoot; use process_ejections::process_ejections; use process_exit_queue::process_exit_queue; use process_slashings::process_slashings; use process_validator_registry::process_validator_registry; -use rayon::prelude::*; use ssz::TreeHash; use std::collections::HashMap; use types::*; use validator_statuses::{TotalBalances, ValidatorStatuses}; use winning_root::{winning_root, WinningRoot}; +pub mod apply_rewards; pub mod errors; pub mod get_attestation_participants; pub mod inclusion_distance; @@ -43,13 +43,13 @@ pub fn per_epoch_processing(state: &mut BeaconState, spec: &ChainSpec) -> Result process_eth1_data(state, spec); - process_justification(state, &statuses.total_balances, spec); + update_justification_and_finalization(state, &statuses.total_balances, spec)?; // Crosslinks let winning_root_for_shards = process_crosslinks(state, spec)?; // Rewards and Penalities - process_rewards_and_penalities(state, &mut statuses, &winning_root_for_shards, spec)?; + apply_rewards(state, &mut statuses, &winning_root_for_shards, spec)?; // Ejections process_ejections(state, spec)?; @@ -62,7 +62,7 @@ pub fn per_epoch_processing(state: &mut BeaconState, spec: &ChainSpec) -> Result // Final updates update_active_tree_index_roots(state, spec)?; update_latest_slashed_balances(state, spec)?; - clean_attestations(state); + state.previous_epoch_attestations = vec![]; // Rotate the epoch caches to suit the epoch transition. state.advance_caches(); @@ -113,83 +113,68 @@ pub fn process_eth1_data(state: &mut BeaconState, spec: &ChainSpec) { /// - `justified_epoch` /// - `previous_justified_epoch` /// -/// Spec v0.4.0 -pub fn process_justification( +/// Spec v0.5.0 +pub fn update_justification_and_finalization( state: &mut BeaconState, total_balances: &TotalBalances, spec: &ChainSpec, -) { +) -> Result<(), Error> { let previous_epoch = state.previous_epoch(spec); let current_epoch = state.current_epoch(spec); let mut new_justified_epoch = state.current_justified_epoch; + let mut new_finalized_epoch = state.finalized_epoch; + + // Rotate the justification bitfield up one epoch to make room for the current 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 * total_balances.previous_epoch_boundary_attesters) >= (2 * total_balances.previous_epoch) + // If the previous epoch gets justified, full the second last bit. + if (total_balances.previous_epoch_boundary_attesters * 3) >= (total_balances.previous_epoch * 2) { - state.justification_bitfield |= 2; new_justified_epoch = previous_epoch; + state.justification_bitfield |= 2; } - // 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 * total_balances.current_epoch_boundary_attesters) >= (2 * total_balances.current_epoch) { - state.justification_bitfield |= 1; + // If the current epoch gets justified, fill the last bit. + if (total_balances.current_epoch_boundary_attesters * 3) >= (total_balances.current_epoch * 2) { new_justified_epoch = current_epoch; + state.justification_bitfield |= 1; } - // 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; + let bitfield = state.justification_bitfield; + + // The 2nd/3rd/4th most recent epochs are all justified, the 2nd using the 4th as source. + if ((bitfield >> 1) % 8 == 0b111) & (state.previous_justified_epoch == current_epoch - 3) { + new_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; + // The 2nd/3rd most recent epochs are both justified, the 2nd using the 3rd as source. + if ((bitfield >> 1) % 4 == 0b11) & (state.previous_justified_epoch == current_epoch - 2) { + new_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.current_justified_epoch == previous_epoch - 1) - { - state.finalized_epoch = state.current_justified_epoch; + // The 1st/2nd/3rd most recent epochs are all justified, the 1st using the 2nd as source. + if (bitfield % 8 == 0b111) & (state.current_justified_epoch == current_epoch - 2) { + new_finalized_epoch = state.current_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.current_justified_epoch == previous_epoch) - { - state.finalized_epoch = state.current_justified_epoch; + // The 1st/2nd most recent epochs are both justified, the 1st using the 2nd as source. + if (bitfield % 4 == 0b11) & (state.current_justified_epoch == current_epoch - 1) { + new_finalized_epoch = state.current_justified_epoch; } state.previous_justified_epoch = state.current_justified_epoch; - state.current_justified_epoch = new_justified_epoch; + state.previous_justified_root = state.current_justified_root; + + if new_justified_epoch != state.current_justified_epoch { + state.current_justified_epoch = new_justified_epoch; + state.current_justified_root = + *state.get_block_root(new_justified_epoch.start_slot(spec.slots_per_epoch), spec)?; + } + + if new_finalized_epoch != state.finalized_epoch { + state.finalized_epoch = new_finalized_epoch; + state.finalized_root = + *state.get_block_root(new_finalized_epoch.start_slot(spec.slots_per_epoch), spec)?; + } + + Ok(()) } /// Updates the following fields on the `BeaconState`: @@ -239,201 +224,6 @@ pub fn process_crosslinks( Ok(winning_root_for_shards) } -/// Updates the following fields on the BeaconState: -/// -/// - `validator_balances` -/// -/// Spec v0.4.0 -pub fn process_rewards_and_penalities( - state: &mut BeaconState, - statuses: &mut ValidatorStatuses, - winning_root_for_shards: &WinningRootHashSet, - spec: &ChainSpec, -) -> Result<(), Error> { - let next_epoch = state.next_epoch(spec); - - statuses.process_winning_roots(state, winning_root_for_shards, spec)?; - - let total_balances = &statuses.total_balances; - - let base_reward_quotient = - total_balances.previous_epoch.integer_sqrt() / spec.base_reward_quotient; - - // Guard against a divide-by-zero during the validator balance update. - if base_reward_quotient == 0 { - return Err(Error::BaseRewardQuotientIsZero); - } - // Guard against a divide-by-zero during the validator balance update. - if total_balances.previous_epoch == 0 { - return Err(Error::PreviousTotalBalanceIsZero); - } - // Guard against an out-of-bounds during the validator balance update. - if statuses.statuses.len() != state.validator_balances.len() { - return Err(Error::ValidatorStatusesInconsistent); - } - - // Justification and finalization - - let epochs_since_finality = next_epoch - state.finalized_epoch; - - state.validator_balances = state - .validator_balances - .par_iter() - .enumerate() - .map(|(index, &balance)| { - let mut balance = balance; - let status = &statuses.statuses[index]; - let base_reward = get_base_reward(state, index, total_balances.previous_epoch, spec) - .expect( - "Cannot fail to access a validator balance when iterating validator balances.", - ); - - if epochs_since_finality <= 4 { - // Expected FFG source - if status.is_previous_epoch_attester { - safe_add_assign!( - balance, - base_reward * total_balances.previous_epoch_attesters - / total_balances.previous_epoch - ); - } else if status.is_active_in_previous_epoch { - safe_sub_assign!(balance, base_reward); - } - - // Expected FFG target - if status.is_previous_epoch_boundary_attester { - safe_add_assign!( - balance, - base_reward * total_balances.previous_epoch_boundary_attesters - / total_balances.previous_epoch - ); - } else if status.is_active_in_previous_epoch { - safe_sub_assign!(balance, base_reward); - } - - // Expected beacon chain head - if status.is_previous_epoch_head_attester { - safe_add_assign!( - balance, - base_reward * total_balances.previous_epoch_head_attesters - / total_balances.previous_epoch - ); - } else if status.is_active_in_previous_epoch { - safe_sub_assign!(balance, base_reward); - }; - } else { - let inactivity_penalty = get_inactivity_penalty( - state, - index, - epochs_since_finality.as_u64(), - total_balances.previous_epoch, - spec, - ) - .expect( - "Cannot fail to access a validator balance when iterating validator balances.", - ); - - if status.is_active_in_previous_epoch { - if !status.is_previous_epoch_attester { - safe_sub_assign!(balance, inactivity_penalty); - } - if !status.is_previous_epoch_boundary_attester { - safe_sub_assign!(balance, inactivity_penalty); - } - if !status.is_previous_epoch_head_attester { - safe_sub_assign!(balance, inactivity_penalty); - } - - if state.validator_registry[index].slashed { - let base_reward = - get_base_reward(state, index, total_balances.previous_epoch, spec).expect( - "Cannot fail to access a validator balance when iterating validator balances.", - ); - safe_sub_assign!(balance, 2 * inactivity_penalty + base_reward); - } - } - } - - // Crosslinks - - if let Some(ref info) = status.winning_root_info { - safe_add_assign!( - balance, - base_reward * info.total_attesting_balance / info.total_committee_balance - ); - } else { - safe_sub_assign!(balance, base_reward); - } - - balance - }) - .collect(); - - // Attestation inclusion - - // Guard against an out-of-bounds during the attester inclusion balance update. - if statuses.statuses.len() != state.validator_registry.len() { - return Err(Error::ValidatorStatusesInconsistent); - } - - for (index, _validator) in state.validator_registry.iter().enumerate() { - let status = &statuses.statuses[index]; - - if status.is_previous_epoch_attester { - let proposer_index = status.inclusion_info.proposer_index; - let inclusion_distance = status.inclusion_info.distance; - - let base_reward = - get_base_reward(state, proposer_index, total_balances.previous_epoch, spec).expect( - "Cannot fail to access a validator balance when iterating validator balances.", - ); - - if inclusion_distance > 0 && inclusion_distance < Slot::max_value() { - safe_add_assign!( - state.validator_balances[proposer_index], - base_reward * spec.min_attestation_inclusion_delay - / inclusion_distance.as_u64() - ) - } - } - } - - Ok(()) -} - -/// Returns the base reward for some validator. -/// -/// Spec v0.5.0 -pub fn get_base_reward( - state: &BeaconState, - index: usize, - previous_total_balance: u64, - spec: &ChainSpec, -) -> Result { - if previous_total_balance == 0 { - Ok(0) - } else { - let adjusted_quotient = previous_total_balance.integer_sqrt() / spec.base_reward_quotient; - Ok(state.get_effective_balance(index, spec)? / adjusted_quotient / 5) - } -} - -/// Returns the inactivity penalty for some validator. -/// -/// Spec v0.5.0 -pub fn get_inactivity_penalty( - state: &BeaconState, - index: usize, - epochs_since_finality: u64, - previous_total_balance: u64, - spec: &ChainSpec, -) -> Result { - Ok(get_base_reward(state, index, previous_total_balance, spec)? - + state.get_effective_balance(index, spec)? * epochs_since_finality - / spec.inactivity_penalty_quotient - / 2) -} - /// Updates the state's `latest_active_index_roots` field with a tree hash the active validator /// indices for the next epoch. /// @@ -472,10 +262,3 @@ pub fn update_latest_slashed_balances( Ok(()) } - -/// Removes all pending attestations from the previous epoch. -/// -/// Spec v0.4.0 -pub fn clean_attestations(state: &mut BeaconState) { - state.previous_epoch_attestations = vec![]; -} diff --git a/eth2/state_processing/src/per_epoch_processing/apply_rewards.rs b/eth2/state_processing/src/per_epoch_processing/apply_rewards.rs new file mode 100644 index 000000000..5254e0710 --- /dev/null +++ b/eth2/state_processing/src/per_epoch_processing/apply_rewards.rs @@ -0,0 +1,317 @@ +use super::validator_statuses::{TotalBalances, ValidatorStatus, ValidatorStatuses}; +use super::{Error, WinningRootHashSet}; +use integer_sqrt::IntegerSquareRoot; +use types::*; + +#[derive(Default, Clone)] +pub struct Delta { + pub rewards: u64, + pub penalties: u64, +} + +impl std::ops::AddAssign for Delta { + /// Use wrapping addition as that is how it's defined in the spec. + fn add_assign(&mut self, other: Delta) { + self.rewards += other.rewards; + self.penalties += other.penalties; + } +} + +/// Apply attester and proposer rewards. +/// +/// Spec v0.5.0 +pub fn apply_rewards( + state: &mut BeaconState, + validator_statuses: &mut ValidatorStatuses, + winning_root_for_shards: &WinningRootHashSet, + spec: &ChainSpec, +) -> Result<(), Error> { + // Guard against an out-of-bounds during the validator balance update. + if validator_statuses.statuses.len() != state.validator_balances.len() { + return Err(Error::ValidatorStatusesInconsistent); + } + // Guard against an out-of-bounds during the attester inclusion balance update. + if validator_statuses.statuses.len() != state.validator_registry.len() { + return Err(Error::ValidatorStatusesInconsistent); + } + + let mut deltas = vec![Delta::default(); state.validator_balances.len()]; + + get_justification_and_finalization_deltas(&mut deltas, state, &validator_statuses, spec)?; + get_crosslink_deltas(&mut deltas, state, &validator_statuses, spec)?; + + // Apply the proposer deltas if we are finalizing normally. + // + // This is executed slightly differently to the spec because of the way our functions are + // structured. It should be functionally equivalent. + if epochs_since_finality(state, spec) <= 4 { + get_proposer_deltas( + &mut deltas, + state, + validator_statuses, + winning_root_for_shards, + spec, + )?; + } + + // Apply the deltas, over-flowing but not under-flowing (saturating at 0 instead). + for (i, delta) in deltas.iter().enumerate() { + state.validator_balances[i] += delta.rewards; + state.validator_balances[i] = state.validator_balances[i].saturating_sub(delta.penalties); + } + + Ok(()) +} + +/// Applies the attestation inclusion reward to each proposer for every validator who included an +/// attestation in the previous epoch. +/// +/// Spec v0.5.0 +fn get_proposer_deltas( + deltas: &mut Vec, + state: &mut BeaconState, + validator_statuses: &mut ValidatorStatuses, + winning_root_for_shards: &WinningRootHashSet, + spec: &ChainSpec, +) -> Result<(), Error> { + // Update statuses with the information from winning roots. + validator_statuses.process_winning_roots(state, winning_root_for_shards, spec)?; + + for (index, validator) in validator_statuses.statuses.iter().enumerate() { + let mut delta = Delta::default(); + + if validator.is_previous_epoch_attester { + let inclusion = validator + .inclusion_info + .expect("It is a logic error for an attester not to have an inclusion distance."); + + let base_reward = get_base_reward( + state, + inclusion.proposer_index, + validator_statuses.total_balances.previous_epoch, + spec, + )?; + + if inclusion.proposer_index >= deltas.len() { + return Err(Error::ValidatorStatusesInconsistent); + } + + delta.rewards += base_reward / spec.attestation_inclusion_reward_quotient; + } + + deltas[index] += delta; + } + + Ok(()) +} + +/// Apply rewards for participation in attestations during the previous epoch. +/// +/// Spec v0.5.0 +fn get_justification_and_finalization_deltas( + deltas: &mut Vec, + state: &BeaconState, + validator_statuses: &ValidatorStatuses, + spec: &ChainSpec, +) -> Result<(), Error> { + let epochs_since_finality = epochs_since_finality(state, spec); + + for (index, validator) in validator_statuses.statuses.iter().enumerate() { + let base_reward = get_base_reward( + state, + index, + validator_statuses.total_balances.previous_epoch, + spec, + )?; + let inactivity_penalty = get_inactivity_penalty( + state, + index, + epochs_since_finality.as_u64(), + validator_statuses.total_balances.previous_epoch, + spec, + )?; + + let delta = if epochs_since_finality <= 4 { + compute_normal_justification_and_finalization_delta( + &validator, + &validator_statuses.total_balances, + base_reward, + spec, + ) + } else { + compute_inactivity_leak_delta(&validator, base_reward, inactivity_penalty, spec) + }; + + deltas[index] += delta; + } + + Ok(()) +} + +/// Determine the delta for a single validator, if the chain is finalizing normally. +/// +/// Spec v0.5.0 +fn compute_normal_justification_and_finalization_delta( + validator: &ValidatorStatus, + total_balances: &TotalBalances, + base_reward: u64, + spec: &ChainSpec, +) -> Delta { + let mut delta = Delta::default(); + + let boundary_attesting_balance = total_balances.previous_epoch_boundary_attesters; + let total_balance = total_balances.previous_epoch; + let total_attesting_balance = total_balances.previous_epoch_attesters; + let matching_head_balance = total_balances.previous_epoch_boundary_attesters; + + // Expected FFG source. + if validator.is_previous_epoch_attester { + delta.rewards += base_reward * total_attesting_balance / total_balance; + // Inclusion speed bonus + let inclusion = validator + .inclusion_info + .expect("It is a logic error for an attester not to have an inclusion distance."); + delta.rewards += + base_reward * spec.min_attestation_inclusion_delay / inclusion.distance.as_u64(); + } else if validator.is_active_in_previous_epoch { + delta.penalties += base_reward; + } + + // Expected FFG target. + if validator.is_previous_epoch_boundary_attester { + delta.rewards += base_reward / boundary_attesting_balance / total_balance; + } else if validator.is_active_in_previous_epoch { + delta.penalties += base_reward; + } + + // Expected head. + if validator.is_previous_epoch_head_attester { + delta.rewards += base_reward * matching_head_balance / total_balance; + } else if validator.is_active_in_previous_epoch { + delta.penalties += base_reward; + }; + + // Proposer bonus is handled in `apply_proposer_deltas`. + // + // This function only computes the delta for a single validator, so it cannot also return a + // delta for a validator. + + delta +} + +/// Determine the delta for a single delta, assuming the chain is _not_ finalizing normally. +/// +/// Spec v0.5.0 +fn compute_inactivity_leak_delta( + validator: &ValidatorStatus, + base_reward: u64, + inactivity_penalty: u64, + spec: &ChainSpec, +) -> Delta { + let mut delta = Delta::default(); + + if validator.is_active_in_previous_epoch { + if !validator.is_previous_epoch_attester { + delta.penalties += inactivity_penalty; + } else { + // If a validator did attest, apply a small penalty for getting attestations included + // late. + let inclusion = validator + .inclusion_info + .expect("It is a logic error for an attester not to have an inclusion distance."); + delta.rewards += + base_reward * spec.min_attestation_inclusion_delay / inclusion.distance.as_u64(); + delta.penalties += base_reward; + } + + if !validator.is_previous_epoch_boundary_attester { + delta.penalties += inactivity_penalty; + } + + if !validator.is_previous_epoch_head_attester { + delta.penalties += inactivity_penalty; + } + } + + // Penalize slashed-but-inactive validators as though they were active but offline. + if !validator.is_active_in_previous_epoch + & validator.is_slashed + & !validator.is_withdrawable_in_current_epoch + { + delta.penalties += 2 * inactivity_penalty + base_reward; + } + + delta +} + +/// Calculate the deltas based upon the winning roots for attestations during the previous epoch. +/// +/// Spec v0.5.0 +fn get_crosslink_deltas( + deltas: &mut Vec, + state: &BeaconState, + validator_statuses: &ValidatorStatuses, + spec: &ChainSpec, +) -> Result<(), Error> { + for (index, validator) in validator_statuses.statuses.iter().enumerate() { + let mut delta = Delta::default(); + + let base_reward = get_base_reward( + state, + index, + validator_statuses.total_balances.previous_epoch, + spec, + )?; + + if let Some(ref winning_root) = validator.winning_root_info { + delta.rewards += base_reward * winning_root.total_attesting_balance + / winning_root.total_committee_balance + } else { + delta.penalties += base_reward; + } + + deltas[index] += delta; + } + + Ok(()) +} + +/// Returns the base reward for some validator. +/// +/// Spec v0.5.0 +fn get_base_reward( + state: &BeaconState, + index: usize, + previous_total_balance: u64, + spec: &ChainSpec, +) -> Result { + if previous_total_balance == 0 { + Ok(0) + } else { + let adjusted_quotient = previous_total_balance.integer_sqrt() / spec.base_reward_quotient; + Ok(state.get_effective_balance(index, spec)? / adjusted_quotient / 5) + } +} + +/// Returns the inactivity penalty for some validator. +/// +/// Spec v0.5.0 +fn get_inactivity_penalty( + state: &BeaconState, + index: usize, + epochs_since_finality: u64, + previous_total_balance: u64, + spec: &ChainSpec, +) -> Result { + Ok(get_base_reward(state, index, previous_total_balance, spec)? + + state.get_effective_balance(index, spec)? * epochs_since_finality + / spec.inactivity_penalty_quotient + / 2) +} + +/// Returns the epochs since the last finalized epoch. +/// +/// Spec v0.5.0 +fn epochs_since_finality(state: &BeaconState, spec: &ChainSpec) -> Epoch { + state.current_epoch(spec) + 1 - state.finalized_epoch +} diff --git a/eth2/state_processing/src/per_epoch_processing/errors.rs b/eth2/state_processing/src/per_epoch_processing/errors.rs index 94fc0cca5..4632e83bb 100644 --- a/eth2/state_processing/src/per_epoch_processing/errors.rs +++ b/eth2/state_processing/src/per_epoch_processing/errors.rs @@ -9,6 +9,7 @@ pub enum EpochProcessingError { PreviousTotalBalanceIsZero, InclusionDistanceZero, ValidatorStatusesInconsistent, + DeltasInconsistent, /// Unable to get the inclusion distance for a validator that should have an inclusion /// distance. This indicates an internal inconsistency. /// diff --git a/eth2/state_processing/src/per_epoch_processing/validator_statuses.rs b/eth2/state_processing/src/per_epoch_processing/validator_statuses.rs index bcbca8244..50f3ec372 100644 --- a/eth2/state_processing/src/per_epoch_processing/validator_statuses.rs +++ b/eth2/state_processing/src/per_epoch_processing/validator_statuses.rs @@ -23,7 +23,7 @@ pub struct WinningRootInfo { } /// The information required to reward a block producer for including an attestation in a block. -#[derive(Clone)] +#[derive(Clone, Copy)] pub struct InclusionInfo { /// The earliest slot a validator had an attestation included in the previous epoch. pub slot: Slot, @@ -59,7 +59,11 @@ impl InclusionInfo { /// Information required to reward some validator during the current and previous epoch. #[derive(Default, Clone)] -pub struct AttesterStatus { +pub struct ValidatorStatus { + /// True if the validator has been slashed, ever. + pub is_slashed: bool, + /// True if the validator can withdraw in the current epoch. + pub is_withdrawable_in_current_epoch: bool, /// True if the validator was active in the state's _current_ epoch. pub is_active_in_current_epoch: bool, /// True if the validator was active in the state's _previous_ epoch. @@ -81,14 +85,14 @@ pub struct AttesterStatus { /// Information used to reward the block producer of this validators earliest-included /// attestation. - pub inclusion_info: InclusionInfo, + pub inclusion_info: Option, /// Information used to reward/penalize the validator if they voted in the super-majority for /// some shard block. pub winning_root_info: Option, } -impl AttesterStatus { - /// Accepts some `other` `AttesterStatus` and updates `self` if required. +impl ValidatorStatus { + /// Accepts some `other` `ValidatorStatus` and updates `self` if required. /// /// Will never set one of the `bool` fields to `false`, it will only set it to `true` if other /// contains a `true` field. @@ -97,6 +101,8 @@ impl AttesterStatus { pub fn update(&mut self, other: &Self) { // Update all the bool fields, only updating `self` if `other` is true (never setting // `self` to false). + set_self_if_other_is_true!(self, other, is_slashed); + set_self_if_other_is_true!(self, other, is_withdrawable_in_current_epoch); set_self_if_other_is_true!(self, other, is_active_in_current_epoch); set_self_if_other_is_true!(self, other, is_active_in_previous_epoch); set_self_if_other_is_true!(self, other, is_current_epoch_attester); @@ -105,7 +111,13 @@ impl AttesterStatus { set_self_if_other_is_true!(self, other, is_previous_epoch_boundary_attester); set_self_if_other_is_true!(self, other, is_previous_epoch_head_attester); - self.inclusion_info.update(&other.inclusion_info); + if let Some(other_info) = other.inclusion_info { + if let Some(self_info) = self.inclusion_info.as_mut() { + self_info.update(&other_info); + } else { + self.inclusion_info = other.inclusion_info; + } + } } } @@ -137,7 +149,7 @@ pub struct TotalBalances { #[derive(Clone)] pub struct ValidatorStatuses { /// Information about each individual validator from the state's validator registy. - pub statuses: Vec, + pub statuses: Vec, /// Summed balances for various sets of validators. pub total_balances: TotalBalances, } @@ -154,7 +166,12 @@ impl ValidatorStatuses { let mut total_balances = TotalBalances::default(); for (i, validator) in state.validator_registry.iter().enumerate() { - let mut status = AttesterStatus::default(); + let mut status = ValidatorStatus { + is_slashed: validator.slashed, + is_withdrawable_in_current_epoch: validator + .is_withdrawable_at(state.current_epoch(spec)), + ..ValidatorStatus::default() + }; if validator.is_active_at(state.current_epoch(spec)) { status.is_active_in_current_epoch = true; @@ -193,10 +210,10 @@ impl ValidatorStatuses { get_attestation_participants(state, &a.data, &a.aggregation_bitfield, spec)?; let attesting_balance = state.get_total_balance(&attesting_indices, spec)?; - let mut status = AttesterStatus::default(); + let mut status = ValidatorStatus::default(); // Profile this attestation, updating the total balances and generating an - // `AttesterStatus` object that applies to all participants in the attestation. + // `ValidatorStatus` object that applies to all participants in the attestation. if is_from_epoch(a, state.current_epoch(spec), spec) { self.total_balances.current_epoch_attesters += attesting_balance; status.is_current_epoch_attester = true; @@ -211,7 +228,7 @@ impl ValidatorStatuses { // The inclusion slot and distance are only required for previous epoch attesters. let relative_epoch = RelativeEpoch::from_slot(state.slot, a.data.slot, spec)?; - status.inclusion_info = InclusionInfo { + status.inclusion_info = Some(InclusionInfo { slot: a.inclusion_slot, distance: inclusion_distance(a), proposer_index: state.get_beacon_proposer_index( @@ -219,7 +236,7 @@ impl ValidatorStatuses { relative_epoch, spec, )?, - }; + }); if has_common_epoch_boundary_root(a, state, state.previous_epoch(spec), spec)? { self.total_balances.previous_epoch_boundary_attesters += attesting_balance; From 61f6fe25e7760ef60d1ee58207211a7493096a66 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 19 Mar 2019 17:26:20 +1100 Subject: [PATCH 06/10] Tidy reward processing --- .../src/per_epoch_processing/apply_rewards.rs | 59 ++++++++++++------- 1 file changed, 38 insertions(+), 21 deletions(-) diff --git a/eth2/state_processing/src/per_epoch_processing/apply_rewards.rs b/eth2/state_processing/src/per_epoch_processing/apply_rewards.rs index 5254e0710..ce5fccb21 100644 --- a/eth2/state_processing/src/per_epoch_processing/apply_rewards.rs +++ b/eth2/state_processing/src/per_epoch_processing/apply_rewards.rs @@ -3,10 +3,23 @@ use super::{Error, WinningRootHashSet}; use integer_sqrt::IntegerSquareRoot; use types::*; +/// Use to track the changes to a validators balance. #[derive(Default, Clone)] pub struct Delta { - pub rewards: u64, - pub penalties: u64, + rewards: u64, + penalties: u64, +} + +impl Delta { + /// Reward the validator with the `reward`. + pub fn reward(&mut self, reward: u64) { + self.rewards += reward; + } + + /// Penalize the validator with the `penalty`. + pub fn penalize(&mut self, penalty: u64) { + self.penalties += penalty; + } } impl std::ops::AddAssign for Delta { @@ -96,7 +109,7 @@ fn get_proposer_deltas( return Err(Error::ValidatorStatusesInconsistent); } - delta.rewards += base_reward / spec.attestation_inclusion_reward_quotient; + delta.reward(base_reward / spec.attestation_inclusion_reward_quotient); } deltas[index] += delta; @@ -166,29 +179,30 @@ fn compute_normal_justification_and_finalization_delta( // Expected FFG source. if validator.is_previous_epoch_attester { - delta.rewards += base_reward * total_attesting_balance / total_balance; + delta.reward(base_reward * total_attesting_balance / total_balance); // Inclusion speed bonus let inclusion = validator .inclusion_info .expect("It is a logic error for an attester not to have an inclusion distance."); - delta.rewards += - base_reward * spec.min_attestation_inclusion_delay / inclusion.distance.as_u64(); + delta.reward( + base_reward * spec.min_attestation_inclusion_delay / inclusion.distance.as_u64(), + ); } else if validator.is_active_in_previous_epoch { - delta.penalties += base_reward; + delta.penalize(base_reward); } // Expected FFG target. if validator.is_previous_epoch_boundary_attester { - delta.rewards += base_reward / boundary_attesting_balance / total_balance; + delta.reward(base_reward / boundary_attesting_balance / total_balance); } else if validator.is_active_in_previous_epoch { - delta.penalties += base_reward; + delta.penalize(base_reward); } // Expected head. if validator.is_previous_epoch_head_attester { - delta.rewards += base_reward * matching_head_balance / total_balance; + delta.reward(base_reward * matching_head_balance / total_balance); } else if validator.is_active_in_previous_epoch { - delta.penalties += base_reward; + delta.penalize(base_reward); }; // Proposer bonus is handled in `apply_proposer_deltas`. @@ -212,24 +226,25 @@ fn compute_inactivity_leak_delta( if validator.is_active_in_previous_epoch { if !validator.is_previous_epoch_attester { - delta.penalties += inactivity_penalty; + delta.penalize(inactivity_penalty); } else { // If a validator did attest, apply a small penalty for getting attestations included // late. let inclusion = validator .inclusion_info .expect("It is a logic error for an attester not to have an inclusion distance."); - delta.rewards += - base_reward * spec.min_attestation_inclusion_delay / inclusion.distance.as_u64(); - delta.penalties += base_reward; + delta.reward( + base_reward * spec.min_attestation_inclusion_delay / inclusion.distance.as_u64(), + ); + delta.penalize(base_reward); } if !validator.is_previous_epoch_boundary_attester { - delta.penalties += inactivity_penalty; + delta.reward(inactivity_penalty); } if !validator.is_previous_epoch_head_attester { - delta.penalties += inactivity_penalty; + delta.penalize(inactivity_penalty); } } @@ -238,7 +253,7 @@ fn compute_inactivity_leak_delta( & validator.is_slashed & !validator.is_withdrawable_in_current_epoch { - delta.penalties += 2 * inactivity_penalty + base_reward; + delta.penalize(2 * inactivity_penalty + base_reward); } delta @@ -264,10 +279,12 @@ fn get_crosslink_deltas( )?; if let Some(ref winning_root) = validator.winning_root_info { - delta.rewards += base_reward * winning_root.total_attesting_balance - / winning_root.total_committee_balance + delta.reward( + base_reward * winning_root.total_attesting_balance + / winning_root.total_committee_balance, + ); } else { - delta.penalties += base_reward; + delta.penalize(base_reward); } deltas[index] += delta; From 35b90728c72dfc043ed4aacba8919014f01a4c08 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 19 Mar 2019 19:27:10 +1100 Subject: [PATCH 07/10] Push more epoch processing fns to 0.5.0 --- .../src/common/verify_bitfield.rs | 2 +- .../src/per_epoch_processing.rs | 122 +++++++------- .../process_validator_registry.rs | 70 -------- .../update_registry_and_shuffling_data.rs | 151 ++++++++++++++++++ .../update_validator_registry.rs | 52 ------ eth2/types/src/beacon_state.rs | 74 +++++---- 6 files changed, 252 insertions(+), 219 deletions(-) delete mode 100644 eth2/state_processing/src/per_epoch_processing/process_validator_registry.rs create mode 100644 eth2/state_processing/src/per_epoch_processing/update_registry_and_shuffling_data.rs delete mode 100644 eth2/state_processing/src/per_epoch_processing/update_validator_registry.rs diff --git a/eth2/state_processing/src/common/verify_bitfield.rs b/eth2/state_processing/src/common/verify_bitfield.rs index 8ff5c96ca..03fcdbb67 100644 --- a/eth2/state_processing/src/common/verify_bitfield.rs +++ b/eth2/state_processing/src/common/verify_bitfield.rs @@ -4,7 +4,7 @@ use types::*; /// /// Is title `verify_bitfield` in spec. /// -/// Spec v0.4.0 +/// Spec v0.5.0 pub fn verify_bitfield_length(bitfield: &Bitfield, committee_size: usize) -> bool { if bitfield.num_bytes() != ((committee_size + 7) / 8) { return false; diff --git a/eth2/state_processing/src/per_epoch_processing.rs b/eth2/state_processing/src/per_epoch_processing.rs index 24f4a1e1f..b917510f2 100644 --- a/eth2/state_processing/src/per_epoch_processing.rs +++ b/eth2/state_processing/src/per_epoch_processing.rs @@ -3,10 +3,10 @@ use errors::EpochProcessingError as Error; use process_ejections::process_ejections; use process_exit_queue::process_exit_queue; use process_slashings::process_slashings; -use process_validator_registry::process_validator_registry; use ssz::TreeHash; use std::collections::HashMap; use types::*; +use update_registry_and_shuffling_data::update_registry_and_shuffling_data; use validator_statuses::{TotalBalances, ValidatorStatuses}; use winning_root::{winning_root, WinningRoot}; @@ -17,9 +17,8 @@ pub mod inclusion_distance; pub mod process_ejections; pub mod process_exit_queue; pub mod process_slashings; -pub mod process_validator_registry; pub mod tests; -pub mod update_validator_registry; +pub mod update_registry_and_shuffling_data; pub mod validator_statuses; pub mod winning_root; @@ -39,30 +38,34 @@ pub fn per_epoch_processing(state: &mut BeaconState, spec: &ChainSpec) -> Result state.build_epoch_cache(RelativeEpoch::Previous, spec)?; state.build_epoch_cache(RelativeEpoch::Current, spec)?; - let mut statuses = initialize_validator_statuses(&state, spec)?; + // Load the struct we use to assign validators into sets based on their participation. + // + // E.g., attestation in the previous epoch, attested to the head, etc. + let mut statuses = ValidatorStatuses::new(state, spec)?; + statuses.process_attestations(&state, spec)?; process_eth1_data(state, spec); update_justification_and_finalization(state, &statuses.total_balances, spec)?; - // Crosslinks + // Crosslinks. let winning_root_for_shards = process_crosslinks(state, spec)?; - // Rewards and Penalities + // Rewards and Penalities. apply_rewards(state, &mut statuses, &winning_root_for_shards, spec)?; - // Ejections + // Ejections. process_ejections(state, spec)?; - // Validator Registry - process_validator_registry(state, spec)?; + // Validator Registry. + update_registry_and_shuffling_data(state, statuses.total_balances.current_epoch, spec)?; + + // Slashings and exit queue. process_slashings(state, spec)?; process_exit_queue(state, spec); - // Final updates - update_active_tree_index_roots(state, spec)?; - update_latest_slashed_balances(state, spec)?; - state.previous_epoch_attestations = vec![]; + // Final updates. + finish_epoch_update(state, spec)?; // Rotate the epoch caches to suit the epoch transition. state.advance_caches(); @@ -70,25 +73,6 @@ pub fn per_epoch_processing(state: &mut BeaconState, spec: &ChainSpec) -> Result Ok(()) } -/// Calculates various sets of attesters, including: -/// -/// - current epoch attesters -/// - current epoch boundary attesters -/// - previous epoch attesters -/// - etc. -/// -/// Spec v0.5.0 -pub fn initialize_validator_statuses( - state: &BeaconState, - spec: &ChainSpec, -) -> Result { - let mut statuses = ValidatorStatuses::new(state, spec)?; - - statuses.process_attestations(&state, spec)?; - - Ok(statuses) -} - /// Maybe resets the eth1 period. /// /// Spec v0.5.0 @@ -224,41 +208,53 @@ pub fn process_crosslinks( Ok(winning_root_for_shards) } -/// Updates the state's `latest_active_index_roots` field with a tree hash the active validator -/// indices for the next epoch. +/// Finish up an epoch update. /// -/// Spec v0.4.0 -pub fn update_active_tree_index_roots( - state: &mut BeaconState, - spec: &ChainSpec, -) -> Result<(), Error> { - let next_epoch = state.next_epoch(spec); - - let active_tree_root = state - .get_active_validator_indices(next_epoch + Epoch::from(spec.activation_exit_delay)) - .to_vec() - .hash_tree_root(); - - state.set_active_index_root(next_epoch, Hash256::from_slice(&active_tree_root[..]), spec)?; - - Ok(()) -} - -/// Advances the state's `latest_slashed_balances` field. -/// -/// Spec v0.4.0 -pub fn update_latest_slashed_balances( - state: &mut BeaconState, - spec: &ChainSpec, -) -> Result<(), Error> { +/// Spec v0.5.0 +pub fn finish_epoch_update(state: &mut BeaconState, spec: &ChainSpec) -> Result<(), Error> { let current_epoch = state.current_epoch(spec); let next_epoch = state.next_epoch(spec); - state.set_slashed_balance( - next_epoch, - state.get_slashed_balance(current_epoch, spec)?, - spec, - )?; + // This is a hack to allow us to update index roots and slashed balances for the next epoch. + // + // The indentation here is to make it obvious where the weird stuff happens. + { + state.slot += 1; + + // Set active index root + let active_index_root = Hash256::from_slice( + &state + .get_active_validator_indices(next_epoch + spec.activation_exit_delay) + .hash_tree_root()[..], + ); + state.set_active_index_root(next_epoch, active_index_root, spec)?; + + // Set total slashed balances + state.set_slashed_balance( + next_epoch, + state.get_slashed_balance(current_epoch, spec)?, + spec, + )?; + + // Set randao mix + state.set_randao_mix( + next_epoch, + *state.get_randao_mix(current_epoch, spec)?, + spec, + )?; + + state.slot -= 1; + } + + if next_epoch.as_u64() % (spec.slots_per_historical_root as u64 / spec.slots_per_epoch) == 0 { + let historical_batch: HistoricalBatch = state.historical_batch(); + state + .historical_roots + .push(Hash256::from_slice(&historical_batch.hash_tree_root()[..])); + } + + state.previous_epoch_attestations = state.current_epoch_attestations.clone(); + state.current_epoch_attestations = vec![]; Ok(()) } diff --git a/eth2/state_processing/src/per_epoch_processing/process_validator_registry.rs b/eth2/state_processing/src/per_epoch_processing/process_validator_registry.rs deleted file mode 100644 index 85d6c37f6..000000000 --- a/eth2/state_processing/src/per_epoch_processing/process_validator_registry.rs +++ /dev/null @@ -1,70 +0,0 @@ -use super::update_validator_registry::update_validator_registry; -use super::Error; -use types::*; - -/// Peforms a validator registry update, if required. -/// -/// Spec v0.4.0 -pub 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; - - state.previous_shuffling_seed = state.current_shuffling_seed; - - if should_update_validator_registry(state, spec)? { - update_validator_registry(state, spec)?; - - state.current_shuffling_epoch = next_epoch; - state.current_shuffling_start_shard = (state.current_shuffling_start_shard - + spec.get_epoch_committee_count( - state - .get_cached_active_validator_indices(RelativeEpoch::Current, spec)? - .len(), - ) as u64) - % spec.shard_count; - state.current_shuffling_seed = state.generate_seed(state.current_shuffling_epoch, spec)? - } else { - 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)? - } - } - - Ok(()) -} - -/// Returns `true` if the validator registry should be updated during an epoch processing. -/// -/// Spec v0.5.0 -pub fn should_update_validator_registry( - state: &BeaconState, - spec: &ChainSpec, -) -> Result { - if state.finalized_epoch <= state.validator_registry_update_epoch { - return Ok(false); - } - - let num_active_validators = state - .get_cached_active_validator_indices(RelativeEpoch::Current, spec)? - .len(); - let current_epoch_committee_count = spec.get_epoch_committee_count(num_active_validators); - - for shard in (0..current_epoch_committee_count) - .into_iter() - .map(|i| (state.current_shuffling_start_shard + i as u64) % spec.shard_count) - { - if state.latest_crosslinks[shard as usize].epoch <= state.validator_registry_update_epoch { - return Ok(false); - } - } - - Ok(true) -} diff --git a/eth2/state_processing/src/per_epoch_processing/update_registry_and_shuffling_data.rs b/eth2/state_processing/src/per_epoch_processing/update_registry_and_shuffling_data.rs new file mode 100644 index 000000000..286ad8140 --- /dev/null +++ b/eth2/state_processing/src/per_epoch_processing/update_registry_and_shuffling_data.rs @@ -0,0 +1,151 @@ +use super::super::common::exit_validator; +use super::Error; +use types::*; + +/// Peforms a validator registry update, if required. +/// +/// Spec v0.5.0 +pub fn update_registry_and_shuffling_data( + state: &mut BeaconState, + current_total_balance: u64, + spec: &ChainSpec, +) -> Result<(), Error> { + // First set previous shuffling data to current shuffling data. + state.previous_shuffling_epoch = state.current_shuffling_epoch; + state.previous_shuffling_start_shard = state.previous_shuffling_start_shard; + state.previous_shuffling_seed = state.previous_shuffling_seed; + + let current_epoch = state.current_epoch(spec); + let next_epoch = current_epoch + 1; + + // Check we should update, and if so, update. + if should_update_validator_registry(state, spec)? { + update_validator_registry(state, current_total_balance, spec)?; + + // If we update the registry, update the shuffling data and shards as well. + state.current_shuffling_epoch = next_epoch; + state.current_shuffling_start_shard = { + let active_validators = + state.get_cached_active_validator_indices(RelativeEpoch::Current, spec)?; + let epoch_committee_count = spec.get_epoch_committee_count(active_validators.len()); + + (state.current_shuffling_start_shard + epoch_committee_count) % spec.shard_count + }; + state.current_shuffling_seed = state.generate_seed(state.current_shuffling_epoch, spec)?; + } else { + // If processing at least on crosslink keeps failing, the reshuffle every power of two, but + // don't update the current_shuffling_start_shard. + let epochs_since_last_update = current_epoch - state.validator_registry_update_epoch; + + if epochs_since_last_update > 1 && epochs_since_last_update.is_power_of_two() { + state.current_shuffling_epoch = next_epoch; + state.current_shuffling_seed = + state.generate_seed(state.current_shuffling_epoch, spec)?; + } + } + + Ok(()) +} + +/// Returns `true` if the validator registry should be updated during an epoch processing. +/// +/// Spec v0.5.0 +pub fn should_update_validator_registry( + state: &BeaconState, + spec: &ChainSpec, +) -> Result { + if state.finalized_epoch <= state.validator_registry_update_epoch { + return Ok(false); + } + + let num_active_validators = state + .get_cached_active_validator_indices(RelativeEpoch::Current, spec)? + .len(); + let current_epoch_committee_count = spec.get_epoch_committee_count(num_active_validators); + + for shard in (0..current_epoch_committee_count) + .into_iter() + .map(|i| (state.current_shuffling_start_shard + i as u64) % spec.shard_count) + { + if state.latest_crosslinks[shard as usize].epoch <= state.validator_registry_update_epoch { + return Ok(false); + } + } + + Ok(true) +} + +/// Update validator registry, activating/exiting validators if possible. +/// +/// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. +/// +/// Spec v0.5.0 +pub fn update_validator_registry( + state: &mut BeaconState, + current_total_balance: u64, + spec: &ChainSpec, +) -> Result<(), Error> { + let current_epoch = state.current_epoch(spec); + + let max_balance_churn = std::cmp::max( + spec.max_deposit_amount, + current_total_balance / (2 * spec.max_balance_churn_quotient), + ); + + // Activate validators within the allowable balance churn. + let mut balance_churn = 0; + for index in 0..state.validator_registry.len() { + let not_activated = + state.validator_registry[index].activation_epoch == spec.far_future_epoch; + let has_enough_balance = state.validator_balances[index] >= spec.max_deposit_amount; + + if not_activated && has_enough_balance { + // Check the balance churn would be within the allowance. + balance_churn += state.get_effective_balance(index, spec)?; + if balance_churn > max_balance_churn { + break; + } + + activate_validator(state, index, false, spec); + } + } + + // Exit validators within the allowable balance churn. + let mut balance_churn = 0; + for index in 0..state.validator_registry.len() { + let not_exited = state.validator_registry[index].exit_epoch == spec.far_future_epoch; + let has_initiated_exit = state.validator_registry[index].initiated_exit; + + if not_exited && has_initiated_exit { + // Check the balance churn would be within the allowance. + balance_churn += state.get_effective_balance(index, spec)?; + if balance_churn > max_balance_churn { + break; + } + + exit_validator(state, index, spec)?; + } + } + + state.validator_registry_update_epoch = current_epoch; + + Ok(()) +} + +/// Activate the validator of the given ``index``. +/// +/// Spec v0.5.0 +pub fn activate_validator( + state: &mut BeaconState, + validator_index: usize, + is_genesis: bool, + spec: &ChainSpec, +) { + let current_epoch = state.current_epoch(spec); + + state.validator_registry[validator_index].activation_epoch = if is_genesis { + spec.genesis_epoch + } else { + state.get_delayed_activation_exit_epoch(current_epoch, spec) + } +} diff --git a/eth2/state_processing/src/per_epoch_processing/update_validator_registry.rs b/eth2/state_processing/src/per_epoch_processing/update_validator_registry.rs deleted file mode 100644 index ecf05ce6f..000000000 --- a/eth2/state_processing/src/per_epoch_processing/update_validator_registry.rs +++ /dev/null @@ -1,52 +0,0 @@ -use crate::common::exit_validator; -use types::{BeaconStateError as Error, *}; - -/// Update validator registry, activating/exiting validators if possible. -/// -/// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. -/// -/// Spec v0.4.0 -pub fn update_validator_registry(state: &mut BeaconState, spec: &ChainSpec) -> Result<(), Error> { - let current_epoch = state.current_epoch(spec); - let active_validator_indices = - state.get_cached_active_validator_indices(RelativeEpoch::Current, spec)?; - let total_balance = state.get_total_balance(&active_validator_indices[..], spec)?; - - let max_balance_churn = std::cmp::max( - spec.max_deposit_amount, - total_balance / (2 * spec.max_balance_churn_quotient), - ); - - let mut balance_churn = 0; - for index in 0..state.validator_registry.len() { - let validator = &state.validator_registry[index]; - - if (validator.activation_epoch == spec.far_future_epoch) - & (state.validator_balances[index] == spec.max_deposit_amount) - { - balance_churn += state.get_effective_balance(index, spec)?; - if balance_churn > max_balance_churn { - break; - } - state.activate_validator(index, false, spec); - } - } - - let mut balance_churn = 0; - for index in 0..state.validator_registry.len() { - let validator = &state.validator_registry[index]; - - if (validator.exit_epoch == spec.far_future_epoch) & (validator.initiated_exit) { - balance_churn += state.get_effective_balance(index, spec)?; - if balance_churn > max_balance_churn { - break; - } - - exit_validator(state, index, spec)?; - } - } - - state.validator_registry_update_epoch = current_epoch; - - Ok(()) -} diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index 22e7c6ecf..1a165c9a9 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -193,6 +193,13 @@ impl BeaconState { Hash256::from_slice(&self.hash_tree_root()[..]) } + pub fn historical_batch(&self) -> HistoricalBatch { + HistoricalBatch { + block_roots: self.latest_block_roots.clone(), + state_roots: self.latest_state_roots.clone(), + } + } + /// If a validator pubkey exists in the validator registry, returns `Some(i)`, otherwise /// returns `None`. /// @@ -382,6 +389,26 @@ impl BeaconState { Ok(self.latest_block_roots[i] = block_root) } + /// Safely obtains the index for `latest_randao_mixes` + /// + /// Spec v0.5.0 + fn get_randao_mix_index(&self, epoch: Epoch, spec: &ChainSpec) -> Result { + let current_epoch = self.current_epoch(spec); + + if (current_epoch - (spec.latest_randao_mixes_length as u64) < epoch) + & (epoch <= current_epoch) + { + let i = epoch.as_usize() % spec.latest_randao_mixes_length; + if i < self.latest_randao_mixes.len() { + Ok(i) + } else { + Err(Error::InsufficientRandaoMixes) + } + } else { + Err(Error::EpochOutOfBounds) + } + } + /// XOR-assigns the existing `epoch` randao mix with the hash of the `signature`. /// /// # Errors: @@ -406,24 +433,23 @@ impl BeaconState { /// Return the randao mix at a recent ``epoch``. /// - /// # Errors: - /// - `InsufficientRandaoMixes` if `self.latest_randao_mixes` is shorter than - /// `spec.latest_randao_mixes_length`. - /// - `EpochOutOfBounds` if the state no longer stores randao mixes for the given `epoch`. - /// /// Spec v0.5.0 pub fn get_randao_mix(&self, epoch: Epoch, spec: &ChainSpec) -> Result<&Hash256, Error> { - let current_epoch = self.current_epoch(spec); + let i = self.get_randao_mix_index(epoch, spec)?; + Ok(&self.latest_randao_mixes[i]) + } - if (current_epoch - (spec.latest_randao_mixes_length as u64) < epoch) - & (epoch <= current_epoch) - { - self.latest_randao_mixes - .get(epoch.as_usize() % spec.latest_randao_mixes_length) - .ok_or_else(|| Error::InsufficientRandaoMixes) - } else { - Err(Error::EpochOutOfBounds) - } + /// Set the randao mix at a recent ``epoch``. + /// + /// Spec v0.5.0 + pub fn set_randao_mix( + &mut self, + epoch: Epoch, + mix: Hash256, + spec: &ChainSpec, + ) -> Result<(), Error> { + let i = self.get_randao_mix_index(epoch, spec)?; + Ok(self.latest_randao_mixes[i] = mix) } /// Safely obtains the index for `latest_active_index_roots`, given some `epoch`. @@ -588,24 +614,6 @@ impl BeaconState { epoch + 1 + spec.activation_exit_delay } - /// Activate the validator of the given ``index``. - /// - /// Spec v0.5.0 - pub fn activate_validator( - &mut self, - validator_index: usize, - is_genesis: bool, - spec: &ChainSpec, - ) { - let current_epoch = self.current_epoch(spec); - - self.validator_registry[validator_index].activation_epoch = if is_genesis { - spec.genesis_epoch - } else { - self.get_delayed_activation_exit_epoch(current_epoch, spec) - } - } - /// Initiate an exit for the validator of the given `index`. /// /// Spec v0.5.0 From baca2c90abacfc8fafcaf3bfa590242a0ca8f084 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 19 Mar 2019 19:43:31 +1100 Subject: [PATCH 08/10] Add last of 0.5.0 upgrades. Woo! --- .../src/per_epoch_processing.rs | 31 ++++++++++------ .../process_exit_queue.rs | 4 +-- .../per_epoch_processing/process_slashings.rs | 36 +++++++++---------- 3 files changed, 40 insertions(+), 31 deletions(-) diff --git a/eth2/state_processing/src/per_epoch_processing.rs b/eth2/state_processing/src/per_epoch_processing.rs index b917510f2..fcdc668f4 100644 --- a/eth2/state_processing/src/per_epoch_processing.rs +++ b/eth2/state_processing/src/per_epoch_processing.rs @@ -32,7 +32,7 @@ pub type WinningRootHashSet = HashMap; /// Mutates the given `BeaconState`, returning early if an error is encountered. If an error is /// returned, a state might be "half-processed" and therefore in an invalid state. /// -/// Spec v0.4.0 +/// Spec v0.5.0 pub fn per_epoch_processing(state: &mut BeaconState, spec: &ChainSpec) -> Result<(), Error> { // Ensure the previous and next epoch caches are built. state.build_epoch_cache(RelativeEpoch::Previous, spec)?; @@ -41,27 +41,38 @@ pub fn per_epoch_processing(state: &mut BeaconState, spec: &ChainSpec) -> Result // Load the struct we use to assign validators into sets based on their participation. // // E.g., attestation in the previous epoch, attested to the head, etc. - let mut statuses = ValidatorStatuses::new(state, spec)?; - statuses.process_attestations(&state, spec)?; + let mut validator_statuses = ValidatorStatuses::new(state, spec)?; + validator_statuses.process_attestations(&state, spec)?; - process_eth1_data(state, spec); - - update_justification_and_finalization(state, &statuses.total_balances, spec)?; + // Justification. + update_justification_and_finalization(state, &validator_statuses.total_balances, spec)?; // Crosslinks. let winning_root_for_shards = process_crosslinks(state, spec)?; + // Eth1 data. + maybe_reset_eth1_period(state, spec); + // Rewards and Penalities. - apply_rewards(state, &mut statuses, &winning_root_for_shards, spec)?; + apply_rewards( + state, + &mut validator_statuses, + &winning_root_for_shards, + spec, + )?; // Ejections. process_ejections(state, spec)?; // Validator Registry. - update_registry_and_shuffling_data(state, statuses.total_balances.current_epoch, spec)?; + update_registry_and_shuffling_data( + state, + validator_statuses.total_balances.current_epoch, + spec, + )?; // Slashings and exit queue. - process_slashings(state, spec)?; + process_slashings(state, validator_statuses.total_balances.current_epoch, spec)?; process_exit_queue(state, spec); // Final updates. @@ -76,7 +87,7 @@ pub fn per_epoch_processing(state: &mut BeaconState, spec: &ChainSpec) -> Result /// Maybe resets the eth1 period. /// /// Spec v0.5.0 -pub fn process_eth1_data(state: &mut BeaconState, spec: &ChainSpec) { +pub fn maybe_reset_eth1_period(state: &mut BeaconState, spec: &ChainSpec) { let next_epoch = state.next_epoch(spec); let voting_period = spec.epochs_per_eth1_voting_period; diff --git a/eth2/state_processing/src/per_epoch_processing/process_exit_queue.rs b/eth2/state_processing/src/per_epoch_processing/process_exit_queue.rs index f672c97be..074db1d08 100644 --- a/eth2/state_processing/src/per_epoch_processing/process_exit_queue.rs +++ b/eth2/state_processing/src/per_epoch_processing/process_exit_queue.rs @@ -21,8 +21,8 @@ pub fn process_exit_queue(state: &mut BeaconState, spec: &ChainSpec) { .collect(); eligable_indices.sort_by_key(|i| state.validator_registry[*i].exit_epoch); - for (withdrawn_so_far, index) in eligable_indices.iter().enumerate() { - if withdrawn_so_far as u64 >= spec.max_exit_dequeues_per_epoch { + for (dequeues, index) in eligable_indices.iter().enumerate() { + if dequeues as u64 >= spec.max_exit_dequeues_per_epoch { break; } prepare_validator_for_withdrawal(state, *index, spec); diff --git a/eth2/state_processing/src/per_epoch_processing/process_slashings.rs b/eth2/state_processing/src/per_epoch_processing/process_slashings.rs index 19c1e519b..88777472c 100644 --- a/eth2/state_processing/src/per_epoch_processing/process_slashings.rs +++ b/eth2/state_processing/src/per_epoch_processing/process_slashings.rs @@ -2,34 +2,32 @@ use types::{BeaconStateError as Error, *}; /// Process slashings. /// -/// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. -/// -/// Spec v0.4.0 -pub fn process_slashings(state: &mut BeaconState, spec: &ChainSpec) -> Result<(), Error> { +/// Spec v0.5.0 +pub fn process_slashings( + state: &mut BeaconState, + current_total_balance: u64, + spec: &ChainSpec, +) -> Result<(), Error> { let current_epoch = state.current_epoch(spec); - let active_validator_indices = - state.get_cached_active_validator_indices(RelativeEpoch::Current, spec)?; - let total_balance = state.get_total_balance(&active_validator_indices[..], spec)?; + + let total_at_start = state.get_slashed_balance(current_epoch + 1, spec)?; + let total_at_end = state.get_slashed_balance(current_epoch, spec)?; + let total_penalities = total_at_end - total_at_start; for (index, validator) in state.validator_registry.iter().enumerate() { - if validator.slashed - && (current_epoch - == validator.withdrawable_epoch - Epoch::from(spec.latest_slashed_exit_length / 2)) - { - // TODO: check the following two lines are correct. - let total_at_start = state.get_slashed_balance(current_epoch + 1, spec)?; - let total_at_end = state.get_slashed_balance(current_epoch, spec)?; - - let total_penalities = total_at_end.saturating_sub(total_at_start); + let should_penalize = current_epoch.as_usize() + == validator.withdrawable_epoch.as_usize() - spec.latest_slashed_exit_length / 2; + if validator.slashed && should_penalize { let effective_balance = state.get_effective_balance(index, spec)?; + let penalty = std::cmp::max( - effective_balance * std::cmp::min(total_penalities * 3, total_balance) - / total_balance, + effective_balance * std::cmp::min(total_penalities * 3, current_total_balance) + / current_total_balance, effective_balance / spec.min_penalty_quotient, ); - safe_sub_assign!(state.validator_balances[index], penalty); + state.validator_balances[index] -= penalty; } } From 8f23aefb29f997e46de59ce8b571196b203c3ef6 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 19 Mar 2019 19:55:17 +1100 Subject: [PATCH 09/10] Adds comments to new epoch cache fns. --- eth2/types/src/beacon_state/epoch_cache.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/eth2/types/src/beacon_state/epoch_cache.rs b/eth2/types/src/beacon_state/epoch_cache.rs index ca8bcc70e..6eebf1da3 100644 --- a/eth2/types/src/beacon_state/epoch_cache.rs +++ b/eth2/types/src/beacon_state/epoch_cache.rs @@ -107,6 +107,7 @@ impl EpochCache { }) } + /// Return a vec of `CrosslinkCommittee` for a given slot. pub fn get_crosslink_committees_at_slot( &self, slot: Slot, @@ -116,6 +117,8 @@ impl EpochCache { .get_crosslink_committees_at_slot(slot, spec) } + /// Return `Some(CrosslinkCommittee)` if the given shard has a committee during the given + /// `epoch`. pub fn get_crosslink_committee_for_shard( &self, shard: Shard, @@ -131,6 +134,10 @@ impl EpochCache { } } +/// Returns a list of all `validator_registry` indices where the validator is active at the given +/// `epoch`. +/// +/// Spec v0.5.0 pub fn get_active_validator_indices(validators: &[Validator], epoch: Epoch) -> Vec { let mut active = Vec::with_capacity(validators.len()); @@ -145,13 +152,17 @@ pub fn get_active_validator_indices(validators: &[Validator], epoch: Epoch) -> V active } +/// Contains all `CrosslinkCommittees` for an epoch. #[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize)] pub struct EpochCrosslinkCommittees { + /// The epoch the committees are present in. epoch: Epoch, + /// Each commitee for each slot of the epoch. pub crosslink_committees: Vec>, } impl EpochCrosslinkCommittees { + /// Return a new instances where all slots have zero committees. fn new(epoch: Epoch, spec: &ChainSpec) -> Self { Self { epoch, @@ -159,6 +170,7 @@ impl EpochCrosslinkCommittees { } } + /// Return a vec of `CrosslinkCommittee` for a given slot. fn get_crosslink_committees_at_slot( &self, slot: Slot, @@ -176,6 +188,7 @@ impl EpochCrosslinkCommittees { } } +/// Builds an `EpochCrosslinkCommittees` object. pub struct EpochCrosslinkCommitteesBuilder { epoch: Epoch, shuffling_start_shard: Shard, @@ -185,6 +198,7 @@ pub struct EpochCrosslinkCommitteesBuilder { } impl EpochCrosslinkCommitteesBuilder { + /// Instantiates a builder that will build for the `state`'s previous epoch. pub fn for_previous_epoch( state: &BeaconState, active_validator_indices: Vec, @@ -199,6 +213,7 @@ impl EpochCrosslinkCommitteesBuilder { } } + /// Instantiates a builder that will build for the `state`'s next epoch. pub fn for_current_epoch( state: &BeaconState, active_validator_indices: Vec, @@ -213,6 +228,10 @@ impl EpochCrosslinkCommitteesBuilder { } } + /// Instantiates a builder that will build for the `state`'s next epoch. + /// + /// Note: there are two possible epoch builds for the next epoch, one where there is a registry + /// change and one where there is not. pub fn for_next_epoch( state: &BeaconState, active_validator_indices: Vec, @@ -257,6 +276,7 @@ impl EpochCrosslinkCommitteesBuilder { }) } + /// Consumes the builder, returning a fully-build `EpochCrosslinkCommittee`. pub fn build(self, spec: &ChainSpec) -> Result { // The shuffler fails on a empty list, so if there are no active validator indices, simply // return an empty list. From 84f373fcc2ca1619dc0e3c708dbaa7a04bfa03b8 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 20 Mar 2019 10:51:53 +1100 Subject: [PATCH 10/10] Fix clippy lints --- beacon_node/beacon_chain/src/beacon_chain.rs | 8 ++--- eth2/fork_choice/src/slow_lmd_ghost.rs | 6 ++-- .../src/per_block_processing.rs | 4 +-- .../validate_attestation.rs | 34 ++++++------------- .../per_block_processing/verify_deposit.rs | 4 +-- .../get_attestation_participants.rs | 2 +- .../update_registry_and_shuffling_data.rs | 1 - eth2/types/src/beacon_block.rs | 6 ++-- eth2/types/src/beacon_state.rs | 17 ++++++---- eth2/types/src/beacon_state/epoch_cache.rs | 1 - eth2/types/src/relative_epoch.rs | 2 +- .../testing_beacon_state_builder.rs | 2 +- .../src/test_utils/testing_deposit_builder.rs | 2 +- 13 files changed, 38 insertions(+), 51 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 01787f95b..816a570c0 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -82,7 +82,7 @@ where let state_root = genesis_state.canonical_root(); state_store.put(&state_root, &ssz_encode(&genesis_state)[..])?; - let block_root = genesis_block.into_header().canonical_root(); + let block_root = genesis_block.block_header().canonical_root(); block_store.put(&block_root, &ssz_encode(&genesis_block)[..])?; let finalized_head = RwLock::new(CheckPoint::new( @@ -189,7 +189,7 @@ where pub fn advance_state(&self, slot: Slot) -> Result<(), SlotProcessingError> { let state_slot = self.state.read().slot; - let latest_block_header = self.head().beacon_block.into_header(); + let latest_block_header = self.head().beacon_block.block_header(); for _ in state_slot.as_u64()..slot.as_u64() { per_slot_processing(&mut *self.state.write(), &latest_block_header, &self.spec)?; @@ -561,7 +561,7 @@ where pub fn process_block(&self, block: BeaconBlock) -> Result { debug!("Processing block with slot {}...", block.slot); - let block_root = block.into_header().canonical_root(); + let block_root = block.block_header().canonical_root(); let present_slot = self.present_slot(); @@ -596,7 +596,7 @@ where // Transition the parent state to the present slot. let mut state = parent_state; - let previous_block_header = parent_block.into_header(); + let previous_block_header = parent_block.block_header(); for _ in state.slot.as_u64()..present_slot.as_u64() { if let Err(e) = per_slot_processing(&mut state, &previous_block_header, &self.spec) { return Ok(BlockProcessingOutcome::InvalidBlock( diff --git a/eth2/fork_choice/src/slow_lmd_ghost.rs b/eth2/fork_choice/src/slow_lmd_ghost.rs index 25d137089..4b236cba4 100644 --- a/eth2/fork_choice/src/slow_lmd_ghost.rs +++ b/eth2/fork_choice/src/slow_lmd_ghost.rs @@ -215,10 +215,8 @@ impl ForkChoice for SlowLMDGhost { head_vote_count = vote_count; } // resolve ties - choose smaller hash - else if vote_count == head_vote_count { - if *child_hash < head_hash { - head_hash = *child_hash; - } + else if vote_count == head_vote_count && *child_hash < head_hash { + head_hash = *child_hash; } } } diff --git a/eth2/state_processing/src/per_block_processing.rs b/eth2/state_processing/src/per_block_processing.rs index 33f953b71..dc83abb3f 100644 --- a/eth2/state_processing/src/per_block_processing.rs +++ b/eth2/state_processing/src/per_block_processing.rs @@ -109,7 +109,7 @@ pub fn process_block_header( Invalid::ParentBlockRootMismatch ); - state.latest_block_header = block.into_temporary_header(spec); + state.latest_block_header = block.temporary_block_header(spec); Ok(()) } @@ -388,7 +388,7 @@ pub fn process_deposits( // Create a new validator. let validator = Validator { pubkey: deposit_input.pubkey.clone(), - withdrawal_credentials: deposit_input.withdrawal_credentials.clone(), + withdrawal_credentials: deposit_input.withdrawal_credentials, activation_epoch: spec.far_future_epoch, exit_epoch: spec.far_future_epoch, withdrawable_epoch: spec.far_future_epoch, diff --git a/eth2/state_processing/src/per_block_processing/validate_attestation.rs b/eth2/state_processing/src/per_block_processing/validate_attestation.rs index 113dbc4ce..2143988a4 100644 --- a/eth2/state_processing/src/per_block_processing/validate_attestation.rs +++ b/eth2/state_processing/src/per_block_processing/validate_attestation.rs @@ -176,17 +176,7 @@ fn validate_attestation_signature_optional( ); if verify_signature { - let attestation_epoch = attestation.data.slot.epoch(spec.slots_per_epoch); - verify_attestation_signature( - state, - committee, - attestation_epoch, - &attestation.aggregation_bitfield, - &attestation.custody_bitfield, - &attestation.data, - &attestation.aggregate_signature, - spec, - )?; + verify_attestation_signature(state, committee, attestation, spec)?; } // Crosslink data root is zero (to be removed in phase 1). @@ -210,32 +200,29 @@ fn validate_attestation_signature_optional( fn verify_attestation_signature( state: &BeaconState, committee: &[usize], - attestation_epoch: Epoch, - aggregation_bitfield: &Bitfield, - custody_bitfield: &Bitfield, - attestation_data: &AttestationData, - aggregate_signature: &AggregateSignature, + a: &Attestation, spec: &ChainSpec, ) -> Result<(), Error> { let mut aggregate_pubs = vec![AggregatePublicKey::new(); 2]; let mut message_exists = vec![false; 2]; + let attestation_epoch = a.data.slot.epoch(spec.slots_per_epoch); for (i, v) in committee.iter().enumerate() { - let validator_signed = aggregation_bitfield.get(i).map_err(|_| { + let validator_signed = a.aggregation_bitfield.get(i).map_err(|_| { Error::Invalid(Invalid::BadAggregationBitfieldLength { committee_len: committee.len(), - bitfield_len: aggregation_bitfield.len(), + bitfield_len: a.aggregation_bitfield.len(), }) })?; if validator_signed { - let custody_bit: bool = match custody_bitfield.get(i) { + let custody_bit: bool = match a.custody_bitfield.get(i) { Ok(bit) => bit, // Invalidate signature if custody_bitfield.len() < committee Err(_) => { return Err(Error::Invalid(Invalid::BadCustodyBitfieldLength { committee_len: committee.len(), - bitfield_len: aggregation_bitfield.len(), + bitfield_len: a.aggregation_bitfield.len(), })); } }; @@ -254,14 +241,14 @@ fn verify_attestation_signature( // Message when custody bitfield is `false` let message_0 = AttestationDataAndCustodyBit { - data: attestation_data.clone(), + data: a.data.clone(), custody_bit: false, } .hash_tree_root(); // Message when custody bitfield is `true` let message_1 = AttestationDataAndCustodyBit { - data: attestation_data.clone(), + data: a.data.clone(), custody_bit: true, } .hash_tree_root(); @@ -283,7 +270,8 @@ fn verify_attestation_signature( let domain = spec.get_domain(attestation_epoch, Domain::Attestation, &state.fork); verify!( - aggregate_signature.verify_multiple(&messages[..], domain, &keys[..]), + a.aggregate_signature + .verify_multiple(&messages[..], domain, &keys[..]), Invalid::BadSignature ); diff --git a/eth2/state_processing/src/per_block_processing/verify_deposit.rs b/eth2/state_processing/src/per_block_processing/verify_deposit.rs index 80d8bc24f..a3a0f5734 100644 --- a/eth2/state_processing/src/per_block_processing/verify_deposit.rs +++ b/eth2/state_processing/src/per_block_processing/verify_deposit.rs @@ -71,9 +71,7 @@ pub fn get_existing_validator_index( ) -> Result, Error> { let deposit_input = &deposit.deposit_data.deposit_input; - let validator_index = state - .get_validator_index(&deposit_input.pubkey)? - .and_then(|i| Some(i)); + let validator_index = state.get_validator_index(&deposit_input.pubkey)?; match validator_index { None => Ok(None), diff --git a/eth2/state_processing/src/per_epoch_processing/get_attestation_participants.rs b/eth2/state_processing/src/per_epoch_processing/get_attestation_participants.rs index 3e52776b1..52ba0274b 100644 --- a/eth2/state_processing/src/per_epoch_processing/get_attestation_participants.rs +++ b/eth2/state_processing/src/per_epoch_processing/get_attestation_participants.rs @@ -28,7 +28,7 @@ pub fn get_attestation_participants( let mut participants = Vec::with_capacity(committee.len()); for (i, validator_index) in committee.iter().enumerate() { match bitfield.get(i) { - Ok(bit) if bit == true => participants.push(*validator_index), + Ok(bit) if bit => participants.push(*validator_index), _ => {} } } diff --git a/eth2/state_processing/src/per_epoch_processing/update_registry_and_shuffling_data.rs b/eth2/state_processing/src/per_epoch_processing/update_registry_and_shuffling_data.rs index 286ad8140..0b18c2571 100644 --- a/eth2/state_processing/src/per_epoch_processing/update_registry_and_shuffling_data.rs +++ b/eth2/state_processing/src/per_epoch_processing/update_registry_and_shuffling_data.rs @@ -64,7 +64,6 @@ pub fn should_update_validator_registry( let current_epoch_committee_count = spec.get_epoch_committee_count(num_active_validators); for shard in (0..current_epoch_committee_count) - .into_iter() .map(|i| (state.current_shuffling_start_shard + i as u64) % spec.shard_count) { if state.latest_crosslinks[shard as usize].epoch <= state.validator_registry_update_epoch { diff --git a/eth2/types/src/beacon_block.rs b/eth2/types/src/beacon_block.rs index b966751ed..6a3f1a354 100644 --- a/eth2/types/src/beacon_block.rs +++ b/eth2/types/src/beacon_block.rs @@ -71,7 +71,7 @@ impl BeaconBlock { /// Note: performs a full tree-hash of `self.body`. /// /// Spec v0.5.0 - pub fn into_header(&self) -> BeaconBlockHeader { + pub fn block_header(&self) -> BeaconBlockHeader { BeaconBlockHeader { slot: self.slot, previous_block_root: self.previous_block_root, @@ -84,11 +84,11 @@ impl BeaconBlock { /// Returns a "temporary" header, where the `state_root` is `spec.zero_hash`. /// /// Spec v0.5.0 - pub fn into_temporary_header(&self, spec: &ChainSpec) -> BeaconBlockHeader { + pub fn temporary_block_header(&self, spec: &ChainSpec) -> BeaconBlockHeader { BeaconBlockHeader { state_root: spec.zero_hash, signature: spec.empty_signature.clone(), - ..self.into_header() + ..self.block_header() } } } diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index 1a165c9a9..1e5278124 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -162,7 +162,7 @@ impl BeaconState { latest_state_roots: vec![spec.zero_hash; spec.slots_per_historical_root], latest_active_index_roots: vec![spec.zero_hash; spec.latest_active_index_roots_length], latest_slashed_balances: vec![0; spec.latest_slashed_exit_length], - latest_block_header: BeaconBlock::empty(spec).into_temporary_header(spec), + latest_block_header: BeaconBlock::empty(spec).temporary_block_header(spec), historical_roots: vec![], /* @@ -386,7 +386,8 @@ impl BeaconState { spec: &ChainSpec, ) -> Result<(), BeaconStateError> { let i = self.get_latest_block_roots_index(slot, spec)?; - Ok(self.latest_block_roots[i] = block_root) + self.latest_block_roots[i] = block_root; + Ok(()) } /// Safely obtains the index for `latest_randao_mixes` @@ -449,7 +450,8 @@ impl BeaconState { spec: &ChainSpec, ) -> Result<(), Error> { let i = self.get_randao_mix_index(epoch, spec)?; - Ok(self.latest_randao_mixes[i] = mix) + self.latest_randao_mixes[i] = mix; + Ok(()) } /// Safely obtains the index for `latest_active_index_roots`, given some `epoch`. @@ -492,7 +494,8 @@ impl BeaconState { spec: &ChainSpec, ) -> Result<(), Error> { let i = self.get_active_index_root_index(epoch, spec)?; - Ok(self.latest_active_index_roots[i] = index_root) + self.latest_active_index_roots[i] = index_root; + Ok(()) } /// Replace `active_index_roots` with clones of `index_root`. @@ -537,7 +540,8 @@ impl BeaconState { spec: &ChainSpec, ) -> Result<(), Error> { let i = self.get_latest_state_roots_index(slot, spec)?; - Ok(self.latest_state_roots[i] = state_root) + self.latest_state_roots[i] = state_root; + Ok(()) } /// Safely obtains the index for `latest_slashed_balances`, given some `epoch`. @@ -573,7 +577,8 @@ impl BeaconState { spec: &ChainSpec, ) -> Result<(), Error> { let i = self.get_slashed_balance_index(epoch, spec)?; - Ok(self.latest_slashed_balances[i] = balance) + self.latest_slashed_balances[i] = balance; + Ok(()) } /// Generate a seed for the given `epoch`. diff --git a/eth2/types/src/beacon_state/epoch_cache.rs b/eth2/types/src/beacon_state/epoch_cache.rs index 6eebf1da3..32d9a643e 100644 --- a/eth2/types/src/beacon_state/epoch_cache.rs +++ b/eth2/types/src/beacon_state/epoch_cache.rs @@ -304,7 +304,6 @@ impl EpochCrosslinkCommitteesBuilder { for (i, slot) in self.epoch.slot_iter(spec.slots_per_epoch).enumerate() { for j in (0..committees.len()) - .into_iter() .skip(i * committees_per_slot) .take(committees_per_slot) { diff --git a/eth2/types/src/relative_epoch.rs b/eth2/types/src/relative_epoch.rs index 6c135b1a6..8f895e97a 100644 --- a/eth2/types/src/relative_epoch.rs +++ b/eth2/types/src/relative_epoch.rs @@ -33,7 +33,7 @@ impl RelativeEpoch { /// Returns the `epoch` that `self` refers to, with respect to the `base` epoch. /// /// Spec v0.5.0 - pub fn into_epoch(&self, base: Epoch) -> Epoch { + pub fn into_epoch(self, base: Epoch) -> Epoch { match self { RelativeEpoch::Previous => base - 1, RelativeEpoch::Current => base, diff --git a/eth2/types/src/test_utils/testing_beacon_state_builder.rs b/eth2/types/src/test_utils/testing_beacon_state_builder.rs index e76a01e49..6945769aa 100644 --- a/eth2/types/src/test_utils/testing_beacon_state_builder.rs +++ b/eth2/types/src/test_utils/testing_beacon_state_builder.rs @@ -214,7 +214,7 @@ impl TestingBeaconStateBuilder { - spec.min_attestation_inclusion_delay; let last_slot = std::cmp::min(state.slot.as_u64(), last_slot); - for slot in first_slot..last_slot + 1 { + for slot in first_slot..=last_slot { let slot = Slot::from(slot); let committees = state diff --git a/eth2/types/src/test_utils/testing_deposit_builder.rs b/eth2/types/src/test_utils/testing_deposit_builder.rs index ee258e7fe..326858c31 100644 --- a/eth2/types/src/test_utils/testing_deposit_builder.rs +++ b/eth2/types/src/test_utils/testing_deposit_builder.rs @@ -47,7 +47,7 @@ impl TestingDepositBuilder { self.deposit .deposit_data .deposit_input - .withdrawal_credentials = withdrawal_credentials.clone(); + .withdrawal_credentials = withdrawal_credentials; self.deposit.deposit_data.deposit_input.proof_of_possession = self .deposit