Skip to content

Commit

Permalink
Update activation and exit queues to churn on stake
Browse files Browse the repository at this point in the history
  • Loading branch information
dapplion committed Jan 30, 2024
1 parent ab6cbb5 commit 8ef1723
Show file tree
Hide file tree
Showing 11 changed files with 237 additions and 22 deletions.
22 changes: 20 additions & 2 deletions beacon_node/store/src/partial_beacon_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,16 @@ where
#[ssz(skip_serializing, skip_deserializing)]
#[superstruct(only(Capella, Deneb))]
pub historical_summaries: Option<VariableList<HistoricalSummary, T::HistoricalRootsLimit>>,

// MaxEB
#[superstruct(only(Deneb))]
pub deposit_balance_to_consume: Gwei,
#[superstruct(only(Deneb))]
pub pending_balance_deposits: VariableList<PendingBalanceDeposit, T::MaxPendingBalanceDeposits>,
#[superstruct(only(Deneb))]
pub exit_balance_to_consume: Gwei,
#[superstruct(only(Deneb))]
pub earliest_exit_epoch: Epoch,
}

/// Implement the conversion function from BeaconState -> PartialBeaconState.
Expand Down Expand Up @@ -240,7 +250,11 @@ impl<T: EthSpec> PartialBeaconState<T> {
inactivity_scores,
latest_execution_payload_header,
next_withdrawal_index,
next_withdrawal_validator_index
next_withdrawal_validator_index,
deposit_balance_to_consume,
pending_balance_deposits,
earliest_exit_epoch,
exit_balance_to_consume
],
[historical_summaries]
),
Expand Down Expand Up @@ -485,7 +499,11 @@ impl<E: EthSpec> TryInto<BeaconState<E>> for PartialBeaconState<E> {
inactivity_scores,
latest_execution_payload_header,
next_withdrawal_index,
next_withdrawal_validator_index
next_withdrawal_validator_index,
deposit_balance_to_consume,
pending_balance_deposits,
earliest_exit_epoch,
exit_balance_to_consume
],
[historical_summaries]
),
Expand Down
45 changes: 30 additions & 15 deletions consensus/state_processing/src/common/initiate_validator_exit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,39 @@ pub fn initiate_validator_exit<T: EthSpec>(
return Ok(());
}

// Ensure the exit cache is built.
state.build_exit_cache(spec)?;
let exit_queue_epoch = if state >= ForkName::Deneb {
// Ensure the exit cache is built.
state.build_exit_cache(spec)?;

// Compute exit queue epoch
let delayed_epoch = state.compute_activation_exit_epoch(state.current_epoch(), spec)?;
let mut exit_queue_epoch = state
.exit_cache()
.max_epoch()?
.map_or(delayed_epoch, |epoch| max(epoch, delayed_epoch));
let exit_queue_churn = state.exit_cache().get_churn_at(exit_queue_epoch)?;
// Compute exit queue epoch
let delayed_epoch = state.compute_activation_exit_epoch(state.current_epoch(), spec)?;
let mut exit_queue_epoch = state
.exit_cache()
.max_epoch()?
.map_or(delayed_epoch, |epoch| max(epoch, delayed_epoch));
let exit_queue_churn = state.exit_cache().get_churn_at(exit_queue_epoch)?;

if exit_queue_churn >= state.get_churn_limit(spec)? {
exit_queue_epoch.safe_add_assign(1)?;
}
if exit_queue_churn >= state.get_churn_limit(spec)? {
exit_queue_epoch.safe_add_assign(1)?;
}

state
.exit_cache_mut()
.record_validator_exit(exit_queue_epoch)?;
exit_queue_epoch
} else {
// TODO: implement with cache
state.compute_exit_epoch_and_update_churn(
state
.validators()
.get(index)
.ok_or(Error::BalancesOutOfBounds(index))?
.effective_balance
.into(),
spec,
)?
};

