mirror of
https://github.com/sigp/lighthouse.git
synced 2026-07-03 04:44:28 +00:00
Merge remote-tracking branch 'origin/unstable' into tree-states
This commit is contained in:
@@ -1,13 +1,17 @@
|
||||
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,
|
||||
per_epoch_processing::altair::participation_cache,
|
||||
};
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::BTreeSet;
|
||||
@@ -19,6 +23,7 @@ use types::{
|
||||
EthSpec, ExecPayload, ExecutionBlockHash, Hash256, IndexedAttestation, RelativeEpoch,
|
||||
SignedBeaconBlock, Slot,
|
||||
};
|
||||
use types::{ProgressiveBalancesCache, ProgressiveBalancesMode};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error<T> {
|
||||
@@ -72,8 +77,9 @@ pub enum Error<T> {
|
||||
proposer_boost_root: Hash256,
|
||||
},
|
||||
UnrealizedVoteProcessing(state_processing::EpochProcessingError),
|
||||
ParticipationCacheBuild(participation_cache::Error),
|
||||
ParticipationCacheError(ParticipationCacheError),
|
||||
ValidatorStatuses(BeaconStateError),
|
||||
ProgressiveBalancesCacheCheckFailed(String),
|
||||
}
|
||||
|
||||
impl<T> From<InvalidAttestation> for Error<T> {
|
||||
@@ -94,6 +100,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 {
|
||||
@@ -644,7 +662,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
|
||||
@@ -738,43 +758,79 @@ 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 {
|
||||
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::Capella(_)
|
||||
| BeaconBlockRef::Merge(_)
|
||||
| BeaconBlockRef::Altair(_) => match progressive_balances_mode {
|
||||
ProgressiveBalancesMode::Disabled => {
|
||||
let participation_cache = ParticipationCache::new(state, spec)?;
|
||||
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))
|
||||
.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))?;
|
||||
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
|
||||
@@ -1500,6 +1556,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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user