Skip to content

Commit 4ab5a77

Browse files
authored
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
1 parent 150b117 commit 4ab5a77

File tree

6 files changed

+253
-81
lines changed

6 files changed

+253
-81
lines changed

beacon_node/beacon_chain/src/beacon_chain.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4832,7 +4832,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
48324832
let proposal_epoch = proposal_slot.epoch(T::EthSpec::slots_per_epoch());
48334833
if head_state.current_epoch() == proposal_epoch {
48344834
return get_expected_withdrawals(&unadvanced_state, &self.spec)
4835-
.map(|(withdrawals, _)| withdrawals)
4835+
.map(|(withdrawals, _, _)| withdrawals)
48364836
.map_err(Error::PrepareProposerFailed);
48374837
}
48384838

@@ -4850,7 +4850,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
48504850
&self.spec,
48514851
)?;
48524852
get_expected_withdrawals(&advanced_state, &self.spec)
4853-
.map(|(withdrawals, _)| withdrawals)
4853+
.map(|(withdrawals, _, _)| withdrawals)
48544854
.map_err(Error::PrepareProposerFailed)
48554855
}
48564856

beacon_node/http_api/src/builder_states.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ pub fn get_next_withdrawals<T: BeaconChainTypes>(
3232
}
3333

3434
match get_expected_withdrawals(&state, &chain.spec) {
35-
Ok((withdrawals, _)) => Ok(withdrawals),
35+
Ok((withdrawals, _, _)) => Ok(withdrawals),
3636
Err(e) => Err(warp_utils::reject::custom_server_error(format!(
3737
"failed to get expected withdrawal: {:?}",
3838
e

consensus/state_processing/src/per_block_processing.rs

Lines changed: 74 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ pub mod deneb;
3030
pub mod errors;
3131
mod is_valid_indexed_attestation;
3232
pub mod process_operations;
33+
pub mod process_withdrawals;
3334
pub mod signature_sets;
3435
pub mod tests;
3536
mod verify_attestation;
@@ -39,7 +40,6 @@ mod verify_deposit;
3940
mod verify_exit;
4041
mod verify_proposer_slashing;
4142

42-
use crate::common::decrease_balance;
4343
use crate::common::update_progressive_balances_cache::{
4444
initialize_progressive_balances_cache, update_progressive_balances_metrics,
4545
};
@@ -171,13 +171,20 @@ pub fn per_block_processing<E: EthSpec, Payload: AbstractExecPayload<E>>(
171171
// previous block.
172172
if is_execution_enabled(state, block.body()) {
173173
let body = block.body();
174-
// TODO(EIP-7732): build out process_withdrawals variant for gloas
175-
process_withdrawals::<E, Payload>(state, body.execution_payload()?, spec)?;
176-
process_execution_payload::<E, Payload>(state, body, spec)?;
177-
}
174+
if state.fork_name_unchecked().gloas_enabled() {
175+
process_withdrawals::gloas::process_withdrawals::<E>(state, spec)?;
178176

179-
// TODO(EIP-7732): build out process_execution_bid
180-
// process_execution_bid(state, block, verify_signatures, spec)?;
177+
// TODO(EIP-7732): build out process_execution_bid
178+
// process_execution_bid(state, block, verify_signatures, spec)?;
179+
} else {
180+
process_withdrawals::capella::process_withdrawals::<E, Payload>(
181+
state,
182+
body.execution_payload()?,
183+
spec,
184+
)?;
185+
process_execution_payload::<E, Payload>(state, body, spec)?;
186+
}
187+
}
181188

182189
process_randao(state, block, verify_randao, ctxt, spec)?;
183190
process_eth1_data(state, block.body().eth1_data())?;
@@ -515,17 +522,70 @@ pub fn compute_timestamp_at_slot<E: EthSpec>(
515522

516523
/// Compute the next batch of withdrawals which should be included in a block.
517524
///
518-
/// https://github.com/ethereum/consensus-specs/blob/dev/specs/electra/beacon-chain.md#new-get_expected_withdrawals
525+
/// https://ethereum.github.io/consensus-specs/specs/gloas/beacon-chain/#modified-get_expected_withdrawals
526+
#[allow(clippy::type_complexity)]
519527
pub fn get_expected_withdrawals<E: EthSpec>(
520528
state: &BeaconState<E>,
521529
spec: &ChainSpec,
522-
) -> Result<(Withdrawals<E>, Option<usize>), BlockProcessingError> {
530+
) -> Result<(Withdrawals<E>, Option<usize>, Option<usize>), BlockProcessingError> {
523531
let epoch = state.current_epoch();
524532
let mut withdrawal_index = state.next_withdrawal_index()?;
525533
let mut validator_index = state.next_withdrawal_validator_index()?;
526534
let mut withdrawals = Vec::<Withdrawal>::with_capacity(E::max_withdrawals_per_payload());
527535
let fork_name = state.fork_name_unchecked();
528536

537+
// [New in Gloas:EIP7732]
538+
// Sweep for builder payments
539+
let processed_builder_withdrawals_count =
540+
if let Ok(builder_pending_withdrawals) = state.builder_pending_withdrawals() {
541+
let mut processed_builder_withdrawals_count = 0;
542+
for withdrawal in builder_pending_withdrawals {
543+
if withdrawal.withdrawable_epoch > epoch
544+
|| withdrawals.len().safe_add(1)? == E::max_withdrawals_per_payload()
545+
{
546+
break;
547+
}
548+
549+
if process_withdrawals::is_builder_payment_withdrawable(state, withdrawal)? {
550+
let total_withdrawn = withdrawals
551+
.iter()
552+
.filter_map(|w| {
553+
(w.validator_index == withdrawal.builder_index).then_some(w.amount)
554+
})
555+
.safe_sum()?;
556+
let balance = state
557+
.get_balance(withdrawal.builder_index as usize)?
558+
.safe_sub(total_withdrawn)?;
559+
let builder = state.get_validator(withdrawal.builder_index as usize)?;
560+
561+
let withdrawable_balance = if builder.slashed {
562+
std::cmp::min(balance, withdrawal.amount)
563+
} else if balance > spec.min_activation_balance {
564+
std::cmp::min(
565+
balance.safe_sub(spec.min_activation_balance)?,
566+
withdrawal.amount,
567+
)
568+
} else {
569+
0
570+
};
571+
572+
if withdrawable_balance > 0 {
573+
withdrawals.push(Withdrawal {
574+
index: withdrawal_index,
575+
validator_index: withdrawal.builder_index,
576+
address: withdrawal.fee_recipient,
577+
amount: withdrawable_balance,
578+
});
579+
withdrawal_index.safe_add_assign(1)?;
580+
}
581+
}
582+
processed_builder_withdrawals_count.safe_add_assign(1)?;
583+
}
584+
Some(processed_builder_withdrawals_count)
585+
} else {
586+
None
587+
};
588+
529589
// [New in Electra:EIP7251]
530590
// Consume pending partial withdrawals
531591
let processed_partial_withdrawals_count =
@@ -626,71 +686,9 @@ pub fn get_expected_withdrawals<E: EthSpec>(
626686
.safe_rem(state.validators().len() as u64)?;
627687
}
628688

629-
Ok((withdrawals.into(), processed_partial_withdrawals_count))
630-
}
631-
632-
/// Apply withdrawals to the state.
633-
/// TODO(EIP-7732): abstract this out and create gloas variant
634-
pub fn process_withdrawals<E: EthSpec, Payload: AbstractExecPayload<E>>(
635-
state: &mut BeaconState<E>,
636-
payload: Payload::Ref<'_>,
637-
spec: &ChainSpec,
638-
) -> Result<(), BlockProcessingError> {
639-
if state.fork_name_unchecked().capella_enabled() {
640-
let (expected_withdrawals, processed_partial_withdrawals_count) =
641-
get_expected_withdrawals(state, spec)?;
642-
let expected_root = expected_withdrawals.tree_hash_root();
643-
let withdrawals_root = payload.withdrawals_root()?;
644-
645-
if expected_root != withdrawals_root {
646-
return Err(BlockProcessingError::WithdrawalsRootMismatch {
647-
expected: expected_root,
648-
found: withdrawals_root,
649-
});
650-
}
651-
652-
for withdrawal in expected_withdrawals.iter() {
653-
decrease_balance(
654-
state,
655-
withdrawal.validator_index as usize,
656-
withdrawal.amount,
657-
)?;
658-
}
659-
660-
// Update pending partial withdrawals [New in Electra:EIP7251]
661-
if let Some(processed_partial_withdrawals_count) = processed_partial_withdrawals_count {
662-
state
663-
.pending_partial_withdrawals_mut()?
664-
.pop_front(processed_partial_withdrawals_count)?;
665-
}
666-
667-
// Update the next withdrawal index if this block contained withdrawals
668-
if let Some(latest_withdrawal) = expected_withdrawals.last() {
669-
*state.next_withdrawal_index_mut()? = latest_withdrawal.index.safe_add(1)?;
670-
671-
// Update the next validator index to start the next withdrawal sweep
672-
if expected_withdrawals.len() == E::max_withdrawals_per_payload() {
673-
// Next sweep starts after the latest withdrawal's validator index
674-
let next_validator_index = latest_withdrawal
675-
.validator_index
676-
.safe_add(1)?
677-
.safe_rem(state.validators().len() as u64)?;
678-
*state.next_withdrawal_validator_index_mut()? = next_validator_index;
679-
}
680-
}
681-
682-
// Advance sweep by the max length of the sweep if there was not a full set of withdrawals
683-
if expected_withdrawals.len() != E::max_withdrawals_per_payload() {
684-
let next_validator_index = state
685-
.next_withdrawal_validator_index()?
686-
.safe_add(spec.max_validators_per_withdrawals_sweep)?
687-
.safe_rem(state.validators().len() as u64)?;
688-
*state.next_withdrawal_validator_index_mut()? = next_validator_index;
689-
}
690-
691-
Ok(())
692-
} else {
693-
// these shouldn't even be encountered but they're here for completeness
694-
Ok(())
695-
}
689+
Ok((
690+
withdrawals.into(),
691+
processed_builder_withdrawals_count,
692+
processed_partial_withdrawals_count,
693+
))
696694
}
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
use super::errors::BlockProcessingError;
2+
use super::get_expected_withdrawals;
3+
use crate::common::decrease_balance;
4+
use safe_arith::SafeArith;
5+
use tree_hash::TreeHash;
6+
use types::{
7+
AbstractExecPayload, BeaconState, BuilderPendingWithdrawal, ChainSpec, EthSpec, ExecPayload,
8+
List, Withdrawals,
9+
};
10+
11+
/// Check if a builder payment is withdrawable.
12+
/// A builder payment is withdrawable if the builder is not slashed or
13+
/// the builder's withdrawable epoch has been reached.
14+
pub fn is_builder_payment_withdrawable<E: EthSpec>(
15+
state: &BeaconState<E>,
16+
withdrawal: &BuilderPendingWithdrawal,
17+
) -> Result<bool, BlockProcessingError> {
18+
let builder = state.get_validator(withdrawal.builder_index as usize)?;
19+
let current_epoch = state.current_epoch();
20+
21+
Ok(builder.withdrawable_epoch >= current_epoch || !builder.slashed)
22+
}
23+
24+
fn process_withdrawals_common<E: EthSpec>(
25+
state: &mut BeaconState<E>,
26+
expected_withdrawals: Withdrawals<E>,
27+
partial_withdrawals_count: Option<usize>,
28+
spec: &ChainSpec,
29+
) -> Result<(), BlockProcessingError> {
30+
match state {
31+
BeaconState::Capella(_)
32+
| BeaconState::Deneb(_)
33+
| BeaconState::Electra(_)
34+
| BeaconState::Fulu(_)
35+
| BeaconState::Gloas(_) => {
36+
// Update pending partial withdrawals [New in Electra:EIP7251]
37+
if let Some(partial_withdrawals_count) = partial_withdrawals_count {
38+
state
39+
.pending_partial_withdrawals_mut()?
40+
.pop_front(partial_withdrawals_count)?;
41+
}
42+
43+
// Update the next withdrawal index if this block contained withdrawals
44+
if let Some(latest_withdrawal) = expected_withdrawals.last() {
45+
*state.next_withdrawal_index_mut()? = latest_withdrawal.index.safe_add(1)?;
46+
47+
// Update the next validator index to start the next withdrawal sweep
48+
if expected_withdrawals.len() == E::max_withdrawals_per_payload() {
49+
// Next sweep starts after the latest withdrawal's validator index
50+
let next_validator_index = latest_withdrawal
51+
.validator_index
52+
.safe_add(1)?
53+
.safe_rem(state.validators().len() as u64)?;
54+
*state.next_withdrawal_validator_index_mut()? = next_validator_index;
55+
}
56+
}
57+
58+
// Advance sweep by the max length of the sweep if there was not a full set of withdrawals
59+
if expected_withdrawals.len() != E::max_withdrawals_per_payload() {
60+
let next_validator_index = state
61+
.next_withdrawal_validator_index()?
62+
.safe_add(spec.max_validators_per_withdrawals_sweep)?
63+
.safe_rem(state.validators().len() as u64)?;
64+
*state.next_withdrawal_validator_index_mut()? = next_validator_index;
65+
}
66+
67+
Ok(())
68+
}
69+
// these shouldn't even be encountered but they're here for completeness
70+
BeaconState::Base(_) | BeaconState::Altair(_) | BeaconState::Bellatrix(_) => Ok(()),
71+
}
72+
}
73+
74+
pub mod capella {
75+
use super::*;
76+
/// Apply withdrawals to the state.
77+
pub fn process_withdrawals<E: EthSpec, Payload: AbstractExecPayload<E>>(
78+
state: &mut BeaconState<E>,
79+
payload: Payload::Ref<'_>,
80+
spec: &ChainSpec,
81+
) -> Result<(), BlockProcessingError> {
82+
// check if capella enabled because this function will run on the merge block where the fork is technically still Bellatrix
83+
if state.fork_name_unchecked().capella_enabled() {
84+
let (expected_withdrawals, _, partial_withdrawals_count) =
85+
get_expected_withdrawals(state, spec)?;
86+
87+
let expected_root = expected_withdrawals.tree_hash_root();
88+
let withdrawals_root = payload.withdrawals_root()?;
89+
if expected_root != withdrawals_root {
90+
return Err(BlockProcessingError::WithdrawalsRootMismatch {
91+
expected: expected_root,
92+
found: withdrawals_root,
93+
});
94+
}
95+
96+
for withdrawal in expected_withdrawals.iter() {
97+
decrease_balance(
98+
state,
99+
withdrawal.validator_index as usize,
100+
withdrawal.amount,
101+
)?;
102+
}
103+
104+
process_withdrawals_common(state, expected_withdrawals, partial_withdrawals_count, spec)
105+
} else {
106+
// these shouldn't even be encountered but they're here for completeness
107+
Ok(())
108+
}
109+
}
110+
}
111+
pub mod gloas {
112+
use super::*;
113+
114+
// TODO(EIP-7732): Add comprehensive tests for Gloas `process_withdrawals`:
115+
// Similar to Capella version, these will be tested via:
116+
// 1. EF consensus-spec tests in `testing/ef_tests/src/cases/operations.rs`
117+
// 2. Integration tests via full block processing
118+
// 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.
119+
// https://github.com/sigp/lighthouse/pull/8273
120+
/// Apply withdrawals to the state.
121+
pub fn process_withdrawals<E: EthSpec>(
122+
state: &mut BeaconState<E>,
123+
spec: &ChainSpec,
124+
) -> Result<(), BlockProcessingError> {
125+
if !state.is_parent_block_full() {
126+
return Ok(());
127+
}
128+
129+
let (expected_withdrawals, builder_withdrawals_count, partial_withdrawals_count) =
130+
get_expected_withdrawals(state, spec)?;
131+
132+
*state.latest_withdrawals_root_mut()? = expected_withdrawals.tree_hash_root();
133+
134+
for withdrawal in expected_withdrawals.iter() {
135+
decrease_balance(
136+
state,
137+
withdrawal.validator_index as usize,
138+
withdrawal.amount,
139+
)?;
140+
}
141+
142+
if let (Ok(builder_pending_withdrawals), Some(builder_count)) = (
143+
state.builder_pending_withdrawals(),
144+
builder_withdrawals_count,
145+
) {
146+
let mut updated_builder_withdrawals =
147+
Vec::with_capacity(E::builder_pending_withdrawals_limit());
148+
149+
for (i, withdrawal) in builder_pending_withdrawals.iter().enumerate() {
150+
if i < builder_count {
151+
if !is_builder_payment_withdrawable(state, withdrawal)? {
152+
updated_builder_withdrawals.push(withdrawal.clone());
153+
}
154+
} else {
155+
updated_builder_withdrawals.push(withdrawal.clone());
156+
}
157+
}
158+
159+
*state.builder_pending_withdrawals_mut()? = List::new(updated_builder_withdrawals)?;
160+
}
161+
162+
process_withdrawals_common(state, expected_withdrawals, partial_withdrawals_count, spec)?;
163+
164+
Ok(())
165+
}
166+
}

consensus/types/src/beacon_state.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2204,6 +2204,7 @@ impl<E: EthSpec> BeaconState<E> {
22042204
}
22052205
}
22062206

2207+
/// Return true if the parent block was full (both beacon block and execution payload were present).
22072208
pub fn is_parent_block_full(&self) -> bool {
22082209
match self {
22092210
BeaconState::Base(_) | BeaconState::Altair(_) => false,

0 commit comments

Comments
 (0)