use crate::common::decrease_balance; use crate::per_block_processing::builder::{ convert_builder_index_to_validator_index, convert_validator_index_to_builder_index, is_builder_index, }; use crate::per_block_processing::errors::BlockProcessingError; use milhouse::List; use safe_arith::{SafeArith, SafeArithIter}; use tree_hash::TreeHash; use types::{ AbstractExecPayload, BeaconState, BeaconStateError, ChainSpec, EthSpec, ExecPayload, ExpectedWithdrawals, ExpectedWithdrawalsCapella, ExpectedWithdrawalsElectra, ExpectedWithdrawalsGloas, Validator, Withdrawal, Withdrawals, }; /// Compute the next batch of withdrawals which should be included in a block. /// /// https://ethereum.github.io/consensus-specs/specs/gloas/beacon-chain/#modified-get_expected_withdrawals #[allow(clippy::type_complexity)] pub fn get_expected_withdrawals( state: &BeaconState, spec: &ChainSpec, ) -> Result, BlockProcessingError> { let mut withdrawal_index = state.next_withdrawal_index()?; let mut withdrawals = Vec::::with_capacity(E::max_withdrawals_per_payload()); // [New in Gloas:EIP7732] // Get builder withdrawals let processed_builder_withdrawals_count = get_builder_withdrawals(state, &mut withdrawal_index, &mut withdrawals)?; // [New in Electra:EIP7251] // Get partial withdrawals. let processed_partial_withdrawals_count = get_pending_partial_withdrawals(state, &mut withdrawal_index, &mut withdrawals, spec)?; // [New in Gloas:EIP7732] // Get builders sweep withdrawals let processed_builders_sweep_count = get_builders_sweep_withdrawals(state, &mut withdrawal_index, &mut withdrawals)?; // Get validators sweep withdrawals let processed_sweep_withdrawals_count = get_validators_sweep_withdrawals(state, &mut withdrawal_index, &mut withdrawals, spec)?; let withdrawals = withdrawals .try_into() .map_err(BlockProcessingError::SszTypesError)?; let fork_name = state.fork_name_unchecked(); if fork_name.gloas_enabled() { Ok(ExpectedWithdrawals::Gloas(ExpectedWithdrawalsGloas { withdrawals, processed_builder_withdrawals_count: processed_builder_withdrawals_count .ok_or(BlockProcessingError::IncorrectExpectedWithdrawalsVariant)?, processed_partial_withdrawals_count: processed_partial_withdrawals_count .ok_or(BlockProcessingError::IncorrectExpectedWithdrawalsVariant)?, processed_builders_sweep_count: processed_builders_sweep_count .ok_or(BlockProcessingError::IncorrectExpectedWithdrawalsVariant)?, processed_sweep_withdrawals_count, })) } else if fork_name.electra_enabled() { Ok(ExpectedWithdrawals::Electra(ExpectedWithdrawalsElectra { withdrawals, processed_partial_withdrawals_count: processed_partial_withdrawals_count .ok_or(BlockProcessingError::IncorrectExpectedWithdrawalsVariant)?, processed_sweep_withdrawals_count, })) } else { Ok(ExpectedWithdrawals::Capella(ExpectedWithdrawalsCapella { withdrawals, processed_sweep_withdrawals_count, })) } } pub fn get_builder_withdrawals( state: &BeaconState, withdrawal_index: &mut u64, withdrawals: &mut Vec, ) -> Result, BlockProcessingError> { let Ok(builder_pending_withdrawals) = state.builder_pending_withdrawals() else { // Pre-Gloas, nothing to do. return Ok(None); }; let withdrawals_limit = E::max_withdrawals_per_payload().safe_sub(1)?; block_verify!( withdrawals.len() <= withdrawals_limit, BlockProcessingError::WithdrawalsLimitExceeded { limit: withdrawals_limit, prior_withdrawals: withdrawals.len() } ); let mut processed_count = 0; for withdrawal in builder_pending_withdrawals { let has_reached_limit = withdrawals.len() == withdrawals_limit; if has_reached_limit { break; } let builder_index = withdrawal.builder_index; withdrawals.push(Withdrawal { index: *withdrawal_index, validator_index: convert_builder_index_to_validator_index(builder_index), address: withdrawal.fee_recipient, amount: withdrawal.amount, }); withdrawal_index.safe_add_assign(1)?; processed_count.safe_add_assign(1)?; } Ok(Some(processed_count)) } pub fn get_pending_partial_withdrawals( state: &BeaconState, withdrawal_index: &mut u64, withdrawals: &mut Vec, spec: &ChainSpec, ) -> Result, BlockProcessingError> { let Ok(pending_partial_withdrawals) = state.pending_partial_withdrawals() else { // Pre-Electra nothing to do. return Ok(None); }; let epoch = state.current_epoch(); let withdrawals_limit = std::cmp::min( withdrawals .len() .safe_add(spec.max_pending_partials_per_withdrawals_sweep as usize)?, E::max_withdrawals_per_payload().safe_sub(1)?, ); block_verify!( withdrawals.len() <= withdrawals_limit, BlockProcessingError::WithdrawalsLimitExceeded { limit: withdrawals_limit, prior_withdrawals: withdrawals.len() } ); let mut processed_count = 0; for withdrawal in pending_partial_withdrawals { let is_withdrawable = withdrawal.withdrawable_epoch <= epoch; let has_reached_limit = withdrawals.len() >= withdrawals_limit; if !is_withdrawable || has_reached_limit { break; } let validator_index = withdrawal.validator_index; let validator = state.get_validator(validator_index as usize)?; let balance = get_balance_after_withdrawals(state, validator_index, withdrawals)?; if is_eligible_for_partial_withdrawals(validator, balance, spec) { let withdrawal_amount = std::cmp::min( balance.safe_sub(spec.min_activation_balance)?, withdrawal.amount, ); withdrawals.push(Withdrawal { index: *withdrawal_index, validator_index, address: validator .get_execution_withdrawal_address(spec) .ok_or(BeaconStateError::NonExecutionAddressWithdrawalCredential)?, amount: withdrawal_amount, }); withdrawal_index.safe_add_assign(1)?; } processed_count.safe_add_assign(1)?; } Ok(Some(processed_count)) } /// Get withdrawals from the builders sweep. /// /// This function iterates through builders starting from `next_withdrawal_builder_index` /// and adds withdrawals for builders whose withdrawable_epoch has been reached and have balance. /// /// https://ethereum.github.io/consensus-specs/specs/gloas/beacon-chain/#new-get_builders_sweep_withdrawals pub fn get_builders_sweep_withdrawals( state: &BeaconState, withdrawal_index: &mut u64, withdrawals: &mut Vec, ) -> Result, BlockProcessingError> { let Ok(builders) = state.builders() else { // Pre-Gloas, nothing to do. return Ok(None); }; if builders.is_empty() { return Ok(Some(0)); } let epoch = state.current_epoch(); let builders_limit = std::cmp::min(builders.len(), E::max_builders_per_withdrawals_sweep()); let withdrawals_limit = E::max_withdrawals_per_payload().safe_sub(1)?; block_verify!( withdrawals.len() <= withdrawals_limit, BlockProcessingError::WithdrawalsLimitExceeded { limit: withdrawals_limit, prior_withdrawals: withdrawals.len() } ); let mut processed_count: u64 = 0; let mut builder_index = state.next_withdrawal_builder_index()?; for _ in 0..builders_limit { if withdrawals.len() >= withdrawals_limit { break; } let builder = builders .get(builder_index as usize) .ok_or(BeaconStateError::UnknownBuilder(builder_index))?; if builder.withdrawable_epoch <= epoch && builder.balance > 0 { withdrawals.push(Withdrawal { index: *withdrawal_index, validator_index: convert_builder_index_to_validator_index(builder_index), address: builder.execution_address, amount: builder.balance, }); withdrawal_index.safe_add_assign(1)?; } builder_index = builder_index.safe_add(1)?.safe_rem(builders.len() as u64)?; processed_count.safe_add_assign(1)?; } Ok(Some(processed_count)) } /// Get withdrawals from the validator sweep. /// /// This function iterates through validators starting from `next_withdrawal_validator_index` /// and adds full or partial withdrawals for eligible validators. /// /// https://ethereum.github.io/consensus-specs/specs/capella/beacon-chain/#new-get_validators_sweep_withdrawals pub fn get_validators_sweep_withdrawals( state: &BeaconState, withdrawal_index: &mut u64, withdrawals: &mut Vec, spec: &ChainSpec, ) -> Result { let epoch = state.current_epoch(); let fork_name = state.fork_name_unchecked(); let mut validator_index = state.next_withdrawal_validator_index()?; let validators_limit = std::cmp::min( state.validators().len() as u64, spec.max_validators_per_withdrawals_sweep, ); let withdrawals_limit = E::max_withdrawals_per_payload(); // There must be at least one space reserved for validator sweep withdrawals block_verify!( withdrawals.len() < withdrawals_limit, BlockProcessingError::WithdrawalsLimitExceeded { limit: withdrawals_limit, prior_withdrawals: withdrawals.len() } ); let mut processed_count: u64 = 0; for _ in 0..validators_limit { if withdrawals.len() >= withdrawals_limit { break; } let validator = state.get_validator(validator_index as usize)?; let balance = get_balance_after_withdrawals(state, validator_index, withdrawals)?; if validator.is_fully_withdrawable_validator(balance, epoch, spec, fork_name) { withdrawals.push(Withdrawal { index: *withdrawal_index, validator_index, address: validator .get_execution_withdrawal_address(spec) .ok_or(BlockProcessingError::WithdrawalCredentialsInvalid)?, amount: balance, }); withdrawal_index.safe_add_assign(1)?; } else if validator.is_partially_withdrawable_validator(balance, spec, fork_name) { withdrawals.push(Withdrawal { index: *withdrawal_index, validator_index, address: validator .get_execution_withdrawal_address(spec) .ok_or(BlockProcessingError::WithdrawalCredentialsInvalid)?, amount: balance.safe_sub(validator.get_max_effective_balance(spec, fork_name))?, }); withdrawal_index.safe_add_assign(1)?; } validator_index = validator_index .safe_add(1)? .safe_rem(state.validators().len() as u64)?; processed_count.safe_add_assign(1)?; } Ok(processed_count) } pub fn get_balance_after_withdrawals( state: &BeaconState, validator_index: u64, withdrawals: &[Withdrawal], ) -> Result { let withdrawn = withdrawals .iter() .filter(|withdrawal| withdrawal.validator_index == validator_index) .map(|withdrawal| withdrawal.amount) .safe_sum()?; state .get_balance(validator_index as usize)? .safe_sub(withdrawn) .map_err(Into::into) } fn is_eligible_for_partial_withdrawals( validator: &Validator, balance: u64, spec: &ChainSpec, ) -> bool { let has_sufficient_effective_balance = validator.effective_balance >= spec.min_activation_balance; let has_excess_balance = balance > spec.min_activation_balance; validator.exit_epoch == spec.far_future_epoch && has_sufficient_effective_balance && has_excess_balance } fn update_next_withdrawal_index( state: &mut BeaconState, withdrawals: &Withdrawals, ) -> Result<(), BlockProcessingError> { // Update the next withdrawal index if this block contained withdrawals if let Some(latest_withdrawal) = withdrawals.last() { *state.next_withdrawal_index_mut()? = latest_withdrawal.index.safe_add(1)?; } Ok(()) } fn update_payload_expected_withdrawals( state: &mut BeaconState, withdrawals: &Withdrawals, ) -> Result<(), BlockProcessingError> { *state.payload_expected_withdrawals_mut()? = List::new(withdrawals.to_vec())?; Ok(()) } fn update_builder_pending_withdrawals( state: &mut BeaconState, processed_builder_withdrawals_count: u64, ) -> Result<(), BlockProcessingError> { state .builder_pending_withdrawals_mut()? .pop_front(processed_builder_withdrawals_count as usize)?; Ok(()) } fn update_pending_partial_withdrawals( state: &mut BeaconState, processed_partial_withdrawals_count: u64, ) -> Result<(), BlockProcessingError> { state .pending_partial_withdrawals_mut()? .pop_front(processed_partial_withdrawals_count as usize)?; Ok(()) } fn update_next_withdrawal_builder_index( state: &mut BeaconState, processed_builders_sweep_count: u64, ) -> Result<(), BlockProcessingError> { if !state.builders()?.is_empty() { // Update the next builder index to start the next withdrawal sweep let next_index = state .next_withdrawal_builder_index()? .safe_add(processed_builders_sweep_count)?; let next_builder_index = next_index.safe_rem(state.builders()?.len() as u64)?; *state.next_withdrawal_builder_index_mut()? = next_builder_index; } Ok(()) } fn update_next_withdrawal_validator_index( state: &mut BeaconState, withdrawals: &Withdrawals, spec: &ChainSpec, ) -> Result<(), BlockProcessingError> { // Update the next validator index to start the next withdrawal sweep if withdrawals.len() == E::max_withdrawals_per_payload() { // Next sweep starts after the latest withdrawal's validator index let latest_withdrawal = withdrawals .last() .ok_or(BlockProcessingError::MissingLastWithdrawal)?; let next_validator_index = latest_withdrawal .validator_index .safe_add(1)? .safe_rem(state.validators().len() as u64)?; *state.next_withdrawal_validator_index_mut()? = next_validator_index; } else { // Advance sweep by the max length of the sweep if there was not a full set of withdrawals let next_validator_index = state .next_withdrawal_validator_index()? .safe_add(spec.max_validators_per_withdrawals_sweep)? .safe_rem(state.validators().len() as u64)?; *state.next_withdrawal_validator_index_mut()? = next_validator_index; } Ok(()) } pub fn apply_withdrawals( state: &mut BeaconState, withdrawals: &Withdrawals, ) -> Result<(), BlockProcessingError> { for withdrawal in withdrawals { if state.fork_name_unchecked().gloas_enabled() && is_builder_index(withdrawal.validator_index) { let builder_index = convert_validator_index_to_builder_index(withdrawal.validator_index); let builder = state .builders_mut()? .get_mut(builder_index as usize) .ok_or(BeaconStateError::UnknownBuilder(builder_index))?; builder.balance = builder.balance.saturating_sub(withdrawal.amount); } else { decrease_balance( state, withdrawal.validator_index as usize, withdrawal.amount, )?; } } Ok(()) } pub mod capella_electra { use super::*; /// Apply withdrawals to the state. pub fn process_withdrawals>( state: &mut BeaconState, payload: Payload::Ref<'_>, spec: &ChainSpec, ) -> Result<(), BlockProcessingError> { let expected_withdrawals = get_expected_withdrawals(state, spec)?; let expected_root = expected_withdrawals.withdrawals().tree_hash_root(); let withdrawals_root = payload.withdrawals_root()?; if expected_root != withdrawals_root { return Err(BlockProcessingError::WithdrawalsRootMismatch { expected: expected_root, found: withdrawals_root, }); } // Apply expected withdrawals. apply_withdrawals(state, expected_withdrawals.withdrawals())?; // [Common] Update withdrawals fields in the state update_next_withdrawal_index(state, expected_withdrawals.withdrawals())?; // [New in Electra:EIP7251] if let Ok(processed_partial_withdrawals_count) = expected_withdrawals.processed_partial_withdrawals_count() { update_pending_partial_withdrawals(state, processed_partial_withdrawals_count)?; } // [Common from Capella] update_next_withdrawal_validator_index(state, expected_withdrawals.withdrawals(), spec)?; Ok(()) } } pub mod gloas { use super::*; /// Apply withdrawals to the state. pub fn process_withdrawals( state: &mut BeaconState, spec: &ChainSpec, ) -> Result<(), BlockProcessingError> { // Return early if the parent block is empty. if *state.latest_block_hash()? != state.latest_execution_payload_bid()?.block_hash { return Ok(()); } let ExpectedWithdrawals::Gloas(ExpectedWithdrawalsGloas { withdrawals, processed_builder_withdrawals_count, processed_partial_withdrawals_count, processed_builders_sweep_count, processed_sweep_withdrawals_count: _, }) = get_expected_withdrawals(state, spec)? else { return Err(BlockProcessingError::IncorrectExpectedWithdrawalsVariant); }; // Apply expected withdrawals. apply_withdrawals(state, &withdrawals)?; // [Common] Update withdrawals fields in the state update_next_withdrawal_index(state, &withdrawals)?; // [New in Gloas:EIP7732] update_payload_expected_withdrawals(state, &withdrawals)?; // [New in Gloas:EIP7732] update_builder_pending_withdrawals(state, processed_builder_withdrawals_count)?; // [Common from Electra] update_pending_partial_withdrawals(state, processed_partial_withdrawals_count)?; // [New in Gloas:EIP7732] update_next_withdrawal_builder_index(state, processed_builders_sweep_count)?; // [Common from Capella] update_next_withdrawal_validator_index(state, &withdrawals, spec)?; Ok(()) } }