state
.exit_cache_mut()
.record_validator_exit(exit_queue_epoch)?;
state.get_validator_mut(index)?.exit_epoch = exit_queue_epoch;
state.get_validator_mut(index)?.withdrawable_epoch =
exit_queue_epoch.safe_add(spec.min_validator_withdrawability_delay)?;
Expand Down
45 changes: 44 additions & 1 deletion consensus/state_processing/src/per_epoch_processing/capella.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ use crate::per_epoch_processing::{
effective_balance_updates::process_effective_balance_updates,
resets::{process_eth1_data_reset, process_randao_mixes_reset, process_slashings_reset},
};
use types::{BeaconState, ChainSpec, EthSpec, RelativeEpoch};
use safe_arith::SafeArith;
use types::{BeaconState, BeaconStateDeneb, BeaconStateError, ChainSpec, EthSpec, RelativeEpoch};

use crate::common::update_progressive_balances_cache::{
initialize_progressive_balances_cache, update_progressive_balances_on_epoch_transition,
Expand Down Expand Up @@ -55,6 +56,8 @@ pub fn process_epoch<T: EthSpec>(
// Reset eth1 data votes.
process_eth1_data_reset(state)?;

process_pending_balance_deposits(state, spec)?;

// Update effective balances with hysteresis (lag).
process_effective_balance_updates(state, Some(&participation_cache), spec)?;

Expand Down Expand Up @@ -82,3 +85,43 @@ pub fn process_epoch<T: EthSpec>(
sync_committee,
})
}

