mirror of
https://github.com/sigp/lighthouse.git
synced 2026-07-04 05:14:33 +00:00
Implement Gloas withdrawals and refactor (#8692)
Co-Authored-By: Michael Sproul <michael@sigmaprime.io> Co-Authored-By: Michael Sproul <michaelsproul@users.noreply.github.com>
This commit is contained in:
@@ -4889,7 +4889,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
let proposal_epoch = proposal_slot.epoch(T::EthSpec::slots_per_epoch());
|
let proposal_epoch = proposal_slot.epoch(T::EthSpec::slots_per_epoch());
|
||||||
if head_state.current_epoch() == proposal_epoch {
|
if head_state.current_epoch() == proposal_epoch {
|
||||||
return get_expected_withdrawals(&unadvanced_state, &self.spec)
|
return get_expected_withdrawals(&unadvanced_state, &self.spec)
|
||||||
.map(|(withdrawals, _)| withdrawals)
|
.map(Into::into)
|
||||||
.map_err(Error::PrepareProposerFailed);
|
.map_err(Error::PrepareProposerFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4907,7 +4907,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
&self.spec,
|
&self.spec,
|
||||||
)?;
|
)?;
|
||||||
get_expected_withdrawals(&advanced_state, &self.spec)
|
get_expected_withdrawals(&advanced_state, &self.spec)
|
||||||
.map(|(withdrawals, _)| withdrawals)
|
.map(Into::into)
|
||||||
.map_err(Error::PrepareProposerFailed)
|
.map_err(Error::PrepareProposerFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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_block_hash = latest_execution_payload_header.block_hash();
|
||||||
let latest_execution_payload_header_gas_limit = latest_execution_payload_header.gas_limit();
|
let latest_execution_payload_header_gas_limit = latest_execution_payload_header.gas_limit();
|
||||||
let withdrawals = if state.fork_name_unchecked().capella_enabled() {
|
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 {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ pub fn get_next_withdrawals<T: BeaconChainTypes>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
match get_expected_withdrawals(&state, &chain.spec) {
|
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!(
|
Err(e) => Err(warp_utils::reject::custom_server_error(format!(
|
||||||
"failed to get expected withdrawal: {:?}",
|
"failed to get expected withdrawal: {:?}",
|
||||||
e
|
e
|
||||||
|
|||||||
@@ -634,7 +634,7 @@ pub async fn proposer_boost_re_org_test(
|
|||||||
assert_eq!(state_b.slot(), slot_b);
|
assert_eq!(state_b.slot(), slot_b);
|
||||||
let pre_advance_withdrawals = get_expected_withdrawals(&state_b, &harness.chain.spec)
|
let pre_advance_withdrawals = get_expected_withdrawals(&state_b, &harness.chain.spec)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.0
|
.withdrawals()
|
||||||
.to_vec();
|
.to_vec();
|
||||||
complete_state_advance(&mut state_b, None, slot_c, &harness.chain.spec).unwrap();
|
complete_state_advance(&mut state_b, None, slot_c, &harness.chain.spec).unwrap();
|
||||||
|
|
||||||
@@ -724,7 +724,7 @@ pub async fn proposer_boost_re_org_test(
|
|||||||
get_expected_withdrawals(&state_b, &harness.chain.spec)
|
get_expected_withdrawals(&state_b, &harness.chain.spec)
|
||||||
}
|
}
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.0
|
.withdrawals()
|
||||||
.to_vec();
|
.to_vec();
|
||||||
let payload_attribs_withdrawals = payload_attribs.withdrawals().unwrap();
|
let payload_attribs_withdrawals = payload_attribs.withdrawals().unwrap();
|
||||||
assert_eq!(expected_withdrawals, *payload_attribs_withdrawals);
|
assert_eq!(expected_withdrawals, *payload_attribs_withdrawals);
|
||||||
|
|||||||
@@ -6660,7 +6660,8 @@ impl ApiTester {
|
|||||||
}
|
}
|
||||||
let expected_withdrawals = get_expected_withdrawals(&state, &self.chain.spec)
|
let expected_withdrawals = get_expected_withdrawals(&state, &self.chain.spec)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.0;
|
.withdrawals()
|
||||||
|
.to_vec();
|
||||||
|
|
||||||
// fetch expected withdrawals from the client
|
// fetch expected withdrawals from the client
|
||||||
let result = self.client.get_expected_withdrawals(&state_id).await;
|
let result = self.client.get_expected_withdrawals(&state_id).await;
|
||||||
@@ -6668,7 +6669,7 @@ impl ApiTester {
|
|||||||
Ok(withdrawal_response) => {
|
Ok(withdrawal_response) => {
|
||||||
assert_eq!(withdrawal_response.execution_optimistic, Some(false));
|
assert_eq!(withdrawal_response.execution_optimistic, Some(false));
|
||||||
assert_eq!(withdrawal_response.finalized, 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(_) => {
|
Err(_) => {
|
||||||
panic!("query failed incorrectly");
|
panic!("query failed incorrectly");
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::consensus_context::ConsensusContext;
|
use crate::consensus_context::ConsensusContext;
|
||||||
use errors::{BlockOperationError, BlockProcessingError, HeaderInvalid};
|
use errors::{BlockOperationError, BlockProcessingError, HeaderInvalid};
|
||||||
use rayon::prelude::*;
|
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 signature_sets::{block_proposal_signature_set, get_pubkey_from_state, randao_signature_set};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use tree_hash::TreeHash;
|
use tree_hash::TreeHash;
|
||||||
@@ -24,9 +24,11 @@ pub use verify_deposit::{
|
|||||||
get_existing_validator_index, is_valid_deposit_signature, verify_deposit_merkle_proof,
|
get_existing_validator_index, is_valid_deposit_signature, verify_deposit_merkle_proof,
|
||||||
};
|
};
|
||||||
pub use verify_exit::verify_exit;
|
pub use verify_exit::verify_exit;
|
||||||
|
pub use withdrawals::get_expected_withdrawals;
|
||||||
|
|
||||||
pub mod altair;
|
pub mod altair;
|
||||||
pub mod block_signature_verifier;
|
pub mod block_signature_verifier;
|
||||||
|
pub mod builder;
|
||||||
pub mod deneb;
|
pub mod deneb;
|
||||||
pub mod errors;
|
pub mod errors;
|
||||||
mod is_valid_indexed_attestation;
|
mod is_valid_indexed_attestation;
|
||||||
@@ -39,8 +41,8 @@ mod verify_bls_to_execution_change;
|
|||||||
mod verify_deposit;
|
mod verify_deposit;
|
||||||
mod verify_exit;
|
mod verify_exit;
|
||||||
mod verify_proposer_slashing;
|
mod verify_proposer_slashing;
|
||||||
|
pub mod withdrawals;
|
||||||
|
|
||||||
use crate::common::decrease_balance;
|
|
||||||
use crate::common::update_progressive_balances_cache::{
|
use crate::common::update_progressive_balances_cache::{
|
||||||
initialize_progressive_balances_cache, update_progressive_balances_metrics,
|
initialize_progressive_balances_cache, update_progressive_balances_metrics,
|
||||||
};
|
};
|
||||||
@@ -172,14 +174,21 @@ pub fn per_block_processing<E: EthSpec, Payload: AbstractExecPayload<E>>(
|
|||||||
// previous block.
|
// previous block.
|
||||||
if is_execution_enabled(state, block.body()) {
|
if is_execution_enabled(state, block.body()) {
|
||||||
let body = block.body();
|
let body = block.body();
|
||||||
// TODO(EIP-7732): build out process_withdrawals variant for gloas
|
if state.fork_name_unchecked().gloas_enabled() {
|
||||||
process_withdrawals::<E, Payload>(state, body.execution_payload()?, spec)?;
|
withdrawals::gloas::process_withdrawals::<E>(state, spec)?;
|
||||||
process_execution_payload::<E, Payload>(state, body, 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_randao(state, block, verify_randao, ctxt, spec)?;
|
||||||
process_eth1_data(state, block.body().eth1_data())?;
|
process_eth1_data(state, block.body().eth1_data())?;
|
||||||
process_operations(state, block.body(), verify_signatures, ctxt, spec)?;
|
process_operations(state, block.body(), verify_signatures, ctxt, spec)?;
|
||||||
@@ -513,190 +522,3 @@ pub fn compute_timestamp_at_slot<E: EthSpec>(
|
|||||||
.safe_mul(spec.get_slot_duration().as_secs())
|
.safe_mul(spec.get_slot_duration().as_secs())
|
||||||
.and_then(|since_genesis| state.genesis_time().safe_add(since_genesis))
|
.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(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -90,6 +90,14 @@ pub enum BlockProcessingError {
|
|||||||
found: Hash256,
|
found: Hash256,
|
||||||
},
|
},
|
||||||
WithdrawalCredentialsInvalid,
|
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,
|
PendingAttestationInElectra,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,543 @@
|
|||||||
|
use crate::common::decrease_balance;
|
||||||
|
use crate::per_block_processing::builder::{
|
||||||
|
convert_builder_index_to_validator_index, convert_validator_index_to_builder_index,
|
||||||
|
is_builder_index,
|
||||||
|
};
|
||||||
|
use crate::per_block_processing::errors::BlockProcessingError;
|
||||||
|
use milhouse::List;
|
||||||
|
use safe_arith::{SafeArith, SafeArithIter};
|
||||||
|
use tree_hash::TreeHash;
|
||||||
|
use types::{
|
||||||
|
AbstractExecPayload, BeaconState, BeaconStateError, ChainSpec, EthSpec, ExecPayload,
|
||||||
|
ExpectedWithdrawals, ExpectedWithdrawalsCapella, ExpectedWithdrawalsElectra,
|
||||||
|
ExpectedWithdrawalsGloas, Validator, Withdrawal, Withdrawals,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Compute the next batch of withdrawals which should be included in a block.
|
||||||
|
///
|
||||||
|
/// https://ethereum.github.io/consensus-specs/specs/gloas/beacon-chain/#modified-get_expected_withdrawals
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
pub fn get_expected_withdrawals<E: EthSpec>(
|
||||||
|
state: &BeaconState<E>,
|
||||||
|
spec: &ChainSpec,
|
||||||
|
) -> Result<ExpectedWithdrawals<E>, BlockProcessingError> {
|
||||||
|
let mut withdrawal_index = state.next_withdrawal_index()?;
|
||||||
|
let mut withdrawals = Vec::<Withdrawal>::with_capacity(E::max_withdrawals_per_payload());
|
||||||
|
|
||||||
|
// [New in Gloas:EIP7732]
|
||||||
|
// Get builder withdrawals
|
||||||
|
let processed_builder_withdrawals_count =
|
||||||
|
get_builder_withdrawals(state, &mut withdrawal_index, &mut withdrawals)?;
|
||||||
|
|
||||||
|
// [New in Electra:EIP7251]
|
||||||
|
// Get partial 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_sweep_withdrawals_count =
|
||||||
|
get_validators_sweep_withdrawals(state, &mut withdrawal_index, &mut withdrawals, spec)?;
|
||||||
|
|
||||||
|
let withdrawals = withdrawals
|
||||||
|
.try_into()
|
||||||
|
.map_err(BlockProcessingError::SszTypesError)?;
|
||||||
|
|
||||||
|
let fork_name = state.fork_name_unchecked();
|
||||||
|
if fork_name.gloas_enabled() {
|
||||||
|
Ok(ExpectedWithdrawals::Gloas(ExpectedWithdrawalsGloas {
|
||||||
|
withdrawals,
|
||||||
|
processed_builder_withdrawals_count: processed_builder_withdrawals_count
|
||||||
|
.ok_or(BlockProcessingError::IncorrectExpectedWithdrawalsVariant)?,
|
||||||
|
processed_partial_withdrawals_count: processed_partial_withdrawals_count
|
||||||
|
.ok_or(BlockProcessingError::IncorrectExpectedWithdrawalsVariant)?,
|
||||||
|
processed_builders_sweep_count: processed_builders_sweep_count
|
||||||
|
.ok_or(BlockProcessingError::IncorrectExpectedWithdrawalsVariant)?,
|
||||||
|
processed_sweep_withdrawals_count,
|
||||||
|
}))
|
||||||
|
} else if fork_name.electra_enabled() {
|
||||||
|
Ok(ExpectedWithdrawals::Electra(ExpectedWithdrawalsElectra {
|
||||||
|
withdrawals,
|
||||||
|
processed_partial_withdrawals_count: processed_partial_withdrawals_count
|
||||||
|
.ok_or(BlockProcessingError::IncorrectExpectedWithdrawalsVariant)?,
|
||||||
|
processed_sweep_withdrawals_count,
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
Ok(ExpectedWithdrawals::Capella(ExpectedWithdrawalsCapella {
|
||||||
|
withdrawals,
|
||||||
|
processed_sweep_withdrawals_count,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_builder_withdrawals<E: EthSpec>(
|
||||||
|
state: &BeaconState<E>,
|
||||||
|
withdrawal_index: &mut u64,
|
||||||
|
withdrawals: &mut Vec<Withdrawal>,
|
||||||
|
) -> Result<Option<u64>, BlockProcessingError> {
|
||||||
|
let Ok(builder_pending_withdrawals) = state.builder_pending_withdrawals() else {
|
||||||
|
// Pre-Gloas, nothing to do.
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO(gloas): this has already changed on `master`, we need to update at next spec release
|
||||||
|
let withdrawals_limit = E::max_withdrawals_per_payload();
|
||||||
|
|
||||||
|
// TODO(gloas): this assert is from `master`, remove this comment once it is part of the tested
|
||||||
|
// spec version.
|
||||||
|
block_verify!(
|
||||||
|
withdrawals.len() <= withdrawals_limit,
|
||||||
|
BlockProcessingError::WithdrawalsLimitExceeded {
|
||||||
|
limit: withdrawals_limit,
|
||||||
|
prior_withdrawals: withdrawals.len()
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut processed_count = 0;
|
||||||
|
for withdrawal in builder_pending_withdrawals {
|
||||||
|
let has_reached_limit = withdrawals.len() == withdrawals_limit;
|
||||||
|
|
||||||
|
if has_reached_limit {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let builder_index = withdrawal.builder_index;
|
||||||
|
|
||||||
|
withdrawals.push(Withdrawal {
|
||||||
|
index: *withdrawal_index,
|
||||||
|
validator_index: convert_builder_index_to_validator_index(builder_index),
|
||||||
|
address: withdrawal.fee_recipient,
|
||||||
|
amount: withdrawal.amount,
|
||||||
|
});
|
||||||
|
withdrawal_index.safe_add_assign(1)?;
|
||||||
|
processed_count.safe_add_assign(1)?;
|
||||||
|
}
|
||||||
|
Ok(Some(processed_count))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_pending_partial_withdrawals<E: EthSpec>(
|
||||||
|
state: &BeaconState<E>,
|
||||||
|
withdrawal_index: &mut u64,
|
||||||
|
withdrawals: &mut Vec<Withdrawal>,
|
||||||
|
spec: &ChainSpec,
|
||||||
|
) -> Result<Option<u64>, BlockProcessingError> {
|
||||||
|
let Ok(pending_partial_withdrawals) = state.pending_partial_withdrawals() else {
|
||||||
|
// Pre-Electra nothing to do.
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
let epoch = state.current_epoch();
|
||||||
|
|
||||||
|
let withdrawals_limit = std::cmp::min(
|
||||||
|
withdrawals
|
||||||
|
.len()
|
||||||
|
.safe_add(spec.max_pending_partials_per_withdrawals_sweep as usize)?,
|
||||||
|
E::max_withdrawals_per_payload().safe_sub(1)?,
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO(gloas): this assert is from `master`, remove this comment once it is part of the tested
|
||||||
|
// spec version.
|
||||||
|
block_verify!(
|
||||||
|
withdrawals.len() <= withdrawals_limit,
|
||||||
|
BlockProcessingError::WithdrawalsLimitExceeded {
|
||||||
|
limit: withdrawals_limit,
|
||||||
|
prior_withdrawals: withdrawals.len()
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut processed_count = 0;
|
||||||
|
for withdrawal in pending_partial_withdrawals {
|
||||||
|
let is_withdrawable = withdrawal.withdrawable_epoch <= epoch;
|
||||||
|
let has_reached_limit = withdrawals.len() >= withdrawals_limit;
|
||||||
|
|
||||||
|
if !is_withdrawable || has_reached_limit {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let validator_index = withdrawal.validator_index;
|
||||||
|
let validator = state.get_validator(validator_index as usize)?;
|
||||||
|
let balance = get_balance_after_withdrawals(state, validator_index, withdrawals)?;
|
||||||
|
|
||||||
|
if is_eligible_for_partial_withdrawals(validator, balance, spec) {
|
||||||
|
let withdrawal_amount = std::cmp::min(
|
||||||
|
balance.safe_sub(spec.min_activation_balance)?,
|
||||||
|
withdrawal.amount,
|
||||||
|
);
|
||||||
|
withdrawals.push(Withdrawal {
|
||||||
|
index: *withdrawal_index,
|
||||||
|
validator_index,
|
||||||
|
address: validator
|
||||||
|
.get_execution_withdrawal_address(spec)
|
||||||
|
.ok_or(BeaconStateError::NonExecutionAddressWithdrawalCredential)?,
|
||||||
|
amount: withdrawal_amount,
|
||||||
|
});
|
||||||
|
withdrawal_index.safe_add_assign(1)?;
|
||||||
|
}
|
||||||
|
processed_count.safe_add_assign(1)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
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<E: EthSpec>(
|
||||||
|
state: &BeaconState<E>,
|
||||||
|
withdrawal_index: &mut u64,
|
||||||
|
withdrawals: &mut Vec<Withdrawal>,
|
||||||
|
) -> Result<Option<u64>, 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());
|
||||||
|
|
||||||
|
// TODO(gloas): this has already changed on `master`, we should update at the next spec release
|
||||||
|
let withdrawals_limit = E::max_withdrawals_per_payload();
|
||||||
|
|
||||||
|
// TODO(gloas): this assert is from `master`, remove this comment once it is part of the tested
|
||||||
|
// spec version.
|
||||||
|
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`
|
||||||
|
/// and adds full or partial withdrawals for eligible validators.
|
||||||
|
///
|
||||||
|
/// https://ethereum.github.io/consensus-specs/specs/capella/beacon-chain/#new-get_validators_sweep_withdrawals
|
||||||
|
pub fn get_validators_sweep_withdrawals<E: EthSpec>(
|
||||||
|
state: &BeaconState<E>,
|
||||||
|
withdrawal_index: &mut u64,
|
||||||
|
withdrawals: &mut Vec<Withdrawal>,
|
||||||
|
spec: &ChainSpec,
|
||||||
|
) -> Result<u64, BlockProcessingError> {
|
||||||
|
let epoch = state.current_epoch();
|
||||||
|
let fork_name = state.fork_name_unchecked();
|
||||||
|
let mut validator_index = state.next_withdrawal_validator_index()?;
|
||||||
|
let validators_limit = std::cmp::min(
|
||||||
|
state.validators().len() as u64,
|
||||||
|
spec.max_validators_per_withdrawals_sweep,
|
||||||
|
);
|
||||||
|
let withdrawals_limit = E::max_withdrawals_per_payload();
|
||||||
|
|
||||||
|
// There must be at least one space reserved for validator sweep withdrawals
|
||||||
|
block_verify!(
|
||||||
|
withdrawals.len() < withdrawals_limit,
|
||||||
|
BlockProcessingError::WithdrawalsLimitExceeded {
|
||||||
|
limit: withdrawals_limit,
|
||||||
|
prior_withdrawals: withdrawals.len()
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut processed_count: u64 = 0;
|
||||||
|
|
||||||
|
for _ in 0..validators_limit {
|
||||||
|
if withdrawals.len() >= withdrawals_limit {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let validator = state.get_validator(validator_index as usize)?;
|
||||||
|
let balance = get_balance_after_withdrawals(state, validator_index, withdrawals)?;
|
||||||
|
|
||||||
|
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)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
validator_index = validator_index
|
||||||
|
.safe_add(1)?
|
||||||
|
.safe_rem(state.validators().len() as u64)?;
|
||||||
|
processed_count.safe_add_assign(1)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(processed_count)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_balance_after_withdrawals<E: EthSpec>(
|
||||||
|
state: &BeaconState<E>,
|
||||||
|
validator_index: u64,
|
||||||
|
withdrawals: &[Withdrawal],
|
||||||
|
) -> Result<u64, BeaconStateError> {
|
||||||
|
let withdrawn = withdrawals
|
||||||
|
.iter()
|
||||||
|
.filter(|withdrawal| withdrawal.validator_index == validator_index)
|
||||||
|
.map(|withdrawal| withdrawal.amount)
|
||||||
|
.safe_sum()?;
|
||||||
|
state
|
||||||
|
.get_balance(validator_index as usize)?
|
||||||
|
.safe_sub(withdrawn)
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_eligible_for_partial_withdrawals(
|
||||||
|
validator: &Validator,
|
||||||
|
balance: u64,
|
||||||
|
spec: &ChainSpec,
|
||||||
|
) -> bool {
|
||||||
|
let has_sufficient_effective_balance =
|
||||||
|
validator.effective_balance >= spec.min_activation_balance;
|
||||||
|
let has_excess_balance = balance > spec.min_activation_balance;
|
||||||
|
validator.exit_epoch == spec.far_future_epoch
|
||||||
|
&& has_sufficient_effective_balance
|
||||||
|
&& has_excess_balance
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_next_withdrawal_index<E: EthSpec>(
|
||||||
|
state: &mut BeaconState<E>,
|
||||||
|
withdrawals: &Withdrawals<E>,
|
||||||
|
) -> Result<(), BlockProcessingError> {
|
||||||
|
// Update the next withdrawal index if this block contained withdrawals
|
||||||
|
if let Some(latest_withdrawal) = withdrawals.last() {
|
||||||
|
*state.next_withdrawal_index_mut()? = latest_withdrawal.index.safe_add(1)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_payload_expected_withdrawals<E: EthSpec>(
|
||||||
|
state: &mut BeaconState<E>,
|
||||||
|
withdrawals: &Withdrawals<E>,
|
||||||
|
) -> Result<(), BlockProcessingError> {
|
||||||
|
*state.payload_expected_withdrawals_mut()? = List::new(withdrawals.to_vec())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_builder_pending_withdrawals<E: EthSpec>(
|
||||||
|
state: &mut BeaconState<E>,
|
||||||
|
processed_builder_withdrawals_count: u64,
|
||||||
|
) -> Result<(), BlockProcessingError> {
|
||||||
|
state
|
||||||
|
.builder_pending_withdrawals_mut()?
|
||||||
|
.pop_front(processed_builder_withdrawals_count as usize)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_pending_partial_withdrawals<E: EthSpec>(
|
||||||
|
state: &mut BeaconState<E>,
|
||||||
|
processed_partial_withdrawals_count: u64,
|
||||||
|
) -> Result<(), BlockProcessingError> {
|
||||||
|
state
|
||||||
|
.pending_partial_withdrawals_mut()?
|
||||||
|
.pop_front(processed_partial_withdrawals_count as usize)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_next_withdrawal_builder_index<E: EthSpec>(
|
||||||
|
state: &mut BeaconState<E>,
|
||||||
|
processed_builders_sweep_count: u64,
|
||||||
|
) -> Result<(), BlockProcessingError> {
|
||||||
|
if !state.builders()?.is_empty() {
|
||||||
|
// Update the next builder index to start the next withdrawal sweep
|
||||||
|
let next_index = state
|
||||||
|
.next_withdrawal_builder_index()?
|
||||||
|
.safe_add(processed_builders_sweep_count)?;
|
||||||
|
let next_builder_index = next_index.safe_rem(state.builders()?.len() as u64)?;
|
||||||
|
*state.next_withdrawal_builder_index_mut()? = next_builder_index;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_next_withdrawal_validator_index<E: EthSpec>(
|
||||||
|
state: &mut BeaconState<E>,
|
||||||
|
withdrawals: &Withdrawals<E>,
|
||||||
|
spec: &ChainSpec,
|
||||||
|
) -> Result<(), BlockProcessingError> {
|
||||||
|
// Update the next validator index to start the next withdrawal sweep
|
||||||
|
if withdrawals.len() == E::max_withdrawals_per_payload() {
|
||||||
|
// Next sweep starts after the latest withdrawal's validator index
|
||||||
|
let latest_withdrawal = withdrawals
|
||||||
|
.last()
|
||||||
|
.ok_or(BlockProcessingError::MissingLastWithdrawal)?;
|
||||||
|
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;
|
||||||
|
} else {
|
||||||
|
// Advance sweep by the max length of the sweep if there was not a full set of withdrawals
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply_withdrawals<E: EthSpec>(
|
||||||
|
state: &mut BeaconState<E>,
|
||||||
|
withdrawals: &Withdrawals<E>,
|
||||||
|
) -> Result<(), BlockProcessingError> {
|
||||||
|
for withdrawal in withdrawals {
|
||||||
|
if state.fork_name_unchecked().gloas_enabled()
|
||||||
|
&& is_builder_index(withdrawal.validator_index)
|
||||||
|
{
|
||||||
|
let builder_index =
|
||||||
|
convert_validator_index_to_builder_index(withdrawal.validator_index);
|
||||||
|
let builder = state
|
||||||
|
.builders_mut()?
|
||||||
|
.get_mut(builder_index as usize)
|
||||||
|
.ok_or(BeaconStateError::UnknownBuilder(builder_index))?;
|
||||||
|
builder.balance = builder.balance.saturating_sub(withdrawal.amount);
|
||||||
|
} else {
|
||||||
|
decrease_balance(
|
||||||
|
state,
|
||||||
|
withdrawal.validator_index as usize,
|
||||||
|
withdrawal.amount,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod capella_electra {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// Apply withdrawals to the state.
|
||||||
|
pub fn process_withdrawals<E: EthSpec, Payload: AbstractExecPayload<E>>(
|
||||||
|
state: &mut BeaconState<E>,
|
||||||
|
payload: Payload::Ref<'_>,
|
||||||
|
spec: &ChainSpec,
|
||||||
|
) -> Result<(), BlockProcessingError> {
|
||||||
|
let expected_withdrawals = get_expected_withdrawals(state, spec)?;
|
||||||
|
|
||||||
|
let expected_root = expected_withdrawals.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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply expected withdrawals.
|
||||||
|
apply_withdrawals(state, expected_withdrawals.withdrawals())?;
|
||||||
|
|
||||||
|
// [Common] Update withdrawals fields in the state
|
||||||
|
update_next_withdrawal_index(state, expected_withdrawals.withdrawals())?;
|
||||||
|
|
||||||
|
// [New in Electra:EIP7251]
|
||||||
|
if let Ok(processed_partial_withdrawals_count) =
|
||||||
|
expected_withdrawals.processed_partial_withdrawals_count()
|
||||||
|
{
|
||||||
|
update_pending_partial_withdrawals(state, processed_partial_withdrawals_count)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [Common from Capella]
|
||||||
|
update_next_withdrawal_validator_index(state, expected_withdrawals.withdrawals(), spec)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod gloas {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// Apply withdrawals to the state.
|
||||||
|
pub fn process_withdrawals<E: EthSpec>(
|
||||||
|
state: &mut BeaconState<E>,
|
||||||
|
spec: &ChainSpec,
|
||||||
|
) -> Result<(), BlockProcessingError> {
|
||||||
|
if !state.is_parent_block_full() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let ExpectedWithdrawals::Gloas(ExpectedWithdrawalsGloas {
|
||||||
|
withdrawals,
|
||||||
|
processed_builder_withdrawals_count,
|
||||||
|
processed_partial_withdrawals_count,
|
||||||
|
processed_builders_sweep_count,
|
||||||
|
processed_sweep_withdrawals_count: _,
|
||||||
|
}) = get_expected_withdrawals(state, spec)?
|
||||||
|
else {
|
||||||
|
return Err(BlockProcessingError::IncorrectExpectedWithdrawalsVariant);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Apply expected withdrawals.
|
||||||
|
apply_withdrawals(state, &withdrawals)?;
|
||||||
|
|
||||||
|
// [Common] Update withdrawals fields in the state
|
||||||
|
update_next_withdrawal_index(state, &withdrawals)?;
|
||||||
|
|
||||||
|
// [New in Gloas:EIP7732]
|
||||||
|
update_payload_expected_withdrawals(state, &withdrawals)?;
|
||||||
|
|
||||||
|
// [New in Gloas:EIP7732]
|
||||||
|
update_builder_pending_withdrawals(state, processed_builder_withdrawals_count)?;
|
||||||
|
|
||||||
|
// [Common from Electra]
|
||||||
|
update_pending_partial_withdrawals(state, processed_partial_withdrawals_count)?;
|
||||||
|
|
||||||
|
// [New in Gloas:EIP7732]
|
||||||
|
update_next_withdrawal_builder_index(state, processed_builders_sweep_count)?;
|
||||||
|
|
||||||
|
// [Common from Capella]
|
||||||
|
update_next_withdrawal_validator_index(state, &withdrawals, spec)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,13 +23,13 @@ use tree_hash_derive::TreeHash;
|
|||||||
use typenum::Unsigned;
|
use typenum::Unsigned;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Builder, BuilderIndex, BuilderPendingPayment, BuilderPendingWithdrawal, ExecutionBlockHash,
|
ExecutionBlockHash, ExecutionPayloadBid, Withdrawal,
|
||||||
ExecutionPayloadBid, Withdrawal,
|
|
||||||
attestation::{
|
attestation::{
|
||||||
AttestationData, AttestationDuty, BeaconCommittee, Checkpoint, CommitteeIndex, PTC,
|
AttestationData, AttestationDuty, BeaconCommittee, Checkpoint, CommitteeIndex, PTC,
|
||||||
ParticipationFlags, PendingAttestation,
|
ParticipationFlags, PendingAttestation,
|
||||||
},
|
},
|
||||||
block::{BeaconBlock, BeaconBlockHeader, SignedBeaconBlockHash},
|
block::{BeaconBlock, BeaconBlockHeader, SignedBeaconBlockHash},
|
||||||
|
builder::{Builder, BuilderIndex, BuilderPendingPayment, BuilderPendingWithdrawal},
|
||||||
consolidation::PendingConsolidation,
|
consolidation::PendingConsolidation,
|
||||||
core::{ChainSpec, Domain, Epoch, EthSpec, Hash256, RelativeEpoch, RelativeEpochError, Slot},
|
core::{ChainSpec, Domain, Epoch, EthSpec, Hash256, RelativeEpoch, RelativeEpochError, Slot},
|
||||||
deposit::PendingDeposit,
|
deposit::PendingDeposit,
|
||||||
@@ -68,6 +68,7 @@ pub enum BeaconStateError {
|
|||||||
EpochOutOfBounds,
|
EpochOutOfBounds,
|
||||||
SlotOutOfBounds,
|
SlotOutOfBounds,
|
||||||
UnknownValidator(usize),
|
UnknownValidator(usize),
|
||||||
|
UnknownBuilder(BuilderIndex),
|
||||||
UnableToDetermineProducer,
|
UnableToDetermineProducer,
|
||||||
InvalidBitfield,
|
InvalidBitfield,
|
||||||
EmptyCommittee,
|
EmptyCommittee,
|
||||||
|
|||||||
29
consensus/types/src/withdrawal/expected_withdrawals.rs
Normal file
29
consensus/types/src/withdrawal/expected_withdrawals.rs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
use crate::{EthSpec, Withdrawals};
|
||||||
|
use superstruct::superstruct;
|
||||||
|
|
||||||
|
#[superstruct(
|
||||||
|
variants(Capella, Electra, Gloas),
|
||||||
|
variant_attributes(derive(Debug, PartialEq, Clone))
|
||||||
|
)]
|
||||||
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
|
pub struct ExpectedWithdrawals<E: EthSpec> {
|
||||||
|
pub withdrawals: Withdrawals<E>,
|
||||||
|
#[superstruct(only(Gloas), partial_getter(copy))]
|
||||||
|
pub processed_builder_withdrawals_count: u64,
|
||||||
|
#[superstruct(only(Electra, Gloas), partial_getter(copy))]
|
||||||
|
pub processed_partial_withdrawals_count: u64,
|
||||||
|
#[superstruct(only(Gloas), partial_getter(copy))]
|
||||||
|
pub processed_builders_sweep_count: u64,
|
||||||
|
#[superstruct(getter(copy))]
|
||||||
|
pub processed_sweep_withdrawals_count: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: EthSpec> From<ExpectedWithdrawals<E>> for Withdrawals<E> {
|
||||||
|
fn from(expected_withdrawals: ExpectedWithdrawals<E>) -> Withdrawals<E> {
|
||||||
|
match expected_withdrawals {
|
||||||
|
ExpectedWithdrawals::Capella(ew) => ew.withdrawals,
|
||||||
|
ExpectedWithdrawals::Electra(ew) => ew.withdrawals,
|
||||||
|
ExpectedWithdrawals::Gloas(ew) => ew.withdrawals,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,13 @@
|
|||||||
|
mod expected_withdrawals;
|
||||||
mod pending_partial_withdrawal;
|
mod pending_partial_withdrawal;
|
||||||
mod withdrawal;
|
mod withdrawal;
|
||||||
mod withdrawal_credentials;
|
mod withdrawal_credentials;
|
||||||
mod withdrawal_request;
|
mod withdrawal_request;
|
||||||
|
|
||||||
|
pub use expected_withdrawals::{
|
||||||
|
ExpectedWithdrawals, ExpectedWithdrawalsCapella, ExpectedWithdrawalsElectra,
|
||||||
|
ExpectedWithdrawalsGloas,
|
||||||
|
};
|
||||||
pub use pending_partial_withdrawal::PendingPartialWithdrawal;
|
pub use pending_partial_withdrawal::PendingPartialWithdrawal;
|
||||||
pub use withdrawal::{Withdrawal, Withdrawals};
|
pub use withdrawal::{Withdrawal, Withdrawals};
|
||||||
pub use withdrawal_credentials::WithdrawalCredentials;
|
pub use withdrawal_credentials::WithdrawalCredentials;
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ use state_processing::{
|
|||||||
altair_deneb, base, process_attester_slashings, process_bls_to_execution_changes,
|
altair_deneb, base, process_attester_slashings, process_bls_to_execution_changes,
|
||||||
process_deposits, process_exits, process_proposer_slashings,
|
process_deposits, process_exits, process_proposer_slashings,
|
||||||
},
|
},
|
||||||
process_sync_aggregate, process_withdrawals,
|
process_sync_aggregate, withdrawals,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
@@ -45,7 +45,7 @@ struct ExecutionMetadata {
|
|||||||
/// Newtype for testing withdrawals.
|
/// Newtype for testing withdrawals.
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
pub struct WithdrawalsPayload<E: EthSpec> {
|
pub struct WithdrawalsPayload<E: EthSpec> {
|
||||||
payload: FullPayload<E>,
|
payload: ExecutionPayload<E>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -408,9 +408,7 @@ impl<E: EthSpec> Operation<E> for WithdrawalsPayload<E> {
|
|||||||
ssz_decode_file_with(path, |bytes| {
|
ssz_decode_file_with(path, |bytes| {
|
||||||
ExecutionPayload::from_ssz_bytes_by_fork(bytes, fork_name)
|
ExecutionPayload::from_ssz_bytes_by_fork(bytes, fork_name)
|
||||||
})
|
})
|
||||||
.map(|payload| WithdrawalsPayload {
|
.map(|payload| WithdrawalsPayload { payload })
|
||||||
payload: payload.into(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_to(
|
fn apply_to(
|
||||||
@@ -419,8 +417,16 @@ impl<E: EthSpec> Operation<E> for WithdrawalsPayload<E> {
|
|||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
_: &Operations<E, Self>,
|
_: &Operations<E, Self>,
|
||||||
) -> Result<(), BlockProcessingError> {
|
) -> Result<(), BlockProcessingError> {
|
||||||
// TODO(EIP-7732): implement separate gloas and non-gloas variants of process_withdrawals
|
if state.fork_name_unchecked().gloas_enabled() {
|
||||||
process_withdrawals::<_, FullPayload<_>>(state, self.payload.to_ref(), spec)
|
withdrawals::gloas::process_withdrawals(state, spec)
|
||||||
|
} else {
|
||||||
|
let full_payload = FullPayload::from(self.payload.clone());
|
||||||
|
withdrawals::capella_electra::process_withdrawals::<_, FullPayload<_>>(
|
||||||
|
state,
|
||||||
|
full_payload.to_ref(),
|
||||||
|
spec,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1117,6 +1117,17 @@ impl<E: EthSpec + TypeName, O: Operation<E>> Handler for OperationsHandler<E, O>
|
|||||||
fn handler_name(&self) -> String {
|
fn handler_name(&self) -> String {
|
||||||
O::handler_name()
|
O::handler_name()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn disabled_forks(&self) -> Vec<ForkName> {
|
||||||
|
// TODO(gloas): Can be removed once we enable Gloas on all tests
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_enabled_for_fork(&self, fork_name: ForkName) -> bool {
|
||||||
|
// TODO(gloas): So far only withdrawals tests are enabled for Gloas.
|
||||||
|
Self::Case::is_enabled_for_fork(fork_name)
|
||||||
|
&& (!fork_name.gloas_enabled() || self.handler_name() == "withdrawals")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Educe)]
|
#[derive(Educe)]
|
||||||
|
|||||||
Reference in New Issue
Block a user