Skip to content
4 changes: 2 additions & 2 deletions beacon_node/beacon_chain/src/beacon_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4889,7 +4889,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
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(Into::into)
.map_err(Error::PrepareProposerFailed);
}

Expand All @@ -4907,7 +4907,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
&self.spec,
)?;
get_expected_withdrawals(&advanced_state, &self.spec)
.map(|(withdrawals, _)| withdrawals)
.map(Into::into)
.map_err(Error::PrepareProposerFailed)
}

Expand Down
2 changes: 1 addition & 1 deletion beacon_node/beacon_chain/src/execution_payload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@ pub fn get_execution_payload<T: BeaconChainTypes>(
let latest_execution_payload_header_block_hash = latest_execution_payload_header.block_hash();
let latest_execution_payload_header_gas_limit = latest_execution_payload_header.gas_limit();
let withdrawals = if state.fork_name_unchecked().capella_enabled() {
Some(get_expected_withdrawals(state, spec)?.0.into())
Some(Withdrawals::<T::EthSpec>::from(get_expected_withdrawals(state, spec)?).into())
} else {
None
};
Expand Down
2 changes: 1 addition & 1 deletion beacon_node/http_api/src/builder_states.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub fn get_next_withdrawals<T: BeaconChainTypes>(
}

match get_expected_withdrawals(&state, &chain.spec) {
Ok((withdrawals, _)) => Ok(withdrawals),
Ok(expected_withdrawals) => Ok(expected_withdrawals.into()),
Err(e) => Err(warp_utils::reject::custom_server_error(format!(
"failed to get expected withdrawal: {:?}",
e
Expand Down
4 changes: 2 additions & 2 deletions beacon_node/http_api/tests/interactive_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -634,7 +634,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
.withdrawals()
.to_vec();
complete_state_advance(&mut state_b, None, slot_c, &harness.chain.spec).unwrap();

Expand Down Expand Up @@ -724,7 +724,7 @@ pub async fn proposer_boost_re_org_test(
get_expected_withdrawals(&state_b, &harness.chain.spec)
}
.unwrap()
.0
.withdrawals()
.to_vec();
let payload_attribs_withdrawals = payload_attribs.withdrawals().unwrap();
assert_eq!(expected_withdrawals, *payload_attribs_withdrawals);
Expand Down
5 changes: 3 additions & 2 deletions beacon_node/http_api/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6660,15 +6660,16 @@ impl ApiTester {
}
let expected_withdrawals = get_expected_withdrawals(&state, &self.chain.spec)
.unwrap()
.0;
.withdrawals()
.to_vec();

// fetch expected withdrawals from the client
let result = self.client.get_expected_withdrawals(&state_id).await;
match result {
Ok(withdrawal_response) => {
assert_eq!(withdrawal_response.execution_optimistic, Some(false));
assert_eq!(withdrawal_response.finalized, Some(false));
assert_eq!(withdrawal_response.data, expected_withdrawals.to_vec());
assert_eq!(withdrawal_response.data, expected_withdrawals);
}
Err(_) => {
panic!("query failed incorrectly");
Expand Down
212 changes: 17 additions & 195 deletions consensus/state_processing/src/per_block_processing.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::consensus_context::ConsensusContext;
use errors::{BlockOperationError, BlockProcessingError, HeaderInvalid};
use rayon::prelude::*;
use safe_arith::{ArithError, SafeArith, SafeArithIter};
use safe_arith::{ArithError, SafeArith};
use signature_sets::{block_proposal_signature_set, get_pubkey_from_state, randao_signature_set};
use std::borrow::Cow;
use tree_hash::TreeHash;
Expand All @@ -24,9 +24,11 @@ pub use verify_deposit::{
get_existing_validator_index, is_valid_deposit_signature, verify_deposit_merkle_proof,
};
pub use verify_exit::verify_exit;
pub use withdrawals::get_expected_withdrawals;

pub mod altair;
pub mod block_signature_verifier;
pub mod builder;
pub mod deneb;
pub mod errors;
mod is_valid_indexed_attestation;
Expand All @@ -39,8 +41,8 @@ mod verify_bls_to_execution_change;
mod verify_deposit;
mod verify_exit;
mod verify_proposer_slashing;
pub mod withdrawals;

use crate::common::decrease_balance;
use crate::common::update_progressive_balances_cache::{
initialize_progressive_balances_cache, update_progressive_balances_metrics,
};
Expand Down Expand Up @@ -172,14 +174,21 @@ pub fn per_block_processing<E: EthSpec, Payload: AbstractExecPayload<E>>(
// previous block.
if is_execution_enabled(state, block.body()) {
let body = block.body();
// TODO(EIP-7732): build out process_withdrawals variant for gloas
process_withdrawals::<E, Payload>(state, body.execution_payload()?, spec)?;
process_execution_payload::<E, Payload>(state, body, spec)?;
if state.fork_name_unchecked().gloas_enabled() {
withdrawals::gloas::process_withdrawals::<E>(state, spec)?;
// TODO(EIP-7732): process execution payload bid
} else {
if state.fork_name_unchecked().capella_enabled() {
withdrawals::capella_electra::process_withdrawals::<E, Payload>(
state,
body.execution_payload()?,
spec,
)?;
}
process_execution_payload::<E, Payload>(state, body, spec)?;
}
}

// TODO(EIP-7732): build out process_execution_bid
// process_execution_bid(state, block, verify_signatures, spec)?;

process_randao(state, block, verify_randao, ctxt, spec)?;
process_eth1_data(state, block.body().eth1_data())?;
process_operations(state, block.body(), verify_signatures, ctxt, spec)?;
Expand Down Expand Up @@ -513,190 +522,3 @@ pub fn compute_timestamp_at_slot<E: EthSpec>(
.safe_mul(spec.get_slot_duration().as_secs())
.and_then(|since_genesis| state.genesis_time().safe_add(since_genesis))
}

/// Compute the next batch of withdrawals which should be included in a block.
///
/// https://github.com/ethereum/consensus-specs/blob/dev/specs/electra/beacon-chain.md#new-get_expected_withdrawals
pub fn get_expected_withdrawals<E: EthSpec>(
state: &BeaconState<E>,
spec: &ChainSpec,
) -> Result<(Withdrawals<E>, Option<usize>), 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::<Withdrawal>::with_capacity(E::max_withdrawals_per_payload());
let fork_name = state.fork_name_unchecked();

// [New in Electra:EIP7251]
// Consume pending partial withdrawals
let processed_partial_withdrawals_count =
if let Ok(pending_partial_withdrawals) = state.pending_partial_withdrawals() {
let mut processed_partial_withdrawals_count = 0;
for withdrawal in pending_partial_withdrawals {
if withdrawal.withdrawable_epoch > epoch
|| withdrawals.len() == spec.max_pending_partials_per_withdrawals_sweep as usize
{
break;
}

let validator = state.get_validator(withdrawal.validator_index as usize)?;

let has_sufficient_effective_balance =
validator.effective_balance >= spec.min_activation_balance;
let total_withdrawn = withdrawals
.iter()
.filter_map(|w| {
(w.validator_index == withdrawal.validator_index).then_some(w.amount)
})
.safe_sum()?;
let balance = state
.get_balance(withdrawal.validator_index as usize)?
.safe_sub(total_withdrawn)?;
let has_excess_balance = 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(
balance.safe_sub(spec.min_activation_balance)?,
withdrawal.amount,
);
withdrawals.push(Withdrawal {
index: withdrawal_index,
validator_index: withdrawal.validator_index,
address: validator
.get_execution_withdrawal_address(spec)
.ok_or(BeaconStateError::NonExecutionAddressWithdrawalCredential)?,
amount: withdrawable_balance,
});
withdrawal_index.safe_add_assign(1)?;
}
processed_partial_withdrawals_count.safe_add_assign(1)?;
}
Some(processed_partial_withdrawals_count)
} else {
None
};

let bound = std::cmp::min(
state.validators().len() as u64,
spec.max_validators_per_withdrawals_sweep,
);
for _ in 0..bound {
let validator = state.get_validator(validator_index as usize)?;
let partially_withdrawn_balance = withdrawals
.iter()
.filter_map(|withdrawal| {
(withdrawal.validator_index == validator_index).then_some(withdrawal.amount)
})
.safe_sum()?;
let balance = state
.balances()
.get(validator_index as usize)
.ok_or(BeaconStateError::BalancesOutOfBounds(
validator_index as usize,
))?
.safe_sub(partially_withdrawn_balance)?;
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)?;
}
if withdrawals.len() == E::max_withdrawals_per_payload() {
break;
}
validator_index = validator_index
.safe_add(1)?
.safe_rem(state.validators().len() as u64)?;
}

Ok((
withdrawals
.try_into()
.map_err(BlockProcessingError::SszTypesError)?,
processed_partial_withdrawals_count,
))
}

/// Apply withdrawals to the state.
/// TODO(EIP-7732): abstract this out and create gloas variant
pub fn process_withdrawals<E: EthSpec, Payload: AbstractExecPayload<E>>(
state: &mut BeaconState<E>,
payload: Payload::Ref<'_>,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
if state.fork_name_unchecked().capella_enabled() {
let (expected_withdrawals, processed_partial_withdrawals_count) =
get_expected_withdrawals(state, spec)?;
let expected_root = expected_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,
});
}

for withdrawal in expected_withdrawals.iter() {
decrease_balance(
state,
withdrawal.validator_index as usize,
withdrawal.amount,
)?;
}

// Update pending partial withdrawals [New in Electra:EIP7251]
if let Some(processed_partial_withdrawals_count) = processed_partial_withdrawals_count {
state
.pending_partial_withdrawals_mut()?
.pop_front(processed_partial_withdrawals_count)?;
}

// 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)?;

// Update the next validator index to start the next withdrawal sweep
if expected_withdrawals.len() == E::max_withdrawals_per_payload() {
// Next sweep starts after the latest withdrawal's validator index
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;
}
}

// Advance sweep by the max length of the sweep if there was not a full set of withdrawals
if expected_withdrawals.len() != E::max_withdrawals_per_payload() {
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(())
} else {
// these shouldn't even be encountered but they're here for completeness
Ok(())
}
}
13 changes: 13 additions & 0 deletions consensus/state_processing/src/per_block_processing/builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use types::{builder::BuilderIndex, consts::gloas::BUILDER_INDEX_FLAG};

pub fn is_builder_index(validator_index: u64) -> bool {
validator_index & BUILDER_INDEX_FLAG != 0
}

pub fn convert_builder_index_to_validator_index(builder_index: BuilderIndex) -> u64 {
builder_index | BUILDER_INDEX_FLAG
}

pub fn convert_validator_index_to_builder_index(validator_index: u64) -> BuilderIndex {
validator_index & !BUILDER_INDEX_FLAG
}
8 changes: 8 additions & 0 deletions consensus/state_processing/src/per_block_processing/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,14 @@ pub enum BlockProcessingError {
found: Hash256,
},
WithdrawalCredentialsInvalid,
/// This should be unreachable unless there's a logical flaw in the spec for withdrawals.
WithdrawalsLimitExceeded {
limit: usize,
prior_withdrawals: usize,
},
/// Unreachable unless there's a logic error in LH.
IncorrectExpectedWithdrawalsVariant,
MissingLastWithdrawal,
PendingAttestationInElectra,
}

Expand Down
Loading