From adc0335ff887e955124cbe404bc7377e572f7c85 Mon Sep 17 00:00:00 2001 From: Pawan Dhananjay Date: Thu, 25 Apr 2024 16:28:34 -0700 Subject: [PATCH 1/5] Add new helpers --- consensus/types/src/beacon_state.rs | 127 ++++++++++++++++++++++++++++ consensus/types/src/validator.rs | 42 +++++++-- 2 files changed, 164 insertions(+), 5 deletions(-) diff --git a/consensus/types/src/beacon_state.rs b/consensus/types/src/beacon_state.rs index ef5cd23646b..4c8cdb0c504 100644 --- a/consensus/types/src/beacon_state.rs +++ b/consensus/types/src/beacon_state.rs @@ -2092,6 +2092,40 @@ impl BeaconState { .map_err(Into::into) } + pub fn get_active_balance( + &self, + validator_index: usize, + spec: &ChainSpec, + ) -> Result { + let max_effective_balance = self + .validators() + .get(validator_index) + .map(|validator| validator.get_validator_max_effective_balance(spec)) + .ok_or(Error::UnknownValidator(validator_index))?; + // TODO(pawan): this is assuming balances and validat + Ok(std::cmp::min( + *self + .balances() + .get(validator_index) + .ok_or(Error::UnknownValidator(validator_index))?, + max_effective_balance, + )) + } + + pub fn get_pending_balance_to_withdraw(&self, validator_index: u64) -> Result { + Ok(self + .pending_partial_withdrawals()? + .iter() + .filter_map(|withdrawal| { + if withdrawal.index == validator_index { + Some(withdrawal.amount) + } else { + None + } + }) + .sum()) + } + // ******* Electra mutators ******* pub fn queue_excess_active_balance( @@ -2142,6 +2176,99 @@ impl BeaconState { .map_err(Into::into) } + pub fn switch_to_compounding_validator( + &mut self, + validator_index: u64, + spec: &ChainSpec, + ) -> Result<(), Error> { + let validator = self + .validators_mut() + .get_mut(validator_index) + .ok_or(Error::UnknownValidator(validator_index))?; + if validator.has_eth1_withdrawal_credential(spec) { + validator.withdrawal_credentials = + spec.compounding_withdrawal_prefix_byte + validator.withdrawal_credentials[1..]; + self.queue_excess_active_balance(validator_index, spec) + } else { + Ok(()) + } + } + + pub fn compute_exit_epoch_and_update_churn( + &self, + exit_balance: u64, + spec: &ChainSpec, + ) -> Result { + let mut earliest_exit_epoch = std::cmp::max( + self.earliest_exit_epoch()?, + self.compute_activation_exit_epoch(self.current_epoch()), + ); + + let per_epoch_churn = self.get_activation_exit_churn_limit(spec)?; + // New epoch for exits + let mut exit_balance_to_consume = if self.earliest_exit_epoch()? < earliest_exit_epoch { + per_epoch_churn + } else { + self.exit_balance_to_consume()? + }; + + // Exit doesn't fit in the current earliest epoch + if exit_balance > exit_balance_to_consume { + let balance_to_process = exit_balance - exit_balance_to_consume; + let additional_epochs = balance_to_process + .safe_sub(1) + .safe_div(per_epoch_churn)? + .safe_add(1); + earliest_exit_epoch.safe_add_assign(additional_epochs)?; + exit_balance_to_consume + .safe_add_assign(additional_epochs.safe_mul(per_epoch_churn)?)?; + } + // Consume the balance and update state variables + self.exit_balance_to_consume_mut() = exit_balance_to_consume.safe_sub(exit_balance)?; + self.earliest_exit_epoch_mut() = earliest_exit_epoch; + + Ok(self.earliest_exit_epoch()) + } + + pub fn compute_consolidation_epoch_and_update_churn( + &mut self, + consolidation_balance: u64, + spec: &ChainSpec, + ) -> Result { + let mut earliest_consolidation_epoch = std::cmp::max( + self.earliest_consolidation_epoch()?, + self.compute_activation_exit_epoch(self.current_epoch(), spec)?, + ); + + let per_epoch_consolidation_churn = self.get_consolidation_churn_limit(spec)?; + + // New epoch for consolidations + let mut consolidation_balance_to_consume = + if self.earliest_consolidation_epoch()? < earliest_consolidation_epoch { + per_epoch_consolidation_churn + } else { + self.consolidation_balance_to_consume() + }; + // Consolidation doesn't fit in the current earliest epoch + if consolidation_balance > consolidation_balance_to_consume { + let balance_to_process = + consolidation_balance.safe_sub(consolidation_balance_to_consume)?; + let additional_epochs = balance_to_process + .safe_sub(1)? + .safe_div(per_epoch_consolidation_churn)? + .safe_add(1)?; + earliest_consolidation_epoch.safe_add_assign(additional_epochs)?; + consolidation_balance_to_consume + .safe_add_assign(additional_epochs.safe_mul(per_epoch_consolidation_churn)?)?; + // Consume the balance and update state variables + self.consolidation_balance_to_consume_mut() = + consolidation_balance_to_consume.safe_sub(consolidation_balance)?; + self.earliest_consolidation_epoch_mut() = earliest_consolidation_epoch; + + Ok(self.earliest_consolidation_epoch()) + } + } + #[allow(clippy::arithmetic_side_effects)] pub fn rebase_on(&mut self, base: &Self, spec: &ChainSpec) -> Result<(), Error> { // Required for macros (which use type-hints internally). diff --git a/consensus/types/src/validator.rs b/consensus/types/src/validator.rs index 5fd18552f6b..c70c08ec937 100644 --- a/consensus/types/src/validator.rs +++ b/consensus/types/src/validator.rs @@ -57,10 +57,10 @@ impl Validator { /// Returns `true` if the validator is eligible to join the activation queue. /// - /// Spec v0.12.1 + /// Modified in electra 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.effective_balance >= spec.min_activation_balance } /// Returns `true` if the validator is eligible to be activated. @@ -135,10 +135,42 @@ impl Validator { } /// Returns `true` if the validator is partially withdrawable. + /// + /// Note: Modified in electra. pub fn is_partially_withdrawable_validator(&self, balance: u64, spec: &ChainSpec) -> bool { - self.has_eth1_withdrawal_credential(spec) - && self.effective_balance == spec.max_effective_balance - && balance > spec.max_effective_balance + let max_effective_balance = self.get_validator_max_effective_balance(spec); + let has_max_effective_balance = self.effective_balance == max_effective_balance; + let has_excess_balance = balance > max_effective_balance; + self.has_execution_withdrawal_credential(spec) + && has_max_effective_balance + && has_excess_balance + } + + /// Returns `true` if the validator has a 0x01 or 0x02 prefixed withdrawal credential. + pub fn has_execution_withdrawal_credential(&self, spec: &ChainSpec) -> bool { + self.has_compounding_withdrawal_credential(spec) + || self.has_eth1_withdrawal_credential(spec) + } + + /// Returns `true` if the validator if fully withdrawable. + pub fn is_fully_withdrawable_validator( + &self, + balance: u64, + epoch: Epoch, + spec: &ChainSpec, + ) -> bool { + self.has_execution_withdrawal_credential(spec) + && self.withdrawable_epoch <= epoch + && balance > 0 + } + + /// Returns the max effective balance for a validator in gwei. + pub fn get_validator_max_effective_balance(&self, spec: &ChainSpec) -> u64 { + if self.has_compounding_withdrawal_credential(spec) { + spec.max_effective_balance_electra + } else { + spec.min_activation_balance + } } } From 1d838e7987aa189e32742fd57a830d16e91eedcf Mon Sep 17 00:00:00 2001 From: Pawan Dhananjay Date: Thu, 25 Apr 2024 23:49:54 -0700 Subject: [PATCH 2/5] Fix some stuff --- consensus/types/src/beacon_state.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/consensus/types/src/beacon_state.rs b/consensus/types/src/beacon_state.rs index 4c8cdb0c504..50353c1b79d 100644 --- a/consensus/types/src/beacon_state.rs +++ b/consensus/types/src/beacon_state.rs @@ -2092,6 +2092,7 @@ impl BeaconState { .map_err(Into::into) } + /// Get active balance for the given `validator_index`. pub fn get_active_balance( &self, validator_index: usize, @@ -2102,7 +2103,6 @@ impl BeaconState { .get(validator_index) .map(|validator| validator.get_validator_max_effective_balance(spec)) .ok_or(Error::UnknownValidator(validator_index))?; - // TODO(pawan): this is assuming balances and validat Ok(std::cmp::min( *self .balances() @@ -2176,6 +2176,7 @@ impl BeaconState { .map_err(Into::into) } + /// Change the withdrawal prefix of the given `validator_index` to the compounding withdrawal validator prefix. pub fn switch_to_compounding_validator( &mut self, validator_index: u64, @@ -2188,10 +2189,9 @@ impl BeaconState { if validator.has_eth1_withdrawal_credential(spec) { validator.withdrawal_credentials = spec.compounding_withdrawal_prefix_byte + validator.withdrawal_credentials[1..]; - self.queue_excess_active_balance(validator_index, spec) - } else { - Ok(()) + self.queue_excess_active_balance(validator_index, spec)?; } + Ok(()) } pub fn compute_exit_epoch_and_update_churn( From d37d35d1a1895f430800c0417d393bef1d2aa66f Mon Sep 17 00:00:00 2001 From: Pawan Dhananjay Date: Fri, 26 Apr 2024 13:22:25 -0700 Subject: [PATCH 3/5] Fix compilation errors --- consensus/types/src/beacon_state.rs | 42 +++++++++++++++-------------- consensus/types/src/validator.rs | 2 ++ 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/consensus/types/src/beacon_state.rs b/consensus/types/src/beacon_state.rs index 50353c1b79d..8f2f8761a9c 100644 --- a/consensus/types/src/beacon_state.rs +++ b/consensus/types/src/beacon_state.rs @@ -2112,12 +2112,12 @@ impl BeaconState { )) } - pub fn get_pending_balance_to_withdraw(&self, validator_index: u64) -> Result { + pub fn get_pending_balance_to_withdraw(&self, validator_index: usize) -> Result { Ok(self .pending_partial_withdrawals()? .iter() .filter_map(|withdrawal| { - if withdrawal.index == validator_index { + if withdrawal.index as usize == validator_index { Some(withdrawal.amount) } else { None @@ -2179,7 +2179,7 @@ impl BeaconState { /// Change the withdrawal prefix of the given `validator_index` to the compounding withdrawal validator prefix. pub fn switch_to_compounding_validator( &mut self, - validator_index: u64, + validator_index: usize, spec: &ChainSpec, ) -> Result<(), Error> { let validator = self @@ -2187,21 +2187,21 @@ impl BeaconState { .get_mut(validator_index) .ok_or(Error::UnknownValidator(validator_index))?; if validator.has_eth1_withdrawal_credential(spec) { - validator.withdrawal_credentials = - spec.compounding_withdrawal_prefix_byte + validator.withdrawal_credentials[1..]; + validator.withdrawal_credentials.as_fixed_bytes_mut()[0] = + spec.compounding_withdrawal_prefix_byte; self.queue_excess_active_balance(validator_index, spec)?; } Ok(()) } pub fn compute_exit_epoch_and_update_churn( - &self, + &mut self, exit_balance: u64, spec: &ChainSpec, ) -> Result { let mut earliest_exit_epoch = std::cmp::max( self.earliest_exit_epoch()?, - self.compute_activation_exit_epoch(self.current_epoch()), + self.compute_activation_exit_epoch(self.current_epoch(), spec)?, ); let per_epoch_churn = self.get_activation_exit_churn_limit(spec)?; @@ -2214,20 +2214,21 @@ impl BeaconState { // Exit doesn't fit in the current earliest epoch if exit_balance > exit_balance_to_consume { - let balance_to_process = exit_balance - exit_balance_to_consume; + let balance_to_process = exit_balance.safe_sub(exit_balance_to_consume)?; let additional_epochs = balance_to_process - .safe_sub(1) + .safe_sub(1)? .safe_div(per_epoch_churn)? - .safe_add(1); + .safe_add(1)?; earliest_exit_epoch.safe_add_assign(additional_epochs)?; exit_balance_to_consume .safe_add_assign(additional_epochs.safe_mul(per_epoch_churn)?)?; } + let state = self.as_electra_mut()?; // Consume the balance and update state variables - self.exit_balance_to_consume_mut() = exit_balance_to_consume.safe_sub(exit_balance)?; - self.earliest_exit_epoch_mut() = earliest_exit_epoch; + state.exit_balance_to_consume = exit_balance_to_consume.safe_sub(exit_balance)?; + state.earliest_exit_epoch = earliest_exit_epoch; - Ok(self.earliest_exit_epoch()) + Ok(state.earliest_exit_epoch) } pub fn compute_consolidation_epoch_and_update_churn( @@ -2247,7 +2248,7 @@ impl BeaconState { if self.earliest_consolidation_epoch()? < earliest_consolidation_epoch { per_epoch_consolidation_churn } else { - self.consolidation_balance_to_consume() + self.consolidation_balance_to_consume()? }; // Consolidation doesn't fit in the current earliest epoch if consolidation_balance > consolidation_balance_to_consume { @@ -2260,13 +2261,14 @@ impl BeaconState { earliest_consolidation_epoch.safe_add_assign(additional_epochs)?; consolidation_balance_to_consume .safe_add_assign(additional_epochs.safe_mul(per_epoch_consolidation_churn)?)?; - // Consume the balance and update state variables - self.consolidation_balance_to_consume_mut() = - consolidation_balance_to_consume.safe_sub(consolidation_balance)?; - self.earliest_consolidation_epoch_mut() = earliest_consolidation_epoch; - - Ok(self.earliest_consolidation_epoch()) } + // Consume the balance and update state variables + let state = self.as_electra_mut()?; + state.consolidation_balance_to_consume = + consolidation_balance_to_consume.safe_sub(consolidation_balance)?; + state.earliest_consolidation_epoch = earliest_consolidation_epoch; + + Ok(state.earliest_consolidation_epoch) } #[allow(clippy::arithmetic_side_effects)] diff --git a/consensus/types/src/validator.rs b/consensus/types/src/validator.rs index c70c08ec937..1321b570074 100644 --- a/consensus/types/src/validator.rs +++ b/consensus/types/src/validator.rs @@ -153,6 +153,8 @@ impl Validator { } /// Returns `true` if the validator if fully withdrawable. + /// + /// Modified in electra. pub fn is_fully_withdrawable_validator( &self, balance: u64, From 4985a95617aa16dbe1d33c37cf68b59322b05fed Mon Sep 17 00:00:00 2001 From: Pawan Dhananjay Date: Fri, 26 Apr 2024 13:30:14 -0700 Subject: [PATCH 4/5] lint --- consensus/types/src/beacon_state.rs | 16 +++++++--------- consensus/types/src/validator.rs | 2 +- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/consensus/types/src/beacon_state.rs b/consensus/types/src/beacon_state.rs index 8f2f8761a9c..577f282a556 100644 --- a/consensus/types/src/beacon_state.rs +++ b/consensus/types/src/beacon_state.rs @@ -2113,17 +2113,15 @@ impl BeaconState { } pub fn get_pending_balance_to_withdraw(&self, validator_index: usize) -> Result { - Ok(self + let mut pending_balance = 0; + for withdrawal in self .pending_partial_withdrawals()? .iter() - .filter_map(|withdrawal| { - if withdrawal.index as usize == validator_index { - Some(withdrawal.amount) - } else { - None - } - }) - .sum()) + .filter(|withdrawal| withdrawal.index as usize == validator_index) + { + pending_balance.safe_add_assign(withdrawal.amount)?; + } + Ok(pending_balance) } // ******* Electra mutators ******* diff --git a/consensus/types/src/validator.rs b/consensus/types/src/validator.rs index 1321b570074..a84bb401268 100644 --- a/consensus/types/src/validator.rs +++ b/consensus/types/src/validator.rs @@ -153,7 +153,7 @@ impl Validator { } /// Returns `true` if the validator if fully withdrawable. - /// + /// /// Modified in electra. pub fn is_fully_withdrawable_validator( &self, From c9510abe6c30c9f78b126a6a765616fd5019600f Mon Sep 17 00:00:00 2001 From: Pawan Dhananjay Date: Fri, 26 Apr 2024 16:49:18 -0700 Subject: [PATCH 5/5] Address review --- consensus/types/src/validator.rs | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/consensus/types/src/validator.rs b/consensus/types/src/validator.rs index a84bb401268..8ed449ec8a7 100644 --- a/consensus/types/src/validator.rs +++ b/consensus/types/src/validator.rs @@ -130,8 +130,12 @@ impl Validator { } /// Returns `true` if the validator is fully withdrawable at some epoch. + /// + /// Note: Modified in electra. pub fn is_fully_withdrawable_at(&self, balance: u64, epoch: Epoch, spec: &ChainSpec) -> bool { - self.has_eth1_withdrawal_credential(spec) && self.withdrawable_epoch <= epoch && balance > 0 + self.has_execution_withdrawal_credential(spec) + && self.withdrawable_epoch <= epoch + && balance > 0 } /// Returns `true` if the validator is partially withdrawable. @@ -152,20 +156,6 @@ impl Validator { || self.has_eth1_withdrawal_credential(spec) } - /// Returns `true` if the validator if fully withdrawable. - /// - /// Modified in electra. - pub fn is_fully_withdrawable_validator( - &self, - balance: u64, - epoch: Epoch, - spec: &ChainSpec, - ) -> bool { - self.has_execution_withdrawal_credential(spec) - && self.withdrawable_epoch <= epoch - && balance > 0 - } - /// Returns the max effective balance for a validator in gwei. pub fn get_validator_max_effective_balance(&self, spec: &ChainSpec) -> u64 { if self.has_compounding_withdrawal_credential(spec) {