From 2684bb6fb038e13f77a55353b7d5809199fece0a Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Thu, 18 Apr 2024 11:56:33 -0600 Subject: [PATCH] wip: helpers --- ethereum-consensus/src/electra/helpers.rs | 483 +++++++++++++++++++++- 1 file changed, 471 insertions(+), 12 deletions(-) diff --git a/ethereum-consensus/src/electra/helpers.rs b/ethereum-consensus/src/electra/helpers.rs index 16298d18b..5a5ab9aa6 100644 --- a/ethereum-consensus/src/electra/helpers.rs +++ b/ethereum-consensus/src/electra/helpers.rs @@ -1,8 +1,50 @@ use crate::{ - phase0::Validator, - primitives::{CommitteeIndex, Gwei, ValidatorIndex}, + altair::get_beacon_committee, + capella::helpers::has_eth1_withdrawal_credential, + electra::{beacon_state::PendingBalanceDeposit, Attestation, BeaconState}, + phase0::{helpers::get_total_active_balance, Validator}, + primitives::{ + Bytes32, CommitteeIndex, Epoch, Gwei, ValidatorIndex, COMPOUNDING_WITHDRAWAL_PREFIX, + FAR_FUTURE_EPOCH, + }, ssz::prelude::*, + state_transition::Context, + Error, }; +use std::collections::HashSet; + +pub fn is_eligible_for_activation_queue(validator: &Validator, context: &Context) -> bool { + let is_not_yet_eligible = validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH; + let has_min_activation_balance = validator.effective_balance >= context.min_activation_balance; + is_not_yet_eligible && has_min_activation_balance +} + +pub fn is_compounding_withdrawal_credential(withdrawal_credentials: &Bytes32) -> bool { + withdrawal_credentials[0] == COMPOUNDING_WITHDRAWAL_PREFIX +} + +pub fn has_compounding_withdrawal_credential(validator: &Validator) -> bool { + is_compounding_withdrawal_credential(&validator.withdrawal_credentials) +} + +pub fn has_execution_withdrawal_credential(validator: &Validator) -> bool { + has_compounding_withdrawal_credential(validator) || has_eth1_withdrawal_credential(validator) +} + +pub fn is_fully_withdrawable_validator(validator: &Validator, balance: Gwei, epoch: Epoch) -> bool { + has_execution_withdrawal_credential(validator) && + validator.withdrawable_epoch <= epoch && + balance > 0 +} + +pub fn is_partially_withdrawable_validator(validator: &Validator, balance: Gwei) -> bool { + let max_effective_balance = get_validator_max_effective_balance(validator); + let has_max_effective_balance = validator.effective_balance == max_effective_balance; + let has_excess_balance = balance > max_effective_balance; + has_execution_withdrawal_credential(validator) && + has_max_effective_balance && + has_excess_balance +} pub fn get_committee_indices( committee_bits: Bitvector, @@ -10,22 +52,439 @@ pub fn get_committee_indices( committee_bits.iter().enumerate().flat_map(|(i, bit)| bit.then_some(i)).collect() } -pub fn get_activation_exit_churn_limit() -> Gwei { - todo!() +pub fn get_validator_max_effective_balance(validator: &Validator, context: &Context) -> Gwei { + if has_compounding_withdrawal_credential(validator) { + context.max_effective_balance_electra + } else { + context.min_activation_balance + } +} + +pub fn get_balance_churn_limit< + const SLOTS_PER_HISTORICAL_ROOT: usize, + const HISTORICAL_ROOTS_LIMIT: usize, + const ETH1_DATA_VOTES_BOUND: usize, + const VALIDATOR_REGISTRY_LIMIT: usize, + const EPOCHS_PER_HISTORICAL_VECTOR: usize, + const EPOCHS_PER_SLASHINGS_VECTOR: usize, + const MAX_VALIDATORS_PER_COMMITTEE: usize, + const SYNC_COMMITTEE_SIZE: usize, + const BYTES_PER_LOGS_BLOOM: usize, + const MAX_EXTRA_DATA_BYTES: usize, + const PENDING_BALANCE_DEPOSITS_LIMIT: usize, + const PENDING_PARTIAL_WITHDRAWALS_LIMIT: usize, + const PENDING_CONSOLIDATIONS_LIMIT: usize, +>( + state: &BeaconState< + SLOTS_PER_HISTORICAL_ROOT, + HISTORICAL_ROOTS_LIMIT, + ETH1_DATA_VOTES_BOUND, + VALIDATOR_REGISTRY_LIMIT, + EPOCHS_PER_HISTORICAL_VECTOR, + EPOCHS_PER_SLASHINGS_VECTOR, + MAX_VALIDATORS_PER_COMMITTEE, + SYNC_COMMITTEE_SIZE, + BYTES_PER_LOGS_BLOOM, + MAX_EXTRA_DATA_BYTES, + PENDING_BALANCE_DEPOSITS_LIMIT, + PENDING_PARTIAL_WITHDRAWALS_LIMIT, + PENDING_CONSOLIDATIONS_LIMIT, + >, + context: &Context, +) -> Result { + let churn_limit = get_total_active_balance(state, context)? / context.churn_limit_quotient; + let churn = context.min_per_epoch_churn_limit_electra.max(churn_limit); + Ok(churn - churn % context.effective_balance_increment) +} + +pub fn get_activation_exit_churn_limit< + const SLOTS_PER_HISTORICAL_ROOT: usize, + const HISTORICAL_ROOTS_LIMIT: usize, + const ETH1_DATA_VOTES_BOUND: usize, + const VALIDATOR_REGISTRY_LIMIT: usize, + const EPOCHS_PER_HISTORICAL_VECTOR: usize, + const EPOCHS_PER_SLASHINGS_VECTOR: usize, + const MAX_VALIDATORS_PER_COMMITTEE: usize, + const SYNC_COMMITTEE_SIZE: usize, + const BYTES_PER_LOGS_BLOOM: usize, + const MAX_EXTRA_DATA_BYTES: usize, + const PENDING_BALANCE_DEPOSITS_LIMIT: usize, + const PENDING_PARTIAL_WITHDRAWALS_LIMIT: usize, + const PENDING_CONSOLIDATIONS_LIMIT: usize, +>( + state: &BeaconState< + SLOTS_PER_HISTORICAL_ROOT, + HISTORICAL_ROOTS_LIMIT, + ETH1_DATA_VOTES_BOUND, + VALIDATOR_REGISTRY_LIMIT, + EPOCHS_PER_HISTORICAL_VECTOR, + EPOCHS_PER_SLASHINGS_VECTOR, + MAX_VALIDATORS_PER_COMMITTEE, + SYNC_COMMITTEE_SIZE, + BYTES_PER_LOGS_BLOOM, + MAX_EXTRA_DATA_BYTES, + PENDING_BALANCE_DEPOSITS_LIMIT, + PENDING_PARTIAL_WITHDRAWALS_LIMIT, + PENDING_CONSOLIDATIONS_LIMIT, + >, + context: &Context, +) -> Result { + get_balance_churn_limit(state, context) + .map(|churn_limit| context.max_per_epoch_activation_exit_churn_limit.min(churn_limit)) } -pub fn get_consolidation_churn_limit() -> Gwei { - todo!() +pub fn get_consolidation_churn_limit< + const SLOTS_PER_HISTORICAL_ROOT: usize, + const HISTORICAL_ROOTS_LIMIT: usize, + const ETH1_DATA_VOTES_BOUND: usize, + const VALIDATOR_REGISTRY_LIMIT: usize, + const EPOCHS_PER_HISTORICAL_VECTOR: usize, + const EPOCHS_PER_SLASHINGS_VECTOR: usize, + const MAX_VALIDATORS_PER_COMMITTEE: usize, + const SYNC_COMMITTEE_SIZE: usize, + const BYTES_PER_LOGS_BLOOM: usize, + const MAX_EXTRA_DATA_BYTES: usize, + const PENDING_BALANCE_DEPOSITS_LIMIT: usize, + const PENDING_PARTIAL_WITHDRAWALS_LIMIT: usize, + const PENDING_CONSOLIDATIONS_LIMIT: usize, +>( + state: &BeaconState< + SLOTS_PER_HISTORICAL_ROOT, + HISTORICAL_ROOTS_LIMIT, + ETH1_DATA_VOTES_BOUND, + VALIDATOR_REGISTRY_LIMIT, + EPOCHS_PER_HISTORICAL_VECTOR, + EPOCHS_PER_SLASHINGS_VECTOR, + MAX_VALIDATORS_PER_COMMITTEE, + SYNC_COMMITTEE_SIZE, + BYTES_PER_LOGS_BLOOM, + MAX_EXTRA_DATA_BYTES, + PENDING_BALANCE_DEPOSITS_LIMIT, + PENDING_PARTIAL_WITHDRAWALS_LIMIT, + PENDING_CONSOLIDATIONS_LIMIT, + >, + context: &Context, +) -> Result { + Ok(get_balance_churn_limit(state, context)? - get_activation_exit_churn_limit(state, context)?) } -pub fn queue_entire_balance_and_reset_validator(_index: ValidatorIndex) { - todo!() +pub fn get_active_balance< + const SLOTS_PER_HISTORICAL_ROOT: usize, + const HISTORICAL_ROOTS_LIMIT: usize, + const ETH1_DATA_VOTES_BOUND: usize, + const VALIDATOR_REGISTRY_LIMIT: usize, + const EPOCHS_PER_HISTORICAL_VECTOR: usize, + const EPOCHS_PER_SLASHINGS_VECTOR: usize, + const MAX_VALIDATORS_PER_COMMITTEE: usize, + const SYNC_COMMITTEE_SIZE: usize, + const BYTES_PER_LOGS_BLOOM: usize, + const MAX_EXTRA_DATA_BYTES: usize, + const PENDING_BALANCE_DEPOSITS_LIMIT: usize, + const PENDING_PARTIAL_WITHDRAWALS_LIMIT: usize, + const PENDING_CONSOLIDATIONS_LIMIT: usize, +>( + state: &BeaconState< + SLOTS_PER_HISTORICAL_ROOT, + HISTORICAL_ROOTS_LIMIT, + ETH1_DATA_VOTES_BOUND, + VALIDATOR_REGISTRY_LIMIT, + EPOCHS_PER_HISTORICAL_VECTOR, + EPOCHS_PER_SLASHINGS_VECTOR, + MAX_VALIDATORS_PER_COMMITTEE, + SYNC_COMMITTEE_SIZE, + BYTES_PER_LOGS_BLOOM, + MAX_EXTRA_DATA_BYTES, + PENDING_BALANCE_DEPOSITS_LIMIT, + PENDING_PARTIAL_WITHDRAWALS_LIMIT, + PENDING_CONSOLIDATIONS_LIMIT, + >, + validator_index: ValidatorIndex, + context: &Context, +) -> Gwei { + let max_effective_balance = + get_validator_max_effective_balance(&state.validators[validator_index], context); + state.balances[validator_index].min(max_effective_balance) } -pub fn has_compounding_withdrawal_credential(_validator: &Validator) -> bool { - todo!() +pub fn get_pending_balance_to_withdraw< + const SLOTS_PER_HISTORICAL_ROOT: usize, + const HISTORICAL_ROOTS_LIMIT: usize, + const ETH1_DATA_VOTES_BOUND: usize, + const VALIDATOR_REGISTRY_LIMIT: usize, + const EPOCHS_PER_HISTORICAL_VECTOR: usize, + const EPOCHS_PER_SLASHINGS_VECTOR: usize, + const MAX_VALIDATORS_PER_COMMITTEE: usize, + const SYNC_COMMITTEE_SIZE: usize, + const BYTES_PER_LOGS_BLOOM: usize, + const MAX_EXTRA_DATA_BYTES: usize, + const PENDING_BALANCE_DEPOSITS_LIMIT: usize, + const PENDING_PARTIAL_WITHDRAWALS_LIMIT: usize, + const PENDING_CONSOLIDATIONS_LIMIT: usize, +>( + state: &BeaconState< + SLOTS_PER_HISTORICAL_ROOT, + HISTORICAL_ROOTS_LIMIT, + ETH1_DATA_VOTES_BOUND, + VALIDATOR_REGISTRY_LIMIT, + EPOCHS_PER_HISTORICAL_VECTOR, + EPOCHS_PER_SLASHINGS_VECTOR, + MAX_VALIDATORS_PER_COMMITTEE, + SYNC_COMMITTEE_SIZE, + BYTES_PER_LOGS_BLOOM, + MAX_EXTRA_DATA_BYTES, + PENDING_BALANCE_DEPOSITS_LIMIT, + PENDING_PARTIAL_WITHDRAWALS_LIMIT, + PENDING_CONSOLIDATIONS_LIMIT, + >, + validator_index: ValidatorIndex, + context: &Context, +) -> Gwei { + state + .pending_partial_withdrawals + .iter() + .filter_map(|withdrawal| { + if withdrawal.index == validator_index { + Some(withdrawal.amount) + } else { + None + } + }) + .sum() } -pub fn queue_excess_active_balance(_index: ValidatorIndex) { - todo!() +pub fn get_attesting_indices< + const SLOTS_PER_HISTORICAL_ROOT: usize, + const HISTORICAL_ROOTS_LIMIT: usize, + const ETH1_DATA_VOTES_BOUND: usize, + const VALIDATOR_REGISTRY_LIMIT: usize, + const EPOCHS_PER_HISTORICAL_VECTOR: usize, + const EPOCHS_PER_SLASHINGS_VECTOR: usize, + const MAX_VALIDATORS_PER_COMMITTEE: usize, + const SYNC_COMMITTEE_SIZE: usize, + const BYTES_PER_LOGS_BLOOM: usize, + const MAX_EXTRA_DATA_BYTES: usize, + const PENDING_BALANCE_DEPOSITS_LIMIT: usize, + const PENDING_PARTIAL_WITHDRAWALS_LIMIT: usize, + const PENDING_CONSOLIDATIONS_LIMIT: usize, + const MAX_VALIDATORS_PER_SLOT: usize, + const MAX_COMMITTEES_PER_SLOT: usize, +>( + state: &BeaconState< + SLOTS_PER_HISTORICAL_ROOT, + HISTORICAL_ROOTS_LIMIT, + ETH1_DATA_VOTES_BOUND, + VALIDATOR_REGISTRY_LIMIT, + EPOCHS_PER_HISTORICAL_VECTOR, + EPOCHS_PER_SLASHINGS_VECTOR, + MAX_VALIDATORS_PER_COMMITTEE, + SYNC_COMMITTEE_SIZE, + BYTES_PER_LOGS_BLOOM, + MAX_EXTRA_DATA_BYTES, + PENDING_BALANCE_DEPOSITS_LIMIT, + PENDING_PARTIAL_WITHDRAWALS_LIMIT, + PENDING_CONSOLIDATIONS_LIMIT, + >, + attestation: &Attestation, + context: &Context, +) -> Result, Error> { + let committee_indices = get_committee_indices(attestation.committee_bits); + + let mut indices = HashSet::with_capacity(MAX_VALIDATORS_PER_SLOT); + + let mut committee_offset = 0; + + for index in committee_indices { + let committee = get_beacon_committee(state, attestation.data.slot, index, context)?; + for (i, validator_index) in committee.iter().enumerate() { + if attestation.aggregation_bits[committee_offset + i] { + indices.insert(*validator_index); + } + } + committee_offset += 1; + } + + Ok(indices) +} + +pub fn initiate_validator_exit< + const SLOTS_PER_HISTORICAL_ROOT: usize, + const HISTORICAL_ROOTS_LIMIT: usize, + const ETH1_DATA_VOTES_BOUND: usize, + const VALIDATOR_REGISTRY_LIMIT: usize, + const EPOCHS_PER_HISTORICAL_VECTOR: usize, + const EPOCHS_PER_SLASHINGS_VECTOR: usize, + const MAX_VALIDATORS_PER_COMMITTEE: usize, + const SYNC_COMMITTEE_SIZE: usize, + const BYTES_PER_LOGS_BLOOM: usize, + const MAX_EXTRA_DATA_BYTES: usize, + const PENDING_BALANCE_DEPOSITS_LIMIT: usize, + const PENDING_PARTIAL_WITHDRAWALS_LIMIT: usize, + const PENDING_CONSOLIDATIONS_LIMIT: usize, + const MAX_VALIDATORS_PER_SLOT: usize, + const MAX_COMMITTEES_PER_SLOT: usize, +>( + state: &mut BeaconState< + SLOTS_PER_HISTORICAL_ROOT, + HISTORICAL_ROOTS_LIMIT, + ETH1_DATA_VOTES_BOUND, + VALIDATOR_REGISTRY_LIMIT, + EPOCHS_PER_HISTORICAL_VECTOR, + EPOCHS_PER_SLASHINGS_VECTOR, + MAX_VALIDATORS_PER_COMMITTEE, + SYNC_COMMITTEE_SIZE, + BYTES_PER_LOGS_BLOOM, + MAX_EXTRA_DATA_BYTES, + PENDING_BALANCE_DEPOSITS_LIMIT, + PENDING_PARTIAL_WITHDRAWALS_LIMIT, + PENDING_CONSOLIDATIONS_LIMIT, + >, + index: ValidatorIndex, + context: &Context, +) -> Result<(), Error> { + if state.validators[index].exit_epoch != FAR_FUTURE_EPOCH { + return Ok(()) + } + + let exit_queue_epoch = + compute_exit_epoch_and_update_churn(state, state.validators[index].effective_balance)?; + + state.validators[index].exit_epoch = exit_queue_epoch; + state.validators[index].withdrawable_epoch = state.validators[index] + .exit_epoch + .checked_add(context.min_validator_withdrawability_delay) + .ok_or(Error::Overflow)?; + Ok(()) +} + +pub fn switch_to_compounding_validator< + const SLOTS_PER_HISTORICAL_ROOT: usize, + const HISTORICAL_ROOTS_LIMIT: usize, + const ETH1_DATA_VOTES_BOUND: usize, + const VALIDATOR_REGISTRY_LIMIT: usize, + const EPOCHS_PER_HISTORICAL_VECTOR: usize, + const EPOCHS_PER_SLASHINGS_VECTOR: usize, + const MAX_VALIDATORS_PER_COMMITTEE: usize, + const SYNC_COMMITTEE_SIZE: usize, + const BYTES_PER_LOGS_BLOOM: usize, + const MAX_EXTRA_DATA_BYTES: usize, + const PENDING_BALANCE_DEPOSITS_LIMIT: usize, + const PENDING_PARTIAL_WITHDRAWALS_LIMIT: usize, + const PENDING_CONSOLIDATIONS_LIMIT: usize, + const MAX_VALIDATORS_PER_SLOT: usize, + const MAX_COMMITTEES_PER_SLOT: usize, +>( + state: &mut BeaconState< + SLOTS_PER_HISTORICAL_ROOT, + HISTORICAL_ROOTS_LIMIT, + ETH1_DATA_VOTES_BOUND, + VALIDATOR_REGISTRY_LIMIT, + EPOCHS_PER_HISTORICAL_VECTOR, + EPOCHS_PER_SLASHINGS_VECTOR, + MAX_VALIDATORS_PER_COMMITTEE, + SYNC_COMMITTEE_SIZE, + BYTES_PER_LOGS_BLOOM, + MAX_EXTRA_DATA_BYTES, + PENDING_BALANCE_DEPOSITS_LIMIT, + PENDING_PARTIAL_WITHDRAWALS_LIMIT, + PENDING_CONSOLIDATIONS_LIMIT, + >, + index: ValidatorIndex, + context: &Context, +) -> Result<(), Error> { + if has_eth1_withdrawal_credential(&state.validators[index]) { + state.validators[index].withdrawal_credentials[0] = COMPOUNDING_WITHDRAWAL_PREFIX; + queue_excess_active_balance(state, index, context); + } + Ok(()) +} + +pub fn queue_excess_active_balance< + const SLOTS_PER_HISTORICAL_ROOT: usize, + const HISTORICAL_ROOTS_LIMIT: usize, + const ETH1_DATA_VOTES_BOUND: usize, + const VALIDATOR_REGISTRY_LIMIT: usize, + const EPOCHS_PER_HISTORICAL_VECTOR: usize, + const EPOCHS_PER_SLASHINGS_VECTOR: usize, + const MAX_VALIDATORS_PER_COMMITTEE: usize, + const SYNC_COMMITTEE_SIZE: usize, + const BYTES_PER_LOGS_BLOOM: usize, + const MAX_EXTRA_DATA_BYTES: usize, + const PENDING_BALANCE_DEPOSITS_LIMIT: usize, + const PENDING_PARTIAL_WITHDRAWALS_LIMIT: usize, + const PENDING_CONSOLIDATIONS_LIMIT: usize, + const MAX_VALIDATORS_PER_SLOT: usize, + const MAX_COMMITTEES_PER_SLOT: usize, +>( + state: &mut BeaconState< + SLOTS_PER_HISTORICAL_ROOT, + HISTORICAL_ROOTS_LIMIT, + ETH1_DATA_VOTES_BOUND, + VALIDATOR_REGISTRY_LIMIT, + EPOCHS_PER_HISTORICAL_VECTOR, + EPOCHS_PER_SLASHINGS_VECTOR, + MAX_VALIDATORS_PER_COMMITTEE, + SYNC_COMMITTEE_SIZE, + BYTES_PER_LOGS_BLOOM, + MAX_EXTRA_DATA_BYTES, + PENDING_BALANCE_DEPOSITS_LIMIT, + PENDING_PARTIAL_WITHDRAWALS_LIMIT, + PENDING_CONSOLIDATIONS_LIMIT, + >, + index: ValidatorIndex, + context: &Context, +) -> Result<(), Error> { + let balance = state.balances[index]; + if balance > context.min_activation_balance { + let excess_balance = + balance.checked_sub(context.min_activation_balance).ok_or(Error::Underflow)?; + state.balances[index] = context.min_activation_balance; + state.pending_balance_deposits.push(PendingBalanceDeposit { index, amount: excess_balance }) + } + Ok(()) +} + +pub fn queue_entire_balance_and_reset_validator< + const SLOTS_PER_HISTORICAL_ROOT: usize, + const HISTORICAL_ROOTS_LIMIT: usize, + const ETH1_DATA_VOTES_BOUND: usize, + const VALIDATOR_REGISTRY_LIMIT: usize, + const EPOCHS_PER_HISTORICAL_VECTOR: usize, + const EPOCHS_PER_SLASHINGS_VECTOR: usize, + const MAX_VALIDATORS_PER_COMMITTEE: usize, + const SYNC_COMMITTEE_SIZE: usize, + const BYTES_PER_LOGS_BLOOM: usize, + const MAX_EXTRA_DATA_BYTES: usize, + const PENDING_BALANCE_DEPOSITS_LIMIT: usize, + const PENDING_PARTIAL_WITHDRAWALS_LIMIT: usize, + const PENDING_CONSOLIDATIONS_LIMIT: usize, + const MAX_VALIDATORS_PER_SLOT: usize, + const MAX_COMMITTEES_PER_SLOT: usize, +>( + state: &mut BeaconState< + SLOTS_PER_HISTORICAL_ROOT, + HISTORICAL_ROOTS_LIMIT, + ETH1_DATA_VOTES_BOUND, + VALIDATOR_REGISTRY_LIMIT, + EPOCHS_PER_HISTORICAL_VECTOR, + EPOCHS_PER_SLASHINGS_VECTOR, + MAX_VALIDATORS_PER_COMMITTEE, + SYNC_COMMITTEE_SIZE, + BYTES_PER_LOGS_BLOOM, + MAX_EXTRA_DATA_BYTES, + PENDING_BALANCE_DEPOSITS_LIMIT, + PENDING_PARTIAL_WITHDRAWALS_LIMIT, + PENDING_CONSOLIDATIONS_LIMIT, + >, + index: ValidatorIndex, + context: &Context, +) -> Result<(), Error> { + let balance = state.balances[index]; + state.balances[index] = 0; + + let validator = &mut state.validators[index]; + validator.effective_balance = 0; + validator.activation_eligibility_epoch = FAR_FUTURE_EPOCH; + state.pending_balance_deposits.push(PendingBalanceDeposit { index, amount: balance }); + + Ok(()) }