@@ -13,8 +13,8 @@ use std::cmp::{max, min};
1313use std:: collections:: { BTreeSet , HashMap } ;
1414use tracing:: instrument;
1515use types:: {
16- ActivationQueue , BeaconState , BeaconStateError , ChainSpec , Checkpoint , DepositData , Epoch ,
17- EthSpec , ExitCache , ForkName , List , ParticipationFlags , PendingDeposit ,
16+ ActivationQueue , BeaconState , BeaconStateError , BuilderPendingPayment , ChainSpec , Checkpoint ,
17+ DepositData , Epoch , EthSpec , ExitCache , ForkName , List , ParticipationFlags , PendingDeposit ,
1818 ProgressiveBalancesCache , RelativeEpoch , Unsigned , Validator , Vector ,
1919 consts:: altair:: {
2020 NUM_FLAG_INDICES , PARTICIPATION_FLAG_WEIGHTS , TIMELY_HEAD_FLAG_INDEX ,
@@ -32,6 +32,7 @@ pub struct SinglePassConfig {
3232 pub pending_consolidations : bool ,
3333 pub effective_balance_updates : bool ,
3434 pub proposer_lookahead : bool ,
35+ pub builder_pending_payments : bool ,
3536}
3637
3738impl Default for SinglePassConfig {
@@ -51,6 +52,7 @@ impl SinglePassConfig {
5152 pending_consolidations : true ,
5253 effective_balance_updates : true ,
5354 proposer_lookahead : true ,
55+ builder_pending_payments : true ,
5456 }
5557 }
5658
@@ -64,6 +66,7 @@ impl SinglePassConfig {
6466 pending_consolidations : false ,
6567 effective_balance_updates : false ,
6668 proposer_lookahead : false ,
69+ builder_pending_payments : false ,
6770 }
6871 }
6972}
@@ -454,6 +457,12 @@ pub fn process_epoch_single_pass<E: EthSpec>(
454457 ) ?;
455458 }
456459
460+ // Process builder pending payments outside the single-pass loop, as they depend on balances for multiple
461+ // validators and cannot be computed accurately inside the loop.
462+ if fork_name. gloas_enabled ( ) && conf. builder_pending_payments {
463+ process_builder_pending_payments ( state, state_ctxt, spec) ?;
464+ }
465+
457466 // Finally, finish updating effective balance caches. We need this to happen *after* processing
458467 // of pending consolidations, which recomputes some effective balances.
459468 if conf. effective_balance_updates {
@@ -472,7 +481,7 @@ pub fn process_epoch_single_pass<E: EthSpec>(
472481 Ok ( summary)
473482}
474483
475- // TOOO (EIP-7917): use balances cache
484+ // TODO (EIP-7917): use balances cache
476485pub fn process_proposer_lookahead < E : EthSpec > (
477486 state : & mut BeaconState < E > ,
478487 spec : & ChainSpec ,
@@ -502,6 +511,68 @@ pub fn process_proposer_lookahead<E: EthSpec>(
502511 Ok ( ( ) )
503512}
504513
514+ /// Calculate the quorum threshold for builder payments based on total active balance.
515+ fn get_builder_payment_quorum_threshold < E : EthSpec > (
516+ state_ctxt : & StateContext ,
517+ spec : & ChainSpec ,
518+ ) -> Result < u64 , Error > {
519+ let per_slot_balance = state_ctxt
520+ . total_active_balance
521+ . safe_div ( E :: slots_per_epoch ( ) ) ?;
522+ let quorum = per_slot_balance. safe_mul ( spec. builder_payment_threshold_numerator ) ?;
523+ quorum
524+ . safe_div ( spec. builder_payment_threshold_denominator )
525+ . map_err ( Error :: from)
526+ }
527+
528+ /// Process builder pending payments, moving qualifying payments to withdrawals.
529+ /// TODO(EIP-7732): Add EF consensus-spec tests for `process_builder_pending_payments`
530+ /// Currently blocked by EF consensus-spec-tests for Gloas not yet integrated.
531+ fn process_builder_pending_payments < E : EthSpec > (
532+ state : & mut BeaconState < E > ,
533+ state_ctxt : & StateContext ,
534+ spec : & ChainSpec ,
535+ ) -> Result < ( ) , Error > {
536+ let quorum = get_builder_payment_quorum_threshold :: < E > ( state_ctxt, spec) ?;
537+
538+ // Collect qualifying payments
539+ let qualifying_payments = state
540+ . builder_pending_payments ( ) ?
541+ . iter ( )
542+ . take ( E :: slots_per_epoch ( ) as usize )
543+ . filter ( |payment| payment. weight > quorum)
544+ . cloned ( )
545+ . collect :: < Vec < _ > > ( ) ;
546+
547+ // Update `builder_pending_withdrawals` with qualifying `builder_pending_payments`
548+ qualifying_payments
549+ . into_iter ( )
550+ . try_for_each ( |payment| -> Result < ( ) , Error > {
551+ let exit_queue_epoch =
552+ state. compute_exit_epoch_and_update_churn ( payment. withdrawal . amount , spec) ?;
553+ let withdrawable_epoch =
554+ exit_queue_epoch. safe_add ( spec. min_validator_withdrawability_delay ) ?;
555+
556+ let mut withdrawal = payment. withdrawal . clone ( ) ;
557+ withdrawal. withdrawable_epoch = withdrawable_epoch;
558+ state. builder_pending_withdrawals_mut ( ) ?. push ( withdrawal) ?;
559+ Ok ( ( ) )
560+ } ) ?;
561+
562+ // Move remaining `builder_pending_payments` to start of list and set the rest to default
563+ let new_payments = state
564+ . builder_pending_payments ( ) ?
565+ . iter ( )
566+ . skip ( E :: slots_per_epoch ( ) as usize )
567+ . cloned ( )
568+ . chain ( ( 0 ..E :: slots_per_epoch ( ) as usize ) . map ( |_| BuilderPendingPayment :: default ( ) ) )
569+ . collect :: < Vec < _ > > ( ) ;
570+
571+ * state. builder_pending_payments_mut ( ) ? = Vector :: new ( new_payments) ?;
572+
573+ Ok ( ( ) )
574+ }
575+
505576fn process_single_inactivity_update (
506577 inactivity_score : & mut Cow < u64 > ,
507578 validator_info : & ValidatorInfo ,
0 commit comments