diff --git a/consensus/state_processing/src/per_epoch_processing/single_pass.rs b/consensus/state_processing/src/per_epoch_processing/single_pass.rs index 1584e932bdf..bd2531f0976 100644 --- a/consensus/state_processing/src/per_epoch_processing/single_pass.rs +++ b/consensus/state_processing/src/per_epoch_processing/single_pass.rs @@ -32,6 +32,7 @@ pub struct SinglePassConfig { pub pending_consolidations: bool, pub effective_balance_updates: bool, pub proposer_lookahead: bool, + pub builder_pending_payments: bool, } impl Default for SinglePassConfig { @@ -51,6 +52,7 @@ impl SinglePassConfig { pending_consolidations: true, effective_balance_updates: true, proposer_lookahead: true, + builder_pending_payments: true, } } @@ -64,6 +66,7 @@ impl SinglePassConfig { pending_consolidations: false, effective_balance_updates: false, proposer_lookahead: false, + builder_pending_payments: false, } } } @@ -469,10 +472,14 @@ pub fn process_epoch_single_pass( process_proposer_lookahead(state, spec)?; } + if conf.builder_pending_payments && fork_name.gloas_enabled() { + process_builder_pending_payments(state, spec)?; + } + Ok(summary) } -// TOOO(EIP-7917): use balances cache +// TODO(EIP-7917): use balances cache pub fn process_proposer_lookahead( state: &mut BeaconState, spec: &ChainSpec, @@ -502,6 +509,69 @@ pub fn process_proposer_lookahead( Ok(()) } +/// Calculate the quorum threshold for builder payments based on total active balance. +pub fn get_builder_payment_quorum_threshold( + state: &BeaconState, + spec: &ChainSpec, +) -> Result { + let total_active_balance = state.get_total_active_balance()?; + + let quorum = total_active_balance + .safe_div(E::slots_per_epoch())? + .safe_mul(spec.builder_payment_threshold_numerator)?; + + quorum + .safe_div(spec.builder_payment_threshold_denominator) + .map_err(Error::from) +} + +/// Process builder pending payments, moving qualifying payments to withdrawals. +/// TODO(EIP-7732): Add EF consensus-spec tests for `process_builder_pending_payments` +/// Currently blocked by EF consensus-spec-tests for Gloas not yet integrated. +pub fn process_builder_pending_payments( + state: &mut BeaconState, + spec: &ChainSpec, +) -> Result<(), Error> { + let quorum = get_builder_payment_quorum_threshold(state, spec)?; + + // Collect qualifying payments + let qualifying_payments = state + .builder_pending_payments()? + .iter() + .take(E::slots_per_epoch() as usize) + .filter(|payment| payment.weight > quorum) + .cloned() + .collect::>(); + + // Update `builder_pending_withdrawals` with qualifying `builder_pending_payments` + qualifying_payments + .into_iter() + .try_for_each(|payment| -> Result<(), Error> { + let exit_queue_epoch = + state.compute_exit_epoch_and_update_churn(payment.withdrawal.amount, spec)?; + let withdrawable_epoch = + exit_queue_epoch.safe_add(spec.min_validator_withdrawability_delay)?; + + let mut withdrawal = payment.withdrawal.clone(); + withdrawal.withdrawable_epoch = withdrawable_epoch; + state.builder_pending_withdrawals_mut()?.push(withdrawal)?; + Ok(()) + })?; + + // Move remaining `builder_pending_payments` to start of list and set the rest to default + let new_payments = state + .builder_pending_payments()? + .iter() + .skip(E::slots_per_epoch() as usize) + .cloned() + .chain((0..E::slots_per_epoch() as usize).map(|_| types::BuilderPendingPayment::default())) + .collect::>(); + + *state.builder_pending_payments_mut()? = Vector::new(new_payments)?; + + Ok(()) +} + fn process_single_inactivity_update( inactivity_score: &mut Cow, validator_info: &ValidatorInfo, diff --git a/consensus/state_processing/src/per_slot_processing.rs b/consensus/state_processing/src/per_slot_processing.rs index 8695054e1e7..10ce216c3c5 100644 --- a/consensus/state_processing/src/per_slot_processing.rs +++ b/consensus/state_processing/src/per_slot_processing.rs @@ -13,6 +13,7 @@ pub enum Error { EpochProcessingError(EpochProcessingError), ArithError(ArithError), InconsistentStateFork(InconsistentFork), + BitfieldError(ssz::BitfieldError), } impl From for Error { @@ -21,6 +22,12 @@ impl From for Error { } } +impl From for Error { + fn from(e: ssz::BitfieldError) -> Self { + Self::BitfieldError(e) + } +} + /// Advances a state forward by one slot, performing per-epoch processing if required. /// /// If the root of the supplied `state` is known, then it can be passed as `state_root`. If @@ -49,6 +56,18 @@ pub fn per_slot_processing( state.slot_mut().safe_add_assign(1)?; + // Unset the next payload availability + if state.fork_name_unchecked().gloas_enabled() { + let next_slot_index = state + .slot() + .as_usize() + .safe_add(1)? + .safe_rem(E::slots_per_historical_root())?; + state + .execution_payload_availability_mut()? + .set(next_slot_index, false)?; + } + // Process fork upgrades here. Note that multiple upgrades can potentially run // in sequence if they are scheduled in the same Epoch (common in testnets) if state.slot().safe_rem(E::slots_per_epoch())? == 0 {