From 8b4c037fb4ae43ec91d45f210dc8e1c90ddb5acc Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Tue, 20 Jan 2026 17:07:27 +1100 Subject: [PATCH] Add `get_builders_sweep_withdrawals` and fix Gloas withdrawal tests. --- beacon_node/beacon_chain/src/beacon_chain.rs | 4 +- beacon_node/http_api/src/builder_states.rs | 2 +- .../src/per_block_processing.rs | 72 ++++++++++++++++++- .../process_withdrawals.rs | 15 +++- consensus/types/src/state/beacon_state.rs | 1 + 5 files changed, 87 insertions(+), 7 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 3f0380e43f..8a13f0ff39 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -4794,7 +4794,7 @@ impl BeaconChain { let proposal_epoch = proposal_slot.epoch(T::EthSpec::slots_per_epoch()); if head_state.current_epoch() == proposal_epoch { return get_expected_withdrawals(&unadvanced_state, &self.spec) - .map(|(withdrawals, _, _, _)| withdrawals) + .map(|(withdrawals, _, _, _, _)| withdrawals) .map_err(Error::PrepareProposerFailed); } @@ -4812,7 +4812,7 @@ impl BeaconChain { &self.spec, )?; get_expected_withdrawals(&advanced_state, &self.spec) - .map(|(withdrawals, _, _, _)| withdrawals) + .map(|(withdrawals, _, _, _, _)| withdrawals) .map_err(Error::PrepareProposerFailed) } diff --git a/beacon_node/http_api/src/builder_states.rs b/beacon_node/http_api/src/builder_states.rs index a5a8c28fce..aa9ae95654 100644 --- a/beacon_node/http_api/src/builder_states.rs +++ b/beacon_node/http_api/src/builder_states.rs @@ -32,7 +32,7 @@ pub fn get_next_withdrawals( } match get_expected_withdrawals(&state, &chain.spec) { - Ok((withdrawals, _, _, _)) => Ok(withdrawals), + Ok((withdrawals, _, _, _, _)) => Ok(withdrawals), Err(e) => Err(warp_utils::reject::custom_server_error(format!( "failed to get expected withdrawal: {:?}", e diff --git a/consensus/state_processing/src/per_block_processing.rs b/consensus/state_processing/src/per_block_processing.rs index c9b193ff7a..ae7e4bb0a5 100644 --- a/consensus/state_processing/src/per_block_processing.rs +++ b/consensus/state_processing/src/per_block_processing.rs @@ -659,6 +659,70 @@ pub fn get_pending_partial_withdrawals( 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()); + // Reserve one slot for validator sweep withdrawals + let withdrawals_limit = E::max_withdrawals_per_payload().saturating_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` @@ -737,7 +801,7 @@ pub fn get_validators_sweep_withdrawals( pub fn get_expected_withdrawals( state: &BeaconState, spec: &ChainSpec, -) -> Result<(Withdrawals, Option, Option, u64), BlockProcessingError> { +) -> Result<(Withdrawals, Option, Option, Option, u64), BlockProcessingError> { let mut withdrawal_index = state.next_withdrawal_index()?; let mut withdrawals = Vec::::with_capacity(E::max_withdrawals_per_payload()); @@ -751,6 +815,11 @@ pub fn get_expected_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_validators_sweep_count = get_validators_sweep_withdrawals(state, &mut withdrawal_index, &mut withdrawals, spec)?; @@ -761,6 +830,7 @@ pub fn get_expected_withdrawals( .map_err(BlockProcessingError::SszTypesError)?, processed_builder_withdrawals_count, processed_partial_withdrawals_count, + processed_builders_sweep_count, processed_validators_sweep_count, )) } diff --git a/consensus/state_processing/src/per_block_processing/process_withdrawals.rs b/consensus/state_processing/src/per_block_processing/process_withdrawals.rs index a4ca222186..419db5950c 100644 --- a/consensus/state_processing/src/per_block_processing/process_withdrawals.rs +++ b/consensus/state_processing/src/per_block_processing/process_withdrawals.rs @@ -82,7 +82,7 @@ pub mod capella { ) -> Result<(), BlockProcessingError> { // check if capella enabled because this function will run on the merge block where the fork is technically still Bellatrix if state.fork_name_unchecked().capella_enabled() { - let (expected_withdrawals, _, partial_withdrawals_count, _) = + let (expected_withdrawals, _, partial_withdrawals_count, _, _) = get_expected_withdrawals(state, spec)?; let expected_root = expected_withdrawals.tree_hash_root(); @@ -127,8 +127,14 @@ pub mod gloas { return Ok(()); } - let (expected_withdrawals, builder_withdrawals_count, partial_withdrawals_count, _) = - get_expected_withdrawals(state, spec)?; + // TODO(EIP-7732): Use processed_builders_sweep_count to call update_next_withdrawal_builder_index + let ( + expected_withdrawals, + builder_withdrawals_count, + partial_withdrawals_count, + _processed_builders_sweep_count, + _, + ) = get_expected_withdrawals(state, spec)?; for withdrawal in expected_withdrawals.iter() { decrease_balance( @@ -158,6 +164,9 @@ pub mod gloas { *state.builder_pending_withdrawals_mut()? = List::new(updated_builder_withdrawals)?; } + // [New in Gloas:EIP7732] update_payload_expected_withdrawals + *state.payload_expected_withdrawals_mut()? = List::new(expected_withdrawals.to_vec())?; + process_withdrawals_common(state, expected_withdrawals, partial_withdrawals_count, spec)?; Ok(()) diff --git a/consensus/types/src/state/beacon_state.rs b/consensus/types/src/state/beacon_state.rs index 0416e066c5..0e6897cf05 100644 --- a/consensus/types/src/state/beacon_state.rs +++ b/consensus/types/src/state/beacon_state.rs @@ -68,6 +68,7 @@ pub enum BeaconStateError { EpochOutOfBounds, SlotOutOfBounds, UnknownValidator(usize), + UnknownBuilder(u64), UnableToDetermineProducer, InvalidBitfield, EmptyCommittee,