mirror of
https://github.com/sigp/lighthouse.git
synced 2026-05-07 00:42:42 +00:00
Gloas modify process_withdrawals (#8281)
* add process withdrawals logic * fix process_withdrawals test * updates per consensus spec v1.6.0-beta.1 release * add todo for is_parent_block_full
This commit is contained in:
@@ -4832,7 +4832,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(|(withdrawals, _, _)| withdrawals)
|
||||||
.map_err(Error::PrepareProposerFailed);
|
.map_err(Error::PrepareProposerFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4850,7 +4850,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(|(withdrawals, _, _)| withdrawals)
|
||||||
.map_err(Error::PrepareProposerFailed)
|
.map_err(Error::PrepareProposerFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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((withdrawals, _, _)) => Ok(withdrawals),
|
||||||
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
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ pub mod deneb;
|
|||||||
pub mod errors;
|
pub mod errors;
|
||||||
mod is_valid_indexed_attestation;
|
mod is_valid_indexed_attestation;
|
||||||
pub mod process_operations;
|
pub mod process_operations;
|
||||||
|
pub mod process_withdrawals;
|
||||||
pub mod signature_sets;
|
pub mod signature_sets;
|
||||||
pub mod tests;
|
pub mod tests;
|
||||||
mod verify_attestation;
|
mod verify_attestation;
|
||||||
@@ -39,7 +40,6 @@ mod verify_deposit;
|
|||||||
mod verify_exit;
|
mod verify_exit;
|
||||||
mod verify_proposer_slashing;
|
mod verify_proposer_slashing;
|
||||||
|
|
||||||
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,
|
||||||
};
|
};
|
||||||
@@ -171,13 +171,20 @@ 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)?;
|
process_withdrawals::gloas::process_withdrawals::<E>(state, spec)?;
|
||||||
process_execution_payload::<E, Payload>(state, body, spec)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(EIP-7732): build out process_execution_bid
|
// TODO(EIP-7732): build out process_execution_bid
|
||||||
// process_execution_bid(state, block, verify_signatures, spec)?;
|
// process_execution_bid(state, block, verify_signatures, spec)?;
|
||||||
|
} else {
|
||||||
|
process_withdrawals::capella::process_withdrawals::<E, Payload>(
|
||||||
|
state,
|
||||||
|
body.execution_payload()?,
|
||||||
|
spec,
|
||||||
|
)?;
|
||||||
|
process_execution_payload::<E, Payload>(state, body, 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())?;
|
||||||
@@ -515,17 +522,70 @@ pub fn compute_timestamp_at_slot<E: EthSpec>(
|
|||||||
|
|
||||||
/// Compute the next batch of withdrawals which should be included in a block.
|
/// 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
|
/// 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>(
|
pub fn get_expected_withdrawals<E: EthSpec>(
|
||||||
state: &BeaconState<E>,
|
state: &BeaconState<E>,
|
||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
) -> Result<(Withdrawals<E>, Option<usize>), BlockProcessingError> {
|
) -> Result<(Withdrawals<E>, Option<usize>, Option<usize>), BlockProcessingError> {
|
||||||
let epoch = state.current_epoch();
|
let epoch = state.current_epoch();
|
||||||
let mut withdrawal_index = state.next_withdrawal_index()?;
|
let mut withdrawal_index = state.next_withdrawal_index()?;
|
||||||
let mut validator_index = state.next_withdrawal_validator_index()?;
|
let mut validator_index = state.next_withdrawal_validator_index()?;
|
||||||
let mut withdrawals = Vec::<Withdrawal>::with_capacity(E::max_withdrawals_per_payload());
|
let mut withdrawals = Vec::<Withdrawal>::with_capacity(E::max_withdrawals_per_payload());
|
||||||
let fork_name = state.fork_name_unchecked();
|
let fork_name = state.fork_name_unchecked();
|
||||||
|
|
||||||
|
// [New in Gloas:EIP7732]
|
||||||
|
// Sweep for builder payments
|
||||||
|
let processed_builder_withdrawals_count =
|
||||||
|
if let Ok(builder_pending_withdrawals) = state.builder_pending_withdrawals() {
|
||||||
|
let mut processed_builder_withdrawals_count = 0;
|
||||||
|
for withdrawal in builder_pending_withdrawals {
|
||||||
|
if withdrawal.withdrawable_epoch > epoch
|
||||||
|
|| withdrawals.len().safe_add(1)? == E::max_withdrawals_per_payload()
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if process_withdrawals::is_builder_payment_withdrawable(state, withdrawal)? {
|
||||||
|
let total_withdrawn = withdrawals
|
||||||
|
.iter()
|
||||||
|
.filter_map(|w| {
|
||||||
|
(w.validator_index == withdrawal.builder_index).then_some(w.amount)
|
||||||
|
})
|
||||||
|
.safe_sum()?;
|
||||||
|
let balance = state
|
||||||
|
.get_balance(withdrawal.builder_index as usize)?
|
||||||
|
.safe_sub(total_withdrawn)?;
|
||||||
|
let builder = state.get_validator(withdrawal.builder_index as usize)?;
|
||||||
|
|
||||||
|
let withdrawable_balance = if builder.slashed {
|
||||||
|
std::cmp::min(balance, withdrawal.amount)
|
||||||
|
} else if balance > spec.min_activation_balance {
|
||||||
|
std::cmp::min(
|
||||||
|
balance.safe_sub(spec.min_activation_balance)?,
|
||||||
|
withdrawal.amount,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
if withdrawable_balance > 0 {
|
||||||
|
withdrawals.push(Withdrawal {
|
||||||
|
index: withdrawal_index,
|
||||||
|
validator_index: withdrawal.builder_index,
|
||||||
|
address: withdrawal.fee_recipient,
|
||||||
|
amount: withdrawable_balance,
|
||||||
|
});
|
||||||
|
withdrawal_index.safe_add_assign(1)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
processed_builder_withdrawals_count.safe_add_assign(1)?;
|
||||||
|
}
|
||||||
|
Some(processed_builder_withdrawals_count)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
// [New in Electra:EIP7251]
|
// [New in Electra:EIP7251]
|
||||||
// Consume pending partial withdrawals
|
// Consume pending partial withdrawals
|
||||||
let processed_partial_withdrawals_count =
|
let processed_partial_withdrawals_count =
|
||||||
@@ -626,71 +686,9 @@ pub fn get_expected_withdrawals<E: EthSpec>(
|
|||||||
.safe_rem(state.validators().len() as u64)?;
|
.safe_rem(state.validators().len() as u64)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((withdrawals.into(), processed_partial_withdrawals_count))
|
Ok((
|
||||||
}
|
withdrawals.into(),
|
||||||
|
processed_builder_withdrawals_count,
|
||||||
/// Apply withdrawals to the state.
|
processed_partial_withdrawals_count,
|
||||||
/// 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,166 @@
|
|||||||
|
use super::errors::BlockProcessingError;
|
||||||
|
use super::get_expected_withdrawals;
|
||||||
|
use crate::common::decrease_balance;
|
||||||
|
use safe_arith::SafeArith;
|
||||||
|
use tree_hash::TreeHash;
|
||||||
|
use types::{
|
||||||
|
AbstractExecPayload, BeaconState, BuilderPendingWithdrawal, ChainSpec, EthSpec, ExecPayload,
|
||||||
|
List, Withdrawals,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Check if a builder payment is withdrawable.
|
||||||
|
/// A builder payment is withdrawable if the builder is not slashed or
|
||||||
|
/// the builder's withdrawable epoch has been reached.
|
||||||
|
pub fn is_builder_payment_withdrawable<E: EthSpec>(
|
||||||
|
state: &BeaconState<E>,
|
||||||
|
withdrawal: &BuilderPendingWithdrawal,
|
||||||
|
) -> Result<bool, BlockProcessingError> {
|
||||||
|
let builder = state.get_validator(withdrawal.builder_index as usize)?;
|
||||||
|
let current_epoch = state.current_epoch();
|
||||||
|
|
||||||
|
Ok(builder.withdrawable_epoch >= current_epoch || !builder.slashed)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_withdrawals_common<E: EthSpec>(
|
||||||
|
state: &mut BeaconState<E>,
|
||||||
|
expected_withdrawals: Withdrawals<E>,
|
||||||
|
partial_withdrawals_count: Option<usize>,
|
||||||
|
spec: &ChainSpec,
|
||||||
|
) -> Result<(), BlockProcessingError> {
|
||||||
|
match state {
|
||||||
|
BeaconState::Capella(_)
|
||||||
|
| BeaconState::Deneb(_)
|
||||||
|
| BeaconState::Electra(_)
|
||||||
|
| BeaconState::Fulu(_)
|
||||||
|
| BeaconState::Gloas(_) => {
|
||||||
|
// Update pending partial withdrawals [New in Electra:EIP7251]
|
||||||
|
if let Some(partial_withdrawals_count) = partial_withdrawals_count {
|
||||||
|
state
|
||||||
|
.pending_partial_withdrawals_mut()?
|
||||||
|
.pop_front(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(())
|
||||||
|
}
|
||||||
|
// these shouldn't even be encountered but they're here for completeness
|
||||||
|
BeaconState::Base(_) | BeaconState::Altair(_) | BeaconState::Bellatrix(_) => Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod capella {
|
||||||
|
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> {
|
||||||
|
// check if capella enabled because this function will run on the merge block where the fork is technically still Bellatrix
|
||||||
|
if state.fork_name_unchecked().capella_enabled() {
|
||||||
|
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()?;
|
||||||
|
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,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
process_withdrawals_common(state, expected_withdrawals, partial_withdrawals_count, spec)
|
||||||
|
} else {
|
||||||
|
// these shouldn't even be encountered but they're here for completeness
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub mod gloas {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
// TODO(EIP-7732): Add comprehensive tests for Gloas `process_withdrawals`:
|
||||||
|
// Similar to Capella version, these will be tested via:
|
||||||
|
// 1. EF consensus-spec tests in `testing/ef_tests/src/cases/operations.rs`
|
||||||
|
// 2. Integration tests via full block processing
|
||||||
|
// These tests would currently fail due to incomplete Gloas block structure as mentioned here, so we will implement them after block and payload processing is in a good state.
|
||||||
|
// https://github.com/sigp/lighthouse/pull/8273
|
||||||
|
/// 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 (expected_withdrawals, builder_withdrawals_count, partial_withdrawals_count) =
|
||||||
|
get_expected_withdrawals(state, spec)?;
|
||||||
|
|
||||||
|
*state.latest_withdrawals_root_mut()? = expected_withdrawals.tree_hash_root();
|
||||||
|
|
||||||
|
for withdrawal in expected_withdrawals.iter() {
|
||||||
|
decrease_balance(
|
||||||
|
state,
|
||||||
|
withdrawal.validator_index as usize,
|
||||||
|
withdrawal.amount,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let (Ok(builder_pending_withdrawals), Some(builder_count)) = (
|
||||||
|
state.builder_pending_withdrawals(),
|
||||||
|
builder_withdrawals_count,
|
||||||
|
) {
|
||||||
|
let mut updated_builder_withdrawals =
|
||||||
|
Vec::with_capacity(E::builder_pending_withdrawals_limit());
|
||||||
|
|
||||||
|
for (i, withdrawal) in builder_pending_withdrawals.iter().enumerate() {
|
||||||
|
if i < builder_count {
|
||||||
|
if !is_builder_payment_withdrawable(state, withdrawal)? {
|
||||||
|
updated_builder_withdrawals.push(withdrawal.clone());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
updated_builder_withdrawals.push(withdrawal.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*state.builder_pending_withdrawals_mut()? = List::new(updated_builder_withdrawals)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
process_withdrawals_common(state, expected_withdrawals, partial_withdrawals_count, spec)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2204,6 +2204,7 @@ impl<E: EthSpec> BeaconState<E> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return true if the parent block was full (both beacon block and execution payload were present).
|
||||||
pub fn is_parent_block_full(&self) -> bool {
|
pub fn is_parent_block_full(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
BeaconState::Base(_) | BeaconState::Altair(_) => false,
|
BeaconState::Base(_) | BeaconState::Altair(_) => false,
|
||||||
|
|||||||
@@ -419,8 +419,15 @@ 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)
|
process_withdrawals::gloas::process_withdrawals(state, spec)
|
||||||
|
} else {
|
||||||
|
process_withdrawals::capella::process_withdrawals::<_, FullPayload<_>>(
|
||||||
|
state,
|
||||||
|
self.payload.to_ref(),
|
||||||
|
spec,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user