spec: initiate_validator_exit v0.6.1
Added a new field `exit_cache` to the BeaconState, which caches the number of validators exiting at each epoch.
This commit is contained in:
parent
5c03f7b06c
commit
5394726caf
39
eth2/state_processing/src/common/exit.rs
Normal file
39
eth2/state_processing/src/common/exit.rs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
use std::cmp::max;
|
||||||
|
use types::{BeaconStateError as Error, *};
|
||||||
|
|
||||||
|
/// Initiate the exit of the validator of the given `index`.
|
||||||
|
///
|
||||||
|
/// Spec v0.6.1
|
||||||
|
pub fn initiate_validator_exit(
|
||||||
|
state: &mut BeaconState,
|
||||||
|
index: usize,
|
||||||
|
spec: &ChainSpec,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
if index >= state.validator_registry.len() {
|
||||||
|
return Err(Error::UnknownValidator);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return if the validator already initiated exit
|
||||||
|
if state.validator_registry[index].exit_epoch != spec.far_future_epoch {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute exit queue epoch
|
||||||
|
let delayed_epoch = state.get_delayed_activation_exit_epoch(state.current_epoch(spec), spec);
|
||||||
|
let mut exit_queue_epoch = state
|
||||||
|
.exit_cache
|
||||||
|
.max_epoch()
|
||||||
|
.map_or(delayed_epoch, |epoch| max(epoch, delayed_epoch));
|
||||||
|
let exit_queue_churn = state.exit_cache.get_churn_at(exit_queue_epoch);
|
||||||
|
|
||||||
|
if exit_queue_churn >= state.get_churn_limit(spec)? {
|
||||||
|
exit_queue_epoch += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.exit_cache.record_validator_exit(exit_queue_epoch);
|
||||||
|
state.validator_registry[index].exit_epoch = exit_queue_epoch;
|
||||||
|
state.validator_registry[index].withdrawable_epoch =
|
||||||
|
exit_queue_epoch + spec.min_validator_withdrawability_delay;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -1,22 +0,0 @@
|
|||||||
use types::{BeaconStateError as Error, *};
|
|
||||||
|
|
||||||
/// Exit the validator of the given `index`.
|
|
||||||
///
|
|
||||||
/// Spec v0.5.1
|
|
||||||
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(())
|
|
||||||
}
|
|
@ -1,7 +1,7 @@
|
|||||||
mod exit_validator;
|
mod exit;
|
||||||
mod slash_validator;
|
mod slash_validator;
|
||||||
mod verify_bitfield;
|
mod verify_bitfield;
|
||||||
|
|
||||||
pub use exit_validator::exit_validator;
|
pub use exit::initiate_validator_exit;
|
||||||
pub use slash_validator::slash_validator;
|
pub use slash_validator::slash_validator;
|
||||||
pub use verify_bitfield::verify_bitfield_length;
|
pub use verify_bitfield::verify_bitfield_length;
|
||||||
|
@ -1,62 +1,46 @@
|
|||||||
use crate::common::exit_validator;
|
use crate::common::initiate_validator_exit;
|
||||||
use types::{BeaconStateError as Error, *};
|
use types::{BeaconStateError as Error, *};
|
||||||
|
|
||||||
/// Slash the validator with index ``index``.
|
/// Slash the validator with index ``index``.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.1
|
||||||
pub fn slash_validator(
|
pub fn slash_validator(
|
||||||
state: &mut BeaconState,
|
state: &mut BeaconState,
|
||||||
validator_index: usize,
|
slashed_index: usize,
|
||||||
|
opt_whistleblower_index: Option<usize>,
|
||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let current_epoch = state.current_epoch(spec);
|
if slashed_index >= state.validator_registry.len() || slashed_index >= state.balances.len() {
|
||||||
|
|
||||||
if (validator_index >= state.validator_registry.len())
|
|
||||||
| (validator_index >= state.validator_balances.len())
|
|
||||||
{
|
|
||||||
return Err(BeaconStateError::UnknownValidator);
|
return Err(BeaconStateError::UnknownValidator);
|
||||||
}
|
}
|
||||||
|
|
||||||
let validator = &state.validator_registry[validator_index];
|
let current_epoch = state.current_epoch(spec);
|
||||||
|
|
||||||
let effective_balance = state.get_effective_balance(validator_index, spec)?;
|
initiate_validator_exit(state, slashed_index, spec)?;
|
||||||
|
|
||||||
// A validator that is withdrawn cannot be slashed.
|
state.validator_registry[slashed_index].slashed = true;
|
||||||
//
|
state.validator_registry[slashed_index].withdrawable_epoch =
|
||||||
// This constraint will be lifted in Phase 0.
|
current_epoch + Epoch::from(spec.latest_slashed_exit_length);
|
||||||
if state.slot
|
let slashed_balance = state.get_effective_balance(slashed_index, spec)?;
|
||||||
>= validator
|
|
||||||
.withdrawable_epoch
|
|
||||||
.start_slot(spec.slots_per_epoch)
|
|
||||||
{
|
|
||||||
return Err(Error::ValidatorIsWithdrawable);
|
|
||||||
}
|
|
||||||
|
|
||||||
exit_validator(state, validator_index, spec)?;
|
|
||||||
|
|
||||||
state.set_slashed_balance(
|
state.set_slashed_balance(
|
||||||
current_epoch,
|
current_epoch,
|
||||||
state.get_slashed_balance(current_epoch, spec)? + effective_balance,
|
state.get_slashed_balance(current_epoch, spec)? + slashed_balance,
|
||||||
spec,
|
spec,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let whistleblower_index =
|
let proposer_index =
|
||||||
state.get_beacon_proposer_index(state.slot, RelativeEpoch::Current, spec)?;
|
state.get_beacon_proposer_index(state.slot, RelativeEpoch::Current, spec)?;
|
||||||
let whistleblower_reward = effective_balance / spec.whistleblower_reward_quotient;
|
let whistleblower_index = opt_whistleblower_index.unwrap_or(proposer_index);
|
||||||
|
let whistleblowing_reward = slashed_balance / spec.whistleblowing_reward_quotient;
|
||||||
|
let proposer_reward = whistleblowing_reward / spec.proposer_reward_quotient;
|
||||||
|
|
||||||
|
safe_add_assign!(state.balances[proposer_index], proposer_reward);
|
||||||
safe_add_assign!(
|
safe_add_assign!(
|
||||||
state.validator_balances[whistleblower_index as usize],
|
state.balances[whistleblower_index],
|
||||||
whistleblower_reward
|
whistleblowing_reward - proposer_reward
|
||||||
);
|
);
|
||||||
safe_sub_assign!(
|
safe_sub_assign!(state.balances[slashed_index], whistleblowing_reward);
|
||||||
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use self::epoch_cache::{get_active_validator_indices, EpochCache, Error as EpochCacheError};
|
use self::epoch_cache::{get_active_validator_indices, EpochCache, Error as EpochCacheError};
|
||||||
|
use self::exit_cache::ExitCache;
|
||||||
use crate::test_utils::TestRandom;
|
use crate::test_utils::TestRandom;
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use cached_tree_hash::{Error as TreeHashCacheError, TreeHashCache};
|
use cached_tree_hash::{Error as TreeHashCacheError, TreeHashCache};
|
||||||
@ -13,6 +14,7 @@ use tree_hash::TreeHash;
|
|||||||
use tree_hash_derive::{CachedTreeHash, TreeHash};
|
use tree_hash_derive::{CachedTreeHash, TreeHash};
|
||||||
|
|
||||||
mod epoch_cache;
|
mod epoch_cache;
|
||||||
|
mod exit_cache;
|
||||||
mod pubkey_cache;
|
mod pubkey_cache;
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
@ -126,6 +128,12 @@ pub struct BeaconState {
|
|||||||
#[tree_hash(skip_hashing)]
|
#[tree_hash(skip_hashing)]
|
||||||
#[test_random(default)]
|
#[test_random(default)]
|
||||||
pub tree_hash_cache: TreeHashCache,
|
pub tree_hash_cache: TreeHashCache,
|
||||||
|
#[serde(skip_serializing, skip_deserializing)]
|
||||||
|
#[ssz(skip_serializing)]
|
||||||
|
#[ssz(skip_deserializing)]
|
||||||
|
#[tree_hash(skip_hashing)]
|
||||||
|
#[test_random(default)]
|
||||||
|
pub exit_cache: ExitCache,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BeaconState {
|
impl BeaconState {
|
||||||
@ -198,6 +206,7 @@ impl BeaconState {
|
|||||||
],
|
],
|
||||||
pubkey_cache: PubkeyCache::default(),
|
pubkey_cache: PubkeyCache::default(),
|
||||||
tree_hash_cache: TreeHashCache::default(),
|
tree_hash_cache: TreeHashCache::default(),
|
||||||
|
exit_cache: ExitCache::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -634,6 +643,21 @@ impl BeaconState {
|
|||||||
epoch + 1 + spec.activation_exit_delay
|
epoch + 1 + spec.activation_exit_delay
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the churn limit for the current epoch (number of validators who can leave per epoch).
|
||||||
|
///
|
||||||
|
/// Uses the epoch cache, and will error if it isn't initialized.
|
||||||
|
///
|
||||||
|
/// Spec v0.6.1
|
||||||
|
pub fn get_churn_limit(&self, spec: &ChainSpec) -> Result<u64, Error> {
|
||||||
|
Ok(std::cmp::max(
|
||||||
|
spec.min_per_epoch_churn_limit,
|
||||||
|
self.cache(RelativeEpoch::Current, spec)?
|
||||||
|
.active_validator_indices
|
||||||
|
.len() as u64
|
||||||
|
/ spec.churn_limit_quotient,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
/// Initiate an exit for the validator of the given `index`.
|
/// Initiate an exit for the validator of the given `index`.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.5.1
|
||||||
@ -685,6 +709,8 @@ impl BeaconState {
|
|||||||
self.build_epoch_cache(RelativeEpoch::NextWithRegistryChange, spec)?;
|
self.build_epoch_cache(RelativeEpoch::NextWithRegistryChange, spec)?;
|
||||||
self.update_pubkey_cache()?;
|
self.update_pubkey_cache()?;
|
||||||
self.update_tree_hash_cache()?;
|
self.update_tree_hash_cache()?;
|
||||||
|
self.exit_cache
|
||||||
|
.build_from_registry(&self.validator_registry, spec);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
35
eth2/types/src/beacon_state/exit_cache.rs
Normal file
35
eth2/types/src/beacon_state/exit_cache.rs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
use super::{ChainSpec, Epoch, Validator};
|
||||||
|
use serde_derive::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
/// Map from exit epoch to the number of validators known to be exiting/exited at that epoch.
|
||||||
|
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct ExitCache(HashMap<Epoch, u64>);
|
||||||
|
|
||||||
|
impl ExitCache {
|
||||||
|
/// Add all validators with a non-trivial exit epoch to the cache.
|
||||||
|
pub fn build_from_registry(&mut self, validator_registry: &[Validator], spec: &ChainSpec) {
|
||||||
|
validator_registry
|
||||||
|
.iter()
|
||||||
|
.filter(|validator| validator.exit_epoch != spec.far_future_epoch)
|
||||||
|
.for_each(|validator| self.record_validator_exit(validator.exit_epoch));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Record the exit of a single validator in the cache.
|
||||||
|
///
|
||||||
|
/// Must only be called once per exiting validator.
|
||||||
|
pub fn record_validator_exit(&mut self, exit_epoch: Epoch) {
|
||||||
|
*self.0.entry(exit_epoch).or_insert(0) += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the greatest epoch for which validator exits are known.
|
||||||
|
pub fn max_epoch(&self) -> Option<Epoch> {
|
||||||
|
// This could probably be made even faster by caching the maximum.
|
||||||
|
self.0.keys().max().cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the number of validators exiting/exited at a given epoch, or zero if not known.
|
||||||
|
pub fn get_churn_at(&self, epoch: Epoch) -> u64 {
|
||||||
|
self.0.get(&epoch).cloned().unwrap_or(0)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user