diff --git a/account_manager/src/validator/create.rs b/account_manager/src/validator/create.rs index da01121055e..63a7c4be715 100644 --- a/account_manager/src/validator/create.rs +++ b/account_manager/src/validator/create.rs @@ -136,7 +136,7 @@ pub fn cli_run( }; let deposit_gwei = clap_utils::parse_optional(matches, DEPOSIT_GWEI_FLAG)? - .unwrap_or(spec.max_effective_balance); + .unwrap_or(spec.min_activation_balance); let count: Option = clap_utils::parse_optional(matches, COUNT_FLAG)?; let at_most: Option = clap_utils::parse_optional(matches, AT_MOST_FLAG)?; diff --git a/beacon_node/beacon_chain/src/attestation_rewards.rs b/beacon_node/beacon_chain/src/attestation_rewards.rs index abd676d7389..107f2d7d9ee 100644 --- a/beacon_node/beacon_chain/src/attestation_rewards.rs +++ b/beacon_node/beacon_chain/src/attestation_rewards.rs @@ -19,7 +19,7 @@ use store::consts::altair::{ }; use types::consts::altair::WEIGHT_DENOMINATOR; -use types::{BeaconState, Epoch, EthSpec}; +use types::{BeaconState, Epoch, EthSpec, ForkName}; use eth2::types::ValidatorId; use state_processing::common::base::get_base_reward_from_effective_balance; @@ -166,7 +166,9 @@ impl BeaconChain { let base_reward_per_increment = BaseRewardPerIncrement::new(total_active_balance, spec)?; - for effective_balance_eth in 1..=self.max_effective_balance_increment_steps()? { + for effective_balance_eth in + 1..=self.max_effective_balance_increment_steps(state.fork_name_unchecked())? + { let effective_balance = effective_balance_eth.safe_mul(spec.effective_balance_increment)?; let base_reward = @@ -293,10 +295,13 @@ impl BeaconChain { }) } - fn max_effective_balance_increment_steps(&self) -> Result { + fn max_effective_balance_increment_steps( + &self, + fork: ForkName, + ) -> Result { let spec = &self.spec; let max_steps = spec - .max_effective_balance + .max_effective_balance(fork) .safe_div(spec.effective_balance_increment)?; Ok(max_steps) } @@ -340,7 +345,9 @@ impl BeaconChain { let mut ideal_attestation_rewards_list = Vec::new(); - for effective_balance_step in 1..=self.max_effective_balance_increment_steps()? { + for effective_balance_step in + 1..=self.max_effective_balance_increment_steps(state.fork_name_unchecked())? + { let effective_balance = effective_balance_step.safe_mul(spec.effective_balance_increment)?; let base_reward = get_base_reward_from_effective_balance::( diff --git a/beacon_node/beacon_chain/src/attestation_verification.rs b/beacon_node/beacon_chain/src/attestation_verification.rs index d7a8bca4d0f..b5d187ea157 100644 --- a/beacon_node/beacon_chain/src/attestation_verification.rs +++ b/beacon_node/beacon_chain/src/attestation_verification.rs @@ -539,6 +539,19 @@ impl<'a, T: BeaconChainTypes> IndexedAggregatedAttestation<'a, T> { Err(e) => return Err(SignatureNotChecked(&signed_aggregate.message.aggregate, e)), }; + let relevant_epoch = signed_aggregate.message.epoch().saturating_sub(1u64); + let aggregator = signed_aggregate.message.aggregator_index as usize; + + chain + .effective_balances_cache + .load_epoch(chain, relevant_epoch) + .map_err(|e| { + SignatureNotChecked( + &signed_aggregate.message.aggregate, + Error::BeaconChainError(e), + ) + })?; + let indexed_attestation = match map_attestation_committee(chain, attestation, |(committee, _)| { // Note: this clones the signature which is known to be a relatively slow operation. @@ -547,8 +560,28 @@ impl<'a, T: BeaconChainTypes> IndexedAggregatedAttestation<'a, T> { let selection_proof = SelectionProof::from(signed_aggregate.message.selection_proof.clone()); + let factor = if chain + .spec + .fork_name_at_slot::(signed_aggregate.message.aggregate.data.slot) + >= ForkName::Deneb + { + let total_committee_balance = chain + .effective_balances_cache + .get_committee_balance::(&committee) + .map_err(|e| Error::BeaconChainError(e))?; + let aggregator_balance = chain + .effective_balances_cache + .get_effective_balance(relevant_epoch, aggregator) + .map_err(|e| Error::BeaconChainError(e))?; + + // TODO: is it just standard integer division? + (total_committee_balance / aggregator_balance) as usize + } else { + committee.committee.len() + }; + if !selection_proof - .is_aggregator(committee.committee.len(), &chain.spec) + .is_aggregator(factor, &chain.spec) .map_err(|e| Error::BeaconChainError(e.into()))? { return Err(Error::InvalidSelectionProof { aggregator_index }); diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 79fbe577df4..20ac7e84f2b 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -24,6 +24,7 @@ use crate::data_availability_checker::{ Availability, AvailabilityCheckError, AvailableBlock, DataAvailabilityChecker, }; use crate::early_attester_cache::EarlyAttesterCache; +use crate::effective_balances_cache::EffectiveBalancesCache; use crate::errors::{BeaconChainError as Error, BlockProductionError}; use crate::eth1_chain::{Eth1Chain, Eth1ChainBackend}; use crate::eth1_finalization_cache::{Eth1FinalizationCache, Eth1FinalizationData}; @@ -426,6 +427,8 @@ pub struct BeaconChain { pub latest_seen_optimistic_update: Mutex>>, /// Provides information from the Ethereum 1 (PoW) chain. pub eth1_chain: Option>, + /// Caches effective balances for attestation aggregator calculation + pub effective_balances_cache: EffectiveBalancesCache, /// Interfaces with the execution client. pub execution_layer: Option>, /// Stores information about the canonical head and finalized/justified checkpoints of the diff --git a/beacon_node/beacon_chain/src/builder.rs b/beacon_node/beacon_chain/src/builder.rs index 330036d43c0..8e416ccc65f 100644 --- a/beacon_node/beacon_chain/src/builder.rs +++ b/beacon_node/beacon_chain/src/builder.rs @@ -890,6 +890,7 @@ where latest_seen_finality_update: <_>::default(), latest_seen_optimistic_update: <_>::default(), eth1_chain: self.eth1_chain, + effective_balances_cache: <_>::default(), execution_layer: self.execution_layer, genesis_validators_root, genesis_time, @@ -1225,8 +1226,8 @@ mod test { for b in state.balances() { assert_eq!( - *b, spec.max_effective_balance, - "validator balances should be max effective balance" + *b, spec.min_activation_balance, + "validator balances should be min activation balance" ); } diff --git a/beacon_node/beacon_chain/src/effective_balances_cache.rs b/beacon_node/beacon_chain/src/effective_balances_cache.rs new file mode 100644 index 00000000000..6e7e02c8f33 --- /dev/null +++ b/beacon_node/beacon_chain/src/effective_balances_cache.rs @@ -0,0 +1,179 @@ +use crate::{BeaconChain, BeaconChainError, BeaconChainTypes}; +use parking_lot::{RwLock, RwLockUpgradableReadGuard}; +use std::collections::{BTreeMap, HashMap}; +use std::sync::Arc; +use types::{BeaconCommittee, BeaconState, Epoch, EthSpec, ForkName, Slot}; + +// Max number of epochs this cache will store data for +pub const EFFECTIVE_BALANCE_CACHE_SIZE: usize = 8; + +#[derive(Debug)] +pub enum Error { + StateUnavailable(Epoch), + CacheMiss(Epoch), + ValidatorIndexUnknown(usize), + EpochOutOfBounds(Epoch), +} + +#[derive(Clone)] +pub struct EffectiveBalancesCache { + pub effective_balances: Arc>>>>, + pub committee_balances: Arc>>>, +} + +impl Default for EffectiveBalancesCache { + fn default() -> Self { + Self { + effective_balances: Arc::new(RwLock::new(BTreeMap::new())), + committee_balances: Arc::new(RwLock::new(BTreeMap::new())), + } + } +} + +impl EffectiveBalancesCache { + pub fn new() -> Self { + Self::default() + } + + fn load_balances(&self, state: &BeaconState) { + let state_epoch = state.current_epoch(); + let read_lock = self.effective_balances.upgradable_read(); + if read_lock.contains_key(&state_epoch) { + return; + } + + let mut write_lock = RwLockUpgradableReadGuard::upgrade(read_lock); + + // remove the oldest epoch if the cache is full + while write_lock.len() >= EFFECTIVE_BALANCE_CACHE_SIZE { + write_lock.pop_first(); + } + + let validators = state.validators(); + let mut balances = Vec::with_capacity(validators.len()); + for validator in validators { + balances.push(validator.effective_balance); + } + + write_lock.insert(state_epoch, Arc::new(balances)); + } + + pub fn load_epoch( + &self, + chain: &BeaconChain, + epoch: Epoch, + ) -> Result<(), BeaconChainError> { + if chain.spec.fork_name_at_epoch(epoch.saturating_add(1u64)) <= ForkName::Deneb { + // the cache isn't necessary before electra + return Ok(()); + } + // ensure requested epoch is within range + let current_epoch = chain.head().snapshot.beacon_state.current_epoch(); + if epoch.saturating_add(EFFECTIVE_BALANCE_CACHE_SIZE as u64) < current_epoch + || epoch > current_epoch + { + return Err(Error::EpochOutOfBounds(epoch).into()); + } + + // quick check to see if the epoch is already loaded before loading a state from disk + let read_lock = self.effective_balances.read(); + if read_lock.contains_key(&epoch) { + return Ok(()); + } + // loading the state from disk is slow so we don't want to be holding the lock + drop(read_lock); + + if epoch == current_epoch { + self.load_balances(&chain.head().snapshot.beacon_state); + return Ok(()); + } + + // load state from disk + let state_slot = epoch.start_slot(T::EthSpec::slots_per_epoch()); + let state_root = *chain + .head() + .snapshot + .beacon_state + .get_state_root(state_slot)?; + + let state = chain + .get_state(&state_root, Some(state_slot))? + .ok_or(Error::StateUnavailable(epoch))?; + + self.load_balances(&state); + + Ok(()) + } + + // will return the committee balance if the epoch is already loaded + pub fn get_committee_balance( + &self, + beacon_committee: &BeaconCommittee, + ) -> Result { + // We need the previous epoch to calculate the committee balance + let balances_epoch = beacon_committee + .slot + .epoch(E::slots_per_epoch()) + .saturating_sub(1u64); + + // see if the value is cached in the committee_balances + let read_lock = self.committee_balances.read(); + if let Some(committee_balance) = read_lock + .get(&balances_epoch) + .and_then(|map| map.get(&(beacon_committee.slot, beacon_committee.index))) + .cloned() + { + return Ok(committee_balance); + } + drop(read_lock); + + // grab the epoch balances to calculate the committee balance + let read_lock = self.effective_balances.read(); + let effective_balances = read_lock + .get(&balances_epoch) + .ok_or(Error::CacheMiss(balances_epoch))? + // this clone is cheap because of the Arc + .clone(); + drop(read_lock); + + let mut committee_balance = 0; + for index in beacon_committee.committee { + let balance = effective_balances + .get(*index) + .ok_or(Error::ValidatorIndexUnknown(*index))?; + committee_balance += balance; + } + + // cache the committee balance + let mut write_lock = self.committee_balances.write(); + + write_lock + .entry(balances_epoch) + .or_insert_with(HashMap::new) + .insert( + (beacon_committee.slot, beacon_committee.index), + committee_balance, + ); + + // remove the oldest epoch if the cache is full + while write_lock.len() > EFFECTIVE_BALANCE_CACHE_SIZE { + write_lock.pop_first(); + } + + Ok(committee_balance) + } + + pub fn get_effective_balance( + &self, + epoch: Epoch, + validator_index: usize, + ) -> Result { + self.effective_balances + .read() + .get(&epoch) + .ok_or(Error::CacheMiss(epoch))? + .get(validator_index) + .cloned() + .ok_or(Error::ValidatorIndexUnknown(validator_index).into()) + } +} diff --git a/beacon_node/beacon_chain/src/errors.rs b/beacon_node/beacon_chain/src/errors.rs index 9c1ba06f853..14a861a47e1 100644 --- a/beacon_node/beacon_chain/src/errors.rs +++ b/beacon_node/beacon_chain/src/errors.rs @@ -3,6 +3,7 @@ use crate::beacon_block_streamer::Error as BlockStreamerError; use crate::beacon_chain::ForkChoiceError; use crate::beacon_fork_choice_store::Error as ForkChoiceStoreError; use crate::data_availability_checker::AvailabilityCheckError; +use crate::effective_balances_cache::Error as EffectiveBalancesCacheError; use crate::eth1_chain::Error as Eth1ChainError; use crate::historical_blocks::HistoricalBlockError; use crate::migrate::PruningError; @@ -223,6 +224,7 @@ pub enum BeaconChainError { AvailabilityCheckError(AvailabilityCheckError), LightClientError(LightClientError), UnsupportedFork, + EffectiveBalancesCacheError(EffectiveBalancesCacheError), } easy_from_to!(SlotProcessingError, BeaconChainError); @@ -250,6 +252,7 @@ easy_from_to!(StateAdvanceError, BeaconChainError); easy_from_to!(BlockReplayError, BeaconChainError); easy_from_to!(InconsistentFork, BeaconChainError); easy_from_to!(AvailabilityCheckError, BeaconChainError); +easy_from_to!(EffectiveBalancesCacheError, BeaconChainError); #[derive(Debug)] pub enum BlockProductionError { diff --git a/beacon_node/beacon_chain/src/eth1_chain.rs b/beacon_node/beacon_chain/src/eth1_chain.rs index 8b6c6b37409..f802cd7aa60 100644 --- a/beacon_node/beacon_chain/src/eth1_chain.rs +++ b/beacon_node/beacon_chain/src/eth1_chain.rs @@ -754,7 +754,7 @@ mod test { let mut deposit = DepositData { pubkey: keypair.pk.into(), withdrawal_credentials: Hash256::zero(), - amount: spec.max_effective_balance, + amount: spec.min_activation_balance, signature: Signature::empty().into(), }; diff --git a/beacon_node/beacon_chain/src/lib.rs b/beacon_node/beacon_chain/src/lib.rs index ce841b106c9..374bf2c6f62 100644 --- a/beacon_node/beacon_chain/src/lib.rs +++ b/beacon_node/beacon_chain/src/lib.rs @@ -20,6 +20,7 @@ pub mod chain_config; pub mod data_availability_checker; pub mod deneb_readiness; mod early_attester_cache; +pub mod effective_balances_cache; mod errors; pub mod eth1_chain; mod eth1_finalization_cache; @@ -79,6 +80,7 @@ pub use block_verification::{ pub use block_verification_types::AvailabilityPendingExecutedBlock; pub use block_verification_types::ExecutedBlock; pub use canonical_head::{CachedHead, CanonicalHead, CanonicalHeadRwLock}; +pub use effective_balances_cache::EffectiveBalancesCache; pub use eth1_chain::{Eth1Chain, Eth1ChainBackend}; pub use events::ServerSentEventHandler; pub use execution_layer::EngineState; diff --git a/beacon_node/genesis/src/interop.rs b/beacon_node/genesis/src/interop.rs index d0129834300..812c29d2c30 100644 --- a/beacon_node/genesis/src/interop.rs +++ b/beacon_node/genesis/src/interop.rs @@ -96,7 +96,7 @@ pub fn interop_genesis_state_with_withdrawal_credentials( } let eth1_timestamp = 2_u64.pow(40); - let amount = spec.max_effective_balance; + let amount = spec.min_activation_balance; let datas = keypairs .into_par_iter() @@ -172,8 +172,8 @@ mod test { for b in state.balances() { assert_eq!( - *b, spec.max_effective_balance, - "validator balances should be max effective balance" + *b, spec.min_activation_balance, + "validator balances should be min activation balance" ); } @@ -234,8 +234,8 @@ mod test { for b in state.balances() { assert_eq!( - *b, spec.max_effective_balance, - "validator balances should be max effective balance" + *b, spec.min_activation_balance, + "validator balances should be min activation balance" ); } diff --git a/beacon_node/http_api/src/attester_duties.rs b/beacon_node/http_api/src/attester_duties.rs index f3242a2b374..b5cff3122a8 100644 --- a/beacon_node/http_api/src/attester_duties.rs +++ b/beacon_node/http_api/src/attester_duties.rs @@ -6,7 +6,8 @@ use eth2::types::{self as api_types}; use slot_clock::SlotClock; use state_processing::state_advance::partial_state_advance; use types::{ - AttestationDuty, BeaconState, ChainSpec, CloneConfig, Epoch, EthSpec, Hash256, RelativeEpoch, + AttestationDuty, BeaconState, ChainSpec, CloneConfig, Epoch, EthSpec, ForkName, Hash256, + RelativeEpoch, }; /// The struct that is returned to the requesting HTTP client. @@ -62,6 +63,8 @@ fn cached_attestation_duties( .map_err(warp_utils::reject::beacon_chain_error)?; convert_to_api_response( + &chain.head().snapshot.beacon_state, + request_epoch, duties, request_indices, dependent_root, @@ -153,6 +156,8 @@ fn compute_historic_attester_duties( .map_err(warp_utils::reject::beacon_chain_error)?; convert_to_api_response( + &state, + request_epoch, duties, request_indices, dependent_root, @@ -193,6 +198,8 @@ fn ensure_state_knows_attester_duties_for_epoch( /// Convert the internal representation of attester duties into the format returned to the HTTP /// client. fn convert_to_api_response( + state: &BeaconState, + request_epoch: Epoch, duties: Vec>, indices: &[u64], dependent_root: Hash256, @@ -208,27 +215,58 @@ fn convert_to_api_response( ))); } + // need effective balances for epoch before the requested epoch + let relevant_balances_epoch = request_epoch.saturating_sub(1u64); + chain + .effective_balances_cache + // have to load the effective balances cache for the previous epoch + .load_epoch(chain, relevant_balances_epoch) + .map_err(warp_utils::reject::beacon_chain_error)?; + let usize_indices = indices.iter().map(|i| *i as usize).collect::>(); let index_to_pubkey_map = chain .validator_pubkey_bytes_many(&usize_indices) .map_err(warp_utils::reject::beacon_chain_error)?; - let data = duties - .into_iter() - .zip(indices) - .filter_map(|(duty_opt, &validator_index)| { - let duty = duty_opt?; - Some(api_types::AttesterData { - pubkey: *index_to_pubkey_map.get(&(validator_index as usize))?, - validator_index, - committees_at_slot: duty.committees_at_slot, - committee_index: duty.index, - committee_length: duty.committee_len as u64, - validator_committee_index: duty.committee_position as u64, - slot: duty.slot, - }) - }) - .collect::>(); + let mut data = Vec::new(); + for (duty_opt, &validator_index) in duties.into_iter().zip(indices.iter()) { + let (duty, pubkey) = match ( + duty_opt, + index_to_pubkey_map.get(&(validator_index as usize)), + ) { + (Some(duty), Some(pubkey)) => (duty, pubkey), + _ => continue, + }; + + let committee_length = + if chain.spec.fork_name_at_slot::(duty.slot) >= ForkName::Deneb { + let beacon_committee = state + .get_beacon_committee(duty.slot, duty.index) + .map_err(warp_utils::reject::beacon_state_error)?; + let total_committee_balance = chain + .effective_balances_cache + .get_committee_balance::(&beacon_committee) + .map_err(warp_utils::reject::beacon_chain_error)?; + let aggregator_balance = chain + .effective_balances_cache + .get_effective_balance(relevant_balances_epoch, validator_index as usize) + .map_err(warp_utils::reject::beacon_chain_error)?; + + total_committee_balance / aggregator_balance + } else { + duty.committee_len as u64 + }; + + data.push(api_types::AttesterData { + pubkey: *pubkey, + validator_index, + committees_at_slot: duty.committees_at_slot, + committee_index: duty.index, + committee_length, + validator_committee_index: duty.committee_position as u64, + slot: duty.slot, + }); + } Ok(api_types::DutiesResponse { dependent_root, diff --git a/beacon_node/store/src/partial_beacon_state.rs b/beacon_node/store/src/partial_beacon_state.rs index 1fb5751a0a9..accc107045e 100644 --- a/beacon_node/store/src/partial_beacon_state.rs +++ b/beacon_node/store/src/partial_beacon_state.rs @@ -114,6 +114,16 @@ where #[ssz(skip_serializing, skip_deserializing)] #[superstruct(only(Capella, Deneb))] pub historical_summaries: Option>, + + // MaxEB + #[superstruct(only(Deneb))] + pub deposit_balance_to_consume: Gwei, + #[superstruct(only(Deneb))] + pub pending_balance_deposits: VariableList, + #[superstruct(only(Deneb))] + pub exit_balance_to_consume: Gwei, + #[superstruct(only(Deneb))] + pub earliest_exit_epoch: Epoch, } /// Implement the conversion function from BeaconState -> PartialBeaconState. @@ -240,7 +250,11 @@ impl PartialBeaconState { 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] ), @@ -485,7 +499,11 @@ impl TryInto> for PartialBeaconState { 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] ), diff --git a/common/eth2_network_config/built_in_network_configs/chiado/config.yaml b/common/eth2_network_config/built_in_network_configs/chiado/config.yaml index 8064ea55563..d2ec4e4f710 100644 --- a/common/eth2_network_config/built_in_network_configs/chiado/config.yaml +++ b/common/eth2_network_config/built_in_network_configs/chiado/config.yaml @@ -72,10 +72,12 @@ INACTIVITY_SCORE_RECOVERY_RATE: 16 EJECTION_BALANCE: 16000000000 # 2**2 (= 4) MIN_PER_EPOCH_CHURN_LIMIT: 4 +MIN_PER_EPOCH_CHURN_LIMIT_GWEI: 128 # 2**12 (= 4096) CHURN_LIMIT_QUOTIENT: 4096 # [New in Deneb:EIP7514] 2* MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT: 2 +MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT_GWEI: 256 # Fork choice # --------------------------------------------------------------- diff --git a/common/eth2_network_config/built_in_network_configs/gnosis/config.yaml b/common/eth2_network_config/built_in_network_configs/gnosis/config.yaml index 940fad3615b..af54b31eda8 100644 --- a/common/eth2_network_config/built_in_network_configs/gnosis/config.yaml +++ b/common/eth2_network_config/built_in_network_configs/gnosis/config.yaml @@ -74,8 +74,10 @@ INACTIVITY_SCORE_RECOVERY_RATE: 16 EJECTION_BALANCE: 16000000000 # 2**2 (= 4) MIN_PER_EPOCH_CHURN_LIMIT: 4 +MIN_PER_EPOCH_CHURN_LIMIT_GWEI: 128 # 2**3 (= 8) MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT: 8 +MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT_GWEI: 256 # 2**12 (= 4096) CHURN_LIMIT_QUOTIENT: 4096 diff --git a/common/eth2_network_config/built_in_network_configs/holesky/config.yaml b/common/eth2_network_config/built_in_network_configs/holesky/config.yaml index 0bb72ebd880..b9fcea5ebc2 100644 --- a/common/eth2_network_config/built_in_network_configs/holesky/config.yaml +++ b/common/eth2_network_config/built_in_network_configs/holesky/config.yaml @@ -61,10 +61,12 @@ INACTIVITY_SCORE_RECOVERY_RATE: 16 EJECTION_BALANCE: 28000000000 # 2**2 (= 4) MIN_PER_EPOCH_CHURN_LIMIT: 4 +MIN_PER_EPOCH_CHURN_LIMIT_GWEI: 128 # 2**16 (= 65,536) CHURN_LIMIT_QUOTIENT: 65536 # [New in Deneb:EIP7514] 2**3 (= 8) MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT: 8 +MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT_GWEI: 256 # Fork choice # --------------------------------------------------------------- diff --git a/common/eth2_network_config/built_in_network_configs/mainnet/config.yaml b/common/eth2_network_config/built_in_network_configs/mainnet/config.yaml index ed96df2913c..23378393e45 100644 --- a/common/eth2_network_config/built_in_network_configs/mainnet/config.yaml +++ b/common/eth2_network_config/built_in_network_configs/mainnet/config.yaml @@ -74,8 +74,10 @@ INACTIVITY_SCORE_RECOVERY_RATE: 16 EJECTION_BALANCE: 16000000000 # 2**2 (= 4) MIN_PER_EPOCH_CHURN_LIMIT: 4 +MIN_PER_EPOCH_CHURN_LIMIT_GWEI: 128 # 2**3 (= 8) MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT: 8 +MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT_GWEI: 256 # 2**16 (= 65,536) CHURN_LIMIT_QUOTIENT: 65536 diff --git a/common/eth2_network_config/built_in_network_configs/prater/config.yaml b/common/eth2_network_config/built_in_network_configs/prater/config.yaml index 1928aeb3093..08e3943c7f0 100644 --- a/common/eth2_network_config/built_in_network_configs/prater/config.yaml +++ b/common/eth2_network_config/built_in_network_configs/prater/config.yaml @@ -69,10 +69,12 @@ INACTIVITY_SCORE_RECOVERY_RATE: 16 EJECTION_BALANCE: 16000000000 # 2**2 (= 4) MIN_PER_EPOCH_CHURN_LIMIT: 4 +MIN_PER_EPOCH_CHURN_LIMIT_GWEI: 128 # 2**16 (= 65,536) CHURN_LIMIT_QUOTIENT: 65536 # [New in Deneb:EIP7514] 2**3 (= 8) MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT: 8 +MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT_GWEI: 256 # Fork choice # --------------------------------------------------------------- diff --git a/common/eth2_network_config/built_in_network_configs/sepolia/config.yaml b/common/eth2_network_config/built_in_network_configs/sepolia/config.yaml index 33a5ccb3fe8..4debcb3c564 100644 --- a/common/eth2_network_config/built_in_network_configs/sepolia/config.yaml +++ b/common/eth2_network_config/built_in_network_configs/sepolia/config.yaml @@ -60,10 +60,12 @@ INACTIVITY_SCORE_RECOVERY_RATE: 16 EJECTION_BALANCE: 16000000000 # 2**2 (= 4) MIN_PER_EPOCH_CHURN_LIMIT: 4 +MIN_PER_EPOCH_CHURN_LIMIT_GWEI: 128 # 2**16 (= 65,536) CHURN_LIMIT_QUOTIENT: 65536 # [New in Deneb:EIP7514] 2**3 (= 8) MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT: 8 +MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT_GWEI: 256 # Fork choice # --------------------------------------------------------------- diff --git a/consensus/state_processing/src/common/initiate_validator_exit.rs b/consensus/state_processing/src/common/initiate_validator_exit.rs index 85e5e1df1db..ff77129897d 100644 --- a/consensus/state_processing/src/common/initiate_validator_exit.rs +++ b/consensus/state_processing/src/common/initiate_validator_exit.rs @@ -13,24 +13,39 @@ pub fn initiate_validator_exit( 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)?; diff --git a/consensus/state_processing/src/genesis.rs b/consensus/state_processing/src/genesis.rs index 284a7019f34..c8a9e656d8b 100644 --- a/consensus/state_processing/src/genesis.rs +++ b/consensus/state_processing/src/genesis.rs @@ -134,6 +134,7 @@ pub fn process_activations( state: &mut BeaconState, spec: &ChainSpec, ) -> Result<(), Error> { + let fork = state.fork_name_unchecked(); let (validators, balances, _) = state.validators_and_balances_and_progressive_balances_mut(); for (index, validator) in validators.iter_mut().enumerate() { let balance = balances @@ -142,9 +143,9 @@ pub fn process_activations( .ok_or(Error::BalancesOutOfBounds(index))?; validator.effective_balance = std::cmp::min( balance.safe_sub(balance.safe_rem(spec.effective_balance_increment)?)?, - spec.max_effective_balance, + spec.max_effective_balance(fork), ); - if validator.effective_balance == spec.max_effective_balance { + if validator.effective_balance >= spec.min_activation_balance { validator.activation_eligibility_epoch = T::genesis_epoch(); validator.activation_epoch = T::genesis_epoch(); } diff --git a/consensus/state_processing/src/per_block_processing.rs b/consensus/state_processing/src/per_block_processing.rs index b9a147a5ad5..41ea3e3f792 100644 --- a/consensus/state_processing/src/per_block_processing.rs +++ b/consensus/state_processing/src/per_block_processing.rs @@ -496,6 +496,7 @@ pub fn get_expected_withdrawals( spec: &ChainSpec, ) -> Result, BlockProcessingError> { let epoch = state.current_epoch(); + let fork = state.fork_name_unchecked(); let mut withdrawal_index = state.next_withdrawal_index()?; let mut validator_index = state.next_withdrawal_validator_index()?; let mut withdrawals = vec![]; @@ -519,14 +520,14 @@ pub fn get_expected_withdrawals( amount: balance, }); withdrawal_index.safe_add_assign(1)?; - } else if validator.is_partially_withdrawable_validator(balance, spec) { + } else if validator.is_partially_withdrawable_validator(balance, spec, fork) { withdrawals.push(Withdrawal { index: withdrawal_index, validator_index, address: validator .get_eth1_withdrawal_address(spec) .ok_or(BlockProcessingError::WithdrawalCredentialsInvalid)?, - amount: balance.safe_sub(spec.max_effective_balance)?, + amount: balance.safe_sub(validator.compute_effective_balance_limit(spec, fork))?, }); withdrawal_index.safe_add_assign(1)?; } diff --git a/consensus/state_processing/src/per_block_processing/process_operations.rs b/consensus/state_processing/src/per_block_processing/process_operations.rs index cb24a7ba7ec..12779f32627 100644 --- a/consensus/state_processing/src/per_block_processing/process_operations.rs +++ b/consensus/state_processing/src/per_block_processing/process_operations.rs @@ -402,6 +402,15 @@ pub fn process_deposit( return Ok(()); } + let effective_balance = if state >= ForkName::Deneb { + 0 + } else { + std::cmp::min( + amount.safe_sub(amount.safe_rem(spec.effective_balance_increment)?)?, + spec.max_effective_balance(state.fork_name_unchecked()), + ) + }; + // Create a new validator. let validator = Validator { pubkey: deposit.data.pubkey, @@ -410,10 +419,7 @@ pub fn process_deposit( activation_epoch: spec.far_future_epoch, exit_epoch: spec.far_future_epoch, withdrawable_epoch: spec.far_future_epoch, - effective_balance: std::cmp::min( - amount.safe_sub(amount.safe_rem(spec.effective_balance_increment)?)?, - spec.max_effective_balance, - ), + effective_balance, slashed: false, }; state.validators_mut().push(validator)?; diff --git a/consensus/state_processing/src/per_epoch_processing/capella.rs b/consensus/state_processing/src/per_epoch_processing/capella.rs index 911510ed0ce..f8da3d9c71b 100644 --- a/consensus/state_processing/src/per_epoch_processing/capella.rs +++ b/consensus/state_processing/src/per_epoch_processing/capella.rs @@ -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, @@ -55,6 +56,8 @@ pub fn process_epoch( // 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)?; @@ -82,3 +85,43 @@ pub fn process_epoch( sync_committee, }) } + +pub fn process_pending_balance_deposits( + state: &mut BeaconState, + 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(()) +} diff --git a/consensus/state_processing/src/per_epoch_processing/effective_balance_updates.rs b/consensus/state_processing/src/per_epoch_processing/effective_balance_updates.rs index 1759f7e1402..1ad1fb8eb79 100644 --- a/consensus/state_processing/src/per_epoch_processing/effective_balance_updates.rs +++ b/consensus/state_processing/src/per_epoch_processing/effective_balance_updates.rs @@ -10,6 +10,7 @@ pub fn process_effective_balance_updates( maybe_participation_cache: Option<&ParticipationCache>, spec: &ChainSpec, ) -> Result<(), EpochProcessingError> { + let fork = state.fork_name_unchecked(); let hysteresis_increment = spec .effective_balance_increment .safe_div(spec.hysteresis_quotient)?; @@ -29,7 +30,7 @@ pub fn process_effective_balance_updates( let old_effective_balance = validator.effective_balance; let new_effective_balance = std::cmp::min( balance.safe_sub(balance.safe_rem(spec.effective_balance_increment)?)?, - spec.max_effective_balance, + validator.compute_effective_balance_limit(spec, fork), ); if let Some(participation_cache) = maybe_participation_cache { diff --git a/consensus/state_processing/src/per_epoch_processing/registry_updates.rs b/consensus/state_processing/src/per_epoch_processing/registry_updates.rs index 833be413879..09883c9ac06 100644 --- a/consensus/state_processing/src/per_epoch_processing/registry_updates.rs +++ b/consensus/state_processing/src/per_epoch_processing/registry_updates.rs @@ -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. /// @@ -49,8 +49,14 @@ pub fn process_registry_updates( .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; diff --git a/consensus/state_processing/src/upgrade/deneb.rs b/consensus/state_processing/src/upgrade/deneb.rs index c253a8c1627..5998072186f 100644 --- a/consensus/state_processing/src/upgrade/deneb.rs +++ b/consensus/state_processing/src/upgrade/deneb.rs @@ -61,6 +61,11 @@ pub fn upgrade_to_deneb( 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), diff --git a/consensus/types/benches/benches.rs b/consensus/types/benches/benches.rs index bb2b527109f..e56c3726df5 100644 --- a/consensus/types/benches/benches.rs +++ b/consensus/types/benches/benches.rs @@ -33,7 +33,7 @@ fn get_state(validator_count: usize) -> BeaconState { .map(|&i| Validator { pubkey: generate_deterministic_keypair(i).pk.into(), withdrawal_credentials: Hash256::from_low_u64_le(i as u64), - effective_balance: spec.max_effective_balance, + effective_balance: spec.min_activation_balance, slashed: false, activation_eligibility_epoch: Epoch::new(0), activation_epoch: Epoch::new(0), diff --git a/consensus/types/src/aggregate_and_proof.rs b/consensus/types/src/aggregate_and_proof.rs index ac31e78cb73..672f444a521 100644 --- a/consensus/types/src/aggregate_and_proof.rs +++ b/consensus/types/src/aggregate_and_proof.rs @@ -1,6 +1,6 @@ use super::{ - Attestation, ChainSpec, Domain, EthSpec, Fork, Hash256, PublicKey, SecretKey, SelectionProof, - Signature, SignedRoot, + Attestation, ChainSpec, Domain, Epoch, EthSpec, Fork, Hash256, PublicKey, SecretKey, + SelectionProof, Signature, SignedRoot, }; use crate::test_utils::TestRandom; use serde::{Deserialize, Serialize}; @@ -87,6 +87,10 @@ impl AggregateAndProof { let message = self.aggregate.data.slot.signing_root(domain); self.selection_proof.verify(validator_pubkey, message) } + + pub fn epoch(&self) -> Epoch { + self.aggregate.data.slot.epoch(T::slots_per_epoch()) + } } impl SignedRoot for AggregateAndProof {} diff --git a/consensus/types/src/beacon_state.rs b/consensus/types/src/beacon_state.rs index e2e25f24b82..0eee78d6f31 100644 --- a/consensus/types/src/beacon_state.rs +++ b/consensus/types/src/beacon_state.rs @@ -10,10 +10,9 @@ use int_to_bytes::{int_to_bytes4, int_to_bytes8}; use pubkey_cache::PubkeyCache; use safe_arith::{ArithError, SafeArith}; use serde::{Deserialize, Serialize}; -use ssz::{ssz_encode, Decode, DecodeError, Encode}; +use ssz::{ssz_encode, Decode, DecodeError}; use ssz_derive::{Decode, Encode}; use ssz_types::{typenum::Unsigned, BitVector, FixedVector}; -use std::convert::TryInto; use std::hash::Hash; use std::{fmt, mem, sync::Arc}; use superstruct::superstruct; @@ -317,6 +316,16 @@ where #[superstruct(only(Capella, Deneb))] pub historical_summaries: VariableList, + // MaxEB + #[superstruct(only(Deneb))] + pub deposit_balance_to_consume: Gwei, + #[superstruct(only(Deneb))] + pub pending_balance_deposits: VariableList, + #[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)] @@ -726,7 +735,7 @@ impl BeaconState { let effective_balance = self.get_effective_balance(candidate_index)?; if effective_balance.safe_mul(MAX_RANDOM_BYTE)? >= spec - .max_effective_balance + .max_effective_balance(self.fork_name_unchecked()) .safe_mul(u64::from(random_byte))? { return Ok(candidate_index); @@ -781,32 +790,6 @@ impl BeaconState { } } - /// Return `true` if the validator who produced `slot_signature` is eligible to aggregate. - /// - /// Spec v0.12.1 - pub fn is_aggregator( - &self, - slot: Slot, - index: CommitteeIndex, - slot_signature: &Signature, - spec: &ChainSpec, - ) -> Result { - let committee = self.get_beacon_committee(slot, index)?; - let modulo = std::cmp::max( - 1, - (committee.committee.len() as u64).safe_div(spec.target_aggregators_per_committee)?, - ); - let signature_hash = hash(&slot_signature.as_ssz_bytes()); - let signature_hash_int = u64::from_le_bytes( - signature_hash - .get(0..8) - .and_then(|bytes| bytes.try_into().ok()) - .ok_or(Error::IsAggregatorOutOfBounds)?, - ); - - Ok(signature_hash_int.safe_rem(modulo)? == 0) - } - /// Returns the beacon proposer index for the `slot` in `self.current_epoch()`. /// /// Spec v0.12.1 @@ -918,7 +901,7 @@ impl BeaconState { let effective_balance = self.get_validator(candidate_index)?.effective_balance; if effective_balance.safe_mul(MAX_RANDOM_BYTE)? >= spec - .max_effective_balance + .max_effective_balance(self.fork_name_unchecked()) .safe_mul(u64::from(random_byte))? { sync_committee_indices.push(candidate_index); @@ -1357,6 +1340,58 @@ impl BeaconState { }) } + // Return the churn limit for the current epoch. + pub fn get_churn_limit_gwei(&self, spec: &ChainSpec) -> Result { + 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 { + 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 { + 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. /// @@ -1963,3 +1998,27 @@ impl ForkVersionDeserialize for BeaconState { )) } } + +impl std::cmp::PartialEq for BeaconState { + fn eq(&self, other: &ForkName) -> bool { + self.fork_name_unchecked() == *other + } +} + +impl std::cmp::PartialOrd for BeaconState { + fn partial_cmp(&self, other: &ForkName) -> Option { + other.partial_cmp(&self.fork_name_unchecked()) + } +} + +impl std::cmp::PartialEq for &mut BeaconState { + fn eq(&self, other: &ForkName) -> bool { + self.fork_name_unchecked() == *other + } +} + +impl std::cmp::PartialOrd for &mut BeaconState { + fn partial_cmp(&self, other: &ForkName) -> Option { + other.partial_cmp(&self.fork_name_unchecked()) + } +} diff --git a/consensus/types/src/chain_spec.rs b/consensus/types/src/chain_spec.rs index b2120fb0406..80f2d5de475 100644 --- a/consensus/types/src/chain_spec.rs +++ b/consensus/types/src/chain_spec.rs @@ -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, @@ -65,7 +67,9 @@ pub struct ChainSpec { * Gwei values */ pub min_deposit_amount: u64, - pub max_effective_balance: u64, + pub(crate) max_effective_balance_base: u64, + pub(crate) max_effective_balance_maxeb: u64, + pub min_activation_balance: u64, pub ejection_balance: u64, pub effective_balance_increment: u64, @@ -75,6 +79,7 @@ pub struct ChainSpec { pub genesis_fork_version: [u8; 4], pub bls_withdrawal_prefix_byte: u8, pub eth1_address_withdrawal_prefix_byte: u8, + pub compounding_withdrawal_prefix_byte: u8, /* * Time parameters @@ -356,6 +361,15 @@ impl ChainSpec { } } + pub fn max_effective_balance(&self, fork: ForkName) -> u64 { + match fork { + ForkName::Base | ForkName::Altair | ForkName::Merge | ForkName::Capella => { + self.max_effective_balance_base + } + ForkName::Deneb => self.max_effective_balance_maxeb, + } + } + /// Returns a full `Fork` struct for a given epoch. pub fn fork_at_epoch(&self, epoch: Epoch) -> Fork { let current_fork_name = self.fork_name_at_epoch(epoch); @@ -546,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, @@ -562,7 +578,15 @@ impl ChainSpec { u64::checked_pow(2, 0)?.checked_mul(u64::checked_pow(10, 9)?) }) .expect("calculation does not overflow"), - max_effective_balance: option_wrapper(|| { + max_effective_balance_base: option_wrapper(|| { + u64::checked_pow(2, 5)?.checked_mul(u64::checked_pow(10, 9)?) + }) + .expect("calculation does not overflow"), + max_effective_balance_maxeb: option_wrapper(|| { + u64::checked_pow(2, 5)?.checked_mul(u64::checked_pow(10, 9)?) + }) + .expect("calculation does not overflow"), + min_activation_balance: option_wrapper(|| { u64::checked_pow(2, 5)?.checked_mul(u64::checked_pow(10, 9)?) }) .expect("calculation does not overflow"), @@ -581,6 +605,7 @@ impl ChainSpec { genesis_fork_version: [0; 4], bls_withdrawal_prefix_byte: 0x00, eth1_address_withdrawal_prefix_byte: 0x01, + compounding_withdrawal_prefix_byte: 0x02, /* * Time parameters @@ -805,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, @@ -821,7 +848,15 @@ impl ChainSpec { u64::checked_pow(2, 0)?.checked_mul(u64::checked_pow(10, 9)?) }) .expect("calculation does not overflow"), - max_effective_balance: option_wrapper(|| { + max_effective_balance_base: option_wrapper(|| { + u64::checked_pow(2, 5)?.checked_mul(u64::checked_pow(10, 9)?) + }) + .expect("calculation does not overflow"), + max_effective_balance_maxeb: option_wrapper(|| { + u64::checked_pow(2, 5)?.checked_mul(u64::checked_pow(10, 9)?) + }) + .expect("calculation does not overflow"), + min_activation_balance: option_wrapper(|| { u64::checked_pow(2, 5)?.checked_mul(u64::checked_pow(10, 9)?) }) .expect("calculation does not overflow"), @@ -840,6 +875,7 @@ impl ChainSpec { genesis_fork_version: [0x00, 0x00, 0x00, 0x64], bls_withdrawal_prefix_byte: 0x00, eth1_address_withdrawal_prefix_byte: 0x01, + compounding_withdrawal_prefix_byte: 0x02, /* * Time parameters @@ -1085,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>, @@ -1393,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 }), @@ -1459,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, @@ -1512,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, @@ -1781,7 +1828,9 @@ mod yaml_tests { INACTIVITY_SCORE_RECOVERY_RATE: 16 EJECTION_BALANCE: 16000000000 MIN_PER_EPOCH_CHURN_LIMIT: 4 + MIN_PER_EPOCH_CHURN_LIMIT_GWEI: 128 MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT: 8 + MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT_GWEI: 256 CHURN_LIMIT_QUOTIENT: 65536 PROPOSER_SCORE_BOOST: 40 DEPOSIT_CHAIN_ID: 1 diff --git a/consensus/types/src/deposit.rs b/consensus/types/src/deposit.rs index c818c7d8081..db061f2984d 100644 --- a/consensus/types/src/deposit.rs +++ b/consensus/types/src/deposit.rs @@ -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::*; diff --git a/consensus/types/src/eth_spec.rs b/consensus/types/src/eth_spec.rs index 17baad9c4c7..85fa789c8c5 100644 --- a/consensus/types/src/eth_spec.rs +++ b/consensus/types/src/eth_spec.rs @@ -75,6 +75,7 @@ pub trait EthSpec: type EpochsPerSlashingsVector: Unsigned + Clone + Sync + Send + Debug + PartialEq; type HistoricalRootsLimit: Unsigned + Clone + Sync + Send + Debug + PartialEq; type ValidatorRegistryLimit: Unsigned + Clone + Sync + Send + Debug + PartialEq; + type MaxPendingBalanceDeposits: Unsigned + Clone + Sync + Send + Debug + PartialEq; /* * Max operations per block */ @@ -303,6 +304,7 @@ impl EthSpec for MainnetEthSpec { type EpochsPerSlashingsVector = U8192; type HistoricalRootsLimit = U16777216; type ValidatorRegistryLimit = U1099511627776; + type MaxPendingBalanceDeposits = U1099511627776; type MaxProposerSlashings = U16; type MaxAttesterSlashings = U2; type MaxAttestations = U128; @@ -365,6 +367,7 @@ impl EthSpec for MinimalEthSpec { GenesisEpoch, HistoricalRootsLimit, ValidatorRegistryLimit, + MaxPendingBalanceDeposits, MaxProposerSlashings, MaxAttesterSlashings, MaxAttestations, @@ -406,6 +409,7 @@ impl EthSpec for GnosisEthSpec { type EpochsPerSlashingsVector = U8192; type HistoricalRootsLimit = U16777216; type ValidatorRegistryLimit = U1099511627776; + type MaxPendingBalanceDeposits = U1099511627776; type MaxProposerSlashings = U16; type MaxAttesterSlashings = U2; type MaxAttestations = U128; diff --git a/consensus/types/src/fork_name.rs b/consensus/types/src/fork_name.rs index 6523b2a678c..96b9c24ccb4 100644 --- a/consensus/types/src/fork_name.rs +++ b/consensus/types/src/fork_name.rs @@ -5,7 +5,9 @@ use std::convert::TryFrom; use std::fmt::{self, Display, Formatter}; use std::str::FromStr; -#[derive(Debug, Clone, Copy, Decode, Encode, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive( + Debug, Clone, Copy, Decode, Encode, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, +)] #[serde(try_from = "String")] #[serde(into = "String")] #[ssz(enum_behaviour = "tag")] diff --git a/consensus/types/src/lib.rs b/consensus/types/src/lib.rs index b07b497a2ae..0326b28297c 100644 --- a/consensus/types/src/lib.rs +++ b/consensus/types/src/lib.rs @@ -127,7 +127,7 @@ pub use crate::chain_spec::{ChainSpec, Config, Domain}; pub use crate::checkpoint::Checkpoint; pub use crate::config_and_preset::{ConfigAndPreset, ConfigAndPresetCapella, ConfigAndPresetDeneb}; pub use crate::contribution_and_proof::ContributionAndProof; -pub use crate::deposit::{Deposit, DEPOSIT_TREE_DEPTH}; +pub use crate::deposit::{Deposit, PendingBalanceDeposit, DEPOSIT_TREE_DEPTH}; pub use crate::deposit_data::DepositData; pub use crate::deposit_message::DepositMessage; pub use crate::deposit_tree_snapshot::{DepositTreeSnapshot, FinalizedExecutionBlock}; @@ -184,7 +184,7 @@ pub use crate::signed_bls_to_execution_change::SignedBlsToExecutionChange; pub use crate::signed_contribution_and_proof::SignedContributionAndProof; pub use crate::signed_voluntary_exit::SignedVoluntaryExit; pub use crate::signing_data::{SignedRoot, SigningData}; -pub use crate::slot_epoch::{Epoch, Slot}; +pub use crate::slot_epoch::{Epoch, Gwei, Slot}; pub use crate::subnet_id::SubnetId; pub use crate::sync_aggregate::SyncAggregate; pub use crate::sync_aggregator_selection_data::SyncAggregatorSelectionData; diff --git a/consensus/types/src/preset.rs b/consensus/types/src/preset.rs index 63a372ea1c9..a32053775e2 100644 --- a/consensus/types/src/preset.rs +++ b/consensus/types/src/preset.rs @@ -92,7 +92,7 @@ impl BasePreset { hysteresis_upward_multiplier: spec.hysteresis_upward_multiplier, safe_slots_to_update_justified: spec.safe_slots_to_update_justified, min_deposit_amount: spec.min_deposit_amount, - max_effective_balance: spec.max_effective_balance, + max_effective_balance: spec.max_effective_balance_base, effective_balance_increment: spec.effective_balance_increment, min_attestation_inclusion_delay: spec.min_attestation_inclusion_delay, slots_per_epoch: T::SlotsPerEpoch::to_u64(), diff --git a/consensus/types/src/slot_epoch.rs b/consensus/types/src/slot_epoch.rs index ec659d1dbbf..6098e496a3a 100644 --- a/consensus/types/src/slot_epoch.rs +++ b/consensus/types/src/slot_epoch.rs @@ -24,6 +24,22 @@ use std::iter::Iterator; #[cfg(feature = "legacy-arith")] use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Rem, Sub, SubAssign}; +#[derive( + arbitrary::Arbitrary, + Clone, + Copy, + Default, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Serialize, + Deserialize, +)] +#[serde(transparent)] +pub struct Gwei(#[serde(with = "serde_utils::quoted_u64")] u64); + #[derive( arbitrary::Arbitrary, Clone, @@ -58,6 +74,17 @@ pub struct Epoch(#[serde(with = "serde_utils::quoted_u64")] u64); impl_common!(Slot); impl_common!(Epoch); +impl_common!(Gwei); + +impl Gwei { + pub const fn new(gwei: u64) -> Self { + Self(gwei) + } + + pub fn max_value() -> Self { + Self(u64::max_value()) + } +} impl Slot { pub const fn new(slot: u64) -> Slot { diff --git a/consensus/types/src/validator.rs b/consensus/types/src/validator.rs index 8fbd9009ea5..4c5fea89b80 100644 --- a/consensus/types/src/validator.rs +++ b/consensus/types/src/validator.rs @@ -1,5 +1,5 @@ use crate::{ - test_utils::TestRandom, Address, BeaconState, ChainSpec, Epoch, EthSpec, Hash256, + test_utils::TestRandom, Address, BeaconState, ChainSpec, Epoch, EthSpec, ForkName, Hash256, PublicKeyBytes, }; use serde::{Deserialize, Serialize}; @@ -60,7 +60,7 @@ impl Validator { /// Spec v0.12.1 pub fn is_eligible_for_activation_queue(&self, spec: &ChainSpec) -> bool { self.activation_eligibility_epoch == spec.far_future_epoch - && self.effective_balance == spec.max_effective_balance + && self.effective_balance >= spec.min_activation_balance } /// Returns `true` if the validator is eligible to be activated. @@ -86,6 +86,15 @@ impl Validator { .unwrap_or(false) } + /// Returns `true` if the validator has compounding withdrawal credential. + pub fn has_compounding_withdrawal_credential(&self, spec: &ChainSpec) -> bool { + self.withdrawal_credentials + .as_bytes() + .first() + .map(|byte| *byte == spec.compounding_withdrawal_prefix_byte) + .unwrap_or(false) + } + /// Get the eth1 withdrawal address if this validator has one initialized. pub fn get_eth1_withdrawal_address(&self, spec: &ChainSpec) -> Option
{ self.has_eth1_withdrawal_credential(spec) @@ -114,10 +123,53 @@ impl Validator { } /// Returns `true` if the validator is partially withdrawable. - pub fn is_partially_withdrawable_validator(&self, balance: u64, spec: &ChainSpec) -> bool { + pub fn is_partially_withdrawable_validator( + &self, + balance: u64, + spec: &ChainSpec, + fork: ForkName, + ) -> bool { + let max_effective_balance = spec.max_effective_balance(fork); self.has_eth1_withdrawal_credential(spec) - && self.effective_balance == spec.max_effective_balance - && balance > spec.max_effective_balance + && self.effective_balance == max_effective_balance + && balance > max_effective_balance + } + + pub fn compute_effective_balance_limit(&self, spec: &ChainSpec, fork: ForkName) -> u64 { + match fork { + ForkName::Base | ForkName::Altair | ForkName::Capella | ForkName::Merge => { + spec.min_activation_balance + } + ForkName::Deneb => { + if self.has_compounding_withdrawal_credential(spec) { + spec.max_effective_balance(fork) + } else { + spec.min_activation_balance + } + } + } + } + + /// Get excess balance for partial withdrawals for ``validator``. + pub fn compute_excess_balance(&self, balance: u64, spec: &ChainSpec, fork: ForkName) -> u64 { + match fork { + ForkName::Base | ForkName::Altair | ForkName::Capella | ForkName::Merge => { + if self.has_eth1_withdrawal_credential(spec) { + balance.saturating_sub(spec.min_activation_balance) + } else if self.has_compounding_withdrawal_credential(spec) { + balance.saturating_sub(spec.max_effective_balance_maxeb) + } else { + 0 + } + } + ForkName::Deneb => { + if self.has_eth1_withdrawal_credential(spec) { + balance.saturating_sub(spec.min_activation_balance) + } else { + 0 + } + } + } } } diff --git a/lcli/src/deploy_deposit_contract.rs b/lcli/src/deploy_deposit_contract.rs index 8919ebdaf54..d0d89cfff72 100644 --- a/lcli/src/deploy_deposit_contract.rs +++ b/lcli/src/deploy_deposit_contract.rs @@ -21,7 +21,7 @@ pub fn run(env: Environment, matches: &ArgMatches<'_>) -> Result< // Deposit insecure validators to the deposit contract created if let Some(validator_count) = validator_count { - let amount = env.eth2_config.spec.max_effective_balance; + let amount = env.eth2_config.spec.min_activation_balance; for i in 0..validator_count { println!("Submitting deposit for validator {}...", i); contract.deposit_deterministic_async::(i, amount).await?; diff --git a/lcli/src/new_testnet.rs b/lcli/src/new_testnet.rs index 3a0c7a9f60b..4385960633b 100644 --- a/lcli/src/new_testnet.rs +++ b/lcli/src/new_testnet.rs @@ -55,7 +55,7 @@ pub fn run(testnet_dir_path: PathBuf, matches: &ArgMatches) -> Resul "min-genesis-active-validator-count", min_genesis_active_validator_count ); - maybe_update!("max-effective-balance", max_effective_balance); + maybe_update!("min-activation-balance", min_activation_balance); maybe_update!("effective-balance-increment", effective_balance_increment); maybe_update!("ejection-balance", ejection_balance); maybe_update!("eth1-follow-distance", eth1_follow_distance); @@ -261,7 +261,7 @@ fn initialize_state_with_validators( credentials[0] = spec.bls_withdrawal_prefix_byte; Hash256::from_slice(&credentials) }; - let amount = spec.max_effective_balance; + let amount = spec.min_activation_balance; // Create a new validator. let validator = Validator { pubkey: keypair.0.pk.clone().into(), @@ -272,7 +272,7 @@ fn initialize_state_with_validators( withdrawable_epoch: spec.far_future_epoch, effective_balance: std::cmp::min( amount - amount % (spec.effective_balance_increment), - spec.max_effective_balance, + spec.min_activation_balance, ), slashed: false, }; diff --git a/lighthouse/environment/tests/testnet_dir/config.yaml b/lighthouse/environment/tests/testnet_dir/config.yaml index dbb6819cd89..b181711973f 100644 --- a/lighthouse/environment/tests/testnet_dir/config.yaml +++ b/lighthouse/environment/tests/testnet_dir/config.yaml @@ -67,8 +67,10 @@ INACTIVITY_SCORE_RECOVERY_RATE: 16 EJECTION_BALANCE: 16000000000 # 2**2 (= 4) MIN_PER_EPOCH_CHURN_LIMIT: 4 +MIN_PER_EPOCH_CHURN_LIMIT_GWEI: 128 # 2**3 (= 8) MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT: 8 +MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT_GWEI: 256 # 2**16 (= 65,536) CHURN_LIMIT_QUOTIENT: 65536 diff --git a/lighthouse/tests/validator_manager.rs b/lighthouse/tests/validator_manager.rs index fab1cfebf4b..503689cb1aa 100644 --- a/lighthouse/tests/validator_manager.rs +++ b/lighthouse/tests/validator_manager.rs @@ -115,7 +115,7 @@ pub fn validator_create_defaults() { output_path: PathBuf::from("./meow"), first_index: 0, count: 1, - deposit_gwei: MainnetEthSpec::default_spec().max_effective_balance, + deposit_gwei: MainnetEthSpec::default_spec().min_activation_balance, mnemonic_path: None, stdin_inputs: cfg!(windows) || false, disable_deposits: false, diff --git a/testing/simulator/src/eth1_sim.rs b/testing/simulator/src/eth1_sim.rs index 953dcf5822f..4b3fabc8308 100644 --- a/testing/simulator/src/eth1_sim.rs +++ b/testing/simulator/src/eth1_sim.rs @@ -105,7 +105,7 @@ pub fn run_eth1_sim(matches: &ArgMatches) -> Result<(), String> { let seconds_per_slot = spec.seconds_per_slot; let slot_duration = Duration::from_secs(spec.seconds_per_slot); let initial_validator_count = spec.min_genesis_active_validator_count as usize; - let deposit_amount = env.eth2_config.spec.max_effective_balance; + let deposit_amount = env.eth2_config.spec.min_activation_balance; let context = env.core_context(); diff --git a/validator_client/src/http_api/test_utils.rs b/validator_client/src/http_api/test_utils.rs index 7b0cb51ec17..b2e41678485 100644 --- a/validator_client/src/http_api/test_utils.rs +++ b/validator_client/src/http_api/test_utils.rs @@ -317,7 +317,7 @@ impl ApiTester { builder_proposals: None, builder_boost_factor: None, prefer_builder_proposals: None, - deposit_gwei: E::default_spec().max_effective_balance, + deposit_gwei: E::default_spec().min_activation_balance, }) .collect::>(); @@ -399,7 +399,7 @@ impl ApiTester { let deposit_bytes = serde_utils::hex::decode(&item.eth1_deposit_tx_data).unwrap(); let (deposit_data, _) = - decode_eth1_tx_data(&deposit_bytes, E::default_spec().max_effective_balance) + decode_eth1_tx_data(&deposit_bytes, E::default_spec().min_activation_balance) .unwrap(); assert_eq!( diff --git a/validator_client/src/http_api/tests.rs b/validator_client/src/http_api/tests.rs index f7db76e4ad5..f93b86e11f3 100644 --- a/validator_client/src/http_api/tests.rs +++ b/validator_client/src/http_api/tests.rs @@ -277,7 +277,7 @@ impl ApiTester { builder_proposals: None, builder_boost_factor: None, prefer_builder_proposals: None, - deposit_gwei: E::default_spec().max_effective_balance, + deposit_gwei: E::default_spec().min_activation_balance, }) .collect::>(); @@ -360,7 +360,7 @@ impl ApiTester { serde_utils::hex::decode(&response[i].eth1_deposit_tx_data).unwrap(); let (deposit_data, _) = - decode_eth1_tx_data(&deposit_bytes, E::default_spec().max_effective_balance) + decode_eth1_tx_data(&deposit_bytes, E::default_spec().min_activation_balance) .unwrap(); assert_eq!( diff --git a/validator_manager/src/create_validators.rs b/validator_manager/src/create_validators.rs index 8ab3303d366..7508bdf8d3a 100644 --- a/validator_manager/src/create_validators.rs +++ b/validator_manager/src/create_validators.rs @@ -238,7 +238,7 @@ impl CreateConfig { Ok(Self { output_path: clap_utils::parse_required(matches, OUTPUT_PATH_FLAG)?, deposit_gwei: clap_utils::parse_optional(matches, DEPOSIT_GWEI_FLAG)? - .unwrap_or(spec.max_effective_balance), + .unwrap_or(spec.min_activation_balance), first_index: clap_utils::parse_required(matches, FIRST_INDEX_FLAG)?, count: clap_utils::parse_required(matches, COUNT_FLAG)?, mnemonic_path: clap_utils::parse_optional(matches, MNEMONIC_FLAG)?, @@ -615,7 +615,7 @@ pub mod tests { output_path: output_dir.path().into(), first_index: 0, count: 1, - deposit_gwei: spec.max_effective_balance, + deposit_gwei: spec.min_activation_balance, mnemonic_path: Some(mnemonic_path), stdin_inputs: false, disable_deposits: false,