Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
65 changes: 55 additions & 10 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 @@ -67,6 +68,7 @@
- [Epoch processing](#epoch-processing)
- [Modified `process_epoch`](#modified-process_epoch)
- [New `process_builder_pending_payments`](#new-process_builder_pending_payments)
- [New `process_ptc_window`](#new-process_ptc_window)
- [Block processing](#block-processing)
- [Withdrawals](#withdrawals)
- [New `get_builder_withdrawals`](#new-get_builder_withdrawals)
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_window: Vector[Vector[ValidatorIndex, PTC_SIZE], (2 + MIN_SEED_LOOKAHEAD) * SLOTS_PER_EPOCH]
```

## Dataclasses
Expand Down Expand Up @@ -630,6 +634,26 @@ def compute_proposer_indices(
]
```

#### New `compute_ptc`

```python
def compute_ptc(state: BeaconState, slot: Slot) -> Vector[ValidatorIndex, PTC_SIZE]:
Comment thread
jtraglia marked this conversation as resolved.
"""
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
)
```

### Beacon state accessors

#### Modified `get_next_sync_committee_indices`
Expand Down Expand Up @@ -705,22 +729,21 @@ def get_attestation_participation_flag_indices(

#### New `get_ptc`

*Note*: `get_ptc` uses the cached `ptc_window` for lookups.

```python
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
)
state_epoch = get_current_epoch(state)
if epoch < state_epoch:
assert epoch + 1 == state_epoch
return state.ptc_window[slot % SLOTS_PER_EPOCH]
assert epoch <= state_epoch + MIN_SEED_LOOKAHEAD
offset = (epoch - state_epoch + 1) * SLOTS_PER_EPOCH
return state.ptc_window[offset + slot % SLOTS_PER_EPOCH]
```

#### New `get_indexed_payload_attestation`
Expand Down Expand Up @@ -815,6 +838,9 @@ def process_slot(state: BeaconState) -> None:

#### Modified `process_epoch`

*Note*: The function `process_epoch` is modified in Gloas to call the new
helpers `process_builder_pending_payments` and `process_ptc_window`.

```python
def process_epoch(state: BeaconState) -> None:
process_justification_and_finalization(state)
Expand All @@ -834,6 +860,8 @@ def process_epoch(state: BeaconState) -> None:
process_participation_flag_updates(state)
process_sync_committee_updates(state)
process_proposer_lookahead(state)
# [New in Gloas:EIP7732]
process_ptc_window(state)
```

#### New `process_builder_pending_payments`
Expand All @@ -853,6 +881,23 @@ def process_builder_pending_payments(state: BeaconState) -> None:
state.builder_pending_payments = old_payments + new_payments
```

#### New `process_ptc_window`

```python
def process_ptc_window(state: BeaconState) -> None:
"""
Update the cached PTC window.
"""
# Shift all epochs forward by one
state.ptc_window[: len(state.ptc_window) - SLOTS_PER_EPOCH] = state.ptc_window[SLOTS_PER_EPOCH:]
# Fill in the last epoch
next_epoch = Epoch(get_current_epoch(state) + MIN_SEED_LOOKAHEAD + 1)
start_slot = compute_start_slot_at_epoch(next_epoch)
state.ptc_window[len(state.ptc_window) - SLOTS_PER_EPOCH :] = [
compute_ptc(state, Slot(slot)) for slot in range(start_slot, start_slot + SLOTS_PER_EPOCH)
]
```

### Block processing

```python
Expand Down
28 changes: 28 additions & 0 deletions specs/gloas/fork.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- [Introduction](#introduction)
- [Configuration](#configuration)
- [Helpers](#helpers)
- [New `initialize_ptc_window`](#new-initialize_ptc_window)
- [New `onboard_builders_from_pending_deposits`](#new-onboard_builders_from_pending_deposits)
- [Fork to Gloas](#fork-to-gloas)
- [Fork trigger](#fork-trigger)
Expand All @@ -29,6 +30,31 @@ Warning: this configuration is not definitive.

## Helpers

### New `initialize_ptc_window`

```python
def initialize_ptc_window(
state: BeaconState,
) -> Vector[Vector[ValidatorIndex, PTC_SIZE], (2 + MIN_SEED_LOOKAHEAD) * SLOTS_PER_EPOCH]:
"""
Return the cached PTC window starting from the current epoch.
Used to initialize the ``ptc_window`` field in the beacon state at genesis and after forks.
"""
empty_previous_epoch = [
Vector[ValidatorIndex, PTC_SIZE]([ValidatorIndex(0) for _ in range(PTC_SIZE)])
for _ in range(SLOTS_PER_EPOCH)
]

ptcs = []
current_epoch = get_current_epoch(state)
for e in range(1 + MIN_SEED_LOOKAHEAD):
epoch = Epoch(current_epoch + e)
start_slot = compute_start_slot_at_epoch(epoch)
ptcs += [compute_ptc(state, Slot(start_slot + i)) for i in range(SLOTS_PER_EPOCH)]

return empty_previous_epoch + ptcs
```

### New `onboard_builders_from_pending_deposits`

```python
Expand Down Expand Up @@ -159,6 +185,8 @@ 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_window=initialize_ptc_window(pre),
)

# [New in Gloas:EIP7732]
Expand Down
11 changes: 6 additions & 5 deletions specs/gloas/validator.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,10 @@ validator" to implement Gloas.

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.
`get_ptc_assignment(state, epoch, validator_index)` where
`epoch <= get_current_epoch(state) + MIN_SEED_LOOKAHEAD`, as PTC committee
selection is only stable within the context of the current and next epochs in
the lookahead.

```python
def get_ptc_assignment(
Expand All @@ -62,8 +63,8 @@ def get_ptc_assignment(
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
max_epoch = Epoch(get_current_epoch(state) + MIN_SEED_LOOKAHEAD)
assert epoch <= max_epoch

start_slot = compute_start_slot_at_epoch(epoch)
for slot in range(start_slot, start_slot + SLOTS_PER_EPOCH):
Expand Down
1 change: 1 addition & 0 deletions specs/heze/beacon-chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -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_window: Vector[Vector[ValidatorIndex, PTC_SIZE], (2 + MIN_SEED_LOOKAHEAD) * SLOTS_PER_EPOCH]
```

## Helpers
Expand Down
1 change: 1 addition & 0 deletions specs/heze/fork.md
Original file line number Diff line number Diff line change
Expand Up @@ -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_window=pre.ptc_window,
)

return post
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 epoch processing, so refresh the cached current-epoch PTC.
state.ptc_window = spec.initialize_ptc_window(state)

chosen_slot = None
chosen_index = None
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from eth_consensus_specs.test.context import (
single_phase,
spec_state_test,
with_phases,
)
from eth_consensus_specs.test.helpers.constants import GLOAS
from eth_consensus_specs.test.helpers.epoch_processing import run_epoch_processing_with


@with_phases([GLOAS])
@spec_state_test
@single_phase
def test_process_ptc_window__shifts_all_epochs(spec, state):
"""
Verify that process_ptc_window shifts prev/curr/next correctly
and that get_ptc returns the right committees afterwards.
"""
spec.process_slots(state, state.slot + 2 * spec.SLOTS_PER_EPOCH - 1)

SPE = spec.SLOTS_PER_EPOCH
# Save current and next epoch sections before the shift
curr_epoch_ptc = list(state.ptc_window[SPE : 2 * SPE])
next_epoch_ptc = list(state.ptc_window[2 * SPE : 3 * SPE])

yield from run_epoch_processing_with(spec, state, "process_ptc_window")

# After shift: [curr, next, new_next]
assert list(state.ptc_window[:SPE]) == curr_epoch_ptc
assert list(state.ptc_window[SPE : 2 * SPE]) == next_epoch_ptc

# run_epoch_processing_with does not increment the slot, so do it manually
state.slot += 1

# Now state_epoch = current_epoch + 1
# Previous epoch lookup (current_epoch) should hit the first section
assert spec.get_ptc(state, spec.Slot(state.slot - 1)) == curr_epoch_ptc[-1]
# Current epoch lookup should hit the second section
assert spec.get_ptc(state, state.slot) == next_epoch_ptc[0]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
from eth_consensus_specs.test.context import (
expect_assertion_error,
single_phase,
spec_test,
with_phases,
with_state,
)
from eth_consensus_specs.test.helpers.constants import GLOAS
from eth_consensus_specs.test.helpers.state import next_epoch


def _compute_first_ptc_assignments(spec, state, epoch):
assignments = {}
start_slot = spec.compute_start_slot_at_epoch(epoch)
for slot in range(start_slot, start_slot + spec.SLOTS_PER_EPOCH):
for validator_index in spec.compute_ptc(state, spec.Slot(slot)):
assignments.setdefault(validator_index, spec.Slot(slot))
return assignments


def _run_get_ptc_assignments(spec, state, epoch, valid=True, assignments=None):
if not valid:
expect_assertion_error(
lambda: spec.get_ptc_assignment(state, epoch, spec.ValidatorIndex(0))
)
return

if assignments is None:
assignments = _compute_first_ptc_assignments(spec, state, epoch)
_assert_get_ptc_assignments(spec, state, epoch, assignments)


def _assert_get_ptc_assignments(spec, state, epoch, assignments):
assert len(assignments) > 0

for validator_index, expected_slot in assignments.items():
assert spec.get_ptc_assignment(state, epoch, validator_index) == expected_slot

unassigned_validator = next(
(spec.ValidatorIndex(i) for i in range(len(state.validators)) if i not in assignments),
None,
)
if unassigned_validator is not None:
assert spec.get_ptc_assignment(state, epoch, unassigned_validator) is None


@with_phases([GLOAS])
@spec_test
@with_state
@single_phase
def test_get_ptc_assignment__current_epoch_minus_2(spec, state):
next_epoch(spec, state)
next_epoch(spec, state)

epoch = spec.Epoch(spec.get_current_epoch(state) - 2)
_run_get_ptc_assignments(spec, state, epoch, valid=False)


@with_phases([GLOAS])
@spec_test
@with_state
@single_phase
def test_get_ptc_assignment__current_epoch_minus_1(spec, state):
previous_epoch = spec.get_current_epoch(state)
previous_assignments = _compute_first_ptc_assignments(spec, state, previous_epoch)

next_epoch(spec, state)

_run_get_ptc_assignments(
spec,
state,
previous_epoch,
valid=True,
assignments=previous_assignments,
)


@with_phases([GLOAS])
@spec_test
@with_state
@single_phase
def test_get_ptc_assignment__current_epoch(spec, state):
epoch = spec.get_current_epoch(state)
_run_get_ptc_assignments(spec, state, epoch, valid=True)


@with_phases([GLOAS])
@spec_test
@with_state
@single_phase
def test_get_ptc_assignment__current_epoch_plus_1(spec, state):
epoch = spec.Epoch(spec.get_current_epoch(state) + 1)
_run_get_ptc_assignments(spec, state, epoch, valid=True)


@with_phases([GLOAS])
@spec_test
@with_state
@single_phase
def test_get_ptc_assignment__current_epoch_plus_2(spec, state):
epoch = spec.Epoch(spec.get_current_epoch(state) + 2)
_run_get_ptc_assignments(spec, state, epoch, valid=False)
Original file line number Diff line number Diff line change
Expand Up @@ -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_window = spec.initialize_ptc_window(state)

if is_post_fulu(spec):
# Initialize proposer lookahead list
Expand Down