diff --git a/specs/phase0/fast-confirmation.md b/specs/phase0/fast-confirmation.md index f318b7760e..a5542e73be 100644 --- a/specs/phase0/fast-confirmation.md +++ b/specs/phase0/fast-confirmation.md @@ -645,11 +645,9 @@ def is_confirmed_chain_safe(fcr_store: FastConfirmationStore, confirmed_root: Ro starting from current_epoch_observed_justified_checkpoint are LMD-GHOST safe. """ store = fcr_store.store - # Check if the confirmed_root is descendant of current_epoch_observed_justified_checkpoint - if not is_ancestor( - store, - get_node_for_root(confirmed_root), - get_node_for_root(fcr_store.current_epoch_observed_justified_checkpoint.root), + # Check if the confirmed_root has current_epoch_observed_justified_checkpoint in its chain + if fcr_store.current_epoch_observed_justified_checkpoint != get_checkpoint_for_block( + store, confirmed_root, fcr_store.current_epoch_observed_justified_checkpoint.epoch ): return False diff --git a/tests/core/pyspec/eth_consensus_specs/test/phase0/fast_confirmation/test_reconfirmation.py b/tests/core/pyspec/eth_consensus_specs/test/phase0/fast_confirmation/test_reconfirmation.py index 959af77f51..d1f81fb923 100644 --- a/tests/core/pyspec/eth_consensus_specs/test/phase0/fast_confirmation/test_reconfirmation.py +++ b/tests/core/pyspec/eth_consensus_specs/test/phase0/fast_confirmation/test_reconfirmation.py @@ -113,3 +113,73 @@ def test_reconfirmation_passes_with_empty_slots_prior_first_block(spec, state): assert fcr_store.confirmed_root == fcr.head_root() yield from fcr.get_test_artefacts() + + +@only_generator("too slow") +@with_all_phases_from_to(ALTAIR, GLOAS) +@with_presets([MINIMAL], reason="too slow") +@with_custom_state( + balances_fn=(lambda spec: default_balances(spec, num_validators=64)), + threshold_fn=default_activation_threshold, +) +@spec_test +@single_phase +def test_reconfirmation_fails_for_block_without_uj_checkpoint_in_chain(spec, state): + fcr = FCRTest(spec, seed=1) + store, fcr_store = fcr.initialize(state) + + S = spec.SLOTS_PER_EPOCH + + # Run until the last slot of epoch 2 + fcr.run_slots_with_blocks_and_fast_confirmation(3 * S - 1, participation_rate=100) + + # Pivot at the end of epoch 2 + pivot_root = fcr.next_slot_with_block_and_fast_confirmation( + participation_rate=100, graffiti="pivot" + ) + + # Run till the penultimate slot of epoch 3 + fcr.run_slots_with_blocks_and_fast_confirmation(S - 2, participation_rate=100) + + # Create a conflicting block that justifies a checkpoint not in the chain of confirmed block + # while the block of that checkpoint is in the chain of confirmed block + pivot_attestations = [] + for s in range(3 * S, fcr.current_slot()): + pivot_attestations.extend( + fcr.attest( + block_root=pivot_root, participation_rate=100, slot=s, pool_and_disseminate=False + ) + ) + fcr.add_and_apply_block( + parent_root=pivot_root, + graffiti="conflicting", + attestations=pivot_attestations, + release_att_pool=False, + include_atts=False, + ) + + # Fast confirm one block + fcr.next_slot_with_block_and_fast_confirmation(participation_rate=100) + + # Create the last confirmed block in an epoch and move to the next slot + confirmed_root = fcr.next_slot_with_block(participation_rate=100, graffiti="confirmed") + + # Check preconditions over confirmed_root + # From fresh epoch + assert spec.get_block_epoch(store, confirmed_root) + 1 >= fcr.current_epoch() + # Belongs to canonical chain + assert spec.is_ancestor(store, fcr.head(), confirmed_root) + # Ancestor of the previous_epoch_greatest_unrealized_checkpoint block + assert spec.is_ancestor( + store, confirmed_root, fcr_store.previous_epoch_greatest_unrealized_checkpoint.root + ) + # Does not have a previous_epoch_greatest_unrealized_checkpoint in its chain + assert fcr_store.previous_epoch_greatest_unrealized_checkpoint != spec.get_checkpoint_for_block( + store, confirmed_root, fcr_store.previous_epoch_greatest_unrealized_checkpoint.epoch + ) + + # Run fast confirmation and ensure fall back to finality + fcr.run_fast_confirmation() + assert fcr_store.confirmed_root == store.finalized_checkpoint.root + + yield from fcr.get_test_artefacts()