pub fn process_pending_balance_deposits<T: EthSpec>(
state: &mut BeaconState<T>,
spec: &ChainSpec,
) -> Result<(), Error> {
let activation_exit_churn_limit = state.get_activation_exit_churn_limit(spec)?;

if let BeaconState::Deneb(BeaconStateDeneb {
ref mut deposit_balance_to_consume,
ref mut pending_balance_deposits,
ref mut balances,
..
}) = state
{
deposit_balance_to_consume.safe_add_assign(activation_exit_churn_limit)?;
let mut next_pending_deposit_index = 0;
for pending_balance_deposit in pending_balance_deposits.iter() {
if *deposit_balance_to_consume < pending_balance_deposit.amount {
break;
}

deposit_balance_to_consume.safe_sub_assign(pending_balance_deposit.amount)?;

let index = pending_balance_deposit.index as usize;
balances
.get_mut(index)
.ok_or(BeaconStateError::BalancesOutOfBounds(index))?
.safe_add_assign(pending_balance_deposit.amount)?;

next_pending_deposit_index.safe_add_assign(1)?;
}

// TODO(maxeb), converting to vec to have something while SSZ api supports pop
let mut pending_balance_deposits_vec = pending_balance_deposits.to_vec();
pending_balance_deposits_vec.drain(0..next_pending_deposit_index);
*pending_balance_deposits = pending_balance_deposits_vec.into();
}

Ok(())
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{common::initiate_validator_exit, per_epoch_processing::Error};
use itertools::Itertools;
use safe_arith::SafeArith;
use types::{BeaconState, ChainSpec, EthSpec, Validator};
use types::{BeaconState, ChainSpec, EthSpec, ForkName, Validator};

/// Performs a validator registry update, if required.
///
Expand Down Expand Up @@ -49,8 +49,14 @@ pub fn process_registry_updates<T: EthSpec>(
.map(|(index, _)| index)
.collect_vec();

let activation_churn_limit = if state >= ForkName::Deneb {
// activation churn limit controlled by pending deposits
usize::MAX
} else {
state.get_activation_churn_limit(spec)? as usize
};

// Dequeue validators for activation up to churn limit
let activation_churn_limit = state.get_activation_churn_limit(spec)? as usize;
let delayed_activation_epoch = state.compute_activation_exit_epoch(current_epoch, spec)?;
for index in activation_queue.into_iter().take(activation_churn_limit) {
state.get_validator_mut(index)?.activation_epoch = delayed_activation_epoch;
Expand Down
5 changes: 5 additions & 0 deletions consensus/state_processing/src/upgrade/deneb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ pub fn upgrade_to_deneb<E: EthSpec>(
next_withdrawal_index: pre.next_withdrawal_index,
next_withdrawal_validator_index: pre.next_withdrawal_validator_index,
historical_summaries: pre.historical_summaries.clone(),
// MaxEB
deposit_balance_to_consume: 0u64.into(),
pending_balance_deposits: <_>::default(),
earliest_exit_epoch: 0u64.into(),
exit_balance_to_consume: 0u64.into(),
// Caches
total_active_balance: pre.total_active_balance,
progressive_balances_cache: mem::take(&mut pre.progressive_balances_cache),
Expand Down
62 changes: 62 additions & 0 deletions consensus/types/src/beacon_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,16 @@ where
#[superstruct(only(Capella, Deneb))]
pub historical_summaries: VariableList<HistoricalSummary, T::HistoricalRootsLimit>,

// MaxEB
#[superstruct(only(Deneb))]
pub deposit_balance_to_consume: Gwei,
#[superstruct(only(Deneb))]
pub pending_balance_deposits: VariableList<PendingBalanceDeposit, T::MaxPendingBalanceDeposits>,
#[superstruct(only(Deneb))]
pub exit_balance_to_consume: Gwei,
#[superstruct(only(Deneb))]
pub earliest_exit_epoch: Epoch,

// Caching (not in the spec)
#[serde(skip_serializing, skip_deserializing)]
#[ssz(skip_serializing, skip_deserializing)]
Expand Down Expand Up @@ -1357,6 +1367,58 @@ impl<T: EthSpec> BeaconState<T> {
})
}

// Return the churn limit for the current epoch.
pub fn get_churn_limit_gwei(&self, spec: &ChainSpec) -> Result<Gwei, Error> {
let churn = std::cmp::max(
spec.min_per_epoch_churn_limit_gwei,
Gwei::new(
self.get_total_active_balance()?
.safe_div(spec.churn_limit_quotient)?,
),
);

Ok(churn.safe_sub(churn.safe_rem(Gwei::new(spec.effective_balance_increment))?)?)
}

/// Return the churn limit for the current epoch dedicated to activations and exits.
pub fn get_activation_exit_churn_limit(&self, spec: &ChainSpec) -> Result<Gwei, Error> {
Ok(std::cmp::min(
spec.max_per_epoch_activation_churn_limit_gwei,
self.get_churn_limit_gwei(spec)?,
))
}

pub fn compute_exit_epoch_and_update_churn(
&mut self,
exit_balance: Gwei,
spec: &ChainSpec,
) -> Result<Epoch, Error> {
let earliest_exit_epoch = self.compute_activation_exit_epoch(self.current_epoch(), spec)?;
let per_epoch_churn = self.get_activation_exit_churn_limit(spec)?;
// New epoch for exits.
if *self.earliest_exit_epoch()? < earliest_exit_epoch {
*self.earliest_exit_epoch_mut()? = earliest_exit_epoch;
*self.exit_balance_to_consume_mut()? = per_epoch_churn;
}

// Exit fits in the current earliest epoch.
if exit_balance <= *self.exit_balance_to_consume()? {
self.exit_balance_to_consume_mut()?
.safe_sub_assign(exit_balance)?;
} else {
// Exit doesn't fit in the current earliest epoch.
let balance_to_process = exit_balance.safe_sub(*self.exit_balance_to_consume()?)?;
let additional_epochs =
Epoch::new((balance_to_process.safe_div(per_epoch_churn)?).into());
let remainder = balance_to_process.safe_rem(per_epoch_churn)?;
self.earliest_exit_epoch_mut()?
.safe_add_assign(additional_epochs.safe_add(1)?)?;
*self.exit_balance_to_consume_mut()? = per_epoch_churn.safe_sub(remainder)?;
}

Ok(*self.earliest_exit_epoch()?)
}

/// Returns the `slot`, `index`, `committee_position` and `committee_len` for which a validator must produce an
/// attestation.
///
Expand Down
17 changes: 17 additions & 0 deletions consensus/types/src/chain_spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ pub struct ChainSpec {
pub max_committees_per_slot: usize,
pub target_committee_size: usize,
pub min_per_epoch_churn_limit: u64,
pub min_per_epoch_churn_limit_gwei: Gwei,
pub max_per_epoch_activation_churn_limit: u64,
pub max_per_epoch_activation_churn_limit_gwei: Gwei,
pub churn_limit_quotient: u64,
pub shuffle_round_count: u8,
pub min_genesis_active_validator_count: u64,
Expand Down Expand Up @@ -558,7 +560,9 @@ impl ChainSpec {
max_committees_per_slot: 64,
target_committee_size: 128,
min_per_epoch_churn_limit: 4,
min_per_epoch_churn_limit_gwei: Gwei::new(128),
max_per_epoch_activation_churn_limit: 8,
max_per_epoch_activation_churn_limit_gwei: Gwei::new(256),
churn_limit_quotient: 65_536,
shuffle_round_count: 90,
min_genesis_active_validator_count: 16_384,
Expand Down Expand Up @@ -826,7 +830,9 @@ impl ChainSpec {
max_committees_per_slot: 64,
target_committee_size: 128,
min_per_epoch_churn_limit: 4,
min_per_epoch_churn_limit_gwei: Gwei::new(128),
max_per_epoch_activation_churn_limit: 8,
max_per_epoch_activation_churn_limit_gwei: Gwei::new(256),
churn_limit_quotient: 4_096,
shuffle_round_count: 90,
min_genesis_active_validator_count: 4_096,
Expand Down Expand Up @@ -1115,11 +1121,15 @@ pub struct Config {
ejection_balance: u64,
#[serde(with = "serde_utils::quoted_u64")]
min_per_epoch_churn_limit: u64,
#[serde(with = "serde_utils::quoted_u64")]
min_per_epoch_churn_limit_gwei: Gwei,
#[serde(default = "default_max_per_epoch_activation_churn_limit")]
#[serde(with = "serde_utils::quoted_u64")]
max_per_epoch_activation_churn_limit: u64,
#[serde(with = "serde_utils::quoted_u64")]
churn_limit_quotient: u64,
#[serde(with = "serde_utils::quoted_u64")]
max_per_epoch_activation_churn_limit_gwei: Gwei,

#[serde(skip_serializing_if = "Option::is_none")]
proposer_score_boost: Option<MaybeQuoted<u64>>,
Expand Down Expand Up @@ -1423,7 +1433,10 @@ impl Config {
ejection_balance: spec.ejection_balance,
churn_limit_quotient: spec.churn_limit_quotient,
min_per_epoch_churn_limit: spec.min_per_epoch_churn_limit,
min_per_epoch_churn_limit_gwei: spec.min_per_epoch_churn_limit_gwei,
max_per_epoch_activation_churn_limit: spec.max_per_epoch_activation_churn_limit,
max_per_epoch_activation_churn_limit_gwei: spec
.max_per_epoch_activation_churn_limit_gwei,

proposer_score_boost: spec.proposer_score_boost.map(|value| MaybeQuoted { value }),

Expand Down Expand Up @@ -1489,7 +1502,9 @@ impl Config {
inactivity_score_recovery_rate,
ejection_balance,
min_per_epoch_churn_limit,
min_per_epoch_churn_limit_gwei,
max_per_epoch_activation_churn_limit,
max_per_epoch_activation_churn_limit_gwei,
churn_limit_quotient,
proposer_score_boost,
deposit_chain_id,
Expand Down Expand Up @@ -1542,7 +1557,9 @@ impl Config {
inactivity_score_recovery_rate,
ejection_balance,
min_per_epoch_churn_limit,
min_per_epoch_churn_limit_gwei,
max_per_epoch_activation_churn_limit,
max_per_epoch_activation_churn_limit_gwei,
churn_limit_quotient,
proposer_score_boost: proposer_score_boost.map(|q| q.value),
deposit_chain_id,
Expand Down
18 changes: 18 additions & 0 deletions consensus/types/src/deposit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,24 @@ pub struct Deposit {
pub data: DepositData,
}

#[derive(
arbitrary::Arbitrary,
Debug,
PartialEq,
Hash,
Clone,
Serialize,
Deserialize,
Encode,
Decode,
TreeHash,
TestRandom,
)]
pub struct PendingBalanceDeposit {
pub index: u64,
pub amount: u64,
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
Loading

0 comments on commit 8ef1723

Please sign in to comment.