Merge branch 'remove-into-gossip-verified-block' of https://github.com/realbigsean/lighthouse into merge-unstable-deneb-june-6th
This commit is contained in:
@@ -1,10 +1,15 @@
|
||||
use crate::{ForkChoiceStore, InvalidationOperation};
|
||||
use per_epoch_processing::altair::participation_cache::Error as ParticipationCacheError;
|
||||
use proto_array::{
|
||||
Block as ProtoBlock, DisallowedReOrgOffsets, ExecutionStatus, ProposerHeadError,
|
||||
ProposerHeadInfo, ProtoArrayForkChoice, ReOrgThreshold,
|
||||
};
|
||||
use slog::{crit, debug, warn, Logger};
|
||||
use slog::{crit, debug, error, warn, Logger};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use state_processing::per_epoch_processing::altair::ParticipationCache;
|
||||
use state_processing::per_epoch_processing::{
|
||||
weigh_justification_and_finalization, JustificationAndFinalizationState,
|
||||
};
|
||||
use state_processing::{
|
||||
per_block_processing::errors::AttesterSlashingValidationError, per_epoch_processing,
|
||||
};
|
||||
@@ -18,6 +23,7 @@ use types::{
|
||||
EthSpec, ExecPayload, ExecutionBlockHash, Hash256, IndexedAttestation, RelativeEpoch,
|
||||
SignedBeaconBlock, Slot,
|
||||
};
|
||||
use types::{ProgressiveBalancesCache, ProgressiveBalancesMode};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error<T> {
|
||||
@@ -72,7 +78,9 @@ pub enum Error<T> {
|
||||
},
|
||||
UnrealizedVoteProcessing(state_processing::EpochProcessingError),
|
||||
ParticipationCacheBuild(BeaconStateError),
|
||||
ParticipationCacheError(ParticipationCacheError),
|
||||
ValidatorStatuses(BeaconStateError),
|
||||
ProgressiveBalancesCacheCheckFailed(String),
|
||||
}
|
||||
|
||||
impl<T> From<InvalidAttestation> for Error<T> {
|
||||
@@ -93,6 +101,18 @@ impl<T> From<state_processing::EpochProcessingError> for Error<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<BeaconStateError> for Error<T> {
|
||||
fn from(e: BeaconStateError) -> Self {
|
||||
Error::BeaconStateError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<ParticipationCacheError> for Error<T> {
|
||||
fn from(e: ParticipationCacheError) -> Self {
|
||||
Error::ParticipationCacheError(e)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
/// Controls how fork choice should behave when restoring from a persisted fork choice.
|
||||
pub enum ResetPayloadStatuses {
|
||||
@@ -645,7 +665,9 @@ where
|
||||
block_delay: Duration,
|
||||
state: &BeaconState<E>,
|
||||
payload_verification_status: PayloadVerificationStatus,
|
||||
progressive_balances_mode: ProgressiveBalancesMode,
|
||||
spec: &ChainSpec,
|
||||
log: &Logger,
|
||||
) -> Result<(), Error<T::Error>> {
|
||||
// If this block has already been processed we do not need to reprocess it.
|
||||
// We check this immediately in case re-processing the block mutates some property of the
|
||||
@@ -739,46 +761,84 @@ where
|
||||
parent_justified.epoch == block_epoch && parent_finalized.epoch + 1 >= block_epoch
|
||||
});
|
||||
|
||||
let (unrealized_justified_checkpoint, unrealized_finalized_checkpoint) =
|
||||
if let Some((parent_justified, parent_finalized)) = parent_checkpoints {
|
||||
(parent_justified, parent_finalized)
|
||||
} else {
|
||||
let justification_and_finalization_state = match block {
|
||||
// TODO(deneb): Ensure that the final specification
|
||||
// does not substantially modify per epoch processing.
|
||||
BeaconBlockRef::Deneb(_)
|
||||
| BeaconBlockRef::Capella(_)
|
||||
| BeaconBlockRef::Merge(_)
|
||||
| BeaconBlockRef::Altair(_) => {
|
||||
let participation_cache =
|
||||
per_epoch_processing::altair::ParticipationCache::new(state, spec)
|
||||
.map_err(Error::ParticipationCacheBuild)?;
|
||||
let (unrealized_justified_checkpoint, unrealized_finalized_checkpoint) = if let Some((
|
||||
parent_justified,
|
||||
parent_finalized,
|
||||
)) =
|
||||
parent_checkpoints
|
||||
{
|
||||
(parent_justified, parent_finalized)
|
||||
} else {
|
||||
let justification_and_finalization_state = match block {
|
||||
BeaconBlockRef::Deneb(_)| BeaconBlockRef::Capella(_)
|
||||
| BeaconBlockRef::Merge(_)
|
||||
| BeaconBlockRef::Altair(_) => match progressive_balances_mode {
|
||||
ProgressiveBalancesMode::Disabled => {
|
||||
let participation_cache = ParticipationCache::new(state, spec)
|
||||
.map_err(Error::ParticipationCacheBuild)?;
|
||||
per_epoch_processing::altair::process_justification_and_finalization(
|
||||
state,
|
||||
&participation_cache,
|
||||
)?
|
||||
}
|
||||
BeaconBlockRef::Base(_) => {
|
||||
let mut validator_statuses =
|
||||
per_epoch_processing::base::ValidatorStatuses::new(state, spec)
|
||||
.map_err(Error::ValidatorStatuses)?;
|
||||
validator_statuses
|
||||
.process_attestations(state)
|
||||
.map_err(Error::ValidatorStatuses)?;
|
||||
per_epoch_processing::base::process_justification_and_finalization(
|
||||
state,
|
||||
&validator_statuses.total_balances,
|
||||
spec,
|
||||
)?
|
||||
}
|
||||
};
|
||||
ProgressiveBalancesMode::Fast
|
||||
| ProgressiveBalancesMode::Checked
|
||||
| ProgressiveBalancesMode::Strict => {
|
||||
let maybe_participation_cache = progressive_balances_mode
|
||||
.perform_comparative_checks()
|
||||
.then(|| {
|
||||
ParticipationCache::new(state, spec)
|
||||
.map_err(Error::ParticipationCacheBuild)
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
(
|
||||
justification_and_finalization_state.current_justified_checkpoint(),
|
||||
justification_and_finalization_state.finalized_checkpoint(),
|
||||
)
|
||||
process_justification_and_finalization_from_progressive_cache::<E, T>(
|
||||
state,
|
||||
maybe_participation_cache.as_ref(),
|
||||
)
|
||||
.or_else(|e| {
|
||||
if progressive_balances_mode != ProgressiveBalancesMode::Strict {
|
||||
error!(
|
||||
log,
|
||||
"Processing with progressive balances cache failed";
|
||||
"info" => "falling back to the non-optimized processing method",
|
||||
"error" => ?e,
|
||||
);
|
||||
let participation_cache = maybe_participation_cache
|
||||
.map(Ok)
|
||||
.unwrap_or_else(|| ParticipationCache::new(state, spec))
|
||||
.map_err(Error::ParticipationCacheBuild)?;
|
||||
per_epoch_processing::altair::process_justification_and_finalization(
|
||||
state,
|
||||
&participation_cache,
|
||||
).map_err(Error::from)
|
||||
} else {
|
||||
Err(e)
|
||||
}
|
||||
})?
|
||||
}
|
||||
},
|
||||
BeaconBlockRef::Base(_) => {
|
||||
let mut validator_statuses =
|
||||
per_epoch_processing::base::ValidatorStatuses::new(state, spec)
|
||||
.map_err(Error::ValidatorStatuses)?;
|
||||
validator_statuses
|
||||
.process_attestations(state)
|
||||
.map_err(Error::ValidatorStatuses)?;
|
||||
per_epoch_processing::base::process_justification_and_finalization(
|
||||
state,
|
||||
&validator_statuses.total_balances,
|
||||
spec,
|
||||
)?
|
||||
}
|
||||
};
|
||||
|
||||
(
|
||||
justification_and_finalization_state.current_justified_checkpoint(),
|
||||
justification_and_finalization_state.finalized_checkpoint(),
|
||||
)
|
||||
};
|
||||
|
||||
// Update best known unrealized justified & finalized checkpoints
|
||||
if unrealized_justified_checkpoint.epoch
|
||||
> self.fc_store.unrealized_justified_checkpoint().epoch
|
||||
@@ -1504,6 +1564,92 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Process justification and finalization using progressive cache. Also performs a comparative
|
||||
/// check against the `ParticipationCache` if it is supplied.
|
||||
///
|
||||
/// Returns an error if the cache is not initialized or if there is a mismatch on the comparative check.
|
||||
fn process_justification_and_finalization_from_progressive_cache<E, T>(
|
||||
state: &BeaconState<E>,
|
||||
maybe_participation_cache: Option<&ParticipationCache>,
|
||||
) -> Result<JustificationAndFinalizationState<E>, Error<T::Error>>
|
||||
where
|
||||
E: EthSpec,
|
||||
T: ForkChoiceStore<E>,
|
||||
{
|
||||
let justification_and_finalization_state = JustificationAndFinalizationState::new(state);
|
||||
if state.current_epoch() <= E::genesis_epoch() + 1 {
|
||||
return Ok(justification_and_finalization_state);
|
||||
}
|
||||
|
||||
// Load cached balances
|
||||
let progressive_balances_cache: &ProgressiveBalancesCache = state.progressive_balances_cache();
|
||||
let previous_target_balance =
|
||||
progressive_balances_cache.previous_epoch_target_attesting_balance()?;
|
||||
let current_target_balance =
|
||||
progressive_balances_cache.current_epoch_target_attesting_balance()?;
|
||||
let total_active_balance = state.get_total_active_balance()?;
|
||||
|
||||
if let Some(participation_cache) = maybe_participation_cache {
|
||||
check_progressive_balances::<E, T>(
|
||||
state,
|
||||
participation_cache,
|
||||
previous_target_balance,
|
||||
current_target_balance,
|
||||
total_active_balance,
|
||||
)?;
|
||||
}
|
||||
|
||||
weigh_justification_and_finalization(
|
||||
justification_and_finalization_state,
|
||||
total_active_balance,
|
||||
previous_target_balance,
|
||||
current_target_balance,
|
||||
)
|
||||
.map_err(Error::from)
|
||||
}
|
||||
|
||||
/// Perform comparative checks against `ParticipationCache`, will return error if there's a mismatch.
|
||||
fn check_progressive_balances<E, T>(
|
||||
state: &BeaconState<E>,
|
||||
participation_cache: &ParticipationCache,
|
||||
cached_previous_target_balance: u64,
|
||||
cached_current_target_balance: u64,
|
||||
cached_total_active_balance: u64,
|
||||
) -> Result<(), Error<T::Error>>
|
||||
where
|
||||
E: EthSpec,
|
||||
T: ForkChoiceStore<E>,
|
||||
{
|
||||
let slot = state.slot();
|
||||
let epoch = state.current_epoch();
|
||||
|
||||
// Check previous epoch target balances
|
||||
let previous_target_balance = participation_cache.previous_epoch_target_attesting_balance()?;
|
||||
if previous_target_balance != cached_previous_target_balance {
|
||||
return Err(Error::ProgressiveBalancesCacheCheckFailed(
|
||||
format!("Previous epoch target attesting balance mismatch, slot: {}, epoch: {}, actual: {}, cached: {}", slot, epoch, previous_target_balance, cached_previous_target_balance)
|
||||
));
|
||||
}
|
||||
|
||||
// Check current epoch target balances
|
||||
let current_target_balance = participation_cache.current_epoch_target_attesting_balance()?;
|
||||
if current_target_balance != cached_current_target_balance {
|
||||
return Err(Error::ProgressiveBalancesCacheCheckFailed(
|
||||
format!("Current epoch target attesting balance mismatch, slot: {}, epoch: {}, actual: {}, cached: {}", slot, epoch, current_target_balance, cached_current_target_balance)
|
||||
));
|
||||
}
|
||||
|
||||
// Check current epoch total balances
|
||||
let total_active_balance = participation_cache.current_epoch_total_active_balance();
|
||||
if total_active_balance != cached_total_active_balance {
|
||||
return Err(Error::ProgressiveBalancesCacheCheckFailed(
|
||||
format!("Current epoch total active balance mismatch, slot: {}, epoch: {}, actual: {}, cached: {}", slot, epoch, total_active_balance, cached_total_active_balance)
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Helper struct that is used to encode/decode the state of the `ForkChoice` as SSZ bytes.
|
||||
///
|
||||
/// This is used when persisting the state of the fork choice to disk.
|
||||
|
||||
@@ -17,12 +17,13 @@ use fork_choice::{
|
||||
use store::MemoryStore;
|
||||
use types::{
|
||||
test_utils::generate_deterministic_keypair, BeaconBlockRef, BeaconState, ChainSpec, Checkpoint,
|
||||
Epoch, EthSpec, Hash256, IndexedAttestation, MainnetEthSpec, SignedBeaconBlock, Slot, SubnetId,
|
||||
Epoch, EthSpec, ForkName, Hash256, IndexedAttestation, MainnetEthSpec, ProgressiveBalancesMode,
|
||||
RelativeEpoch, SignedBeaconBlock, Slot, SubnetId,
|
||||
};
|
||||
|
||||
pub type E = MainnetEthSpec;
|
||||
|
||||
pub const VALIDATOR_COUNT: usize = 32;
|
||||
pub const VALIDATOR_COUNT: usize = 64;
|
||||
|
||||
/// Defines some delay between when an attestation is created and when it is mutated.
|
||||
pub enum MutationDelay {
|
||||
@@ -68,6 +69,24 @@ impl ForkChoiceTest {
|
||||
Self { harness }
|
||||
}
|
||||
|
||||
/// Creates a new tester with the specified `ProgressiveBalancesMode` and genesis from latest fork.
|
||||
fn new_with_progressive_balances_mode(mode: ProgressiveBalancesMode) -> ForkChoiceTest {
|
||||
// genesis with latest fork (at least altair required to test the cache)
|
||||
let spec = ForkName::latest().make_genesis_spec(ChainSpec::default());
|
||||
let harness = BeaconChainHarness::builder(MainnetEthSpec)
|
||||
.spec(spec)
|
||||
.chain_config(ChainConfig {
|
||||
progressive_balances_mode: mode,
|
||||
..ChainConfig::default()
|
||||
})
|
||||
.deterministic_keypairs(VALIDATOR_COUNT)
|
||||
.fresh_ephemeral_store()
|
||||
.mock_execution_layer()
|
||||
.build();
|
||||
|
||||
Self { harness }
|
||||
}
|
||||
|
||||
/// Get a value from the `ForkChoice` instantiation.
|
||||
fn get<T, U>(&self, func: T) -> U
|
||||
where
|
||||
@@ -212,6 +231,39 @@ impl ForkChoiceTest {
|
||||
self
|
||||
}
|
||||
|
||||
/// Slash a validator from the previous epoch committee.
|
||||
pub async fn add_previous_epoch_attester_slashing(self) -> Self {
|
||||
let state = self.harness.get_current_state();
|
||||
let previous_epoch_shuffling = state.get_shuffling(RelativeEpoch::Previous).unwrap();
|
||||
let validator_indices = previous_epoch_shuffling
|
||||
.iter()
|
||||
.map(|idx| *idx as u64)
|
||||
.take(1)
|
||||
.collect();
|
||||
|
||||
self.harness
|
||||
.add_attester_slashing(validator_indices)
|
||||
.unwrap();
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Slash the proposer of a block in the previous epoch.
|
||||
pub async fn add_previous_epoch_proposer_slashing(self, slots_per_epoch: u64) -> Self {
|
||||
let previous_epoch_slot = self.harness.get_current_slot() - slots_per_epoch;
|
||||
let previous_epoch_block = self
|
||||
.harness
|
||||
.chain
|
||||
.block_at_slot(previous_epoch_slot, WhenSlotSkipped::None)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let proposer_index: u64 = previous_epoch_block.message().proposer_index();
|
||||
|
||||
self.harness.add_proposer_slashing(proposer_index).unwrap();
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Apply `count` blocks to the chain (without attestations).
|
||||
pub async fn apply_blocks_without_new_attestations(self, count: usize) -> Self {
|
||||
self.harness.advance_slot();
|
||||
@@ -286,7 +338,9 @@ impl ForkChoiceTest {
|
||||
Duration::from_secs(0),
|
||||
&state,
|
||||
PayloadVerificationStatus::Verified,
|
||||
self.harness.chain.config.progressive_balances_mode,
|
||||
&self.harness.chain.spec,
|
||||
self.harness.logger(),
|
||||
)
|
||||
.unwrap();
|
||||
self
|
||||
@@ -328,7 +382,9 @@ impl ForkChoiceTest {
|
||||
Duration::from_secs(0),
|
||||
&state,
|
||||
PayloadVerificationStatus::Verified,
|
||||
self.harness.chain.config.progressive_balances_mode,
|
||||
&self.harness.chain.spec,
|
||||
self.harness.logger(),
|
||||
)
|
||||
.err()
|
||||
.expect("on_block did not return an error");
|
||||
@@ -1287,3 +1343,49 @@ async fn weak_subjectivity_check_epoch_boundary_is_skip_slot_failure() {
|
||||
.assert_finalized_epoch_is_less_than(checkpoint.epoch)
|
||||
.assert_shutdown_signal_sent();
|
||||
}
|
||||
|
||||
/// Checks that `ProgressiveBalancesCache` is updated correctly after an attester slashing event,
|
||||
/// where the slashed validator is a target attester in previous / current epoch.
|
||||
#[tokio::test]
|
||||
async fn progressive_balances_cache_attester_slashing() {
|
||||
ForkChoiceTest::new_with_progressive_balances_mode(ProgressiveBalancesMode::Strict)
|
||||
// first two epochs
|
||||
.apply_blocks_while(|_, state| state.finalized_checkpoint().epoch == 0)
|
||||
.await
|
||||
.unwrap()
|
||||
.add_previous_epoch_attester_slashing()
|
||||
.await
|
||||
// expect fork choice to import blocks successfully after a previous epoch attester is
|
||||
// slashed, i.e. the slashed attester's balance is correctly excluded from
|
||||
// the previous epoch total balance in `ProgressiveBalancesCache`.
|
||||
.apply_blocks(1)
|
||||
.await
|
||||
// expect fork choice to import another epoch of blocks successfully - the slashed
|
||||
// attester's balance should be excluded from the current epoch total balance in
|
||||
// `ProgressiveBalancesCache` as well.
|
||||
.apply_blocks(MainnetEthSpec::slots_per_epoch() as usize)
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Checks that `ProgressiveBalancesCache` is updated correctly after a proposer slashing event,
|
||||
/// where the slashed validator is a target attester in previous / current epoch.
|
||||
#[tokio::test]
|
||||
async fn progressive_balances_cache_proposer_slashing() {
|
||||
ForkChoiceTest::new_with_progressive_balances_mode(ProgressiveBalancesMode::Strict)
|
||||
// first two epochs
|
||||
.apply_blocks_while(|_, state| state.finalized_checkpoint().epoch == 0)
|
||||
.await
|
||||
.unwrap()
|
||||
.add_previous_epoch_proposer_slashing(MainnetEthSpec::slots_per_epoch())
|
||||
.await
|
||||
// expect fork choice to import blocks successfully after a previous epoch proposer is
|
||||
// slashed, i.e. the slashed proposer's balance is correctly excluded from
|
||||
// the previous epoch total balance in `ProgressiveBalancesCache`.
|
||||
.apply_blocks(1)
|
||||
.await
|
||||
// expect fork choice to import another epoch of blocks successfully - the slashed
|
||||
// proposer's balance should be excluded from the current epoch total balance in
|
||||
// `ProgressiveBalancesCache` as well.
|
||||
.apply_blocks(MainnetEthSpec::slots_per_epoch() as usize)
|
||||
.await;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ mod slash_validator;
|
||||
|
||||
pub mod altair;
|
||||
pub mod base;
|
||||
pub mod update_progressive_balances_cache;
|
||||
|
||||
pub use deposit_data_tree::DepositDataTree;
|
||||
pub use get_attestation_participation::get_attestation_participation_flag_indices;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::common::update_progressive_balances_cache::update_progressive_balances_on_slashing;
|
||||
use crate::{
|
||||
common::{decrease_balance, increase_balance, initiate_validator_exit},
|
||||
per_block_processing::errors::BlockProcessingError,
|
||||
@@ -43,6 +44,8 @@ pub fn slash_validator<T: EthSpec>(
|
||||
.safe_div(spec.min_slashing_penalty_quotient_for_state(state))?,
|
||||
)?;
|
||||
|
||||
update_progressive_balances_on_slashing(state, slashed_index)?;
|
||||
|
||||
// Apply proposer and whistleblower rewards
|
||||
let proposer_index = ctxt.get_proposer_index(state, spec)? as usize;
|
||||
let whistleblower_index = opt_whistleblower_index.unwrap_or(proposer_index);
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
/// A collection of all functions that mutates the `ProgressiveBalancesCache`.
|
||||
use crate::metrics::{
|
||||
PARTICIPATION_CURR_EPOCH_TARGET_ATTESTING_GWEI_PROGRESSIVE_TOTAL,
|
||||
PARTICIPATION_PREV_EPOCH_TARGET_ATTESTING_GWEI_PROGRESSIVE_TOTAL,
|
||||
};
|
||||
use crate::per_epoch_processing::altair::ParticipationCache;
|
||||
use crate::{BlockProcessingError, EpochProcessingError};
|
||||
use lighthouse_metrics::set_gauge;
|
||||
use ssz_types::VariableList;
|
||||
use std::borrow::Cow;
|
||||
use types::consts::altair::TIMELY_TARGET_FLAG_INDEX;
|
||||
use types::{
|
||||
is_progressive_balances_enabled, BeaconState, BeaconStateError, ChainSpec, Epoch, EthSpec,
|
||||
ParticipationFlags, ProgressiveBalancesCache,
|
||||
};
|
||||
|
||||
/// Initializes the `ProgressiveBalancesCache` cache using balance values from the
|
||||
/// `ParticipationCache`. If the optional `&ParticipationCache` is not supplied, it will be computed
|
||||
/// from the `BeaconState`.
|
||||
pub fn initialize_progressive_balances_cache<E: EthSpec>(
|
||||
state: &mut BeaconState<E>,
|
||||
maybe_participation_cache: Option<&ParticipationCache>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), BeaconStateError> {
|
||||
if !is_progressive_balances_enabled(state)
|
||||
|| state.progressive_balances_cache().is_initialized()
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let participation_cache = match maybe_participation_cache {
|
||||
Some(cache) => Cow::Borrowed(cache),
|
||||
None => Cow::Owned(ParticipationCache::new(state, spec)?),
|
||||
};
|
||||
|
||||
let previous_epoch_target_attesting_balance = participation_cache
|
||||
.previous_epoch_target_attesting_balance_raw()
|
||||
.map_err(|e| BeaconStateError::ParticipationCacheError(format!("{e:?}")))?;
|
||||
|
||||
let current_epoch_target_attesting_balance = participation_cache
|
||||
.current_epoch_target_attesting_balance_raw()
|
||||
.map_err(|e| BeaconStateError::ParticipationCacheError(format!("{e:?}")))?;
|
||||
|
||||
let current_epoch = state.current_epoch();
|
||||
state.progressive_balances_cache_mut().initialize(
|
||||
current_epoch,
|
||||
previous_epoch_target_attesting_balance,
|
||||
current_epoch_target_attesting_balance,
|
||||
);
|
||||
|
||||
update_progressive_balances_metrics(state.progressive_balances_cache())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Updates the `ProgressiveBalancesCache` when a new target attestation has been processed.
|
||||
pub fn update_progressive_balances_on_attestation<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
epoch: Epoch,
|
||||
validator_index: usize,
|
||||
) -> Result<(), BlockProcessingError> {
|
||||
if is_progressive_balances_enabled(state) {
|
||||
let validator = state.get_validator(validator_index)?;
|
||||
if !validator.slashed {
|
||||
let validator_effective_balance = validator.effective_balance;
|
||||
state
|
||||
.progressive_balances_cache_mut()
|
||||
.on_new_target_attestation(epoch, validator_effective_balance)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Updates the `ProgressiveBalancesCache` when a target attester has been slashed.
|
||||
pub fn update_progressive_balances_on_slashing<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
validator_index: usize,
|
||||
) -> Result<(), BlockProcessingError> {
|
||||
if is_progressive_balances_enabled(state) {
|
||||
let previous_epoch_participation = state.previous_epoch_participation()?;
|
||||
let is_previous_epoch_target_attester =
|
||||
is_target_attester_in_epoch::<T>(previous_epoch_participation, validator_index)?;
|
||||
|
||||
let current_epoch_participation = state.current_epoch_participation()?;
|
||||
let is_current_epoch_target_attester =
|
||||
is_target_attester_in_epoch::<T>(current_epoch_participation, validator_index)?;
|
||||
|
||||
let validator_effective_balance = state.get_effective_balance(validator_index)?;
|
||||
|
||||
state.progressive_balances_cache_mut().on_slashing(
|
||||
is_previous_epoch_target_attester,
|
||||
is_current_epoch_target_attester,
|
||||
validator_effective_balance,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Updates the `ProgressiveBalancesCache` on epoch transition.
|
||||
pub fn update_progressive_balances_on_epoch_transition<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), EpochProcessingError> {
|
||||
if is_progressive_balances_enabled(state) {
|
||||
state
|
||||
.progressive_balances_cache_mut()
|
||||
.on_epoch_transition(spec)?;
|
||||
|
||||
update_progressive_balances_metrics(state.progressive_balances_cache())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_progressive_balances_metrics(
|
||||
cache: &ProgressiveBalancesCache,
|
||||
) -> Result<(), BeaconStateError> {
|
||||
set_gauge(
|
||||
&PARTICIPATION_PREV_EPOCH_TARGET_ATTESTING_GWEI_PROGRESSIVE_TOTAL,
|
||||
cache.previous_epoch_target_attesting_balance()? as i64,
|
||||
);
|
||||
|
||||
set_gauge(
|
||||
&PARTICIPATION_CURR_EPOCH_TARGET_ATTESTING_GWEI_PROGRESSIVE_TOTAL,
|
||||
cache.current_epoch_target_attesting_balance()? as i64,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_target_attester_in_epoch<T: EthSpec>(
|
||||
epoch_participation: &VariableList<ParticipationFlags, T::ValidatorRegistryLimit>,
|
||||
validator_index: usize,
|
||||
) -> Result<bool, BlockProcessingError> {
|
||||
let participation_flags = epoch_participation
|
||||
.get(validator_index)
|
||||
.ok_or(BeaconStateError::UnknownValidator(validator_index))?;
|
||||
participation_flags
|
||||
.has_flag(TIMELY_TARGET_FLAG_INDEX)
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
@@ -111,7 +111,7 @@ pub fn initialize_beacon_state_from_eth1<T: EthSpec>(
|
||||
}
|
||||
|
||||
// Now that we have our validators, initialize the caches (including the committees)
|
||||
state.build_all_caches(spec)?;
|
||||
state.build_caches(spec)?;
|
||||
|
||||
// Set genesis validators root for domain separation and chain versioning
|
||||
*state.genesis_validators_root_mut() = state.update_validators_tree_hash_cache()?;
|
||||
@@ -134,7 +134,7 @@ pub fn process_activations<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
let (validators, balances) = state.validators_and_balances_mut();
|
||||
let (validators, balances, _) = state.validators_and_balances_and_progressive_balances_mut();
|
||||
for (index, validator) in validators.iter_mut().enumerate() {
|
||||
let balance = balances
|
||||
.get(index)
|
||||
|
||||
@@ -23,4 +23,15 @@ lazy_static! {
|
||||
"beacon_participation_prev_epoch_active_gwei_total",
|
||||
"Total effective balance (gwei) of validators active in the previous epoch"
|
||||
);
|
||||
/*
|
||||
* Participation Metrics (progressive balances)
|
||||
*/
|
||||
pub static ref PARTICIPATION_PREV_EPOCH_TARGET_ATTESTING_GWEI_PROGRESSIVE_TOTAL: Result<IntGauge> = try_create_int_gauge(
|
||||
"beacon_participation_prev_epoch_target_attesting_gwei_progressive_total",
|
||||
"Progressive total effective balance (gwei) of validators who attested to the target in the previous epoch"
|
||||
);
|
||||
pub static ref PARTICIPATION_CURR_EPOCH_TARGET_ATTESTING_GWEI_PROGRESSIVE_TOTAL: Result<IntGauge> = try_create_int_gauge(
|
||||
"beacon_participation_curr_epoch_target_attesting_gwei_progressive_total",
|
||||
"Progressive total effective balance (gwei) of validators who attested to the target in the current epoch"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -42,6 +42,9 @@ mod verify_proposer_slashing;
|
||||
use crate::common::decrease_balance;
|
||||
use crate::StateProcessingStrategy;
|
||||
|
||||
use crate::common::update_progressive_balances_cache::{
|
||||
initialize_progressive_balances_cache, update_progressive_balances_metrics,
|
||||
};
|
||||
#[cfg(feature = "arbitrary-fuzz")]
|
||||
use arbitrary::Arbitrary;
|
||||
|
||||
@@ -115,6 +118,8 @@ pub fn per_block_processing<T: EthSpec, Payload: AbstractExecPayload<T>>(
|
||||
.fork_name(spec)
|
||||
.map_err(BlockProcessingError::InconsistentStateFork)?;
|
||||
|
||||
initialize_progressive_balances_cache(state, None, spec)?;
|
||||
|
||||
let verify_signatures = match block_signature_strategy {
|
||||
BlockSignatureStrategy::VerifyBulk => {
|
||||
// Verify all signatures in the block at once.
|
||||
@@ -183,6 +188,10 @@ pub fn per_block_processing<T: EthSpec, Payload: AbstractExecPayload<T>>(
|
||||
)?;
|
||||
}
|
||||
|
||||
if is_progressive_balances_enabled(state) {
|
||||
update_progressive_balances_metrics(state.progressive_balances_cache())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use super::signature_sets::Error as SignatureSetError;
|
||||
use crate::per_epoch_processing::altair::participation_cache;
|
||||
use crate::ContextError;
|
||||
use merkle_proof::MerkleTreeError;
|
||||
use participation_cache::Error as ParticipationCacheError;
|
||||
use safe_arith::ArithError;
|
||||
use ssz::DecodeError;
|
||||
use types::*;
|
||||
@@ -99,6 +101,7 @@ pub enum BlockProcessingError {
|
||||
length: usize,
|
||||
},
|
||||
WithdrawalCredentialsInvalid,
|
||||
ParticipationCacheError(ParticipationCacheError),
|
||||
}
|
||||
|
||||
impl From<BeaconStateError> for BlockProcessingError {
|
||||
@@ -156,6 +159,12 @@ impl From<BlockOperationError<HeaderInvalid>> for BlockProcessingError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParticipationCacheError> for BlockProcessingError {
|
||||
fn from(e: ParticipationCacheError) -> Self {
|
||||
BlockProcessingError::ParticipationCacheError(e)
|
||||
}
|
||||
}
|
||||
|
||||
/// A conversion that consumes `self` and adds an `index` variable to resulting struct.
|
||||
///
|
||||
/// Used here to allow converting an error into an upstream error that points to the object that
|
||||
|
||||
@@ -97,6 +97,8 @@ pub mod base {
|
||||
|
||||
pub mod altair {
|
||||
use super::*;
|
||||
use crate::common::update_progressive_balances_cache::update_progressive_balances_on_attestation;
|
||||
use types::consts::altair::TIMELY_TARGET_FLAG_INDEX;
|
||||
|
||||
pub fn process_attestations<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
@@ -163,6 +165,14 @@ pub mod altair {
|
||||
get_base_reward(state, index, base_reward_per_increment, spec)?
|
||||
.safe_mul(weight)?,
|
||||
)?;
|
||||
|
||||
if flag_index == TIMELY_TARGET_FLAG_INDEX {
|
||||
update_progressive_balances_on_attestation(
|
||||
state,
|
||||
data.target.epoch,
|
||||
index,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -235,6 +245,7 @@ pub fn process_attester_slashings<T: EthSpec>(
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Wrapper function to handle calling the correct version of `process_attestations` based on
|
||||
/// the fork.
|
||||
pub fn process_attestations<T: EthSpec, Payload: AbstractExecPayload<T>>(
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use super::{process_registry_updates, process_slashings, EpochProcessingSummary, Error};
|
||||
use crate::common::update_progressive_balances_cache::{
|
||||
initialize_progressive_balances_cache, update_progressive_balances_on_epoch_transition,
|
||||
};
|
||||
use crate::per_epoch_processing::{
|
||||
effective_balance_updates::process_effective_balance_updates,
|
||||
historical_roots_update::process_historical_roots_update,
|
||||
@@ -31,6 +34,7 @@ pub fn process_epoch<T: EthSpec>(
|
||||
// Pre-compute participating indices and total balances.
|
||||
let participation_cache = ParticipationCache::new(state, spec)?;
|
||||
let sync_committee = state.current_sync_committee()?.clone();
|
||||
initialize_progressive_balances_cache::<T>(state, Some(&participation_cache), spec)?;
|
||||
|
||||
// Justification and finalization.
|
||||
let justification_and_finalization_state =
|
||||
@@ -56,7 +60,7 @@ pub fn process_epoch<T: EthSpec>(
|
||||
process_eth1_data_reset(state)?;
|
||||
|
||||
// Update effective balances with hysteresis (lag).
|
||||
process_effective_balance_updates(state, spec)?;
|
||||
process_effective_balance_updates(state, Some(&participation_cache), spec)?;
|
||||
|
||||
// Reset slashings
|
||||
process_slashings_reset(state)?;
|
||||
@@ -75,6 +79,8 @@ pub fn process_epoch<T: EthSpec>(
|
||||
// Rotate the epoch caches to suit the epoch transition.
|
||||
state.advance_caches(spec)?;
|
||||
|
||||
update_progressive_balances_on_epoch_transition(state, spec)?;
|
||||
|
||||
Ok(EpochProcessingSummary::Altair {
|
||||
participation_cache,
|
||||
sync_committee,
|
||||
|
||||
@@ -11,49 +11,23 @@
|
||||
//! Additionally, this cache is returned from the `altair::process_epoch` function and can be used
|
||||
//! to get useful summaries about the validator participation in an epoch.
|
||||
|
||||
use safe_arith::{ArithError, SafeArith};
|
||||
use types::{
|
||||
consts::altair::{
|
||||
NUM_FLAG_INDICES, TIMELY_HEAD_FLAG_INDEX, TIMELY_SOURCE_FLAG_INDEX,
|
||||
TIMELY_TARGET_FLAG_INDEX,
|
||||
},
|
||||
BeaconState, BeaconStateError, ChainSpec, Epoch, EthSpec, ParticipationFlags, RelativeEpoch,
|
||||
Balance, BeaconState, BeaconStateError, ChainSpec, Epoch, EthSpec, ParticipationFlags,
|
||||
RelativeEpoch,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Error {
|
||||
InvalidFlagIndex(usize),
|
||||
InvalidValidatorIndex(usize),
|
||||
}
|
||||
|
||||
/// A balance which will never be below the specified `minimum`.
|
||||
///
|
||||
/// This is an effort to ensure the `EFFECTIVE_BALANCE_INCREMENT` minimum is always respected.
|
||||
#[derive(PartialEq, Debug, Clone, Copy)]
|
||||
struct Balance {
|
||||
raw: u64,
|
||||
minimum: u64,
|
||||
}
|
||||
|
||||
impl Balance {
|
||||
/// Initialize the balance to `0`, or the given `minimum`.
|
||||
pub fn zero(minimum: u64) -> Self {
|
||||
Self { raw: 0, minimum }
|
||||
}
|
||||
|
||||
/// Returns the balance with respect to the initialization `minimum`.
|
||||
pub fn get(&self) -> u64 {
|
||||
std::cmp::max(self.raw, self.minimum)
|
||||
}
|
||||
|
||||
/// Add-assign to the balance.
|
||||
pub fn safe_add_assign(&mut self, other: u64) -> Result<(), ArithError> {
|
||||
self.raw.safe_add_assign(other)
|
||||
}
|
||||
}
|
||||
|
||||
/// Caches the participation values for one epoch (either the previous or current).
|
||||
#[derive(PartialEq, Debug)]
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
struct SingleEpochParticipationCache {
|
||||
/// Maps an active validator index to their participation flags.
|
||||
///
|
||||
@@ -95,6 +69,14 @@ impl SingleEpochParticipationCache {
|
||||
.ok_or(Error::InvalidFlagIndex(flag_index))
|
||||
}
|
||||
|
||||
/// Returns the raw total balance of attesters who have `flag_index` set.
|
||||
fn total_flag_balance_raw(&self, flag_index: usize) -> Result<Balance, Error> {
|
||||
self.total_flag_balances
|
||||
.get(flag_index)
|
||||
.copied()
|
||||
.ok_or(Error::InvalidFlagIndex(flag_index))
|
||||
}
|
||||
|
||||
/// Returns `true` if `val_index` is active, unslashed and has `flag_index` set.
|
||||
///
|
||||
/// ## Errors
|
||||
@@ -173,7 +155,7 @@ impl SingleEpochParticipationCache {
|
||||
}
|
||||
|
||||
/// Maintains a cache to be used during `altair::process_epoch`.
|
||||
#[derive(PartialEq, Debug)]
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub struct ParticipationCache {
|
||||
current_epoch: Epoch,
|
||||
/// Caches information about active validators pertaining to `self.current_epoch`.
|
||||
@@ -291,6 +273,11 @@ impl ParticipationCache {
|
||||
.total_flag_balance(TIMELY_TARGET_FLAG_INDEX)
|
||||
}
|
||||
|
||||
pub fn current_epoch_target_attesting_balance_raw(&self) -> Result<Balance, Error> {
|
||||
self.current_epoch_participation
|
||||
.total_flag_balance_raw(TIMELY_TARGET_FLAG_INDEX)
|
||||
}
|
||||
|
||||
pub fn previous_epoch_total_active_balance(&self) -> u64 {
|
||||
self.previous_epoch_participation.total_active_balance.get()
|
||||
}
|
||||
@@ -300,6 +287,11 @@ impl ParticipationCache {
|
||||
.total_flag_balance(TIMELY_TARGET_FLAG_INDEX)
|
||||
}
|
||||
|
||||
pub fn previous_epoch_target_attesting_balance_raw(&self) -> Result<Balance, Error> {
|
||||
self.previous_epoch_participation
|
||||
.total_flag_balance_raw(TIMELY_TARGET_FLAG_INDEX)
|
||||
}
|
||||
|
||||
pub fn previous_epoch_source_attesting_balance(&self) -> Result<u64, Error> {
|
||||
self.previous_epoch_participation
|
||||
.total_flag_balance(TIMELY_SOURCE_FLAG_INDEX)
|
||||
|
||||
@@ -52,7 +52,7 @@ pub fn process_epoch<T: EthSpec>(
|
||||
process_eth1_data_reset(state)?;
|
||||
|
||||
// Update effective balances with hysteresis (lag).
|
||||
process_effective_balance_updates(state, spec)?;
|
||||
process_effective_balance_updates(state, None, spec)?;
|
||||
|
||||
// Reset slashings
|
||||
process_slashings_reset(state)?;
|
||||
|
||||
@@ -11,6 +11,9 @@ use crate::per_epoch_processing::{
|
||||
};
|
||||
use types::{BeaconState, ChainSpec, EthSpec, RelativeEpoch};
|
||||
|
||||
use crate::common::update_progressive_balances_cache::{
|
||||
initialize_progressive_balances_cache, update_progressive_balances_on_epoch_transition,
|
||||
};
|
||||
pub use historical_summaries_update::process_historical_summaries_update;
|
||||
|
||||
mod historical_summaries_update;
|
||||
@@ -27,6 +30,7 @@ pub fn process_epoch<T: EthSpec>(
|
||||
// Pre-compute participating indices and total balances.
|
||||
let participation_cache = ParticipationCache::new(state, spec)?;
|
||||
let sync_committee = state.current_sync_committee()?.clone();
|
||||
initialize_progressive_balances_cache(state, Some(&participation_cache), spec)?;
|
||||
|
||||
// Justification and finalization.
|
||||
let justification_and_finalization_state =
|
||||
@@ -52,7 +56,7 @@ pub fn process_epoch<T: EthSpec>(
|
||||
process_eth1_data_reset(state)?;
|
||||
|
||||
// Update effective balances with hysteresis (lag).
|
||||
process_effective_balance_updates(state, spec)?;
|
||||
process_effective_balance_updates(state, Some(&participation_cache), spec)?;
|
||||
|
||||
// Reset slashings
|
||||
process_slashings_reset(state)?;
|
||||
@@ -71,6 +75,8 @@ pub fn process_epoch<T: EthSpec>(
|
||||
// Rotate the epoch caches to suit the epoch transition.
|
||||
state.advance_caches(spec)?;
|
||||
|
||||
update_progressive_balances_on_epoch_transition(state, spec)?;
|
||||
|
||||
Ok(EpochProcessingSummary::Altair {
|
||||
participation_cache,
|
||||
sync_committee,
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
use super::errors::EpochProcessingError;
|
||||
use crate::per_epoch_processing::altair::ParticipationCache;
|
||||
use safe_arith::SafeArith;
|
||||
use types::beacon_state::BeaconState;
|
||||
use types::chain_spec::ChainSpec;
|
||||
use types::{BeaconStateError, EthSpec};
|
||||
use types::{BeaconStateError, EthSpec, ProgressiveBalancesCache};
|
||||
|
||||
pub fn process_effective_balance_updates<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
maybe_participation_cache: Option<&ParticipationCache>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), EpochProcessingError> {
|
||||
let hysteresis_increment = spec
|
||||
@@ -13,7 +15,8 @@ pub fn process_effective_balance_updates<T: EthSpec>(
|
||||
.safe_div(spec.hysteresis_quotient)?;
|
||||
let downward_threshold = hysteresis_increment.safe_mul(spec.hysteresis_downward_multiplier)?;
|
||||
let upward_threshold = hysteresis_increment.safe_mul(spec.hysteresis_upward_multiplier)?;
|
||||
let (validators, balances) = state.validators_and_balances_mut();
|
||||
let (validators, balances, progressive_balances_cache) =
|
||||
state.validators_and_balances_and_progressive_balances_mut();
|
||||
for (index, validator) in validators.iter_mut().enumerate() {
|
||||
let balance = balances
|
||||
.get(index)
|
||||
@@ -23,11 +26,43 @@ pub fn process_effective_balance_updates<T: EthSpec>(
|
||||
if balance.safe_add(downward_threshold)? < validator.effective_balance
|
||||
|| validator.effective_balance.safe_add(upward_threshold)? < balance
|
||||
{
|
||||
validator.effective_balance = std::cmp::min(
|
||||
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,
|
||||
);
|
||||
|
||||
if let Some(participation_cache) = maybe_participation_cache {
|
||||
update_progressive_balances(
|
||||
participation_cache,
|
||||
progressive_balances_cache,
|
||||
index,
|
||||
old_effective_balance,
|
||||
new_effective_balance,
|
||||
)?;
|
||||
}
|
||||
|
||||
validator.effective_balance = new_effective_balance;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_progressive_balances(
|
||||
participation_cache: &ParticipationCache,
|
||||
progressive_balances_cache: &mut ProgressiveBalancesCache,
|
||||
index: usize,
|
||||
old_effective_balance: u64,
|
||||
new_effective_balance: u64,
|
||||
) -> Result<(), EpochProcessingError> {
|
||||
if old_effective_balance != new_effective_balance {
|
||||
let is_current_epoch_target_attester =
|
||||
participation_cache.is_current_epoch_timely_target_attester(index)?;
|
||||
progressive_balances_cache.on_effective_balance_change(
|
||||
is_current_epoch_target_attester,
|
||||
old_effective_balance,
|
||||
new_effective_balance,
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ pub fn process_slashings<T: EthSpec>(
|
||||
total_balance,
|
||||
);
|
||||
|
||||
let (validators, balances) = state.validators_and_balances_mut();
|
||||
let (validators, balances, _) = state.validators_and_balances_and_progressive_balances_mut();
|
||||
for (index, validator) in validators.iter().enumerate() {
|
||||
if validator.slashed
|
||||
&& epoch.safe_add(T::EpochsPerSlashingsVector::to_u64().safe_div(2)?)?
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::common::update_progressive_balances_cache::initialize_progressive_balances_cache;
|
||||
use crate::common::{get_attestation_participation_flag_indices, get_attesting_indices};
|
||||
use std::mem;
|
||||
use std::sync::Arc;
|
||||
@@ -101,6 +102,7 @@ pub fn upgrade_to_altair<E: EthSpec>(
|
||||
next_sync_committee: temp_sync_committee, // not read
|
||||
// Caches
|
||||
total_active_balance: pre.total_active_balance,
|
||||
progressive_balances_cache: mem::take(&mut pre.progressive_balances_cache),
|
||||
committee_caches: mem::take(&mut pre.committee_caches),
|
||||
pubkey_cache: mem::take(&mut pre.pubkey_cache),
|
||||
exit_cache: mem::take(&mut pre.exit_cache),
|
||||
@@ -110,6 +112,8 @@ pub fn upgrade_to_altair<E: EthSpec>(
|
||||
// Fill in previous epoch participation from the pre state's pending attestations.
|
||||
translate_participation(&mut post, &pre.previous_epoch_attestations, spec)?;
|
||||
|
||||
initialize_progressive_balances_cache(&mut post, None, spec)?;
|
||||
|
||||
// Fill in sync committees
|
||||
// Note: A duplicate committee is assigned for the current and next committee at the fork
|
||||
// boundary
|
||||
|
||||
@@ -62,6 +62,7 @@ pub fn upgrade_to_capella<E: EthSpec>(
|
||||
historical_summaries: VariableList::default(),
|
||||
// Caches
|
||||
total_active_balance: pre.total_active_balance,
|
||||
progressive_balances_cache: mem::take(&mut pre.progressive_balances_cache),
|
||||
committee_caches: mem::take(&mut pre.committee_caches),
|
||||
pubkey_cache: mem::take(&mut pre.pubkey_cache),
|
||||
exit_cache: mem::take(&mut pre.exit_cache),
|
||||
|
||||
@@ -60,6 +60,7 @@ pub fn upgrade_to_bellatrix<E: EthSpec>(
|
||||
latest_execution_payload_header: <ExecutionPayloadHeaderMerge<E>>::default(),
|
||||
// Caches
|
||||
total_active_balance: pre.total_active_balance,
|
||||
progressive_balances_cache: mem::take(&mut pre.progressive_balances_cache),
|
||||
committee_caches: mem::take(&mut pre.committee_caches),
|
||||
pubkey_cache: mem::take(&mut pre.pubkey_cache),
|
||||
exit_cache: mem::take(&mut pre.exit_cache),
|
||||
|
||||
@@ -53,6 +53,7 @@ serde_json = "1.0.74"
|
||||
smallvec = "1.8.0"
|
||||
serde_with = "1.13.0"
|
||||
maplit = "1.0.2"
|
||||
strum = { version = "0.24.0", features = ["derive"] }
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.3.3"
|
||||
|
||||
@@ -51,7 +51,7 @@ fn all_benches(c: &mut Criterion) {
|
||||
let spec = Arc::new(MainnetEthSpec::default_spec());
|
||||
|
||||
let mut state = get_state::<MainnetEthSpec>(validator_count);
|
||||
state.build_all_caches(&spec).expect("should build caches");
|
||||
state.build_caches(&spec).expect("should build caches");
|
||||
let state_bytes = state.as_ssz_bytes();
|
||||
|
||||
let inner_state = state.clone();
|
||||
|
||||
@@ -98,7 +98,7 @@ impl<'a, T: EthSpec, Payload: AbstractExecPayload<T>> BeaconBlockBodyRef<'a, T,
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: EthSpec> BeaconBlockBodyRef<'a, T> {
|
||||
impl<'a, T: EthSpec, Payload: AbstractExecPayload<T>> BeaconBlockBodyRef<'a, T, Payload> {
|
||||
/// Get the fork_name of this object
|
||||
pub fn fork_name(self) -> ForkName {
|
||||
match self {
|
||||
|
||||
@@ -26,6 +26,8 @@ pub use self::committee_cache::{
|
||||
compute_committee_index_in_epoch, compute_committee_range_in_epoch, epoch_committee_count,
|
||||
CommitteeCache,
|
||||
};
|
||||
pub use crate::beacon_state::balance::Balance;
|
||||
pub use crate::beacon_state::progressive_balances_cache::*;
|
||||
use crate::historical_summary::HistoricalSummary;
|
||||
pub use clone_config::CloneConfig;
|
||||
pub use eth_spec::*;
|
||||
@@ -34,9 +36,11 @@ pub use tree_hash_cache::BeaconTreeHashCache;
|
||||
|
||||
#[macro_use]
|
||||
mod committee_cache;
|
||||
mod balance;
|
||||
mod clone_config;
|
||||
mod exit_cache;
|
||||
mod iter;
|
||||
mod progressive_balances_cache;
|
||||
mod pubkey_cache;
|
||||
mod tests;
|
||||
mod tree_hash_cache;
|
||||
@@ -101,6 +105,9 @@ pub enum Error {
|
||||
SszTypesError(ssz_types::Error),
|
||||
TreeHashCacheNotInitialized,
|
||||
NonLinearTreeHashCacheHistory,
|
||||
ParticipationCacheError(String),
|
||||
ProgressiveBalancesCacheNotInitialized,
|
||||
ProgressiveBalancesCacheInconsistent,
|
||||
TreeHashCacheSkippedSlot {
|
||||
cache: Slot,
|
||||
state: Slot,
|
||||
@@ -322,6 +329,12 @@ where
|
||||
#[tree_hash(skip_hashing)]
|
||||
#[test_random(default)]
|
||||
#[derivative(Clone(clone_with = "clone_default"))]
|
||||
pub progressive_balances_cache: ProgressiveBalancesCache,
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
#[ssz(skip_serializing, skip_deserializing)]
|
||||
#[tree_hash(skip_hashing)]
|
||||
#[test_random(default)]
|
||||
#[derivative(Clone(clone_with = "clone_default"))]
|
||||
pub committee_caches: [CommitteeCache; CACHED_EPOCHS],
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
#[ssz(skip_serializing, skip_deserializing)]
|
||||
@@ -398,6 +411,7 @@ impl<T: EthSpec> BeaconState<T> {
|
||||
|
||||
// Caching (not in spec)
|
||||
total_active_balance: None,
|
||||
progressive_balances_cache: <_>::default(),
|
||||
committee_caches: [
|
||||
CommitteeCache::default(),
|
||||
CommitteeCache::default(),
|
||||
@@ -776,7 +790,7 @@ impl<T: EthSpec> BeaconState<T> {
|
||||
Ok(signature_hash_int.safe_rem(modulo)? == 0)
|
||||
}
|
||||
|
||||
/// Returns the beacon proposer index for the `slot` in the given `relative_epoch`.
|
||||
/// Returns the beacon proposer index for the `slot` in `self.current_epoch()`.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
pub fn get_beacon_proposer_index(&self, slot: Slot, spec: &ChainSpec) -> Result<usize, Error> {
|
||||
@@ -1169,13 +1183,35 @@ impl<T: EthSpec> BeaconState<T> {
|
||||
}
|
||||
|
||||
/// Convenience accessor for validators and balances simultaneously.
|
||||
pub fn validators_and_balances_mut(&mut self) -> (&mut [Validator], &mut [u64]) {
|
||||
pub fn validators_and_balances_and_progressive_balances_mut(
|
||||
&mut self,
|
||||
) -> (&mut [Validator], &mut [u64], &mut ProgressiveBalancesCache) {
|
||||
match self {
|
||||
BeaconState::Base(state) => (&mut state.validators, &mut state.balances),
|
||||
BeaconState::Altair(state) => (&mut state.validators, &mut state.balances),
|
||||
BeaconState::Merge(state) => (&mut state.validators, &mut state.balances),
|
||||
BeaconState::Capella(state) => (&mut state.validators, &mut state.balances),
|
||||
BeaconState::Deneb(state) => (&mut state.validators, &mut state.balances),
|
||||
BeaconState::Base(state) => (
|
||||
&mut state.validators,
|
||||
&mut state.balances,
|
||||
&mut state.progressive_balances_cache,
|
||||
),
|
||||
BeaconState::Altair(state) => (
|
||||
&mut state.validators,
|
||||
&mut state.balances,
|
||||
&mut state.progressive_balances_cache,
|
||||
),
|
||||
BeaconState::Merge(state) => (
|
||||
&mut state.validators,
|
||||
&mut state.balances,
|
||||
&mut state.progressive_balances_cache,
|
||||
),
|
||||
BeaconState::Capella(state) => (
|
||||
&mut state.validators,
|
||||
&mut state.balances,
|
||||
&mut state.progressive_balances_cache,
|
||||
),
|
||||
BeaconState::Deneb(state) => (
|
||||
&mut state.validators,
|
||||
&mut state.balances,
|
||||
&mut state.progressive_balances_cache,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1402,7 +1438,7 @@ impl<T: EthSpec> BeaconState<T> {
|
||||
}
|
||||
|
||||
/// Build all caches (except the tree hash cache), if they need to be built.
|
||||
pub fn build_all_caches(&mut self, spec: &ChainSpec) -> Result<(), Error> {
|
||||
pub fn build_caches(&mut self, spec: &ChainSpec) -> Result<(), Error> {
|
||||
self.build_all_committee_caches(spec)?;
|
||||
self.update_pubkey_cache()?;
|
||||
self.build_exit_cache(spec)?;
|
||||
@@ -1434,6 +1470,7 @@ impl<T: EthSpec> BeaconState<T> {
|
||||
self.drop_committee_cache(RelativeEpoch::Next)?;
|
||||
self.drop_pubkey_cache();
|
||||
self.drop_tree_hash_cache();
|
||||
self.drop_progressive_balances_cache();
|
||||
*self.exit_cache_mut() = ExitCache::default();
|
||||
Ok(())
|
||||
}
|
||||
@@ -1630,6 +1667,11 @@ impl<T: EthSpec> BeaconState<T> {
|
||||
*self.pubkey_cache_mut() = PubkeyCache::default()
|
||||
}
|
||||
|
||||
/// Completely drops the `progressive_balances_cache` cache, replacing it with a new, empty cache.
|
||||
fn drop_progressive_balances_cache(&mut self) {
|
||||
*self.progressive_balances_cache_mut() = ProgressiveBalancesCache::default();
|
||||
}
|
||||
|
||||
/// Initialize but don't fill the tree hash cache, if it isn't already initialized.
|
||||
pub fn initialize_tree_hash_cache(&mut self) {
|
||||
if !self.tree_hash_cache().is_initialized() {
|
||||
@@ -1702,6 +1744,9 @@ impl<T: EthSpec> BeaconState<T> {
|
||||
if config.tree_hash_cache {
|
||||
*res.tree_hash_cache_mut() = self.tree_hash_cache().clone();
|
||||
}
|
||||
if config.progressive_balances_cache {
|
||||
*res.progressive_balances_cache_mut() = self.progressive_balances_cache().clone();
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
use arbitrary::Arbitrary;
|
||||
use safe_arith::{ArithError, SafeArith};
|
||||
|
||||
/// A balance which will never be below the specified `minimum`.
|
||||
///
|
||||
/// This is an effort to ensure the `EFFECTIVE_BALANCE_INCREMENT` minimum is always respected.
|
||||
#[derive(PartialEq, Debug, Clone, Copy, Arbitrary)]
|
||||
pub struct Balance {
|
||||
raw: u64,
|
||||
minimum: u64,
|
||||
}
|
||||
|
||||
impl Balance {
|
||||
/// Initialize the balance to `0`, or the given `minimum`.
|
||||
pub fn zero(minimum: u64) -> Self {
|
||||
Self { raw: 0, minimum }
|
||||
}
|
||||
|
||||
/// Returns the balance with respect to the initialization `minimum`.
|
||||
pub fn get(&self) -> u64 {
|
||||
std::cmp::max(self.raw, self.minimum)
|
||||
}
|
||||
|
||||
/// Add-assign to the balance.
|
||||
pub fn safe_add_assign(&mut self, other: u64) -> Result<(), ArithError> {
|
||||
self.raw.safe_add_assign(other)
|
||||
}
|
||||
|
||||
/// Sub-assign to the balance.
|
||||
pub fn safe_sub_assign(&mut self, other: u64) -> Result<(), ArithError> {
|
||||
self.raw.safe_sub_assign(other)
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ pub struct CloneConfig {
|
||||
pub pubkey_cache: bool,
|
||||
pub exit_cache: bool,
|
||||
pub tree_hash_cache: bool,
|
||||
pub progressive_balances_cache: bool,
|
||||
}
|
||||
|
||||
impl CloneConfig {
|
||||
@@ -14,6 +15,7 @@ impl CloneConfig {
|
||||
pubkey_cache: true,
|
||||
exit_cache: true,
|
||||
tree_hash_cache: true,
|
||||
progressive_balances_cache: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
use crate::beacon_state::balance::Balance;
|
||||
use crate::{BeaconState, BeaconStateError, ChainSpec, Epoch, EthSpec};
|
||||
use arbitrary::Arbitrary;
|
||||
use safe_arith::SafeArith;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use strum::{Display, EnumString, EnumVariantNames};
|
||||
|
||||
/// This cache keeps track of the accumulated target attestation balance for the current & previous
|
||||
/// epochs. The cached values can be utilised by fork choice to calculate unrealized justification
|
||||
/// and finalization instead of converting epoch participation arrays to balances for each block we
|
||||
/// process.
|
||||
#[derive(Default, Debug, PartialEq, Arbitrary, Clone)]
|
||||
pub struct ProgressiveBalancesCache {
|
||||
inner: Option<Inner>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Arbitrary, Clone)]
|
||||
struct Inner {
|
||||
pub current_epoch: Epoch,
|
||||
pub previous_epoch_target_attesting_balance: Balance,
|
||||
pub current_epoch_target_attesting_balance: Balance,
|
||||
}
|
||||
|
||||
impl ProgressiveBalancesCache {
|
||||
pub fn initialize(
|
||||
&mut self,
|
||||
current_epoch: Epoch,
|
||||
previous_epoch_target_attesting_balance: Balance,
|
||||
current_epoch_target_attesting_balance: Balance,
|
||||
) {
|
||||
self.inner = Some(Inner {
|
||||
current_epoch,
|
||||
previous_epoch_target_attesting_balance,
|
||||
current_epoch_target_attesting_balance,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn is_initialized(&self) -> bool {
|
||||
self.inner.is_some()
|
||||
}
|
||||
|
||||
/// When a new target attestation has been processed, we update the cached
|
||||
/// `current_epoch_target_attesting_balance` to include the validator effective balance.
|
||||
/// If the epoch is neither the current epoch nor the previous epoch, an error is returned.
|
||||
pub fn on_new_target_attestation(
|
||||
&mut self,
|
||||
epoch: Epoch,
|
||||
validator_effective_balance: u64,
|
||||
) -> Result<(), BeaconStateError> {
|
||||
let cache = self.get_inner_mut()?;
|
||||
|
||||
if epoch == cache.current_epoch {
|
||||
cache
|
||||
.current_epoch_target_attesting_balance
|
||||
.safe_add_assign(validator_effective_balance)?;
|
||||
} else if epoch.safe_add(1)? == cache.current_epoch {
|
||||
cache
|
||||
.previous_epoch_target_attesting_balance
|
||||
.safe_add_assign(validator_effective_balance)?;
|
||||
} else {
|
||||
return Err(BeaconStateError::ProgressiveBalancesCacheInconsistent);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// When a validator is slashed, we reduce the `current_epoch_target_attesting_balance` by the
|
||||
/// validator's effective balance to exclude the validator weight.
|
||||
pub fn on_slashing(
|
||||
&mut self,
|
||||
is_previous_epoch_target_attester: bool,
|
||||
is_current_epoch_target_attester: bool,
|
||||
effective_balance: u64,
|
||||
) -> Result<(), BeaconStateError> {
|
||||
let cache = self.get_inner_mut()?;
|
||||
if is_previous_epoch_target_attester {
|
||||
cache
|
||||
.previous_epoch_target_attesting_balance
|
||||
.safe_sub_assign(effective_balance)?;
|
||||
}
|
||||
if is_current_epoch_target_attester {
|
||||
cache
|
||||
.current_epoch_target_attesting_balance
|
||||
.safe_sub_assign(effective_balance)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// When a current epoch target attester has its effective balance changed, we adjust the
|
||||
/// its share of the target attesting balance in the cache.
|
||||
pub fn on_effective_balance_change(
|
||||
&mut self,
|
||||
is_current_epoch_target_attester: bool,
|
||||
old_effective_balance: u64,
|
||||
new_effective_balance: u64,
|
||||
) -> Result<(), BeaconStateError> {
|
||||
let cache = self.get_inner_mut()?;
|
||||
if is_current_epoch_target_attester {
|
||||
if new_effective_balance > old_effective_balance {
|
||||
cache
|
||||
.current_epoch_target_attesting_balance
|
||||
.safe_add_assign(new_effective_balance.safe_sub(old_effective_balance)?)?;
|
||||
} else {
|
||||
cache
|
||||
.current_epoch_target_attesting_balance
|
||||
.safe_sub_assign(old_effective_balance.safe_sub(new_effective_balance)?)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// On epoch transition, the balance from current epoch is shifted to previous epoch, and the
|
||||
/// current epoch balance is reset to 0.
|
||||
pub fn on_epoch_transition(&mut self, spec: &ChainSpec) -> Result<(), BeaconStateError> {
|
||||
let cache = self.get_inner_mut()?;
|
||||
cache.current_epoch.safe_add_assign(1)?;
|
||||
cache.previous_epoch_target_attesting_balance =
|
||||
cache.current_epoch_target_attesting_balance;
|
||||
cache.current_epoch_target_attesting_balance =
|
||||
Balance::zero(spec.effective_balance_increment);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn previous_epoch_target_attesting_balance(&self) -> Result<u64, BeaconStateError> {
|
||||
Ok(self
|
||||
.get_inner()?
|
||||
.previous_epoch_target_attesting_balance
|
||||
.get())
|
||||
}
|
||||
|
||||
pub fn current_epoch_target_attesting_balance(&self) -> Result<u64, BeaconStateError> {
|
||||
Ok(self
|
||||
.get_inner()?
|
||||
.current_epoch_target_attesting_balance
|
||||
.get())
|
||||
}
|
||||
|
||||
fn get_inner_mut(&mut self) -> Result<&mut Inner, BeaconStateError> {
|
||||
self.inner
|
||||
.as_mut()
|
||||
.ok_or(BeaconStateError::ProgressiveBalancesCacheNotInitialized)
|
||||
}
|
||||
|
||||
fn get_inner(&self) -> Result<&Inner, BeaconStateError> {
|
||||
self.inner
|
||||
.as_ref()
|
||||
.ok_or(BeaconStateError::ProgressiveBalancesCacheNotInitialized)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug, PartialEq, Eq, Clone, Copy, Deserialize, Serialize, Display, EnumString, EnumVariantNames,
|
||||
)]
|
||||
#[strum(serialize_all = "lowercase")]
|
||||
pub enum ProgressiveBalancesMode {
|
||||
/// Disable the usage of progressive cache, and use the existing `ParticipationCache` calculation.
|
||||
Disabled,
|
||||
/// Enable the usage of progressive cache, with checks against the `ParticipationCache` and falls
|
||||
/// back to the existing calculation if there is a balance mismatch.
|
||||
Checked,
|
||||
/// Enable the usage of progressive cache, with checks against the `ParticipationCache`. Errors
|
||||
/// if there is a balance mismatch. Used in testing only.
|
||||
Strict,
|
||||
/// Enable the usage of progressive cache, with no comparative checks against the
|
||||
/// `ParticipationCache`. This is fast but an experimental mode, use with caution.
|
||||
Fast,
|
||||
}
|
||||
|
||||
impl ProgressiveBalancesMode {
|
||||
pub fn perform_comparative_checks(&self) -> bool {
|
||||
match self {
|
||||
Self::Disabled | Self::Fast => false,
|
||||
Self::Checked | Self::Strict => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `ProgressiveBalancesCache` is only enabled from `Altair` as it requires `ParticipationCache`.
|
||||
pub fn is_progressive_balances_enabled<E: EthSpec>(state: &BeaconState<E>) -> bool {
|
||||
match state {
|
||||
BeaconState::Base(_) => false,
|
||||
BeaconState::Altair(_) | BeaconState::Merge(_) | BeaconState::Capella(_) => true,
|
||||
}
|
||||
}
|
||||
@@ -219,17 +219,18 @@ async fn clone_config() {
|
||||
|
||||
let mut state = build_state::<MinimalEthSpec>(16).await;
|
||||
|
||||
state.build_all_caches(&spec).unwrap();
|
||||
state.build_caches(&spec).unwrap();
|
||||
state
|
||||
.update_tree_hash_cache()
|
||||
.expect("should update tree hash cache");
|
||||
|
||||
let num_caches = 4;
|
||||
let num_caches = 5;
|
||||
let all_configs = (0..2u8.pow(num_caches)).map(|i| CloneConfig {
|
||||
committee_caches: (i & 1) != 0,
|
||||
pubkey_cache: ((i >> 1) & 1) != 0,
|
||||
exit_cache: ((i >> 2) & 1) != 0,
|
||||
tree_hash_cache: ((i >> 3) & 1) != 0,
|
||||
progressive_balances_cache: ((i >> 4) & 1) != 0,
|
||||
});
|
||||
|
||||
for config in all_configs {
|
||||
|
||||
Reference in New Issue
Block a user