This commit is contained in:
Eitan Seri- Levi
2026-04-04 01:02:55 -07:00
parent 9306767d1a
commit a12969a4d2
4 changed files with 27 additions and 29 deletions

View File

@@ -174,10 +174,11 @@ where
// Pre-gloas the anchor state MUST be on an epoch boundary (it should be advanced by the caller). // Pre-gloas the anchor state MUST be on an epoch boundary (it should be advanced by the caller).
// Post-gloas this requirement is relaxed. // Post-gloas this requirement is relaxed.
if !anchor_state.fork_name_unchecked().gloas_enabled() && !anchor_state if !anchor_state.fork_name_unchecked().gloas_enabled()
.slot() && !anchor_state
.as_u64() .slot()
.is_multiple_of(E::slots_per_epoch()) .as_u64()
.is_multiple_of(E::slots_per_epoch())
{ {
return Err(Error::UnalignedCheckpoint { return Err(Error::UnalignedCheckpoint {
block_slot: anchor_block_header.slot, block_slot: anchor_block_header.slot,

View File

@@ -5465,12 +5465,14 @@ fn check_finalization(harness: &TestHarness, expected_slot: u64) {
); );
} }
// Checkpoint sync with a Gloas Pending state at a non-epoch-boundary slot. // Verify that post-gloas checkpoint sync accepts a non-epoch aligned state and builds
// the chain.
// //
// Post-Gloas, the finalized state is always the post-block (Pending) state. // Since post-gloas checkpoint sync states are always the post block state, if the epoch boundary
// If the epoch boundary slot is skipped, the checkpoint state will not be // slot is skipped, we'll receive a checkpoint state that is not epoch aligned.
// epoch-aligned. This test verifies that checkpoint sync accepts such states //
// and builds the chain correctly. // Example: slot `n` is the epoch boundary slot and is skipped. We'll receive the post block state for
// slot `n - 1`. This is the state before the payload for slot `n - 1` was processed.
#[tokio::test] #[tokio::test]
async fn weak_subjectivity_sync_gloas_pending_non_aligned() { async fn weak_subjectivity_sync_gloas_pending_non_aligned() {
if !fork_name_from_env().is_some_and(|f| f.gloas_enabled()) { if !fork_name_from_env().is_some_and(|f| f.gloas_enabled()) {
@@ -5480,8 +5482,6 @@ async fn weak_subjectivity_sync_gloas_pending_non_aligned() {
let spec = test_spec::<E>(); let spec = test_spec::<E>();
// Build a chain with a skipped slot at the epoch boundary. // Build a chain with a skipped slot at the epoch boundary.
// For MinimalEthSpec (8 slots/epoch), skip slot 8 so the last block before
// the epoch boundary is at slot 7 (not epoch-aligned).
let epoch_boundary_slot = E::slots_per_epoch(); let epoch_boundary_slot = E::slots_per_epoch();
let num_initial_slots = E::slots_per_epoch() * 4; let num_initial_slots = E::slots_per_epoch() * 4;
let checkpoint_slot = Slot::new(epoch_boundary_slot); let checkpoint_slot = Slot::new(epoch_boundary_slot);
@@ -5489,8 +5489,7 @@ async fn weak_subjectivity_sync_gloas_pending_non_aligned() {
let slots = (1..num_initial_slots) let slots = (1..num_initial_slots)
.map(Slot::new) .map(Slot::new)
.filter(|&slot| { .filter(|&slot| {
// Skip the epoch boundary slot so the checkpoint resolves to the // Skip the epoch boundary slot
// block at slot epoch_boundary - 1.
slot.as_u64() != epoch_boundary_slot slot.as_u64() != epoch_boundary_slot
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@@ -5510,7 +5509,7 @@ async fn weak_subjectivity_sync_gloas_pending_non_aligned() {
) )
.await; .await;
// Extract the checkpoint block and its Pending (post-block) state. // Extract the checkpoint block and its Pending state.
let wss_block_root = harness let wss_block_root = harness
.chain .chain
.block_root_at_slot(checkpoint_slot, WhenSlotSkipped::Prev) .block_root_at_slot(checkpoint_slot, WhenSlotSkipped::Prev)
@@ -5526,20 +5525,23 @@ async fn weak_subjectivity_sync_gloas_pending_non_aligned() {
// The block's state_root points to the Pending state in Gloas. // The block's state_root points to the Pending state in Gloas.
let wss_state_root = wss_block.state_root(); let wss_state_root = wss_block.state_root();
let wss_state = full_store let wss_state = full_store
.get_state(&wss_state_root, Some(wss_block.slot()), CACHE_STATE_IN_TESTS) .get_state(
&wss_state_root,
Some(wss_block.slot()),
CACHE_STATE_IN_TESTS,
)
.unwrap() .unwrap()
.unwrap(); .unwrap();
// Verify test preconditions: state is Pending and not epoch-aligned.
assert_eq!( assert_eq!(
wss_state.payload_status(), wss_state.payload_status(),
StatePayloadStatus::Pending, StatePayloadStatus::Pending,
"Checkpoint state should be Pending (post-block, pre-payload)" "Checkpoint state should be Pending"
); );
assert_ne!( assert_ne!(
wss_state.slot() % E::slots_per_epoch(), wss_state.slot() % E::slots_per_epoch(),
0, 0,
"Test invalid: checkpoint state is epoch-aligned, expected non-aligned" "Checkpoint state is epoch-aligned, expected non-aligned"
); );
let wss_blobs_opt = harness let wss_blobs_opt = harness
@@ -5575,12 +5577,7 @@ async fn weak_subjectivity_sync_gloas_pending_non_aligned() {
.store(store.clone()) .store(store.clone())
.custom_spec(spec.clone().into()) .custom_spec(spec.clone().into())
.task_executor(harness.chain.task_executor.clone()) .task_executor(harness.chain.task_executor.clone())
.weak_subjectivity_state( .weak_subjectivity_state(wss_state, wss_block, wss_blobs_opt, genesis_state)
wss_state,
wss_block,
wss_blobs_opt,
genesis_state,
)
.unwrap() .unwrap()
.store_migrator_config(MigratorConfig::default().blocking()) .store_migrator_config(MigratorConfig::default().blocking())
.slot_clock(slot_clock) .slot_clock(slot_clock)
@@ -5599,7 +5596,7 @@ async fn weak_subjectivity_sync_gloas_pending_non_aligned() {
let chain = beacon_chain.unwrap(); let chain = beacon_chain.unwrap();
// The head state should be at the block's slot (not advanced to the epoch boundary). // The head state should be at the block's slot
assert_eq!( assert_eq!(
chain.head_snapshot().beacon_state.slot(), chain.head_snapshot().beacon_state.slot(),
Slot::new(epoch_boundary_slot - 1), Slot::new(epoch_boundary_slot - 1),

View File

@@ -127,9 +127,7 @@ impl<E: EthSpec> StateCache<E> {
/// Used by checkpoint sync to initialize the finalized state in the state cache. /// Used by checkpoint sync to initialize the finalized state in the state cache.
/// ///
/// Post-gloas the checkpoint state may not be epoch-aligned, e.g when the epoch boundary /// Post-gloas the checkpoint state may not be epoch-aligned, e.g when the epoch boundary
/// slot is skipped. We relax the epoch-alignment requirement for the initial state only. /// slot is skipped. Regular finalization updates should use `update_finalized_state`.
/// Runtime finalization updates should use [`update_finalized_state`](Self::update_finalized_state),
/// which enforces alignment.
pub fn set_initial_finalized_state( pub fn set_initial_finalized_state(
&mut self, &mut self,
state_root: Hash256, state_root: Hash256,

View File

@@ -398,7 +398,9 @@ where
) -> Result<Self, Error<T::Error>> { ) -> Result<Self, Error<T::Error>> {
// Pre-gloas sanity check: the anchor must lie on an epoch boundary. // Pre-gloas sanity check: the anchor must lie on an epoch boundary.
// Post-gloas we relax this requirement // Post-gloas we relax this requirement
if !anchor_state.fork_name_unchecked().gloas_enabled() && anchor_state.slot() % E::slots_per_epoch() != 0 { if !anchor_state.fork_name_unchecked().gloas_enabled()
&& anchor_state.slot() % E::slots_per_epoch() != 0
{
return Err(Error::InvalidAnchor { return Err(Error::InvalidAnchor {
block_slot: anchor_block.slot(), block_slot: anchor_block.slot(),
state_slot: anchor_state.slot(), state_slot: anchor_state.slot(),