This commit is contained in:
Eitan Seri-Levi
2026-06-02 10:34:02 +03:00
parent 9c8a577412
commit 7c0bbd5cac
2 changed files with 80 additions and 1 deletions

View File

@@ -2058,6 +2058,85 @@ async fn gloas_get_head_can_return_justified_empty_payload_branch() {
assert_eq!(payload_status, PayloadStatus::Empty);
}
// Post-Gloas, `get_beacon_proposer_indices` excludes slashed validators. The lookup prefers the
// slashings cache but falls back to reading validators directly when the cache is cold (e.g. block
// replay / state reconstruction). This test asserts the cold-cache fallback produces exactly the
// same proposers as the warm cache, and that neither path ever selects a slashed validator.
#[tokio::test]
async fn gloas_proposer_indices_exclude_slashed_with_cold_cache() {
let spec = test_spec::<E>();
if !spec.fork_name_at_epoch(Epoch::new(0)).gloas_enabled() {
return;
}
let harness = BeaconChainHarness::builder(MainnetEthSpec)
.spec(spec.into())
.chain_config(ChainConfig {
archive: true,
..ChainConfig::default()
})
.keypairs(KEYPAIRS[0..VALIDATOR_COUNT].to_vec())
.node_custody_type(NodeCustodyType::Supernode)
.fresh_ephemeral_store()
.mock_execution_layer()
.build();
// Advance a couple of epochs so we have a fully-formed Gloas state.
harness
.extend_slots(E::slots_per_epoch() as usize * 2)
.await;
let spec = harness.chain.spec.clone();
let mut state = harness.get_current_state();
let current_epoch = state.current_epoch();
// Slash every odd-indexed validator directly on the state. A large slashed fraction ensures
// the filter materially changes the candidate set, so a regressed (non-filtering) fallback
// would diverge from the cache rather than passing by luck.
let mut slashed_count = 0;
for index in (1..VALIDATOR_COUNT).step_by(2) {
state.get_validator_mut(index).unwrap().slashed = true;
slashed_count += 1;
}
assert!(
slashed_count > 0,
"test must actually slash some validators"
);
// `current + 2` is beyond `next_epoch`, so it bypasses the cached proposer-lookahead
// early-return and exercises the slashed-filter path in `get_beacon_proposer_indices`.
let request_epoch = current_epoch + 2;
// Warm path: rebuild the slashings cache so it reflects the validators we just slashed.
state.drop_all_caches().unwrap();
state.build_slashings_cache().unwrap();
assert!(state.slashings_cache_is_initialized());
let warm = state
.get_beacon_proposer_indices(request_epoch, &spec)
.unwrap();
// Cold path: drop the slashings cache so the lookup falls back to reading validators directly.
state.drop_all_caches().unwrap();
assert!(!state.slashings_cache_is_initialized());
let cold = state
.get_beacon_proposer_indices(request_epoch, &spec)
.unwrap();
// The fallback must produce exactly the same proposers as the warm cache.
assert_eq!(
warm, cold,
"cold-cache fallback diverged from the slashings cache"
);
// And in both cases, no slashed validator may be selected as a proposer.
for proposer in &cold {
assert!(
!state.get_validator(*proposer).unwrap().slashed,
"slashed validator {proposer} was selected as a proposer"
);
}
}
// This is a regression test for this bug:
// https://github.com/sigp/lighthouse/issues/4332#issuecomment-1565092279
#[tokio::test]

View File

@@ -1,6 +1,6 @@
# To download/extract nightly tests, run:
# CONSENSUS_SPECS_TEST_VERSION=nightly make
CONSENSUS_SPECS_TEST_VERSION ?= v1.7.0-alpha.8
CONSENSUS_SPECS_TEST_VERSION ?= v1.7.0-alpha.9
REPO_NAME := consensus-spec-tests
OUTPUT_DIR := ./$(REPO_NAME)