Skip to content
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

Add max epoch activation churn limit (EIP-7514) to Deneb #3499

Merged
merged 15 commits into from
Sep 18, 2023
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ tests/core/pyspec/eth2spec/capella/
tests/core/pyspec/eth2spec/deneb/
tests/core/pyspec/eth2spec/eip6110/
tests/core/pyspec/eth2spec/eip7002/
tests/core/pyspec/eth2spec/eip7668/
tests/core/pyspec/eth2spec/whisk/

# coverage reports
Expand Down
6 changes: 6 additions & 0 deletions configs/mainnet.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ EIP6110_FORK_EPOCH: 18446744073709551615
# EIP7002
EIP7002_FORK_VERSION: 0x05000000 # temporary stub
EIP7002_FORK_EPOCH: 18446744073709551615
# EIP7668
EIP7668_FORK_VERSION: 0x05000000 # temporary stub
EIP7668_FORK_EPOCH: 18446744073709551615
# WHISK
WHISK_FORK_VERSION: 0x06000000 # temporary stub
WHISK_FORK_EPOCH: 18446744073709551615
Expand Down Expand Up @@ -146,3 +149,6 @@ BLOB_SIDECAR_SUBNET_COUNT: 6
WHISK_EPOCHS_PER_SHUFFLING_PHASE: 256
# `Epoch(2)`
WHISK_PROPOSER_SELECTION_GAP: 2

# EIP7668
MAX_PER_EPOCH_INBOUND_CHURN_LIMIT: 12
6 changes: 6 additions & 0 deletions configs/minimal.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ EIP6110_FORK_EPOCH: 18446744073709551615
# EIP7002
EIP7002_FORK_VERSION: 0x05000001
EIP7002_FORK_EPOCH: 18446744073709551615
# EIP7668
EIP7668_FORK_VERSION: 0x05000001 # temporary stub
EIP7668_FORK_EPOCH: 18446744073709551615
# WHISK
WHISK_FORK_VERSION: 0x06000001
WHISK_FORK_EPOCH: 18446744073709551615
Expand Down Expand Up @@ -145,3 +148,6 @@ BLOB_SIDECAR_SUBNET_COUNT: 6
# Whisk
WHISK_EPOCHS_PER_SHUFFLING_PHASE: 4
WHISK_PROPOSER_SELECTION_GAP: 1

# EIP7668
MAX_PER_EPOCH_INBOUND_CHURN_LIMIT: 12
1 change: 1 addition & 0 deletions pysetup/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
DENEB = 'deneb'
EIP6110 = 'eip6110'
EIP7002 = 'eip7002'
EIP7668 = 'eip7668'
WHISK = 'whisk'


Expand Down
2 changes: 2 additions & 0 deletions pysetup/md_doc_paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
EIP6110,
WHISK,
EIP7002,
EIP7668,
)


Expand All @@ -21,6 +22,7 @@
EIP6110: DENEB,
WHISK: CAPELLA,
EIP7002: CAPELLA,
EIP7668: CAPELLA,
}

ALL_FORKS = list(PREVIOUS_FORK_OF.keys())
Expand Down
3 changes: 2 additions & 1 deletion pysetup/spec_builders/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
from .deneb import DenebSpecBuilder
from .eip6110 import EIP6110SpecBuilder
from .eip7002 import EIP7002SpecBuilder
from .eip7668 import EIP7668SpecBuilder
from .whisk import WhiskSpecBuilder


spec_builders = {
builder.fork: builder
for builder in (
Phase0SpecBuilder, AltairSpecBuilder, BellatrixSpecBuilder, CapellaSpecBuilder, DenebSpecBuilder,
EIP6110SpecBuilder, EIP7002SpecBuilder, WhiskSpecBuilder,
EIP6110SpecBuilder, EIP7002SpecBuilder, EIP7668SpecBuilder, WhiskSpecBuilder,
)
}
12 changes: 12 additions & 0 deletions pysetup/spec_builders/eip7668.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from .base import BaseSpecBuilder
from ..constants import EIP7668


