From e2e82ff1b99bef656224564d3a45266093a46266 Mon Sep 17 00:00:00 2001 From: realbigsean Date: Mon, 6 May 2024 18:56:16 -0400 Subject: [PATCH] process withdrawals updates --- beacon_node/beacon_chain/src/beacon_chain.rs | 5 +- .../beacon_chain/src/execution_payload.rs | 2 +- beacon_node/http_api/src/builder_states.rs | 2 +- .../http_api/tests/interactive_tests.rs | 2 + beacon_node/http_api/tests/tests.rs | 4 +- .../src/per_block_processing.rs | 76 +++++++++++++++++-- consensus/types/src/beacon_state.rs | 4 +- consensus/types/src/validator.rs | 21 +++-- 8 files changed, 99 insertions(+), 17 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index cd47068977..99d8eac8aa 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -4435,6 +4435,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_err(Error::PrepareProposerFailed); } @@ -4452,7 +4453,9 @@ impl BeaconChain { proposal_epoch.start_slot(T::EthSpec::slots_per_epoch()), &self.spec, )?; - get_expected_withdrawals(&advanced_state, &self.spec).map_err(Error::PrepareProposerFailed) + get_expected_withdrawals(&advanced_state, &self.spec) + .map(|(withdrawals, _)| withdrawals) + .map_err(Error::PrepareProposerFailed) } /// Determine whether a fork choice update to the execution layer should be overridden. diff --git a/beacon_node/beacon_chain/src/execution_payload.rs b/beacon_node/beacon_chain/src/execution_payload.rs index cbffe36342..a6e0d247dc 100644 --- a/beacon_node/beacon_chain/src/execution_payload.rs +++ b/beacon_node/beacon_chain/src/execution_payload.rs @@ -413,7 +413,7 @@ pub fn get_execution_payload( state.latest_execution_payload_header()?.block_hash(); let withdrawals = match state { &BeaconState::Capella(_) | &BeaconState::Deneb(_) | &BeaconState::Electra(_) => { - Some(get_expected_withdrawals(state, spec)?.into()) + Some(get_expected_withdrawals(state, spec)?.0.into()) } &BeaconState::Bellatrix(_) => None, // These shouldn't happen but they're here to make the pattern irrefutable diff --git a/beacon_node/http_api/src/builder_states.rs b/beacon_node/http_api/src/builder_states.rs index a540113ab4..54f2c0efa8 100644 --- a/beacon_node/http_api/src/builder_states.rs +++ b/beacon_node/http_api/src/builder_states.rs @@ -33,7 +33,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/beacon_node/http_api/tests/interactive_tests.rs b/beacon_node/http_api/tests/interactive_tests.rs index 529dc852e9..711820ccac 100644 --- a/beacon_node/http_api/tests/interactive_tests.rs +++ b/beacon_node/http_api/tests/interactive_tests.rs @@ -610,6 +610,7 @@ pub async fn proposer_boost_re_org_test( assert_eq!(state_b.slot(), slot_b); let pre_advance_withdrawals = get_expected_withdrawals(&state_b, &harness.chain.spec) .unwrap() + .0 .to_vec(); complete_state_advance(&mut state_b, None, slot_c, &harness.chain.spec).unwrap(); @@ -696,6 +697,7 @@ pub async fn proposer_boost_re_org_test( get_expected_withdrawals(&state_b, &harness.chain.spec) } .unwrap() + .0 .to_vec(); let payload_attribs_withdrawals = payload_attribs.withdrawals().unwrap(); assert_eq!(expected_withdrawals, *payload_attribs_withdrawals); diff --git a/beacon_node/http_api/tests/tests.rs b/beacon_node/http_api/tests/tests.rs index 3653929bda..0c246dc7d9 100644 --- a/beacon_node/http_api/tests/tests.rs +++ b/beacon_node/http_api/tests/tests.rs @@ -5448,7 +5448,9 @@ impl ApiTester { &self.chain.spec, ); } - let expected_withdrawals = get_expected_withdrawals(&state, &self.chain.spec).unwrap(); + let expected_withdrawals = get_expected_withdrawals(&state, &self.chain.spec) + .unwrap() + .0; // fetch expected withdrawals from the client let result = self.client.get_expected_withdrawals(&state_id).await; diff --git a/consensus/state_processing/src/per_block_processing.rs b/consensus/state_processing/src/per_block_processing.rs index 98671f82b9..4be4ac9581 100644 --- a/consensus/state_processing/src/per_block_processing.rs +++ b/consensus/state_processing/src/per_block_processing.rs @@ -503,13 +503,61 @@ pub fn compute_timestamp_at_slot( pub fn get_expected_withdrawals( state: &BeaconState, spec: &ChainSpec, -) -> Result, BlockProcessingError> { +) -> Result<(Withdrawals, Option), BlockProcessingError> { let epoch = state.current_epoch(); let mut withdrawal_index = state.next_withdrawal_index()?; let mut validator_index = state.next_withdrawal_validator_index()?; let mut withdrawals = vec![]; let fork_name = state.fork_name_unchecked(); + // [New in Electra:EIP7251] + // Consume pending partial withdrawals + let partial_withdrawals_count = + if let Ok(partial_withdrawals) = state.pending_partial_withdrawals() { + for withdrawal in partial_withdrawals { + if withdrawal.withdrawable_epoch > epoch + || withdrawals.len() == spec.max_pending_partials_per_withdrawals_sweep as usize + { + break; + } + + let withdrawal_balance = state + .balances() + .get(withdrawal.index as usize) + .copied() + .ok_or(BeaconStateError::BalancesOutOfBounds( + withdrawal.index as usize, + ))?; + let validator = state.validators().get(withdrawal.index as usize).ok_or( + BeaconStateError::UnknownValidator(withdrawal.index as usize), + )?; + + let has_sufficient_effective_balance = + validator.effective_balance >= spec.min_activation_balance; + let has_excess_balance = withdrawal_balance > spec.min_activation_balance; + + if validator.exit_epoch == spec.far_future_epoch + && has_sufficient_effective_balance + && has_excess_balance + { + let withdrawable_balance = std::cmp::min( + withdrawal_balance.safe_sub(spec.min_activation_balance)?, + withdrawal.amount, + ); + withdrawals.push(Withdrawal { + index: withdrawal_index, + validator_index: withdrawal.index, + address: Address::from_slice(&validator.withdrawal_credentials[12..]), + amount: withdrawable_balance, + }); + withdrawal_index.safe_add_assign(1)?; + } + } + Some(withdrawals.len()) + } else { + None + }; + let bound = std::cmp::min( state.validators().len() as u64, spec.max_validators_per_withdrawals_sweep, @@ -536,7 +584,10 @@ pub fn get_expected_withdrawals( address: validator .get_eth1_withdrawal_address(spec) .ok_or(BlockProcessingError::WithdrawalCredentialsInvalid)?, - amount: balance.safe_sub(spec.max_effective_balance)?, + amount: balance.safe_sub( + validator + .get_validator_max_effective_balance(spec, state.fork_name_unchecked()), + )?, }); withdrawal_index.safe_add_assign(1)?; } @@ -548,7 +599,7 @@ pub fn get_expected_withdrawals( .safe_rem(state.validators().len() as u64)?; } - Ok(withdrawals.into()) + Ok((withdrawals.into(), partial_withdrawals_count)) } /// Apply withdrawals to the state. @@ -558,9 +609,9 @@ pub fn process_withdrawals>( spec: &ChainSpec, ) -> Result<(), BlockProcessingError> { match state { - BeaconState::Bellatrix(_) => Ok(()), BeaconState::Capella(_) | BeaconState::Deneb(_) | BeaconState::Electra(_) => { - let expected_withdrawals = get_expected_withdrawals(state, spec)?; + let (expected_withdrawals, partial_withdrawals_count) = + get_expected_withdrawals(state, spec)?; let expected_root = expected_withdrawals.tree_hash_root(); let withdrawals_root = payload.withdrawals_root()?; @@ -579,6 +630,19 @@ pub fn process_withdrawals>( )?; } + // Update pending partial withdrawals [New in Electra:EIP7251] + if let Some(partial_withdrawals_count) = partial_withdrawals_count { + let new_partial_withdrawals = state + .pending_partial_withdrawals()? + .to_vec() + .get(partial_withdrawals_count..) + .ok_or(BeaconStateError::PartialWithdrawalCountInvalid( + partial_withdrawals_count, + ))? + .to_vec(); + *state.pending_partial_withdrawals_mut()? = List::new(new_partial_withdrawals)?; + } + // Update the next withdrawal index if this block contained withdrawals if let Some(latest_withdrawal) = expected_withdrawals.last() { *state.next_withdrawal_index_mut()? = latest_withdrawal.index.safe_add(1)?; @@ -606,6 +670,6 @@ pub fn process_withdrawals>( Ok(()) } // these shouldn't even be encountered but they're here for completeness - BeaconState::Base(_) | BeaconState::Altair(_) => Ok(()), + BeaconState::Base(_) | BeaconState::Altair(_) | BeaconState::Bellatrix(_) => Ok(()), } } diff --git a/consensus/types/src/beacon_state.rs b/consensus/types/src/beacon_state.rs index 577f282a55..dda87c5dd5 100644 --- a/consensus/types/src/beacon_state.rs +++ b/consensus/types/src/beacon_state.rs @@ -159,6 +159,7 @@ pub enum Error { IndexNotSupported(usize), InvalidFlagIndex(usize), MerkleTreeError(merkle_proof::MerkleTreeError), + PartialWithdrawalCountInvalid(usize), } /// Control whether an epoch-indexed field can be indexed at the next epoch or not. @@ -2097,11 +2098,12 @@ impl BeaconState { &self, validator_index: usize, spec: &ChainSpec, + current_fork: ForkName, ) -> Result { let max_effective_balance = self .validators() .get(validator_index) - .map(|validator| validator.get_validator_max_effective_balance(spec)) + .map(|validator| validator.get_validator_max_effective_balance(spec, current_fork)) .ok_or(Error::UnknownValidator(validator_index))?; Ok(std::cmp::min( *self diff --git a/consensus/types/src/validator.rs b/consensus/types/src/validator.rs index 9e26d1eeca..3287942dfe 100644 --- a/consensus/types/src/validator.rs +++ b/consensus/types/src/validator.rs @@ -203,7 +203,7 @@ impl Validator { current_fork: ForkName, ) -> bool { if current_fork >= ForkName::Electra { - self.is_partially_withdrawable_validator_electra(balance, spec) + self.is_partially_withdrawable_validator_electra(balance, spec, current_fork) } else { self.is_partially_withdrawable_validator_capella(balance, spec) } @@ -223,8 +223,9 @@ impl Validator { &self, balance: u64, spec: &ChainSpec, + current_fork: ForkName, ) -> bool { - let max_effective_balance = self.get_validator_max_effective_balance(spec); + let max_effective_balance = self.get_validator_max_effective_balance(spec, current_fork); let has_max_effective_balance = self.effective_balance == max_effective_balance; let has_excess_balance = balance > max_effective_balance; self.has_execution_withdrawal_credential(spec) @@ -239,11 +240,19 @@ impl Validator { } /// Returns the max effective balance for a validator in gwei. - pub fn get_validator_max_effective_balance(&self, spec: &ChainSpec) -> u64 { - if self.has_compounding_withdrawal_credential(spec) { - spec.max_effective_balance_electra + pub fn get_validator_max_effective_balance( + &self, + spec: &ChainSpec, + current_fork: ForkName, + ) -> u64 { + if current_fork >= ForkName::Electra { + if self.has_compounding_withdrawal_credential(spec) { + spec.max_effective_balance_electra + } else { + spec.min_activation_balance + } } else { - spec.min_activation_balance + spec.max_effective_balance } } }