Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
e73b5d8
EIP-XXXX Stabilize next epoch proposer lookahead
linoscope Mar 23, 2025
fb3cd63
EIP-XXXX add simple happy case tests
linoscope Mar 23, 2025
3fbe39c
fixup: Code clean up
linoscope Mar 24, 2025
0a37fe7
fixup: Target correct fork
linoscope Mar 24, 2025
a4dd9eb
Merge branch 'dev' into stabalize-next-epoch-lookahead
jtraglia Mar 24, 2025
9b82cb4
Clean up comment
linoscope Mar 25, 2025
7485399
refactor based on review comments
linoscope Mar 25, 2025
6a1d923
fix fork check in genesis helper
linoscope Mar 25, 2025
8fb1a58
improve comments
linoscope Mar 25, 2025
208f368
fix off-by-one error
linoscope Mar 25, 2025
af1892b
fix bug in seed calculation
linoscope Mar 26, 2025
53df021
add proper EIP number (EIP7917)
linoscope Mar 26, 2025
94d9dde
fix lint errors
linoscope Mar 27, 2025
83daa1e
add test to check lookahead consistency before/after Fulu fork
linoscope Mar 29, 2025
b1b0144
add tests for lookahead changes due to EB changes
linoscope Apr 1, 2025
9610b02
fix test folder
linoscope Apr 4, 2025
898432d
fix import
linoscope Apr 4, 2025
0414e6c
fix imports
linoscope Apr 7, 2025
78b3810
add generator EIP-7917 tests
linoscope Apr 7, 2025
291bed7
Merge branch 'dev' into stabalize-next-epoch-lookahead
jtraglia Apr 7, 2025
a54040e
Run make lint
jtraglia Apr 7, 2025
cede1ae
better yielding of vectors
linoscope Apr 8, 2025
3c9cf75
move helper and fix type
linoscope Apr 17, 2025
ec0243d
Merge branch 'dev' into stabalize-next-epoch-lookahead
jtraglia Apr 21, 2025
835df3d
Run make lint
jtraglia Apr 21, 2025
1860d06
move state accessors outside of function
linoscope May 23, 2025
faaf52b
use helper in test
linoscope May 23, 2025
dfce6e4
Move some tests to the `sanity` format to generate vectors for EIP-7917
leolara May 26, 2025
016b45d
Merge remote-tracking branch 'origin/dev' into stabalize-next-epoch-l…
linoscope May 27, 2025
98adbee
Merge branch 'dev' into stabalize-next-epoch-lookahead
jtraglia May 30, 2025
a53408e
Run make lint
jtraglia May 30, 2025
ef950a6
Make test less reliant on specific RANDAO
linoscope May 30, 2025
9d5a6be
Break up two long lines
jtraglia May 30, 2025
c17c9dd
Use double backticks in docstrings
jtraglia May 30, 2025
ac6b213
fix yield statement in test
linoscope May 31, 2025
c8811a0
Change proposer_lookahead type from List to Vector
linoscope Jun 2, 2025
e9266b2
Compute seed and indicies inside compute_proposer_indicies
linoscope Jun 2, 2025
79bf80d
refactor and introduce get_beacon_proposer_indices
linoscope Jun 3, 2025
5874370
remove trailing whitespaces
linoscope Jun 3, 2025
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
165 changes: 165 additions & 0 deletions specs/fulu/beacon-chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,18 @@
- [Execution](#execution)
- [Execution payload](#execution-payload)
- [Modified `process_execution_payload`](#modified-process_execution_payload)
- [Containers](#containers)
- [Extended Containers](#extended-containers)
- [`BeaconState`](#beaconstate)
- [Helper functions](#helper-functions)
- [Misc](#misc)
- [New `compute_proposer_indices`](#new-compute_proposer_indices)
- [New `compute_proposer_lookahead`](#new-compute_proposer_lookahead)
- [Beacon state accessors](#beacon-state-accessors)
- [Modified `get_beacon_proposer_index`](#modified-get_beacon_proposer_index)
- [Epoch processing](#epoch-processing)
- [Modified `process_epoch`](#modified-process_epoch)
- [New `process_proposer_lookahead`](#new-process_proposer_lookahead)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->
Expand Down Expand Up @@ -76,3 +88,156 @@ def process_execution_payload(state: BeaconState, body: BeaconBlockBody, executi
excess_blob_gas=payload.excess_blob_gas,
)
```

## Containers

### Extended Containers

#### `BeaconState`

*Note*: The `BeaconState` container is extended with the `proposer_lookahead` field, which is a list of validator indices covering the full lookahead period, starting from the beginning of the current epoch. For example, `proposer_lookahead[0]` is the validator index for the first proposer in the current epoch, `proposer_lookahead[1]` is the validator index for the next proposer in the current epoch, and so forth. The length of the `proposer_lookahead` list is `(MIN_SEED_LOOKAHEAD + 1) * SLOTS_PER_EPOCH`, reflecting how far ahead proposer indices are computed based on the `MIN_SEED_LOOKAHEAD` parameter.

```python
class BeaconState(Container):
# Versioning
genesis_time: uint64
genesis_validators_root: Root
slot: Slot
fork: Fork
# History
latest_block_header: BeaconBlockHeader
block_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT]
state_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT]
historical_roots: List[Root, HISTORICAL_ROOTS_LIMIT]
# Eth1
eth1_data: Eth1Data
eth1_data_votes: List[Eth1Data, EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH]
eth1_deposit_index: uint64
# Registry
validators: List[Validator, VALIDATOR_REGISTRY_LIMIT]
balances: List[Gwei, VALIDATOR_REGISTRY_LIMIT]
# Randomness
randao_mixes: Vector[Bytes32, EPOCHS_PER_HISTORICAL_VECTOR]
# Slashings
slashings: Vector[Gwei, EPOCHS_PER_SLASHINGS_VECTOR] # Per-epoch sums of slashed effective balances
# Participation
previous_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT]
current_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT]
# Finality
justification_bits: Bitvector[JUSTIFICATION_BITS_LENGTH] # Bit set for every recent justified epoch
previous_justified_checkpoint: Checkpoint
current_justified_checkpoint: Checkpoint
finalized_checkpoint: Checkpoint
# Inactivity
inactivity_scores: List[uint64, VALIDATOR_REGISTRY_LIMIT]
# Sync
current_sync_committee: SyncCommittee
next_sync_committee: SyncCommittee
# Execution
latest_execution_payload_header: ExecutionPayloadHeader
# Withdrawals
next_withdrawal_index: WithdrawalIndex
next_withdrawal_validator_index: ValidatorIndex
# Deep history valid from Capella onwards
historical_summaries: List[HistoricalSummary, HISTORICAL_ROOTS_LIMIT]
deposit_requests_start_index: uint64
deposit_balance_to_consume: Gwei
exit_balance_to_consume: Gwei
earliest_exit_epoch: Epoch
consolidation_balance_to_consume: Gwei
earliest_consolidation_epoch: Epoch
pending_deposits: List[PendingDeposit, PENDING_DEPOSITS_LIMIT]
pending_partial_withdrawals: List[PendingPartialWithdrawal, PENDING_PARTIAL_WITHDRAWALS_LIMIT]
pending_consolidations: List[PendingConsolidation, PENDING_CONSOLIDATIONS_LIMIT]
proposer_lookahead: List[ValidatorIndex, (MIN_SEED_LOOKAHEAD + 1) * SLOTS_PER_EPOCH] # [New in Fulu:EIPXXXX]
```

## Helper functions

### Misc

#### New `compute_proposer_indices`

```python
def compute_proposer_indices(state: BeaconState, epoch: Epoch) -> List[ValidatorIndex, SLOTS_PER_EPOCH]:
Copy link

@gfukushima gfukushima May 22, 2025

Choose a reason for hiding this comment

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

Coming from an implementation perspective and comparing this method with its equivalent for a single index compute_proposer_index I think we want to provide epoch_seed and indices to the compute_proposer_indices given that the calculation of these 2 variables require methods defined in the Beacon state accessors level. So something like this:

Suggested change
def compute_proposer_indices(state: BeaconState, epoch: Epoch) -> List[ValidatorIndex, SLOTS_PER_EPOCH]:
def compute_proposer_indices(state: BeaconState, epoch: Epoch, seed: Bytes32, indices: Sequence[ValidatorIndex]) -> List[ValidatorIndex, SLOTS_PER_EPOCH]:

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for reviewing and for the suggestion! I think this makes sense, looking at other functions, they do indeed seem to follow the pattern of placing seed in argument. This requires some changes in code and testing, will do that tomorrow morning.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done here: 1860d06

"""
Return the proposer indices for the given ``epoch``.
"""
proposer_indices = []
indices = get_active_validator_indices(state, epoch)
start_slot = compute_start_slot_at_epoch(epoch)
for slot in range(start_slot, start_slot + SLOTS_PER_EPOCH):
seed = hash(get_seed(state, epoch, DOMAIN_BEACON_PROPOSER) + uint_to_bytes(Slot(slot)))
proposer_indices.append(compute_proposer_index(state, indices, seed))
return proposer_indices
```

#### New `compute_proposer_lookahead`

```python
def compute_proposer_lookahead(state: BeaconState) -> List[ValidatorIndex, (MIN_SEED_LOOKAHEAD + 1) * SLOTS_PER_EPOCH]:
"""
Return the proposer indices for the full available lookahead starting from current epoch.
"""
current_epoch = get_current_epoch(state)
lookahead = []
for i in range(MIN_SEED_LOOKAHEAD + 1):
proposer_indices = compute_proposer_indices(state, Epoch(current_epoch + i))
lookahead.extend(proposer_indices)
return lookahead
```


### Beacon state accessors

#### Modified `get_beacon_proposer_index`

*Note*: The function `get_beacon_proposer_index` is modified to use the pre-calculated `current_proposer_lookahead` instead of calculating it on-demand.

```python
def get_beacon_proposer_index(state: BeaconState) -> ValidatorIndex:
"""
Return the beacon proposer index at the current slot.
"""
slot_offset = state.slot - compute_start_slot_at_epoch(get_current_epoch(state))
return state.proposer_lookahead[slot_offset]
```

### Epoch processing

#### Modified `process_epoch`

*Note*: The function `process_epoch` is modified in Fulu to call `process_proposer_lookahead` to update the `proposer_lookahead` in the beacon state.

```python
def process_epoch(state: BeaconState) -> None:
process_justification_and_finalization(state)
process_inactivity_updates(state)
process_rewards_and_penalties(state)
process_registry_updates(state)
process_slashings(state)
process_eth1_data_reset(state)
process_pending_deposits(state)
process_pending_consolidations(state)
process_effective_balance_updates(state)
process_slashings_reset(state)
process_randao_mixes_reset(state)
process_historical_summaries_update(state)
process_participation_flag_updates(state)
process_sync_committee_updates(state)
process_proposer_lookahead(state) # [New in Fulu:EIPXXXX]
```

#### New `process_proposer_lookahead`

*Note*: This function updates the proposer_lookahead in the beacon state by shifting out proposer indices from the earliest epoch and appending new proposer indices for the latest epoch. With `MIN_SEED_LOOKAHEAD` set to `1`, this means that at the start of epoch `N`, the proposer lookahead for epoch `N+1` will be computed and included in the beacon state's lookahead.

```python
def process_proposer_lookahead(state: BeaconState) -> None:
last_epoch_start = len(state.proposer_lookahead) - SLOTS_PER_EPOCH
# Shift out proposers in the first epoch.
state.proposer_lookahead[:last_epoch_start] = state.proposer_lookahead[SLOTS_PER_EPOCH:]
# Fill in the last epoch with new proposer indices.
last_epoch_proposers = compute_proposer_indices(state, Epoch(get_current_epoch(state) + MIN_SEED_LOOKAHEAD))
state.proposer_lookahead[last_epoch_start:] = last_epoch_proposers
```
1 change: 1 addition & 0 deletions specs/fulu/fork.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ def upgrade_to_fulu(pre: electra.BeaconState) -> BeaconState:
pending_deposits=pre.pending_deposits,
pending_partial_withdrawals=pre.pending_partial_withdrawals,
pending_consolidations=pre.pending_consolidations,
proposer_lookahead=compute_proposer_lookahead(pre), # [New in Fulu:EIPXXXX]
)

return post
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from eth2spec.test.context import spec_state_test, with_fulu_and_later
from eth2spec.test.helpers.epoch_processing import run_epoch_processing_with
from eth2spec.test.helpers.state import next_epoch


@with_fulu_and_later
@spec_state_test
def test_next_epoch_proposer_lookahead_shifted_to_front(spec, state):
"""Test that the next epoch proposer lookahead is shifted to the front at epoch transition."""
# Transition few epochs to pass the MIN_SEED_LOOKAHEAD
next_epoch(spec, state)
next_epoch(spec, state)
# Get initial lookahead
initial_lookahead = state.proposer_lookahead.copy()

# Run epoch processing
yield from run_epoch_processing_with(spec, state, 'process_proposer_lookahead')

# Verify lookahead was shifted correctly
assert state.proposer_lookahead[:spec.SLOTS_PER_EPOCH] == initial_lookahead[spec.SLOTS_PER_EPOCH:]


@with_fulu_and_later
@spec_state_test
def test_proposer_lookahead_in_state_matches_computed_lookahead(spec, state):
"""Test that the proposer lookahead in the state matches the lookahead computed on the fly."""
# Transition few epochs to pass the MIN_SEED_LOOKAHEAD
next_epoch(spec, state)
next_epoch(spec, state)

# Run epoch processing
yield from run_epoch_processing_with(spec, state, 'process_proposer_lookahead')

# Verify lookahead in state matches the lookahead computed on the fly
computed_lookahead = spec.compute_proposer_lookahead(state)
assert state.proposer_lookahead == computed_lookahead
2 changes: 2 additions & 0 deletions tests/core/pyspec/eth2spec/test/helpers/genesis.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@ def create_genesis_state(spec, validator_balances, activation_threshold):

if is_post_electra(spec):
state.deposit_requests_start_index = spec.UNSET_DEPOSIT_REQUESTS_START_INDEX
# Initialize proposer lookahead list
state.proposer_lookahead = spec.compute_proposer_lookahead(state)

if is_post_eip7441(spec):
vc = len(state.validators)
Expand Down