Split validator into ValidatorMutable

This commit is contained in:
Michael Sproul
2022-09-28 11:43:58 +10:00
parent 9ec454aa52
commit 9a1799f235
22 changed files with 133 additions and 97 deletions

View File

@@ -9,7 +9,7 @@ pub fn initiate_validator_exit<T: EthSpec>(
spec: &ChainSpec,
) -> Result<(), Error> {
// Return if the validator already initiated exit
if state.get_validator(index)?.exit_epoch != spec.far_future_epoch {
if state.get_validator(index)?.exit_epoch() != spec.far_future_epoch {
return Ok(());
}
@@ -34,8 +34,8 @@ pub fn initiate_validator_exit<T: EthSpec>(
// FIXME(sproul): could avoid this second lookup with some clever borrowing
let mut validator = state.get_validator_mut(index)?;
validator.exit_epoch = exit_queue_epoch;
validator.withdrawable_epoch =
validator.mutable.exit_epoch = exit_queue_epoch;
validator.mutable.withdrawable_epoch =
exit_queue_epoch.safe_add(spec.min_validator_withdrawability_delay)?;
Ok(())

View File

@@ -18,12 +18,12 @@ pub fn slash_validator<T: EthSpec>(
initiate_validator_exit(state, slashed_index, spec)?;
let validator = state.get_validator_mut(slashed_index)?;
validator.slashed = true;
validator.withdrawable_epoch = cmp::max(
validator.withdrawable_epoch,
validator.mutable.slashed = true;
validator.mutable.withdrawable_epoch = cmp::max(
validator.withdrawable_epoch(),
epoch.safe_add(T::EpochsPerSlashingsVector::to_u64())?,
);
let validator_effective_balance = validator.effective_balance;
let validator_effective_balance = validator.effective_balance();
state.set_slashings(
epoch,
state

View File

@@ -104,13 +104,13 @@ pub fn process_activations<T: EthSpec>(
.get(index)
.copied()
.ok_or(Error::BalancesOutOfBounds(index))?;
validator.effective_balance = std::cmp::min(
validator.mutable.effective_balance = std::cmp::min(
balance.safe_sub(balance.safe_rem(spec.effective_balance_increment)?)?,
spec.max_effective_balance,
);
if validator.effective_balance == spec.max_effective_balance {
validator.activation_eligibility_epoch = T::genesis_epoch();
validator.activation_epoch = T::genesis_epoch();
if validator.effective_balance() == spec.max_effective_balance {
validator.mutable.activation_eligibility_epoch = T::genesis_epoch();
validator.mutable.activation_epoch = T::genesis_epoch();
}
}
Ok(())

View File

@@ -227,7 +227,7 @@ pub fn process_block_header<T: EthSpec>(
// Verify proposer is not slashed
verify!(
!state.get_validator(proposer_index as usize)?.slashed,
!state.get_validator(proposer_index as usize)?.slashed(),
HeaderInvalid::ProposerSlashed(proposer_index)
);

View File

@@ -146,7 +146,7 @@ pub mod altair {
{
// FIXME(sproul): add effective balance cache here?
validator_participation.add_flag(flag_index)?;
let effective_balance = state.get_validator(index)?.effective_balance;
let effective_balance = state.get_validator(index)?.effective_balance();
proposer_reward_numerator.safe_add_assign(
get_base_reward(effective_balance, base_reward_per_increment, spec)?
.safe_mul(weight)?,
@@ -347,15 +347,17 @@ pub fn process_deposit<T: EthSpec>(
pubkey: deposit.data.pubkey,
withdrawal_credentials: deposit.data.withdrawal_credentials,
}),
activation_eligibility_epoch: spec.far_future_epoch,
activation_epoch: spec.far_future_epoch,
exit_epoch: spec.far_future_epoch,
withdrawable_epoch: spec.far_future_epoch,
effective_balance: std::cmp::min(
amount.safe_sub(amount.safe_rem(spec.effective_balance_increment)?)?,
spec.max_effective_balance,
),
slashed: false,
mutable: ValidatorMutable {
activation_eligibility_epoch: spec.far_future_epoch,
activation_epoch: spec.far_future_epoch,
exit_epoch: spec.far_future_epoch,
withdrawable_epoch: spec.far_future_epoch,
effective_balance: std::cmp::min(
amount.safe_sub(amount.safe_rem(spec.effective_balance_increment)?)?,
spec.max_effective_balance,
),
slashed: false,
},
};
state.validators_mut().push(validator)?;
state.balances_mut().push(deposit.data.amount)?;

View File

@@ -39,7 +39,7 @@ pub fn verify_exit<T: EthSpec>(
// Verify that the validator has not yet exited.
verify!(
validator.exit_epoch == spec.far_future_epoch,
validator.exit_epoch() == spec.far_future_epoch,
ExitInvalid::AlreadyExited(exit.validator_index)
);
@@ -54,7 +54,7 @@ pub fn verify_exit<T: EthSpec>(
// Verify the validator has been active long enough.
let earliest_exit_epoch = validator
.activation_epoch
.activation_epoch()
.safe_add(spec.shard_committee_period)?;
verify!(
state.current_epoch() >= earliest_exit_epoch,

View File

@@ -129,10 +129,10 @@ impl SingleEpochParticipationCache {
// All active validators increase the total active balance.
self.total_active_balance
.safe_add_assign(validator.effective_balance)?;
.safe_add_assign(validator.effective_balance())?;
// Only unslashed validators may proceed.
if validator.slashed {
if validator.slashed() {
return Ok(());
}
@@ -140,7 +140,7 @@ impl SingleEpochParticipationCache {
// are set for `val_index`.
for (flag, balance) in self.total_flag_balances.iter_mut().enumerate() {
if epoch_participation.has_flag(flag)? {
balance.safe_add_assign(validator.effective_balance)?;
balance.safe_add_assign(validator.effective_balance())?;
}
}
@@ -288,11 +288,11 @@ impl ParticipationCache {
)?;
}
if val.slashed
if val.slashed()
&& current_epoch.safe_add(T::EpochsPerSlashingsVector::to_u64().safe_div(2)?)?
== val.withdrawable_epoch
== val.withdrawable_epoch()
{
process_slashings_indices.push((val_index, val.effective_balance));
process_slashings_indices.push((val_index, val.effective_balance()));
}
// Note: a validator might still be "eligible" whilst returning `false` to
@@ -303,10 +303,10 @@ impl ParticipationCache {
}
let mut validator_info = ValidatorInfo {
effective_balance: val.effective_balance,
effective_balance: val.effective_balance(),
base_reward: 0, // not read
is_eligible,
is_slashed: val.slashed,
is_slashed: val.slashed(),
is_active_current_epoch,
is_active_previous_epoch,
previous_epoch_participation: *prev_epoch_flags,
@@ -332,7 +332,7 @@ impl ParticipationCache {
#[allow(clippy::indexing_slicing)]
if is_eligible || is_active_current_epoch {
let effective_balance = val.effective_balance;
let effective_balance = val.effective_balance();
let base_reward =
get_base_reward(effective_balance, base_reward_per_increment, spec)?;
validator_info.base_reward = base_reward;

View File

@@ -198,7 +198,7 @@ impl ValidatorStatuses {
for (i, validator) in state.validators().iter().enumerate() {
let effective_balance = state.get_effective_balance(i)?;
let mut status = ValidatorStatus {
is_slashed: validator.slashed,
is_slashed: validator.slashed(),
is_withdrawable_in_current_epoch: validator
.is_withdrawable_at(state.current_epoch()),
current_epoch_effective_balance: effective_balance,

View File

@@ -28,23 +28,23 @@ pub fn process_effective_balance_updates<T: EthSpec>(
.ok_or(BeaconStateError::BalancesOutOfBounds(index))?;
let new_effective_balance = if balance.safe_add(downward_threshold)?
< validator.effective_balance
|| validator.effective_balance.safe_add(upward_threshold)? < balance
< validator.effective_balance()
|| validator.effective_balance().safe_add(upward_threshold)? < balance
{
std::cmp::min(
balance.safe_sub(balance.safe_rem(spec.effective_balance_increment)?)?,
spec.max_effective_balance,
)
} else {
validator.effective_balance
validator.effective_balance()
};
if validator.is_active_at(next_epoch) {
new_total_active_balance.safe_add_assign(new_effective_balance)?;
}
if new_effective_balance != validator.effective_balance {
validator.to_mut().effective_balance = new_effective_balance;
if new_effective_balance != validator.effective_balance() {
validator.to_mut().mutable.effective_balance = new_effective_balance;
}
}

View File

@@ -17,7 +17,7 @@ pub fn process_registry_updates<T: EthSpec>(
let current_epoch = state.current_epoch();
let is_ejectable = |validator: &Validator| {
validator.is_active_at(current_epoch)
&& validator.effective_balance <= spec.ejection_balance
&& validator.effective_balance() <= spec.ejection_balance
};
let indices_to_update: Vec<_> = state
.validators()
@@ -32,7 +32,7 @@ pub fn process_registry_updates<T: EthSpec>(
for index in indices_to_update {
let validator = state.get_validator_mut(index)?;
if validator.is_eligible_for_activation_queue(spec) {
validator.activation_eligibility_epoch = current_epoch.safe_add(1)?;
validator.mutable.activation_eligibility_epoch = current_epoch.safe_add(1)?;
}
if is_ejectable(validator) {
initiate_validator_exit(state, index, spec)?;
@@ -45,7 +45,7 @@ pub fn process_registry_updates<T: EthSpec>(
.iter()
.enumerate()
.filter(|(_, validator)| validator.is_eligible_for_activation(state, spec))
.sorted_by_key(|(index, validator)| (validator.activation_eligibility_epoch, *index))
.sorted_by_key(|(index, validator)| (validator.activation_eligibility_epoch(), *index))
.map(|(index, _)| index)
.collect_vec();
@@ -53,7 +53,7 @@ pub fn process_registry_updates<T: EthSpec>(
let churn_limit = state.get_churn_limit(spec)? as usize;
let delayed_activation_epoch = state.compute_activation_exit_epoch(current_epoch, spec)?;
for index in activation_queue.into_iter().take(churn_limit) {
state.get_validator_mut(index)?.activation_epoch = delayed_activation_epoch;
state.get_validator_mut(index)?.mutable.activation_epoch = delayed_activation_epoch;
}
Ok(())

View File

@@ -26,9 +26,9 @@ pub fn process_slashings<T: EthSpec>(
.iter()
.enumerate()
.filter(|(_, validator)| {
validator.slashed && target_withdrawable_epoch == validator.withdrawable_epoch
validator.slashed() && target_withdrawable_epoch == validator.withdrawable_epoch()
})
.map(|(index, validator)| (index, validator.effective_balance))
.map(|(index, validator)| (index, validator.effective_balance()))
.collect()
});

View File

@@ -833,7 +833,7 @@ impl<T: EthSpec> BeaconState<T> {
.get(shuffled_index)
.ok_or(Error::ShuffleIndexOutOfBounds(shuffled_index))?;
let random_byte = Self::shuffling_random_byte(i, seed.as_bytes())?;
let effective_balance = self.get_validator(candidate_index)?.effective_balance;
let effective_balance = self.get_validator(candidate_index)?.effective_balance();
if effective_balance.safe_mul(MAX_RANDOM_BYTE)?
>= spec
.max_effective_balance
@@ -1196,7 +1196,7 @@ impl<T: EthSpec> BeaconState<T> {
/// Return the effective balance for a validator with the given `validator_index`.
pub fn get_effective_balance(&self, validator_index: usize) -> Result<u64, Error> {
self.get_validator(validator_index)
.map(|v| v.effective_balance)
.map(|v| v.effective_balance())
}
/// Get the inactivity score for a single validator.
@@ -1291,7 +1291,7 @@ impl<T: EthSpec> BeaconState<T> {
for validator in self.validators() {
if validator.is_active_at(epoch) {
total_active_balance.safe_add_assign(validator.effective_balance)?;
total_active_balance.safe_add_assign(validator.effective_balance())?;
}
}
Ok(std::cmp::max(
@@ -1636,7 +1636,7 @@ impl<T: EthSpec> BeaconState<T> {
/// a tangible speed improvement in state processing.
pub fn is_eligible_validator(&self, previous_epoch: Epoch, val: &Validator) -> bool {
val.is_active_at(previous_epoch)
|| (val.slashed && previous_epoch + Epoch::new(1) < val.withdrawable_epoch)
|| (val.slashed() && previous_epoch + Epoch::new(1) < val.withdrawable_epoch())
}
/// Passing `previous_epoch` to this function rather than computing it internally provides

View File

@@ -23,8 +23,8 @@ impl ExitCache {
// Add all validators with a non-default exit epoch to the cache.
validators
.into_iter()
.filter(|validator| validator.exit_epoch != spec.far_future_epoch)
.try_for_each(|validator| exit_cache.record_validator_exit(validator.exit_epoch))?;
.filter(|validator| validator.exit_epoch() != spec.far_future_epoch)
.try_for_each(|validator| exit_cache.record_validator_exit(validator.exit_epoch()))?;
Ok(exit_cache)
}

View File

@@ -160,7 +160,7 @@ pub use crate::sync_committee_subscription::SyncCommitteeSubscription;
pub use crate::sync_duty::SyncDuty;
pub use crate::sync_selection_proof::SyncSelectionProof;
pub use crate::sync_subnet_id::SyncSubnetId;
pub use crate::validator::{Validator, ValidatorImmutable};
pub use crate::validator::{Validator, ValidatorImmutable, ValidatorMutable};
pub use crate::validator_registration_data::*;
pub use crate::validator_subscription::ValidatorSubscription;
pub use crate::voluntary_exit::VoluntaryExit;

View File

@@ -12,8 +12,16 @@ const NUM_FIELDS: usize = 8;
/// Information about a `BeaconChain` validator.
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, TestRandom)]
// FIXME(sproul): fix serialize/deserialize impl
pub struct Validator {
pub immutable: Arc<ValidatorImmutable>,
pub mutable: ValidatorMutable,
}
/// The mutable fields of a validator.
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, TestRandom)]
pub struct ValidatorMutable {
#[serde(with = "eth2_serde_utils::quoted_u64")]
pub effective_balance: u64,
pub slashed: bool,
@@ -48,32 +56,56 @@ impl Validator {
self.immutable.withdrawal_credentials
}
pub fn effective_balance(&self) -> u64 {
self.mutable.effective_balance
}
pub fn slashed(&self) -> bool {
self.mutable.slashed
}
pub fn activation_eligibility_epoch(&self) -> Epoch {
self.mutable.activation_eligibility_epoch
}
pub fn activation_epoch(&self) -> Epoch {
self.mutable.activation_epoch
}
pub fn exit_epoch(&self) -> Epoch {
self.mutable.exit_epoch
}
pub fn withdrawable_epoch(&self) -> Epoch {
self.mutable.withdrawable_epoch
}
/// Returns `true` if the validator is considered active at some epoch.
pub fn is_active_at(&self, epoch: Epoch) -> bool {
self.activation_epoch <= epoch && epoch < self.exit_epoch
self.activation_epoch() <= epoch && epoch < self.exit_epoch()
}
/// Returns `true` if the validator is slashable at some epoch.
pub fn is_slashable_at(&self, epoch: Epoch) -> bool {
!self.slashed && self.activation_epoch <= epoch && epoch < self.withdrawable_epoch
!self.slashed() && self.activation_epoch() <= epoch && epoch < self.withdrawable_epoch()
}
/// Returns `true` if the validator is considered exited at some epoch.
pub fn is_exited_at(&self, epoch: Epoch) -> bool {
self.exit_epoch <= epoch
self.exit_epoch() <= epoch
}
/// Returns `true` if the validator is able to withdraw at some epoch.
pub fn is_withdrawable_at(&self, epoch: Epoch) -> bool {
epoch >= self.withdrawable_epoch
epoch >= self.withdrawable_epoch()
}
/// Returns `true` if the validator is eligible to join the activation queue.
///
/// Spec v0.12.1
pub fn is_eligible_for_activation_queue(&self, spec: &ChainSpec) -> bool {
self.activation_eligibility_epoch == spec.far_future_epoch
&& self.effective_balance == spec.max_effective_balance
self.activation_eligibility_epoch() == spec.far_future_epoch
&& self.effective_balance() == spec.max_effective_balance
}
/// Returns `true` if the validator is eligible to be activated.
@@ -85,9 +117,9 @@ impl Validator {
spec: &ChainSpec,
) -> bool {
// Placement in queue is finalized
self.activation_eligibility_epoch <= state.finalized_checkpoint().epoch
self.activation_eligibility_epoch() <= state.finalized_checkpoint().epoch
// Has not yet been activated
&& self.activation_epoch == spec.far_future_epoch
&& self.activation_epoch() == spec.far_future_epoch
}
fn tree_hash_root_internal(&self) -> Result<Hash256, tree_hash::Error> {
@@ -95,16 +127,16 @@ impl Validator {
hasher.write(self.pubkey().tree_hash_root().as_bytes())?;
hasher.write(self.withdrawal_credentials().tree_hash_root().as_bytes())?;
hasher.write(self.effective_balance.tree_hash_root().as_bytes())?;
hasher.write(self.slashed.tree_hash_root().as_bytes())?;
hasher.write(self.effective_balance().tree_hash_root().as_bytes())?;
hasher.write(self.slashed().tree_hash_root().as_bytes())?;
hasher.write(
self.activation_eligibility_epoch
self.activation_eligibility_epoch()
.tree_hash_root()
.as_bytes(),
)?;
hasher.write(self.activation_epoch.tree_hash_root().as_bytes())?;
hasher.write(self.exit_epoch.tree_hash_root().as_bytes())?;
hasher.write(self.withdrawable_epoch.tree_hash_root().as_bytes())?;
hasher.write(self.activation_epoch().tree_hash_root().as_bytes())?;
hasher.write(self.exit_epoch().tree_hash_root().as_bytes())?;
hasher.write(self.withdrawable_epoch().tree_hash_root().as_bytes())?;
hasher.finish()
}
@@ -118,12 +150,14 @@ impl Default for Validator {
pubkey: PublicKeyBytes::empty(),
withdrawal_credentials: Hash256::default(),
}),
activation_eligibility_epoch: Epoch::from(std::u64::MAX),
activation_epoch: Epoch::from(std::u64::MAX),
exit_epoch: Epoch::from(std::u64::MAX),
withdrawable_epoch: Epoch::from(std::u64::MAX),
slashed: false,
effective_balance: std::u64::MAX,
mutable: ValidatorMutable {
activation_eligibility_epoch: Epoch::from(std::u64::MAX),
activation_epoch: Epoch::from(std::u64::MAX),
exit_epoch: Epoch::from(std::u64::MAX),
withdrawable_epoch: Epoch::from(std::u64::MAX),
slashed: false,
effective_balance: std::u64::MAX,
},
}
}
}
@@ -160,7 +194,7 @@ mod tests {
assert!(!v.is_active_at(epoch));
assert!(!v.is_exited_at(epoch));
assert!(!v.is_withdrawable_at(epoch));
assert!(!v.slashed);
assert!(!v.slashed());
}
#[test]