From a99e003d2e2ef7e82338fcd35ac5040fd79727cc Mon Sep 17 00:00:00 2001 From: potuz Date: Mon, 9 Mar 2026 12:31:49 -0300 Subject: [PATCH 1/8] Add PTC Lookbehind --- specs/gloas/beacon-chain.md | 52 +++++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/specs/gloas/beacon-chain.md b/specs/gloas/beacon-chain.md index 6731919605..bf3bf9e542 100644 --- a/specs/gloas/beacon-chain.md +++ b/specs/gloas/beacon-chain.md @@ -54,6 +54,7 @@ - [New `compute_balance_weighted_selection`](#new-compute_balance_weighted_selection) - [New `compute_balance_weighted_acceptance`](#new-compute_balance_weighted_acceptance) - [Modified `compute_proposer_indices`](#modified-compute_proposer_indices) + - [New `compute_ptc`](#new-compute_ptc) - [Beacon state accessors](#beacon-state-accessors) - [Modified `get_next_sync_committee_indices`](#modified-get_next_sync_committee_indices) - [Modified `get_attestation_participation_flag_indices`](#modified-get_attestation_participation_flag_indices) @@ -63,6 +64,7 @@ - [Beacon state mutators](#beacon-state-mutators) - [New `initiate_builder_exit`](#new-initiate_builder_exit) - [Beacon chain state transition function](#beacon-chain-state-transition-function) + - [Modified `process_slots`](#modified-process_slots) - [Modified `process_slot`](#modified-process_slot) - [Epoch processing](#epoch-processing) - [Modified `process_epoch`](#modified-process_epoch) @@ -384,6 +386,8 @@ class BeaconState(Container): latest_block_hash: Hash32 # [New in Gloas:EIP7732] payload_expected_withdrawals: List[Withdrawal, MAX_WITHDRAWALS_PER_PAYLOAD] + # [New in Gloas:EIP7732] + ptc_lookbehind: Vector[Vector[ValidatorIndex, PTC_SIZE], 2] ``` ## Dataclasses @@ -630,6 +634,26 @@ def compute_proposer_indices( ] ``` +#### New `compute_ptc` + +```python +def compute_ptc(state: BeaconState) -> Vector[ValidatorIndex, PTC_SIZE]: + """ + Get the payload timeliness committee for the current slot. + """ + epoch = get_current_epoch(state) + seed = hash(get_seed(state, epoch, DOMAIN_PTC_ATTESTER) + uint_to_bytes(state.slot)) + indices: List[ValidatorIndex] = [] + # Concatenate all committees for this slot in order + committees_per_slot = get_committee_count_per_slot(state, epoch) + for i in range(committees_per_slot): + committee = get_beacon_committee(state, state.slot, CommitteeIndex(i)) + indices.extend(committee) + return compute_balance_weighted_selection( + state, indices, seed, size=PTC_SIZE, shuffle_indices=False + ) +``` + ### Beacon state accessors #### Modified `get_next_sync_committee_indices` @@ -710,17 +734,8 @@ def get_ptc(state: BeaconState, slot: Slot) -> Vector[ValidatorIndex, PTC_SIZE]: """ Get the payload timeliness committee for the given ``slot``. """ - epoch = compute_epoch_at_slot(slot) - seed = hash(get_seed(state, epoch, DOMAIN_PTC_ATTESTER) + uint_to_bytes(slot)) - indices: List[ValidatorIndex] = [] - # Concatenate all committees for this slot in order - committees_per_slot = get_committee_count_per_slot(state, epoch) - for i in range(committees_per_slot): - committee = get_beacon_committee(state, slot, CommitteeIndex(i)) - indices.extend(committee) - return compute_balance_weighted_selection( - state, indices, seed, size=PTC_SIZE, shuffle_indices=False - ) + assert slot == state.slot or slot + 1 == state.slot + return state.ptc_lookbehind[1] if slot == state.slot else state.ptc_lookbehind[0] ``` #### New `get_indexed_payload_attestation` @@ -793,6 +808,21 @@ transitions that trigger an unhandled exception (e.g. a failed `assert` or an out-of-range list access) are considered invalid. State transitions that cause an `uint64` overflow or underflow are also considered invalid. +### Modified `process_slots` + +```python +def process_slots(state: BeaconState, slot: Slot) -> None: + assert state.slot < slot + while state.slot < slot: + process_slot(state) + # Process epoch on the start slot of the next epoch + if (state.slot + 1) % SLOTS_PER_EPOCH == 0: + process_epoch(state) + state.slot = Slot(state.slot + 1) + # [New in Gloas:EIP7732] + state.ptc_lookbehind = [state.ptc_lookbehind[1], compute_ptc(state)] +``` + ### Modified `process_slot` ```python From 215962a95ca9ec251da15351024583e79a10eb36 Mon Sep 17 00:00:00 2001 From: potuz Date: Mon, 9 Mar 2026 22:05:00 -0300 Subject: [PATCH 2/8] Deal with the fork and remove get duties helper --- specs/gloas/fork-choice.md | 2 +- specs/gloas/fork.md | 4 ++++ specs/gloas/validator.md | 26 +++----------------------- 3 files changed, 8 insertions(+), 24 deletions(-) diff --git a/specs/gloas/fork-choice.md b/specs/gloas/fork-choice.md index c1dfe470bb..e7e995501b 100644 --- a/specs/gloas/fork-choice.md +++ b/specs/gloas/fork-choice.md @@ -854,11 +854,11 @@ def on_payload_attestation_message( data = ptc_message.data # PTC attestation must be for a known block. If block is unknown, delay consideration until the block is found state = store.block_states[data.beacon_block_root] - ptc = get_ptc(state, data.slot) # PTC votes can only change the vote for their assigned beacon block, return early otherwise if data.slot != state.slot: return # Check that the attester is from the PTC + ptc = get_ptc(state, data.slot) assert ptc_message.validator_index in ptc # Verify the signature and check that its for the current slot if it is coming from the wire diff --git a/specs/gloas/fork.md b/specs/gloas/fork.md index 88184ae982..7b0f54260a 100644 --- a/specs/gloas/fork.md +++ b/specs/gloas/fork.md @@ -159,10 +159,14 @@ def upgrade_to_gloas(pre: fulu.BeaconState) -> BeaconState: latest_block_hash=pre.latest_execution_payload_header.block_hash, # [New in Gloas:EIP7732] payload_expected_withdrawals=[], + # [New in Gloas:EIP7732] + ptc_lookbehind=[[ValidatorIndex(0)] * PTC_SIZE, [ValidatorIndex(0)] * PTC_SIZE] ) # [New in Gloas:EIP7732] onboard_builders_from_pending_deposits(post) + # [New in Gloas:EIP7732] + ptc_lookbehind[1] = compute_ptc(post) return post ``` diff --git a/specs/gloas/validator.md b/specs/gloas/validator.md index d1d95f72a0..764e938e40 100644 --- a/specs/gloas/validator.md +++ b/specs/gloas/validator.md @@ -48,29 +48,9 @@ validator" to implement Gloas. ### Payload timeliness committee A validator may be a member of the new Payload Timeliness Committee (PTC) for a -given slot. To check for PTC assignments, use -`get_ptc_assignment(state, epoch, validator_index)` where `epoch <= next_epoch`, -as PTC committee selection is only stable within the context of the current and -next epoch. - -```python -def get_ptc_assignment( - state: BeaconState, epoch: Epoch, validator_index: ValidatorIndex -) -> Optional[Slot]: - """ - Returns the slot during the requested epoch in which the validator with - index ``validator_index`` is a member of the PTC. Returns None if no - assignment is found. - """ - next_epoch = Epoch(get_current_epoch(state) + 1) - assert epoch <= next_epoch - - start_slot = compute_start_slot_at_epoch(epoch) - for slot in range(start_slot, start_slot + SLOTS_PER_EPOCH): - if validator_index in get_ptc(state, Slot(slot)): - return Slot(slot) - return None -``` +given slot. Validators can check if their validator index is in the PTC for the current slot +by checking if their validator index is in `get_ptc(state)`. +PTC committee selection is only stable within the context of the current epoch. ### Lookahead From f00224c024b3b53fa095935bee473b9cfe122e48 Mon Sep 17 00:00:00 2001 From: Justin Traglia Date: Wed, 11 Mar 2026 10:02:46 -0500 Subject: [PATCH 3/8] Run make lint & improve ptc_lookbehind initialization --- specs/gloas/fork.md | 4 ++-- specs/gloas/validator.md | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/specs/gloas/fork.md b/specs/gloas/fork.md index 7b0f54260a..06913240d6 100644 --- a/specs/gloas/fork.md +++ b/specs/gloas/fork.md @@ -160,13 +160,13 @@ def upgrade_to_gloas(pre: fulu.BeaconState) -> BeaconState: # [New in Gloas:EIP7732] payload_expected_withdrawals=[], # [New in Gloas:EIP7732] - ptc_lookbehind=[[ValidatorIndex(0)] * PTC_SIZE, [ValidatorIndex(0)] * PTC_SIZE] + ptc_lookbehind=[[ValidatorIndex(0)] * PTC_SIZE for _ in range(2)], ) # [New in Gloas:EIP7732] onboard_builders_from_pending_deposits(post) # [New in Gloas:EIP7732] - ptc_lookbehind[1] = compute_ptc(post) + post.ptc_lookbehind[1] = compute_ptc(post) return post ``` diff --git a/specs/gloas/validator.md b/specs/gloas/validator.md index 764e938e40..6ad162ecc2 100644 --- a/specs/gloas/validator.md +++ b/specs/gloas/validator.md @@ -48,9 +48,9 @@ validator" to implement Gloas. ### Payload timeliness committee A validator may be a member of the new Payload Timeliness Committee (PTC) for a -given slot. Validators can check if their validator index is in the PTC for the current slot -by checking if their validator index is in `get_ptc(state)`. -PTC committee selection is only stable within the context of the current epoch. +given slot. Validators can check if their validator index is in the PTC for the +current slot by checking if their validator index is in `get_ptc(state)`. PTC +committee selection is only stable within the context of the current epoch. ### Lookahead From 141a77abf889fc63f78586f15bf6ba678a13d37d Mon Sep 17 00:00:00 2001 From: Justin Traglia Date: Wed, 11 Mar 2026 10:27:24 -0500 Subject: [PATCH 4/8] Fix heze state & add some basic tests --- specs/heze/beacon-chain.md | 1 + specs/heze/fork.md | 1 + .../test_process_payload_attestation.py | 2 + .../test/gloas/sanity/test_slots.py | 49 +++++++++++++++++++ .../test/helpers/genesis.py | 1 + 5 files changed, 54 insertions(+) diff --git a/specs/heze/beacon-chain.md b/specs/heze/beacon-chain.md index a82797401f..4a9e040f24 100644 --- a/specs/heze/beacon-chain.md +++ b/specs/heze/beacon-chain.md @@ -150,6 +150,7 @@ class BeaconState(Container): builder_pending_withdrawals: List[BuilderPendingWithdrawal, BUILDER_PENDING_WITHDRAWALS_LIMIT] latest_block_hash: Hash32 payload_expected_withdrawals: List[Withdrawal, MAX_WITHDRAWALS_PER_PAYLOAD] + ptc_lookbehind: Vector[Vector[ValidatorIndex, PTC_SIZE], 2] ``` ## Helpers diff --git a/specs/heze/fork.md b/specs/heze/fork.md index 01333da7a5..a50a628de2 100644 --- a/specs/heze/fork.md +++ b/specs/heze/fork.md @@ -103,6 +103,7 @@ def upgrade_to_heze(pre: gloas.BeaconState) -> BeaconState: builder_pending_withdrawals=pre.builder_pending_withdrawals, latest_block_hash=pre.latest_block_hash, payload_expected_withdrawals=pre.payload_expected_withdrawals, + ptc_lookbehind=pre.ptc_lookbehind, ) return post diff --git a/tests/core/pyspec/eth_consensus_specs/test/gloas/block_processing/test_process_payload_attestation.py b/tests/core/pyspec/eth_consensus_specs/test/gloas/block_processing/test_process_payload_attestation.py index 15532e76dd..2c6a4188fa 100644 --- a/tests/core/pyspec/eth_consensus_specs/test/gloas/block_processing/test_process_payload_attestation.py +++ b/tests/core/pyspec/eth_consensus_specs/test/gloas/block_processing/test_process_payload_attestation.py @@ -375,6 +375,8 @@ def test_process_payload_attestation_sampling_not_capped(spec, state): low_balance = spec.EFFECTIVE_BALANCE_INCREMENT for validator in state.validators: validator.effective_balance = low_balance + # Direct balance mutations bypass slot processing, so refresh the cached current-slot PTC. + state.ptc_lookbehind[1] = spec.compute_ptc(state) chosen_slot = None chosen_index = None diff --git a/tests/core/pyspec/eth_consensus_specs/test/gloas/sanity/test_slots.py b/tests/core/pyspec/eth_consensus_specs/test/gloas/sanity/test_slots.py index 6bdd59ed1a..d3ad2652f8 100644 --- a/tests/core/pyspec/eth_consensus_specs/test/gloas/sanity/test_slots.py +++ b/tests/core/pyspec/eth_consensus_specs/test/gloas/sanity/test_slots.py @@ -52,3 +52,52 @@ def test_execution_payload_availability_reset_from_unset(spec, state): yield "post", state assert state.execution_payload_availability[next_slot_index] == 0b0 + + +@with_gloas_and_later +@spec_state_test +def test_ptc_lookbehind_rotates_on_slot_advance(spec, state): + """ + Test that process_slots correctly rotates ptc_lookbehind: + old current becomes previous, new current is freshly computed. + """ + current_ptc = list(state.ptc_lookbehind[1]) + + yield "pre", state + yield "slots", 1 + + spec.process_slots(state, state.slot + 1) + + yield "post", state + + # After advancing, old current should become previous + assert list(state.ptc_lookbehind[0]) == current_ptc + # And new current should be freshly computed for the new slot + assert list(state.ptc_lookbehind[1]) == list(spec.compute_ptc(state)) + + +@with_gloas_and_later +@spec_state_test +def test_ptc_lookbehind_rotates_across_epoch_boundary(spec, state): + """ + Test that ptc_lookbehind correctly rotates when crossing an epoch boundary. + """ + # Advance to the last slot of the epoch + target_slot = spec.SLOTS_PER_EPOCH - 1 + if state.slot < target_slot: + spec.process_slots(state, target_slot) + + current_ptc = list(state.ptc_lookbehind[1]) + + yield "pre", state + yield "slots", 1 + + # Cross the epoch boundary + spec.process_slots(state, state.slot + 1) + + yield "post", state + + # Old current should become previous + assert list(state.ptc_lookbehind[0]) == current_ptc + # New current should be computed for the first slot of the new epoch + assert list(state.ptc_lookbehind[1]) == list(spec.compute_ptc(state)) diff --git a/tests/core/pyspec/eth_consensus_specs/test/helpers/genesis.py b/tests/core/pyspec/eth_consensus_specs/test/helpers/genesis.py index 45d25251b0..ff11e0a971 100644 --- a/tests/core/pyspec/eth_consensus_specs/test/helpers/genesis.py +++ b/tests/core/pyspec/eth_consensus_specs/test/helpers/genesis.py @@ -252,6 +252,7 @@ def create_genesis_state(spec, validator_balances, activation_threshold): spec.BuilderPendingPayment() for _ in range(2 * spec.SLOTS_PER_EPOCH) ] state.builder_pending_withdrawals = [] + state.ptc_lookbehind[1] = spec.compute_ptc(state) if is_post_fulu(spec): # Initialize proposer lookahead list From 3622fc24c7d74ca85fd35f3c62cf9c60e42f6d99 Mon Sep 17 00:00:00 2001 From: Justin Traglia Date: Wed, 11 Mar 2026 10:51:09 -0500 Subject: [PATCH 5/8] Fix test_process_payload_attestation_too_old_slot test --- .../block_processing/test_process_payload_attestation.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/core/pyspec/eth_consensus_specs/test/gloas/block_processing/test_process_payload_attestation.py b/tests/core/pyspec/eth_consensus_specs/test/gloas/block_processing/test_process_payload_attestation.py index 2c6a4188fa..920c3ea7f7 100644 --- a/tests/core/pyspec/eth_consensus_specs/test/gloas/block_processing/test_process_payload_attestation.py +++ b/tests/core/pyspec/eth_consensus_specs/test/gloas/block_processing/test_process_payload_attestation.py @@ -232,11 +232,12 @@ def test_process_payload_attestation_too_old_slot(spec, state): """ Test payload attestation for slot too far in the past fails """ - # Advance state to slot 3 - spec.process_slots(state, state.slot + 3) + # Advance to slot 1 and create a valid attestation for slot 0 + spec.process_slots(state, state.slot + 1) + payload_attestation = prepare_signed_payload_attestation(spec, state) - # Try to attest to slot 0 (2 slots ago, should be 1 slot ago) - payload_attestation = prepare_signed_payload_attestation(spec, state, slot=state.slot - 2) + # Advance again so the attestation becomes too old (slot 0 is now 2 slots behind) + spec.process_slots(state, state.slot + 1) yield from run_payload_attestation_processing(spec, state, payload_attestation, valid=False) From 69c1db1d41d4c8f61b4fedfeaf18b34f538f6bd3 Mon Sep 17 00:00:00 2001 From: Justin Traglia Date: Thu, 12 Mar 2026 09:43:09 -0500 Subject: [PATCH 6/8] Check that PTCs are different in tests --- .../test/gloas/sanity/test_slots.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/core/pyspec/eth_consensus_specs/test/gloas/sanity/test_slots.py b/tests/core/pyspec/eth_consensus_specs/test/gloas/sanity/test_slots.py index d3ad2652f8..07c6b0111b 100644 --- a/tests/core/pyspec/eth_consensus_specs/test/gloas/sanity/test_slots.py +++ b/tests/core/pyspec/eth_consensus_specs/test/gloas/sanity/test_slots.py @@ -70,10 +70,13 @@ def test_ptc_lookbehind_rotates_on_slot_advance(spec, state): yield "post", state + new_ptc = list(state.ptc_lookbehind[1]) + # Sanity: the two PTCs should differ, so the rotation test is meaningful + assert current_ptc != new_ptc # After advancing, old current should become previous assert list(state.ptc_lookbehind[0]) == current_ptc # And new current should be freshly computed for the new slot - assert list(state.ptc_lookbehind[1]) == list(spec.compute_ptc(state)) + assert new_ptc == list(spec.compute_ptc(state)) @with_gloas_and_later @@ -97,7 +100,10 @@ def test_ptc_lookbehind_rotates_across_epoch_boundary(spec, state): yield "post", state + new_ptc = list(state.ptc_lookbehind[1]) + # Sanity: the two PTCs should differ, so the rotation test is meaningful + assert current_ptc != new_ptc # Old current should become previous assert list(state.ptc_lookbehind[0]) == current_ptc # New current should be computed for the first slot of the new epoch - assert list(state.ptc_lookbehind[1]) == list(spec.compute_ptc(state)) + assert new_ptc == list(spec.compute_ptc(state)) From b60f6f981a1410540773f98cd07a1cbd1f6f8580 Mon Sep 17 00:00:00 2001 From: Justin Traglia Date: Thu, 12 Mar 2026 12:16:16 -0500 Subject: [PATCH 7/8] Split PTC lookbehind into two fields --- specs/gloas/beacon-chain.md | 9 ++++-- specs/gloas/fork.md | 6 ++-- specs/heze/beacon-chain.md | 3 +- specs/heze/fork.md | 3 +- .../test_process_payload_attestation.py | 2 +- .../test/gloas/sanity/test_slots.py | 28 +++++++++---------- .../test/helpers/genesis.py | 2 +- 7 files changed, 30 insertions(+), 23 deletions(-) diff --git a/specs/gloas/beacon-chain.md b/specs/gloas/beacon-chain.md index bf3bf9e542..55cd2c10d6 100644 --- a/specs/gloas/beacon-chain.md +++ b/specs/gloas/beacon-chain.md @@ -387,7 +387,9 @@ class BeaconState(Container): # [New in Gloas:EIP7732] payload_expected_withdrawals: List[Withdrawal, MAX_WITHDRAWALS_PER_PAYLOAD] # [New in Gloas:EIP7732] - ptc_lookbehind: Vector[Vector[ValidatorIndex, PTC_SIZE], 2] + previous_ptc: Vector[ValidatorIndex, PTC_SIZE] + # [New in Gloas:EIP7732] + current_ptc: Vector[ValidatorIndex, PTC_SIZE] ``` ## Dataclasses @@ -735,7 +737,7 @@ def get_ptc(state: BeaconState, slot: Slot) -> Vector[ValidatorIndex, PTC_SIZE]: Get the payload timeliness committee for the given ``slot``. """ assert slot == state.slot or slot + 1 == state.slot - return state.ptc_lookbehind[1] if slot == state.slot else state.ptc_lookbehind[0] + return state.current_ptc if slot == state.slot else state.previous_ptc ``` #### New `get_indexed_payload_attestation` @@ -820,7 +822,8 @@ def process_slots(state: BeaconState, slot: Slot) -> None: process_epoch(state) state.slot = Slot(state.slot + 1) # [New in Gloas:EIP7732] - state.ptc_lookbehind = [state.ptc_lookbehind[1], compute_ptc(state)] + state.previous_ptc = state.current_ptc + state.current_ptc = compute_ptc(state) ``` ### Modified `process_slot` diff --git a/specs/gloas/fork.md b/specs/gloas/fork.md index 06913240d6..06224f5197 100644 --- a/specs/gloas/fork.md +++ b/specs/gloas/fork.md @@ -160,13 +160,15 @@ def upgrade_to_gloas(pre: fulu.BeaconState) -> BeaconState: # [New in Gloas:EIP7732] payload_expected_withdrawals=[], # [New in Gloas:EIP7732] - ptc_lookbehind=[[ValidatorIndex(0)] * PTC_SIZE for _ in range(2)], + previous_ptc=[ValidatorIndex(0)] * PTC_SIZE, + # [New in Gloas:EIP7732] + current_ptc=[ValidatorIndex(0)] * PTC_SIZE, ) # [New in Gloas:EIP7732] onboard_builders_from_pending_deposits(post) # [New in Gloas:EIP7732] - post.ptc_lookbehind[1] = compute_ptc(post) + post.current_ptc = compute_ptc(post) return post ``` diff --git a/specs/heze/beacon-chain.md b/specs/heze/beacon-chain.md index 4a9e040f24..2a3bc598ce 100644 --- a/specs/heze/beacon-chain.md +++ b/specs/heze/beacon-chain.md @@ -150,7 +150,8 @@ class BeaconState(Container): builder_pending_withdrawals: List[BuilderPendingWithdrawal, BUILDER_PENDING_WITHDRAWALS_LIMIT] latest_block_hash: Hash32 payload_expected_withdrawals: List[Withdrawal, MAX_WITHDRAWALS_PER_PAYLOAD] - ptc_lookbehind: Vector[Vector[ValidatorIndex, PTC_SIZE], 2] + previous_ptc: Vector[ValidatorIndex, PTC_SIZE] + current_ptc: Vector[ValidatorIndex, PTC_SIZE] ``` ## Helpers diff --git a/specs/heze/fork.md b/specs/heze/fork.md index a50a628de2..333652ea7e 100644 --- a/specs/heze/fork.md +++ b/specs/heze/fork.md @@ -103,7 +103,8 @@ def upgrade_to_heze(pre: gloas.BeaconState) -> BeaconState: builder_pending_withdrawals=pre.builder_pending_withdrawals, latest_block_hash=pre.latest_block_hash, payload_expected_withdrawals=pre.payload_expected_withdrawals, - ptc_lookbehind=pre.ptc_lookbehind, + previous_ptc=pre.previous_ptc, + current_ptc=pre.current_ptc, ) return post diff --git a/tests/core/pyspec/eth_consensus_specs/test/gloas/block_processing/test_process_payload_attestation.py b/tests/core/pyspec/eth_consensus_specs/test/gloas/block_processing/test_process_payload_attestation.py index 920c3ea7f7..d09bed7fd6 100644 --- a/tests/core/pyspec/eth_consensus_specs/test/gloas/block_processing/test_process_payload_attestation.py +++ b/tests/core/pyspec/eth_consensus_specs/test/gloas/block_processing/test_process_payload_attestation.py @@ -377,7 +377,7 @@ def test_process_payload_attestation_sampling_not_capped(spec, state): for validator in state.validators: validator.effective_balance = low_balance # Direct balance mutations bypass slot processing, so refresh the cached current-slot PTC. - state.ptc_lookbehind[1] = spec.compute_ptc(state) + state.current_ptc = spec.compute_ptc(state) chosen_slot = None chosen_index = None diff --git a/tests/core/pyspec/eth_consensus_specs/test/gloas/sanity/test_slots.py b/tests/core/pyspec/eth_consensus_specs/test/gloas/sanity/test_slots.py index 07c6b0111b..3764e40d52 100644 --- a/tests/core/pyspec/eth_consensus_specs/test/gloas/sanity/test_slots.py +++ b/tests/core/pyspec/eth_consensus_specs/test/gloas/sanity/test_slots.py @@ -56,12 +56,12 @@ def test_execution_payload_availability_reset_from_unset(spec, state): @with_gloas_and_later @spec_state_test -def test_ptc_lookbehind_rotates_on_slot_advance(spec, state): +def test_ptc_rotates_on_slot_advance(spec, state): """ - Test that process_slots correctly rotates ptc_lookbehind: + Test that process_slots correctly rotates previous_ptc/current_ptc: old current becomes previous, new current is freshly computed. """ - current_ptc = list(state.ptc_lookbehind[1]) + old_current_ptc = list(state.current_ptc) yield "pre", state yield "slots", 1 @@ -70,27 +70,27 @@ def test_ptc_lookbehind_rotates_on_slot_advance(spec, state): yield "post", state - new_ptc = list(state.ptc_lookbehind[1]) + new_current_ptc = list(state.current_ptc) # Sanity: the two PTCs should differ, so the rotation test is meaningful - assert current_ptc != new_ptc + assert old_current_ptc != new_current_ptc # After advancing, old current should become previous - assert list(state.ptc_lookbehind[0]) == current_ptc + assert list(state.previous_ptc) == old_current_ptc # And new current should be freshly computed for the new slot - assert new_ptc == list(spec.compute_ptc(state)) + assert new_current_ptc == list(spec.compute_ptc(state)) @with_gloas_and_later @spec_state_test -def test_ptc_lookbehind_rotates_across_epoch_boundary(spec, state): +def test_ptc_rotates_across_epoch_boundary(spec, state): """ - Test that ptc_lookbehind correctly rotates when crossing an epoch boundary. + Test that previous_ptc/current_ptc correctly rotate when crossing an epoch boundary. """ # Advance to the last slot of the epoch target_slot = spec.SLOTS_PER_EPOCH - 1 if state.slot < target_slot: spec.process_slots(state, target_slot) - current_ptc = list(state.ptc_lookbehind[1]) + old_current_ptc = list(state.current_ptc) yield "pre", state yield "slots", 1 @@ -100,10 +100,10 @@ def test_ptc_lookbehind_rotates_across_epoch_boundary(spec, state): yield "post", state - new_ptc = list(state.ptc_lookbehind[1]) + new_current_ptc = list(state.current_ptc) # Sanity: the two PTCs should differ, so the rotation test is meaningful - assert current_ptc != new_ptc + assert old_current_ptc != new_current_ptc # Old current should become previous - assert list(state.ptc_lookbehind[0]) == current_ptc + assert list(state.previous_ptc) == old_current_ptc # New current should be computed for the first slot of the new epoch - assert new_ptc == list(spec.compute_ptc(state)) + assert new_current_ptc == list(spec.compute_ptc(state)) diff --git a/tests/core/pyspec/eth_consensus_specs/test/helpers/genesis.py b/tests/core/pyspec/eth_consensus_specs/test/helpers/genesis.py index ff11e0a971..05d06ceb5e 100644 --- a/tests/core/pyspec/eth_consensus_specs/test/helpers/genesis.py +++ b/tests/core/pyspec/eth_consensus_specs/test/helpers/genesis.py @@ -252,7 +252,7 @@ def create_genesis_state(spec, validator_balances, activation_threshold): spec.BuilderPendingPayment() for _ in range(2 * spec.SLOTS_PER_EPOCH) ] state.builder_pending_withdrawals = [] - state.ptc_lookbehind[1] = spec.compute_ptc(state) + state.current_ptc = spec.compute_ptc(state) if is_post_fulu(spec): # Initialize proposer lookahead list From d76a278b0ad110ea2e0098c97d37c814680d66fc Mon Sep 17 00:00:00 2001 From: Justin Traglia Date: Thu, 12 Mar 2026 12:18:38 -0500 Subject: [PATCH 8/8] Add extra fork comment to be extra explicit --- specs/gloas/beacon-chain.md | 1 + 1 file changed, 1 insertion(+) diff --git a/specs/gloas/beacon-chain.md b/specs/gloas/beacon-chain.md index 55cd2c10d6..ca84b30eaa 100644 --- a/specs/gloas/beacon-chain.md +++ b/specs/gloas/beacon-chain.md @@ -823,6 +823,7 @@ def process_slots(state: BeaconState, slot: Slot) -> None: state.slot = Slot(state.slot + 1) # [New in Gloas:EIP7732] state.previous_ptc = state.current_ptc + # [New in Gloas:EIP7732] state.current_ptc = compute_ptc(state) ```