diff --git a/beacon_node/execution_layer/src/engine_api/json_structures.rs b/beacon_node/execution_layer/src/engine_api/json_structures.rs index 82d72ca56..fcbd17d73 100644 --- a/beacon_node/execution_layer/src/engine_api/json_structures.rs +++ b/beacon_node/execution_layer/src/engine_api/json_structures.rs @@ -381,6 +381,8 @@ impl From> for JsonExecutionPayload { pub struct JsonWithdrawal { #[serde(with = "eth2_serde_utils::u64_hex_be")] pub index: u64, + #[serde(with = "eth2_serde_utils::u64_hex_be")] + pub validator_index: u64, pub address: Address, #[serde(with = "eth2_serde_utils::u256_hex_be")] pub amount: Uint256, @@ -390,6 +392,7 @@ impl From for JsonWithdrawal { fn from(withdrawal: Withdrawal) -> Self { Self { index: withdrawal.index, + validator_index: withdrawal.validator_index, address: withdrawal.address, amount: Uint256::from((withdrawal.amount as u128) * 1000000000u128), } @@ -400,6 +403,7 @@ impl From for Withdrawal { fn from(jw: JsonWithdrawal) -> Self { Self { index: jw.index, + validator_index: jw.validator_index, address: jw.address, //FIXME(sean) if EE gives us too large a number this panics amount: (jw.amount / 1000000000).as_u64(), diff --git a/consensus/state_processing/src/common/mod.rs b/consensus/state_processing/src/common/mod.rs index 8a2e2439b..531891ee9 100644 --- a/consensus/state_processing/src/common/mod.rs +++ b/consensus/state_processing/src/common/mod.rs @@ -4,6 +4,7 @@ mod get_attesting_indices; mod get_indexed_attestation; mod initiate_validator_exit; mod slash_validator; +mod withdraw_balance; pub mod altair; pub mod base; @@ -14,6 +15,7 @@ pub use get_attesting_indices::{get_attesting_indices, get_attesting_indices_fro pub use get_indexed_attestation::get_indexed_attestation; pub use initiate_validator_exit::initiate_validator_exit; pub use slash_validator::slash_validator; +pub use withdraw_balance::withdraw_balance; use safe_arith::SafeArith; use types::{BeaconState, BeaconStateError, EthSpec}; diff --git a/consensus/state_processing/src/common/withdraw_balance.rs b/consensus/state_processing/src/common/withdraw_balance.rs new file mode 100644 index 000000000..29b09cc0f --- /dev/null +++ b/consensus/state_processing/src/common/withdraw_balance.rs @@ -0,0 +1,28 @@ +use crate::common::decrease_balance; +use safe_arith::SafeArith; +use types::{BeaconStateError as Error, *}; + +pub fn withdraw_balance( + state: &mut BeaconState, + validator_index: usize, + amount: u64, +) -> Result<(), Error> { + decrease_balance(state, validator_index as usize, amount)?; + + let withdrawal_address = Address::from_slice( + &state + .get_validator(validator_index)? + .withdrawal_credentials + .as_bytes()[12..], + ); + let withdrawal = Withdrawal { + index: *state.next_withdrawal_index()?, + validator_index: validator_index as u64, + address: withdrawal_address, + amount, + }; + state.next_withdrawal_index_mut()?.safe_add_assign(1)?; + state.withdrawal_queue_mut()?.push(withdrawal)?; + + Ok(()) +} diff --git a/consensus/state_processing/src/per_epoch_processing/capella.rs b/consensus/state_processing/src/per_epoch_processing/capella.rs index 4886b2805..d1bf71071 100644 --- a/consensus/state_processing/src/per_epoch_processing/capella.rs +++ b/consensus/state_processing/src/per_epoch_processing/capella.rs @@ -66,9 +66,9 @@ pub fn process_epoch( altair::process_sync_committee_updates(state, spec)?; // Withdrawals - process_full_withdrawals(state)?; + process_full_withdrawals(state, spec)?; - process_partial_withdrawals(state)?; + process_partial_withdrawals(state, spec)?; // Rotate the epoch caches to suit the epoch transition. state.advance_caches(spec)?; diff --git a/consensus/state_processing/src/per_epoch_processing/capella/full_withdrawals.rs b/consensus/state_processing/src/per_epoch_processing/capella/full_withdrawals.rs index 13bbc6758..62e4b9111 100644 --- a/consensus/state_processing/src/per_epoch_processing/capella/full_withdrawals.rs +++ b/consensus/state_processing/src/per_epoch_processing/capella/full_withdrawals.rs @@ -1,10 +1,23 @@ +use crate::common::withdraw_balance; use crate::EpochProcessingError; -use types::beacon_state::BeaconState; -use types::eth_spec::EthSpec; +use types::{beacon_state::BeaconState, eth_spec::EthSpec, ChainSpec}; pub fn process_full_withdrawals( - _state: &mut BeaconState, + state: &mut BeaconState, + spec: &ChainSpec, ) -> Result<(), EpochProcessingError> { - todo!("implement this"); + let current_epoch = state.current_epoch(); + // FIXME: is this the most efficient way to do this? + for validator_index in 0..state.validators().len() { + // TODO: is this the correct way to handle validators not existing? + if let (Some(validator), Some(balance)) = ( + state.validators().get(validator_index), + state.balances().get(validator_index), + ) { + if validator.is_fully_withdrawable_at(*balance, current_epoch, spec) { + withdraw_balance(state, validator_index, *balance)?; + } + } + } Ok(()) } diff --git a/consensus/state_processing/src/per_epoch_processing/capella/partial_withdrawals.rs b/consensus/state_processing/src/per_epoch_processing/capella/partial_withdrawals.rs index a648766e2..75576ef6e 100644 --- a/consensus/state_processing/src/per_epoch_processing/capella/partial_withdrawals.rs +++ b/consensus/state_processing/src/per_epoch_processing/capella/partial_withdrawals.rs @@ -1,10 +1,39 @@ +use crate::common::withdraw_balance; use crate::EpochProcessingError; -use types::beacon_state::BeaconState; -use types::eth_spec::EthSpec; +use safe_arith::SafeArith; +use types::{beacon_state::BeaconState, eth_spec::EthSpec, ChainSpec}; pub fn process_partial_withdrawals( - _state: &mut BeaconState, + state: &mut BeaconState, + spec: &ChainSpec, ) -> Result<(), EpochProcessingError> { - todo!("implement this"); + let mut partial_withdrawals_count = 0; + let mut validator_index = *state.next_partial_withdrawal_validator_index()? as usize; + + let n_validators = state.validators().len(); + // FIXME: is this the most efficient way to do this? + for _ in 0..n_validators { + // TODO: is this the correct way to handle validators not existing? + if let (Some(validator), Some(balance)) = ( + state.validators().get(validator_index), + state.balances().get(validator_index), + ) { + if validator.is_partially_withdrawable_validator(*balance, spec) { + withdraw_balance( + state, + validator_index, + *balance - spec.max_effective_balance, + )?; + partial_withdrawals_count.safe_add_assign(1)?; + + validator_index = validator_index.safe_add(1)? % n_validators; + if partial_withdrawals_count == T::max_partial_withdrawals_per_epoch() { + break; + } + } + } + } + *state.next_partial_withdrawal_validator_index_mut()? = validator_index as u64; + Ok(()) } diff --git a/consensus/types/src/chain_spec.rs b/consensus/types/src/chain_spec.rs index 69346ec19..cfb072ecd 100644 --- a/consensus/types/src/chain_spec.rs +++ b/consensus/types/src/chain_spec.rs @@ -73,6 +73,7 @@ pub struct ChainSpec { */ pub genesis_fork_version: [u8; 4], pub bls_withdrawal_prefix_byte: u8, + pub eth1_address_withdrawal_prefix_byte: u8, /* * Time parameters @@ -519,7 +520,8 @@ impl ChainSpec { * Initial Values */ genesis_fork_version: [0; 4], - bls_withdrawal_prefix_byte: 0, + bls_withdrawal_prefix_byte: 0x00, + eth1_address_withdrawal_prefix_byte: 0x01, /* * Time parameters @@ -748,7 +750,8 @@ impl ChainSpec { * Initial Values */ genesis_fork_version: [0x00, 0x00, 0x00, 0x64], - bls_withdrawal_prefix_byte: 0, + bls_withdrawal_prefix_byte: 0x00, + eth1_address_withdrawal_prefix_byte: 0x01, /* * Time parameters diff --git a/consensus/types/src/validator.rs b/consensus/types/src/validator.rs index 21a6b39b6..6e63c943a 100644 --- a/consensus/types/src/validator.rs +++ b/consensus/types/src/validator.rs @@ -65,6 +65,27 @@ impl Validator { // Has not yet been activated && self.activation_epoch == spec.far_future_epoch } + + /// Returns `true` if the validator has eth1 withdrawal credential + pub fn has_eth1_withdrawal_credential(&self, spec: &ChainSpec) -> bool { + self.withdrawal_credentials + .as_bytes() + .first() + .map(|byte| *byte == spec.eth1_address_withdrawal_prefix_byte) + .unwrap_or(false) + } + + /// Returns `true` if the validator is fully withdrawable at some epoch + pub fn is_fully_withdrawable_at(&self, balance: u64, epoch: Epoch, spec: &ChainSpec) -> bool { + self.has_eth1_withdrawal_credential(spec) && self.withdrawable_epoch <= epoch && balance > 0 + } + + /// Returns `true` if the validator is partially withdrawable + pub fn is_partially_withdrawable_validator(&self, balance: u64, spec: &ChainSpec) -> bool { + self.has_eth1_withdrawal_credential(spec) + && self.effective_balance == spec.max_effective_balance + && balance > spec.max_effective_balance + } } impl Default for Validator { diff --git a/consensus/types/src/withdrawal.rs b/consensus/types/src/withdrawal.rs index 736884791..36ee63965 100644 --- a/consensus/types/src/withdrawal.rs +++ b/consensus/types/src/withdrawal.rs @@ -15,6 +15,7 @@ use tree_hash_derive::TreeHash; pub struct Withdrawal { #[serde(with = "eth2_serde_utils::quoted_u64")] pub index: u64, + pub validator_index: u64, pub address: Address, pub amount: u64, }