From 69d2ae206b2c41d37d4c92085d194c0f1fc7687d Mon Sep 17 00:00:00 2001 From: Mikhail Date: Sat, 23 May 2026 18:56:54 +0500 Subject: [PATCH 1/2] Check if block has uj chkp in its chain --- specs/phase0/fast-confirmation.md | 6 +- .../fast_confirmation/test_reconfirmation.py | 68 +++++++++++++++++++ 2 files changed, 71 insertions(+), 3 deletions(-) diff --git a/specs/phase0/fast-confirmation.md b/specs/phase0/fast-confirmation.md index 86dfd0aefb..d972254685 100644 --- a/specs/phase0/fast-confirmation.md +++ b/specs/phase0/fast-confirmation.md @@ -648,9 +648,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, confirmed_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 171de1ff05..bf471ea1c5 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 @@ -111,3 +111,71 @@ def test_reconfirmation_passes_with_empty_slots_prior_first_block(spec, state): assert fcr_store.confirmed_root == fcr.head() 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 From 41caaeacdef3809f2e3a209c0a9b879542c25da4 Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Tue, 26 May 2026 23:08:37 +0500 Subject: [PATCH 2/2] Yield test data --- .../test/phase0/fast_confirmation/test_reconfirmation.py | 2 ++ 1 file changed, 2 insertions(+) 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 bf471ea1c5..63415a829f 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 @@ -179,3 +179,5 @@ def test_reconfirmation_fails_for_block_without_uj_checkpoint_in_chain(spec, sta # 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()