-
Notifications
You must be signed in to change notification settings - Fork 1.2k
EIP-7917: Deterministic proposer lookahead #4190
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 25 commits
e73b5d8
fb3cd63
3fbe39c
0a37fe7
a4dd9eb
9b82cb4
7485399
6a1d923
8fb1a58
208f368
af1892b
53df021
94d9dde
83daa1e
b1b0144
9610b02
898432d
0414e6c
78b3810
291bed7
a54040e
cede1ae
3c9cf75
ec0243d
835df3d
1860d06
faaf52b
dfce6e4
016b45d
98adbee
a53408e
ef950a6
9d5a6be
c17c9dd
ac6b213
c8811a0
e9266b2
79bf80d
5874370
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -11,6 +11,17 @@ | |||||
| - [Block processing](#block-processing) | ||||||
| - [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) | ||||||
| - [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) | ||||||
|
|
||||||
| <!-- mdformat-toc end --> | ||||||
|
|
||||||
|
|
@@ -77,3 +88,137 @@ 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:EIP7917] | ||||||
| ``` | ||||||
|
|
||||||
| ## Helper functions | ||||||
|
|
||||||
| ### Misc | ||||||
|
|
||||||
| #### New `compute_proposer_indices` | ||||||
|
|
||||||
| ```python | ||||||
| def compute_proposer_indices(state: BeaconState, epoch: Epoch) -> List[ValidatorIndex, SLOTS_PER_EPOCH]: | ||||||
|
||||||
| 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]: |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done here: 1860d06
jtraglia marked this conversation as resolved.
Show resolved
Hide resolved
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| from eth2spec.test.context import ( | ||
| spec_state_test, | ||
| with_phases, | ||
| ) | ||
| from eth2spec.test.helpers.state import ( | ||
| next_epoch, | ||
| ) | ||
| from eth2spec.test.helpers.withdrawals import ( | ||
| set_compounding_withdrawal_credential, | ||
| ) | ||
| from eth2spec.test.helpers.attestations import ( | ||
| state_transition_with_full_block, | ||
| ) | ||
|
|
||
| from eth2spec.test.helpers.constants import ELECTRA, FULU | ||
| from eth2spec.test.helpers.state import simulate_lookahead | ||
|
|
||
|
|
||
| def run_test_effective_balance_increase_changes_lookahead(spec, state, expect_lookahead_changed): | ||
| # Advance few epochs to adjust the RANDAO | ||
| next_epoch(spec, state) | ||
| next_epoch(spec, state) | ||
| next_epoch(spec, state) | ||
|
|
||
| # Set all active validators to have balance close to the hysteresis threshold | ||
| current_epoch = spec.get_current_epoch(state) | ||
| active_validator_indices = spec.get_active_validator_indices(state, current_epoch) | ||
| for validator_index in active_validator_indices: | ||
| # Set compounding withdrawal credentials for the validator | ||
| set_compounding_withdrawal_credential(spec, state, validator_index) | ||
| state.validators[validator_index].effective_balance = 32000000000 | ||
| # Set balance to close the next hysteresis threshold | ||
| state.balances[validator_index] = 33250000000 - 1 | ||
|
|
||
| # Calculate the lookahead of next epoch | ||
| next_epoch_lookahead = simulate_lookahead(spec, state)[spec.SLOTS_PER_EPOCH :] | ||
|
|
||
| # Process 1-epoch worth of blocks with attestations | ||
| for _ in range(spec.SLOTS_PER_EPOCH): | ||
| _ = state_transition_with_full_block(spec, state, fill_cur_epoch=True, fill_prev_epoch=True) | ||
|
|
||
| # Calculate the actual lookahead | ||
| actual_lookahead = simulate_lookahead(spec, state)[: spec.SLOTS_PER_EPOCH] | ||
|
|
||
| if expect_lookahead_changed: | ||
| assert next_epoch_lookahead != actual_lookahead | ||
| else: | ||
| assert next_epoch_lookahead == actual_lookahead | ||
|
|
||
|
|
||
| @with_phases(phases=[ELECTRA, FULU]) | ||
| @spec_state_test | ||
| def test_effective_balance_increase_changes_lookahead(spec, state): | ||
| if spec.fork == ELECTRA: | ||
| # Pre-EIP-7917, effective balance changes due to attestation rewards | ||
| # changes the next epoch's lookahead | ||
| run_test_effective_balance_increase_changes_lookahead( | ||
| spec, state, expect_lookahead_changed=True | ||
| ) | ||
| else: | ||
| # Post-EIP-7917, effective balance changes due to attestation rewards | ||
| # do not change the next epoch's lookahead | ||
| run_test_effective_balance_increase_changes_lookahead( | ||
| spec, state, expect_lookahead_changed=False | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| 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 "pre", state | ||
| next_epoch(spec, state) | ||
| yield "post", state | ||
|
|
||
| # 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 "pre", state | ||
| next_epoch(spec, state) | ||
| yield "post", state | ||
|
|
||
| # Verify lookahead in state matches the lookahead computed on the fly | ||
| computed_lookahead = spec.initialize_proposer_lookahead(state) | ||
| assert state.proposer_lookahead == computed_lookahead |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| from eth2spec.test.context import ( | ||
| with_phases, | ||
| spec_test, | ||
| with_state, | ||
| ) | ||
| from eth2spec.test.utils import with_meta_tags | ||
| from eth2spec.test.helpers.constants import ( | ||
| ELECTRA, | ||
| FULU, | ||
| ) | ||
| from eth2spec.test.helpers.fulu.fork import ( | ||
| FULU_FORK_TEST_META_TAGS, | ||
| run_fork_test, | ||
| ) | ||
| from eth2spec.test.helpers.state import next_slot | ||
|
|
||
|
|
||
| @with_phases(phases=[ELECTRA], other_phases=[FULU]) | ||
| @spec_test | ||
| @with_state | ||
| @with_meta_tags(FULU_FORK_TEST_META_TAGS) | ||
| def test_lookahead_consistency_at_fork(spec, phases, state): | ||
| """ | ||
| Test that lookahead is consistent before/after the Fulu fork. | ||
| """ | ||
|
|
||
| # Calculate the current and next epoch lookahead by simulating the state progression | ||
| # with empty slots and calling `get_beacon_proposer_index` (how it was done pre-Fulu) | ||
| pre_fork_proposers = [] | ||
|
||
| simulation_state = state.copy() | ||
| for _ in range(spec.SLOTS_PER_EPOCH * (spec.MIN_SEED_LOOKAHEAD + 1)): | ||
| proposer_index = spec.get_beacon_proposer_index(simulation_state) | ||
| pre_fork_proposers.append(proposer_index) | ||
| next_slot(spec, simulation_state) | ||
|
|
||
| # Upgrade to Fulu | ||
| spec = phases[FULU] | ||
| state = yield from run_fork_test(spec, state) | ||
|
|
||
| # Check if the pre-fork simulation matches the post-fork `state.proposer_lookahead` | ||
| assert pre_fork_proposers == state.proposer_lookahead | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this a list instead of a vector?, if this is a list it is pretty weird to have a tight limit for it. I would go with an SSZ Vector in this case, but if we insist in having a list please set a larger limit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, indeed a vector is more natural then a list, as it is fixed in size. Will change to vector
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed to vector in c8811a0