class EIP7668SpecBuilder(BaseSpecBuilder):
fork: str = EIP7668

@classmethod
def imports(cls, preset_name: str):
return super().imports(preset_name) + f'''
from eth2spec.capella import {preset_name} as capella
'''
88 changes: 88 additions & 0 deletions specs/_features/eip7668/beacon_chain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
Limit churn -- The Beacon Chain

## Table of contents

<!-- TOC -->
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->

- [Introduction](#introduction)
- [Configuration](#configuration)
- [Validator cycle](#validator-cycle)
- [Helper functions](#helper-functions)
- [Beacon state accessors](#beacon-state-accessors)
- [New `get_validator_inbound_churn_limit`](#new-get_validator_inbound_churn_limit)
- [Beacon chain state transition function](#beacon-chain-state-transition-function)
- [Epoch processing](#epoch-processing)
- [Registry updates](#registry-updates)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->

## Introduction

This is the beacon chain specification to limit the max inbound churn value, motivated to limit the validator active set growth rate.

*Note:* This specification is built upon [Capella](../../capella/beacon_chain.md) and is under active development.

## Configuration

### Validator cycle

| Name | Value |
| - | - |
| `MAX_PER_EPOCH_INBOUND_CHURN_LIMIT` | `uint64(12)` (= 12) |
Copy link
Contributor

Choose a reason for hiding this comment

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

would like to examine lower values here

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I projected different levels of ETH staked for each constant and activation time. ethereum/EIPs#7668

What other reasoning do you propose to inform the decision for a specific lower value? Lower values will increase the effectiveness of this feature, but may raise more fairness concerns. I haven't found a strong framework to inform the decision of a specific value


## Helper functions

### Beacon state accessors

#### New `get_validator_inbound_churn_limit`

```python
def get_validator_inbound_churn_limit(state: BeaconState) -> uint64:
"""
Return the validator inbound churn limit for the current epoch.
"""
active_validator_indices = get_active_validator_indices(state, get_current_epoch(state))
return min(
MAX_PER_EPOCH_INBOUND_CHURN_LIMIT,
max(
dapplion marked this conversation as resolved.
Show resolved Hide resolved
MIN_PER_EPOCH_CHURN_LIMIT,
uint64(len(active_validator_indices)) // CHURN_LIMIT_QUOTIENT,
),
)
```

## Beacon chain state transition function

### Epoch processing

#### Registry updates

dapplion marked this conversation as resolved.
Show resolved Hide resolved
```python
def process_registry_updates(state: BeaconState) -> None:
# Process activation eligibility and ejections
for index, validator in enumerate(state.validators):
if is_eligible_for_activation_queue(validator):
validator.activation_eligibility_epoch = get_current_epoch(state) + 1

if (
is_active_validator(validator, get_current_epoch(state))
and validator.effective_balance <= EJECTION_BALANCE
):
initiate_validator_exit(state, ValidatorIndex(index))

# Queue validators eligible for activation and not yet dequeued for activation
activation_queue = sorted([
index for index, validator in enumerate(state.validators)
if is_eligible_for_activation(state, validator)
# Order by the sequence of activation_eligibility_epoch setting and then index
], key=lambda index: (state.validators[index].activation_eligibility_epoch, index))
# Dequeued validators for activation up to churn limit
# [Modified in limit churn]
dapplion marked this conversation as resolved.
Show resolved Hide resolved
for index in activation_queue[:get_validator_inbound_churn_limit(state)]:
validator = state.validators[index]
validator.activation_epoch = compute_activation_exit_epoch(get_current_epoch(state))
```

139 changes: 139 additions & 0 deletions specs/_features/eip7668/fork.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# EIP-7668 -- Fork Logic

**Notice**: This document is a work-in-progress for researchers and implementers.

## Table of contents

<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->

- [Introduction](#introduction)
- [Configuration](#configuration)
- [Helper functions](#helper-functions)
- [Misc](#misc)
- [Modified `compute_fork_version`](#modified-compute_fork_version)
- [Fork to EIP-7668](#fork-to-eip-7668)
- [Fork trigger](#fork-trigger)
- [Upgrading the state](#upgrading-the-state)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

## Introduction

This document describes the process of EIP-7668 upgrade.

## Configuration

Warning: this configuration is not definitive.

| Name | Value |
| - | - |
| `EIP7668_FORK_VERSION` | `Version('0x05000000')` |
| `EIP7668_FORK_EPOCH` | `Epoch(18446744073709551615)` **TBD** |

## Helper functions

### Misc

#### Modified `compute_fork_version`

```python
def compute_fork_version(epoch: Epoch) -> Version:
"""
Return the fork version at the given ``epoch``.
"""
if epoch >= EIP7668_FORK_EPOCH:
return EIP7668_FORK_VERSION
if epoch >= CAPELLA_FORK_EPOCH:
return CAPELLA_FORK_VERSION
if epoch >= BELLATRIX_FORK_EPOCH:
return BELLATRIX_FORK_VERSION
if epoch >= ALTAIR_FORK_EPOCH:
return ALTAIR_FORK_VERSION
return GENESIS_FORK_VERSION
```

## Fork to EIP-7668

### Fork trigger

TBD. This fork is defined for testing purposes, the EIP may be combined with other consensus-layer upgrade.
For now, we assume the condition will be triggered at epoch `EIP7668_FORK_EPOCH`.

Note that for the pure EIP-7668 networks, we don't apply `upgrade_to_eip7668` since it starts with EIP-7668 version logic.

### Upgrading the state

If `state.slot % SLOTS_PER_EPOCH == 0` and `compute_epoch_at_slot(state.slot) == EIP7668_FORK_EPOCH`,
an irregular state change is made to upgrade to EIP-7668.

```python
def upgrade_to_eip7668(pre: capella.BeaconState) -> BeaconState:
epoch = capella.get_current_epoch(pre)
latest_execution_payload_header = ExecutionPayloadHeader(
parent_hash=pre.latest_execution_payload_header.parent_hash,
fee_recipient=pre.latest_execution_payload_header.fee_recipient,
state_root=pre.latest_execution_payload_header.state_root,
receipts_root=pre.latest_execution_payload_header.receipts_root,
logs_bloom=pre.latest_execution_payload_header.logs_bloom,
prev_randao=pre.latest_execution_payload_header.prev_randao,
block_number=pre.latest_execution_payload_header.block_number,
gas_limit=pre.latest_execution_payload_header.gas_limit,
gas_used=pre.latest_execution_payload_header.gas_used,
timestamp=pre.latest_execution_payload_header.timestamp,
extra_data=pre.latest_execution_payload_header.extra_data,
base_fee_per_gas=pre.latest_execution_payload_header.base_fee_per_gas,
block_hash=pre.latest_execution_payload_header.block_hash,
transactions_root=pre.latest_execution_payload_header.transactions_root,
withdrawals_root=pre.latest_execution_payload_header.withdrawals_root,
)
post = BeaconState(
# Versioning
genesis_time=pre.genesis_time,
genesis_validators_root=pre.genesis_validators_root,
slot=pre.slot,
fork=Fork(
previous_version=pre.fork.current_version,
current_version=EIP7668_FORK_VERSION, # [Modified in EIP-7668]
epoch=epoch,
),
# History
latest_block_header=pre.latest_block_header,
block_roots=pre.block_roots,
state_roots=pre.state_roots,
historical_roots=pre.historical_roots,
# Eth1
eth1_data=pre.eth1_data,
eth1_data_votes=pre.eth1_data_votes,
eth1_deposit_index=pre.eth1_deposit_index,
# Registry
validators=pre.validators,
balances=pre.balances,
# Randomness
randao_mixes=pre.randao_mixes,
# Slashings
slashings=pre.slashings,
# Participation
previous_epoch_participation=pre.previous_epoch_participation,
current_epoch_participation=pre.current_epoch_participation,
# Finality
justification_bits=pre.justification_bits,
previous_justified_checkpoint=pre.previous_justified_checkpoint,
current_justified_checkpoint=pre.current_justified_checkpoint,
finalized_checkpoint=pre.finalized_checkpoint,
# Inactivity
inactivity_scores=pre.inactivity_scores,
# Sync
current_sync_committee=pre.current_sync_committee,
next_sync_committee=pre.next_sync_committee,
# Execution-layer
latest_execution_payload_header=latest_execution_payload_header,
# Withdrawals
next_withdrawal_index=pre.next_withdrawal_index,
next_withdrawal_validator_index=pre.next_withdrawal_validator_index,
# Deep history valid from Capella onwards
historical_summaries=pre.historical_summaries,
)

return post
```
5 changes: 4 additions & 1 deletion tests/core/pyspec/eth2spec/test/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@
from eth2spec.deneb import mainnet as spec_deneb_mainnet, minimal as spec_deneb_minimal
from eth2spec.eip6110 import mainnet as spec_eip6110_mainnet, minimal as spec_eip6110_minimal
from eth2spec.eip7002 import mainnet as spec_eip7002_mainnet, minimal as spec_eip7002_minimal
from eth2spec.eip7668 import mainnet as spec_eip7668_mainnet, minimal as spec_eip7668_minimal
from eth2spec.utils import bls

from .exceptions import SkippedTest
from .helpers.constants import (
PHASE0, ALTAIR, BELLATRIX, CAPELLA, DENEB,
EIP6110, EIP7002,
EIP6110, EIP7002, EIP7668,
MINIMAL, MAINNET,
ALL_PHASES,
ALL_FORK_UPGRADES,
Expand Down Expand Up @@ -85,6 +86,7 @@ class ForkMeta:
DENEB: spec_deneb_minimal,
EIP6110: spec_eip6110_minimal,
EIP7002: spec_eip7002_minimal,
EIP7668: spec_eip7668_minimal,
},
MAINNET: {
PHASE0: spec_phase0_mainnet,
Expand All @@ -94,6 +96,7 @@ class ForkMeta:
DENEB: spec_deneb_mainnet,
EIP6110: spec_eip6110_mainnet,
EIP7002: spec_eip7002_mainnet,
EIP7668: spec_eip7668_mainnet,
},
}

Expand Down
2 changes: 2 additions & 0 deletions tests/core/pyspec/eth2spec/test/helpers/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
DAS = SpecForkName('das')
EIP6110 = SpecForkName('eip6110')
EIP7002 = SpecForkName('eip7002')
EIP7668 = SpecForkName('eip7668')

#
# SpecFork settings
Expand All @@ -34,6 +35,7 @@
# Experimental patches
EIP6110,
EIP7002,
EIP7668,
)
# The forks that have light client specs
LIGHT_CLIENT_TESTING_FORKS = (*[item for item in MAINNET_FORKS if item != PHASE0], DENEB)
Expand Down
4 changes: 3 additions & 1 deletion tests/core/pyspec/eth2spec/test/helpers/forks.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
from .constants import (
PHASE0, ALTAIR, BELLATRIX, CAPELLA, DENEB,
EIP6110, EIP7002,
EIP6110, EIP7002, EIP7668,
)


def is_post_fork(a, b):
if a == EIP7668:
return b in [PHASE0, ALTAIR, BELLATRIX, CAPELLA, EIP7668]
if a == EIP7002:
return b in [PHASE0, ALTAIR, BELLATRIX, CAPELLA, EIP7002]
if a == EIP6110:
Expand Down
Loading