Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
67 changes: 57 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_lookbehind`](#new-process_ptc_lookbehind)
- [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_lookbehind: Vector[Vector[ValidatorIndex, PTC_SIZE], (2 + MIN_SEED_LOOKAHEAD)* SLOTS_PER_EPOCH]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

ptc_lookbehind is not entirely accurate right? there's look ahead too.
how about rename it to ptc_window

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 agree that it should be renamed. ptc_window or ptc_cache seem fine to me.

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.

what about ptc_assignments?

```

## 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.
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_lookbehind` 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_lookbehind[slot % SLOTS_PER_EPOCH]
assert epoch <= state_epoch + MIN_SEED_LOOKAHEAD
offset = (epoch - state_epoch + 1) * SLOTS_PER_EPOCH
return state.ptc_lookbehind[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_lookbehind`.

```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_lookbehind(state)
```

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

#### New `process_ptc_lookbehind`

```python
def process_ptc_lookbehind(state: BeaconState) -> None:
"""
Update the cached PTC window.
"""
# Shift out PTC members in the first epoch
last_epoch_start = len(state.ptc_lookbehind) - SLOTS_PER_EPOCH
state.ptc_lookbehind[:last_epoch_start] = state.ptc_lookbehind[SLOTS_PER_EPOCH:]
# Fill in the last epoch with the PTC for the epoch that starts after this boundary
next_epoch = Epoch(get_current_epoch(state) + MIN_SEED_LOOKAHEAD + 1)
start_slot = compute_start_slot_at_epoch(next_epoch)
last_epoch_committees = [
compute_ptc(state, Slot(slot)) for slot in range(start_slot, start_slot + SLOTS_PER_EPOCH)
]
state.ptc_lookbehind[last_epoch_start:] = last_epoch_committees
```

### Block processing

```python
Expand Down
23 changes: 23 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_lookbehind`](#new-initialize_ptc_lookbehind)
- [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,26 @@ Warning: this configuration is not definitive.

## Helpers

### New `initialize_ptc_lookbehind`

```python
def initialize_ptc_lookbehind(
state: BeaconState,
) -> Vector[Vector[ValidatorIndex, PTC_SIZE], 2 * SLOTS_PER_EPOCH]:
"""
Return the cached PTC window starting from the current epoch.
Used to initialize the ``ptc_lookbehind`` field in the beacon state at genesis and after forks.
"""
current_epoch = get_current_epoch(state)
start_slot = compute_start_slot_at_epoch(current_epoch)
empty_previous_epoch = [
Vector[ValidatorIndex, PTC_SIZE]([ValidatorIndex(0) for _ in range(PTC_SIZE)])
for _ in range(SLOTS_PER_EPOCH)
]
current_epoch_ptc = [compute_ptc(state, Slot(start_slot + i)) for i in range(SLOTS_PER_EPOCH)]
return empty_previous_epoch + current_epoch_ptc
```

### New `onboard_builders_from_pending_deposits`

```python
Expand Down Expand Up @@ -159,6 +180,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_lookbehind=initialize_ptc_lookbehind(pre),
)

# [New in Gloas:EIP7732]
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_lookbehind: Vector[Vector[ValidatorIndex, PTC_SIZE], 2 * 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_lookbehind=pre.ptc_lookbehind,
)

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_lookbehind = spec.initialize_ptc_lookbehind(state)

chosen_slot = None
chosen_index = None
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
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


def _compute_epoch_ptc(spec, state, epoch):
start_slot = spec.compute_start_slot_at_epoch(epoch)
return [
spec.compute_ptc(state, spec.Slot(slot))
for slot in range(start_slot, start_slot + spec.SLOTS_PER_EPOCH)
]


@with_phases([GLOAS])
@spec_state_test
@single_phase
def test_process_ptc_lookbehind_rotates_to_next_epoch(spec, state):
spec.process_slots(state, state.slot + spec.SLOTS_PER_EPOCH - 1)

current_epoch = spec.get_current_epoch(state)
current_epoch_ptc = _compute_epoch_ptc(spec, state, current_epoch)
state.ptc_lookbehind = current_epoch_ptc + current_epoch_ptc

next_epoch = spec.Epoch(current_epoch + 1)
next_epoch_ptc = _compute_epoch_ptc(spec, state, next_epoch)

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

assert list(state.ptc_lookbehind[: spec.SLOTS_PER_EPOCH]) == current_epoch_ptc
assert list(state.ptc_lookbehind[spec.SLOTS_PER_EPOCH :]) == next_epoch_ptc

# run_epoch_processing_with does not increment the slot
state.slot += 1

assert spec.get_ptc(state, spec.Slot(state.slot - 1)) == current_epoch_ptc[-1]
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_lookbehind = spec.initialize_ptc_lookbehind(state)

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