From 7c0bbd5cac47291c4dc783a3ca2dcf42e89f025e Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Tue, 2 Jun 2026 10:34:02 +0300 Subject: [PATCH] new test --- .../beacon_chain/tests/block_verification.rs | 79 +++++++++++++++++++ testing/ef_tests/Makefile | 2 +- 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/beacon_node/beacon_chain/tests/block_verification.rs b/beacon_node/beacon_chain/tests/block_verification.rs index e0c39c350b..b3908e4683 100644 --- a/beacon_node/beacon_chain/tests/block_verification.rs +++ b/beacon_node/beacon_chain/tests/block_verification.rs @@ -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::(); + 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] diff --git a/testing/ef_tests/Makefile b/testing/ef_tests/Makefile index f566a89ded..baf7a99e9e 100644 --- a/testing/ef_tests/Makefile +++ b/testing/ef_tests/Makefile @@ -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)