Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 41 additions & 11 deletions specs/gloas/beacon-chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be previous_ptc, current_ptc which is ugly as well, but more like previous forks structures.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've just made this change. I prefer this.

Comment thread
jtraglia marked this conversation as resolved.
Outdated
```

## Dataclasses
Expand Down Expand Up @@ -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`
Expand Down Expand Up @@ -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
Comment thread
jtraglia marked this conversation as resolved.

@ensi321 ensi321 Mar 16, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this change makes get_ptc to be too restrictive on the range of slot it accepts. Validator's PTC assignment should still be determined ahead on a per-epoch basis instead of per-slot basis.

I think we should accept slot [previous slot, end slot of state's current epoch]. Maybe something like

def get_ptc(state: BeaconState, slot: Slot) -> Vector[ValidatorIndex, PTC_SIZE]:                                                     
    epoch = get_current_epoch(state)                                                                                                 
    epoch_start_slot = compute_start_slot_at_epoch(epoch)
    epoch_end_slot = epoch_state_slot + SLOTS_PER_EPOCH                                                      
    assert slot >= state.slot - 1 and slot < epoch_end_slot
    if slot == state.slot:                                                                                                           
        return state.current_ptc                                                                                                     
    if slot + 1 == state.slot:                                                                                                       
        return state.previous_ptc                                                                                                                                                                  
    return compute_ptc(state, slot)                              

So get_ptc_assignment can still take advantage of this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clients are free to simply pass the slot to conpute_ptc for caching purposes. Why should we have these off-protocol elements leaking in the spec?

OTOH one think that should be considered IMO is that if clients will anyway cache them, then most likely having the full cache in the spec is more efficient than keeping it in an ad-hoc in-memory cache that needs to be in-sync with the head state.

return state.ptc_lookbehind[1] if slot == state.slot else state.ptc_lookbehind[0]
```

#### New `get_indexed_payload_attestation`
Expand Down Expand Up @@ -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)]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be moved to process_slot but it's uglier.

```

### Modified `process_slot`

```python
Expand Down
2 changes: 1 addition & 1 deletion specs/gloas/fork-choice.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions specs/gloas/fork.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Comment thread
jtraglia marked this conversation as resolved.
Outdated
)

# [New in Gloas:EIP7732]
onboard_builders_from_pending_deposits(post)
# [New in Gloas:EIP7732]
ptc_lookbehind[1] = compute_ptc(post)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added outside because compute_ptc should only make sense on Gloas states.


return post
```
26 changes: 3 additions & 23 deletions specs/gloas/validator.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sentence was factually wrong which is what started this issue. Removed the helper entirely since there is no need to specify it.


```python
def get_ptc_assignment(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving a note that get_ptc_assignment is still being used in three sections below.

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem to hold anymore.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right above it says:

PTC committee selection is only stable within the context of the current epoch.

which means there is no 1 epoch lookahead.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The committee selection is stable within the context of the current epoch, it doesn't mean that the epoch needs to be cached in the state. It means that any validator that wants to check if it has PTC duties in the current epoch, can do so at the beginning of the epoch. Clients will most likely implement this and cache it no matter which lookahead/lookbehind system we implement in-state.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lookahead section states that PTC is stable for next epoch.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

which section? I'm replying only to your comment that is based on the sentence:

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lookahead section states that PTC is stable for next epoch.

Lookahead section. I left this comment on Lookahead section.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yeah what Github shows is confusing.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

L77-L78:

get_ptc_assignment should be called at the start of each epoch to get the
assignment for the next epoch (current_epoch + 1). A validator should plan for

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh yeah that's definitely wrong.


Expand Down
Loading