mirror of
https://github.com/sigp/lighthouse.git
synced 2026-06-30 03:14:25 +00:00
Gloas alpha spec 9 (#9393)
Changes implemented Ensure bids are for a higher slot than their parent (https://github.com/ethereum/consensus-specs/pull/5302) Ignore PTC attestations for empty assigned slots (https://github.com/ethereum/consensus-specs/pull/5281) Limit should_build_on_full checks to the previous slot (https://github.com/ethereum/consensus-specs/pull/5309) Apply proposer boost if dependent roots match (https://github.com/ethereum/consensus-specs/pull/5306) Exclude slashed validators from proposing (EIP-8045) (https://github.com/ethereum/consensus-specs/pull/5115) Force the proposer to reorg late payloads (https://github.com/ethereum/consensus-specs/pull/5210) Remove support for old deposit mechanism in Fulu (https://github.com/ethereum/consensus-specs/pull/4704) Co-Authored-By: Eitan Seri-Levi <eserilev@ucsc.edu> Co-Authored-By: dapplion <35266934+dapplion@users.noreply.github.com> Co-Authored-By: Eitan Seri-Levi <eserilev@gmail.com> Co-Authored-By: Michael Sproul <michael@sigmaprime.io> Co-Authored-By: Michael Sproul <michaelsproul@users.noreply.github.com>
This commit is contained in:
@@ -1127,21 +1127,33 @@ impl<E: EthSpec> BeaconState<E> {
|
||||
// Post-Fulu we must never compute proposer indices using insufficient lookahead. This
|
||||
// would be very dangerous as it would lead to conflicts between the *true* proposer as
|
||||
// defined by `self.proposer_lookahead` and the output of this function.
|
||||
// With MIN_SEED_LOOKAHEAD=1 (common config), this is equivalent to checking that the
|
||||
// requested epoch is not the current epoch.
|
||||
//
|
||||
// We do not run this check if this function is called from `upgrade_to_fulu`,
|
||||
// which runs *after* the slot is incremented, and needs to compute the proposer
|
||||
// shuffling for the epoch that was just transitioned into.
|
||||
if self.fork_name_unchecked().fulu_enabled()
|
||||
&& epoch < current_epoch.safe_add(spec.min_seed_lookahead)?
|
||||
{
|
||||
return Err(
|
||||
BeaconStateError::ComputeProposerIndicesInsufficientLookahead {
|
||||
current_epoch,
|
||||
request_epoch: epoch,
|
||||
},
|
||||
);
|
||||
// Furthermore, post-Gloas, we must never compute proposers at any slot other than the
|
||||
// dependent root slot itself, as slashings at subsequent slots have the ability to
|
||||
// change the shuffling.
|
||||
//
|
||||
// For simplicity these two checks are combined into a single check on the dependent
|
||||
// slot, which is safe for Fulu and Gloas. This function is always called from
|
||||
// `get_beacon_proposer_indices`, which uses the cached lookahead for `current_epoch` and
|
||||
// `next_epoch`. The only epoch's shuffling that should ordinarily be computed therefore
|
||||
// is `next_epoch + 1`, which for Fulu and Gloas is computed during the epoch transition
|
||||
// in the last slot of `current_epoch` (before the slot is incremented into
|
||||
// `next_epoch`).
|
||||
//
|
||||
// The only case where computation of proposers in `current_epoch` and `next_epoch` is
|
||||
// directly required is during the fork to Fulu itself
|
||||
// (`upgrade_to_fulu`/`initialize_proposer_lookahead`), in which case the state is not
|
||||
// yet the Fulu variant, and we omit the check.
|
||||
if self.fork_name_unchecked().fulu_enabled() {
|
||||
let dependent_slot = spec.proposer_shuffling_decision_slot::<E>(epoch);
|
||||
if self.slot() != dependent_slot {
|
||||
return Err(
|
||||
BeaconStateError::ComputeProposerIndicesInsufficientLookahead {
|
||||
current_epoch,
|
||||
request_epoch: epoch,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Pre-Fulu the situation is reversed, we *should not* compute proposer indices using
|
||||
@@ -1375,7 +1387,7 @@ impl<E: EthSpec> BeaconState<E> {
|
||||
spec: &ChainSpec,
|
||||
) -> Result<Vec<usize>, BeaconStateError> {
|
||||
// This isn't in the spec, but we remove the footgun that is requesting the current epoch
|
||||
// for a Fulu state.
|
||||
// or next epoch for a Fulu state.
|
||||
if let Ok(proposer_lookahead) = self.proposer_lookahead()
|
||||
&& epoch >= self.current_epoch()
|
||||
&& epoch <= self.next_epoch()?
|
||||
@@ -1394,7 +1406,15 @@ impl<E: EthSpec> BeaconState<E> {
|
||||
}
|
||||
|
||||
// Not using the cached validator indices since they are shuffled.
|
||||
let indices = self.get_active_validator_indices(epoch, spec)?;
|
||||
let mut indices = self.get_active_validator_indices(epoch, spec)?;
|
||||
|
||||
// Post-Gloas, slashed validators are excluded from proposer selection
|
||||
if self.fork_name_unchecked().gloas_enabled() {
|
||||
let latest_block_slot = self.latest_block_header().slot;
|
||||
let slashings_cache = self.slashings_cache();
|
||||
slashings_cache.check_initialized(latest_block_slot)?;
|
||||
indices.retain(|&index| !slashings_cache.is_slashed(index));
|
||||
}
|
||||
|
||||
let preimage = self.get_seed(epoch, Domain::BeaconProposer, spec)?;
|
||||
self.compute_proposer_indices(epoch, preimage.as_slice(), &indices, spec)
|
||||
|
||||
@@ -64,3 +64,127 @@ impl SlashingsCache {
|
||||
self.latest_block_slot = Some(latest_block_slot);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{Epoch, Hash256};
|
||||
use bls::PublicKeyBytes;
|
||||
|
||||
/// Build a minimal validator with the given `slashed` flag. The other fields are irrelevant to
|
||||
/// the slashings cache.
|
||||
fn validator(slashed: bool) -> Validator {
|
||||
Validator {
|
||||
pubkey: PublicKeyBytes::empty(),
|
||||
withdrawal_credentials: Hash256::ZERO,
|
||||
effective_balance: 0,
|
||||
slashed,
|
||||
activation_eligibility_epoch: Epoch::new(0),
|
||||
activation_epoch: Epoch::new(0),
|
||||
exit_epoch: Epoch::new(0),
|
||||
withdrawable_epoch: Epoch::new(0),
|
||||
}
|
||||
}
|
||||
|
||||
/// Validators 1 and 3 are slashed, the rest are not.
|
||||
fn validators() -> Vec<Validator> {
|
||||
vec![
|
||||
validator(false),
|
||||
validator(true),
|
||||
validator(false),
|
||||
validator(true),
|
||||
validator(false),
|
||||
]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_captures_slashed_indices() {
|
||||
let validators = validators();
|
||||
let cache = SlashingsCache::new(Slot::new(7), validators.iter());
|
||||
|
||||
// The cache is initialized at the block slot it was built for.
|
||||
assert!(cache.is_initialized(Slot::new(7)));
|
||||
assert!(!cache.is_initialized(Slot::new(8)));
|
||||
|
||||
// Each index reports the same `slashed` status as the source validator.
|
||||
for (index, validator) in validators.iter().enumerate() {
|
||||
assert_eq!(
|
||||
cache.is_slashed(index),
|
||||
validator.slashed,
|
||||
"validator {index} slashed status mismatch"
|
||||
);
|
||||
}
|
||||
|
||||
// An out-of-bounds index is not slashed.
|
||||
assert!(!cache.is_slashed(validators.len()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_is_uninitialized() {
|
||||
let cache = SlashingsCache::default();
|
||||
|
||||
// A default cache is not initialized at any slot.
|
||||
assert!(!cache.is_initialized(Slot::new(0)));
|
||||
assert_eq!(
|
||||
cache.check_initialized(Slot::new(0)),
|
||||
Err(BeaconStateError::SlashingsCacheUninitialized {
|
||||
initialized_slot: None,
|
||||
latest_block_slot: Slot::new(0),
|
||||
})
|
||||
);
|
||||
|
||||
// It reports nothing as slashed. This is exactly why callers must check initialization
|
||||
// before trusting `is_slashed`.
|
||||
assert!(!cache.is_slashed(0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_initialized_matches_block_slot() {
|
||||
let cache = SlashingsCache::new(Slot::new(3), validators().iter());
|
||||
|
||||
assert_eq!(cache.check_initialized(Slot::new(3)), Ok(()));
|
||||
assert_eq!(
|
||||
cache.check_initialized(Slot::new(4)),
|
||||
Err(BeaconStateError::SlashingsCacheUninitialized {
|
||||
initialized_slot: Some(Slot::new(3)),
|
||||
latest_block_slot: Slot::new(4),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_validator_slashing_requires_matching_slot() {
|
||||
let mut cache = SlashingsCache::new(Slot::new(3), validators().iter());
|
||||
|
||||
// Index 0 starts unslashed.
|
||||
assert!(!cache.is_slashed(0));
|
||||
|
||||
// Recording at the initialized slot succeeds and marks the validator slashed.
|
||||
cache.record_validator_slashing(Slot::new(3), 0).unwrap();
|
||||
assert!(cache.is_slashed(0));
|
||||
|
||||
// Recording at a slot the cache is not initialized for errors and leaves the set unchanged.
|
||||
assert_eq!(
|
||||
cache.record_validator_slashing(Slot::new(4), 2),
|
||||
Err(BeaconStateError::SlashingsCacheUninitialized {
|
||||
initialized_slot: Some(Slot::new(3)),
|
||||
latest_block_slot: Slot::new(4),
|
||||
})
|
||||
);
|
||||
assert!(!cache.is_slashed(2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_latest_block_slot_preserves_slashed_set() {
|
||||
let mut cache = SlashingsCache::new(Slot::new(3), validators().iter());
|
||||
|
||||
cache.update_latest_block_slot(Slot::new(4));
|
||||
|
||||
// The initialized slot moves forward without clearing the recorded slashings.
|
||||
assert!(!cache.is_initialized(Slot::new(3)));
|
||||
assert!(cache.is_initialized(Slot::new(4)));
|
||||
assert!(cache.is_slashed(1));
|
||||
assert!(cache.is_slashed(3));
|
||||
assert!(!cache.is_slashed(0));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user