Merge pull request #3499 from dapplion/limit-churn-inbound
Add max epoch activation churn limit (EIP-7514) to Deneb
This commit is contained in:
commit
c88cf05358
|
@ -87,7 +87,8 @@ EJECTION_BALANCE: 16000000000
|
|||
MIN_PER_EPOCH_CHURN_LIMIT: 4
|
||||
# 2**16 (= 65,536)
|
||||
CHURN_LIMIT_QUOTIENT: 65536
|
||||
|
||||
# [New in Deneb:EIP7514] 2**3 (= 8)
|
||||
MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT: 8
|
||||
|
||||
# Fork choice
|
||||
# ---------------------------------------------------------------
|
||||
|
|
|
@ -82,10 +82,12 @@ INACTIVITY_SCORE_BIAS: 4
|
|||
INACTIVITY_SCORE_RECOVERY_RATE: 16
|
||||
# 2**4 * 10**9 (= 16,000,000,000) Gwei
|
||||
EJECTION_BALANCE: 16000000000
|
||||
# 2**2 (= 4)
|
||||
MIN_PER_EPOCH_CHURN_LIMIT: 4
|
||||
# [customized] more easily demonstrate the difference between this value and the activation churn limit
|
||||
MIN_PER_EPOCH_CHURN_LIMIT: 2
|
||||
# [customized] scale queue churn at much lower validator counts for testing
|
||||
CHURN_LIMIT_QUOTIENT: 32
|
||||
# [New in Deneb:EIP7514] [customized]
|
||||
MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT: 4
|
||||
|
||||
|
||||
# Fork choice
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
- [Preset](#preset)
|
||||
- [Execution](#execution)
|
||||
- [Configuration](#configuration)
|
||||
- [Validator cycle](#validator-cycle)
|
||||
- [Containers](#containers)
|
||||
- [Extended containers](#extended-containers)
|
||||
- [`BeaconBlockBody`](#beaconblockbody)
|
||||
|
@ -26,6 +27,7 @@
|
|||
- [`kzg_commitment_to_versioned_hash`](#kzg_commitment_to_versioned_hash)
|
||||
- [Beacon state accessors](#beacon-state-accessors)
|
||||
- [Modified `get_attestation_participation_flag_indices`](#modified-get_attestation_participation_flag_indices)
|
||||
- [New `get_validator_activation_churn_limit`](#new-get_validator_activation_churn_limit)
|
||||
- [Beacon chain state transition function](#beacon-chain-state-transition-function)
|
||||
- [Execution engine](#execution-engine)
|
||||
- [Request data](#request-data)
|
||||
|
@ -40,6 +42,8 @@
|
|||
- [Execution payload](#execution-payload)
|
||||
- [Modified `process_execution_payload`](#modified-process_execution_payload)
|
||||
- [Modified `process_voluntary_exit`](#modified-process_voluntary_exit)
|
||||
- [Epoch processing](#epoch-processing)
|
||||
- [Registry updates](#registry-updates)
|
||||
- [Testing](#testing)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
|
@ -52,6 +56,7 @@ Deneb is a consensus-layer upgrade containing a number of features. Including:
|
|||
* [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844): Shard Blob Transactions scale data-availability of Ethereum in a simple, forwards-compatible manner
|
||||
* [EIP-7044](https://eips.ethereum.org/EIPS/eip-7044): Perpetually Valid Signed Voluntary Exits
|
||||
* [EIP-7045](https://eips.ethereum.org/EIPS/eip-7045): Increase Max Attestation Inclusion Slot
|
||||
* [EIP-7514](https://eips.ethereum.org/EIPS/eip-7514): Add Max Epoch Churn Limit
|
||||
|
||||
## Custom types
|
||||
|
||||
|
@ -89,6 +94,12 @@ and are limited by `MAX_BLOB_GAS_PER_BLOCK // GAS_PER_BLOB`. However the CL limi
|
|||
|
||||
## Configuration
|
||||
|
||||
### Validator cycle
|
||||
|
||||
| Name | Value |
|
||||
| - | - |
|
||||
| `MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT` | `uint64(2**3)` (= 8) |
|
||||
|
||||
## Containers
|
||||
|
||||
### Extended containers
|
||||
|
@ -211,6 +222,16 @@ def get_attestation_participation_flag_indices(state: BeaconState,
|
|||
return participation_flag_indices
|
||||
```
|
||||
|
||||
#### New `get_validator_activation_churn_limit`
|
||||
|
||||
```python
|
||||
def get_validator_activation_churn_limit(state: BeaconState) -> uint64:
|
||||
"""
|
||||
Return the validator activation churn limit for the current epoch.
|
||||
"""
|
||||
return min(MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT, get_validator_churn_limit(state))
|
||||
```
|
||||
|
||||
## Beacon chain state transition function
|
||||
|
||||
### Execution engine
|
||||
|
@ -415,6 +436,38 @@ def process_voluntary_exit(state: BeaconState, signed_voluntary_exit: SignedVolu
|
|||
initiate_validator_exit(state, voluntary_exit.validator_index)
|
||||
```
|
||||
|
||||
### Epoch processing
|
||||
|
||||
#### Registry updates
|
||||
|
||||
*Note*: The function `process_registry_updates` is modified to utilize `get_validator_activation_churn_limit()` to rate limit the activation queue for EIP-7514.
|
||||
|
||||
```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 activation churn limit
|
||||
# [Modified in Deneb:EIP7514]
|
||||
for index in activation_queue[:get_validator_activation_churn_limit(state)]:
|
||||
validator = state.validators[index]
|
||||
validator.activation_epoch = compute_activation_exit_epoch(get_current_epoch(state))
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
*Note*: The function `initialize_beacon_state_from_eth1` is modified for pure Deneb testing only.
|
||||
|
|
|
@ -162,14 +162,34 @@ def default_balances(spec: Spec):
|
|||
return [spec.MAX_EFFECTIVE_BALANCE] * num_validators
|
||||
|
||||
|
||||
def scaled_churn_balances(spec: Spec):
|
||||
def scaled_churn_balances_min_churn_limit(spec: Spec):
|
||||
"""
|
||||
Helper method to create enough validators to scale the churn limit.
|
||||
(This is *firmly* over the churn limit -- thus the +2 instead of just +1)
|
||||
See the second argument of ``max`` in ``get_validator_churn_limit``.
|
||||
Usage: `@with_custom_state(balances_fn=scaled_churn_balances, ...)`
|
||||
Usage: `@with_custom_state(balances_fn=scaled_churn_balances_min_churn_limit, ...)`
|
||||
"""
|
||||
num_validators = spec.config.CHURN_LIMIT_QUOTIENT * (2 + spec.config.MIN_PER_EPOCH_CHURN_LIMIT)
|
||||
num_validators = spec.config.CHURN_LIMIT_QUOTIENT * (spec.config.MIN_PER_EPOCH_CHURN_LIMIT + 2)
|
||||
return [spec.MAX_EFFECTIVE_BALANCE] * num_validators
|
||||
|
||||
|
||||
def scaled_churn_balances_equal_activation_churn_limit(spec: Spec):
|
||||
"""
|
||||
Helper method to create enough validators to scale the churn limit.
|
||||
(This is *firmly* over the churn limit -- thus the +2 instead of just +1)
|
||||
Usage: `@with_custom_state(balances_fn=scaled_churn_balances_exceed_activation_churn_limit, ...)`
|
||||
"""
|
||||
num_validators = spec.config.CHURN_LIMIT_QUOTIENT * (spec.config.MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT)
|
||||
return [spec.MAX_EFFECTIVE_BALANCE] * num_validators
|
||||
|
||||
|
||||
def scaled_churn_balances_exceed_activation_churn_limit(spec: Spec):
|
||||
"""
|
||||
Helper method to create enough validators to scale the churn limit.
|
||||
(This is *firmly* over the churn limit -- thus the +2 instead of just +1)
|
||||
Usage: `@with_custom_state(balances_fn=scaled_churn_balances_exceed_activation_churn_limit, ...)`
|
||||
"""
|
||||
num_validators = spec.config.CHURN_LIMIT_QUOTIENT * (spec.config.MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT + 2)
|
||||
return [spec.MAX_EFFECTIVE_BALANCE] * num_validators
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
from eth2spec.test.helpers.keys import pubkeys
|
||||
from eth2spec.test.helpers.constants import MINIMAL
|
||||
from eth2spec.test.context import (
|
||||
with_deneb_and_later,
|
||||
spec_test,
|
||||
spec_state_test,
|
||||
single_phase,
|
||||
with_custom_state,
|
||||
with_presets,
|
||||
scaled_churn_balances_exceed_activation_churn_limit,
|
||||
scaled_churn_balances_equal_activation_churn_limit,
|
||||
)
|
||||
from eth2spec.test.helpers.epoch_processing import run_epoch_processing_with
|
||||
|
||||
|
||||
def run_process_registry_updates(spec, state):
|
||||
yield from run_epoch_processing_with(spec, state, 'process_registry_updates')
|
||||
|
||||
|
||||
def run_test_activation_churn_limit(spec, state):
|
||||
mock_activations = spec.get_validator_activation_churn_limit(state) * 2
|
||||
|
||||
validator_count_0 = len(state.validators)
|
||||
|
||||
for i in range(mock_activations):
|
||||
index = validator_count_0 + i
|
||||
validator = spec.Validator(
|
||||
pubkey=pubkeys[index],
|
||||
withdrawal_credentials=spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX + b'\x00' * 11 + b'\x56' * 20,
|
||||
activation_eligibility_epoch=0,
|
||||
activation_epoch=spec.FAR_FUTURE_EPOCH,
|
||||
exit_epoch=spec.FAR_FUTURE_EPOCH,
|
||||
withdrawable_epoch=spec.FAR_FUTURE_EPOCH,
|
||||
effective_balance=spec.MAX_EFFECTIVE_BALANCE,
|
||||
)
|
||||
state.validators.append(validator)
|
||||
state.balances.append(spec.MAX_EFFECTIVE_BALANCE)
|
||||
state.previous_epoch_participation.append(spec.ParticipationFlags(0b0000_0000))
|
||||
state.current_epoch_participation.append(spec.ParticipationFlags(0b0000_0000))
|
||||
state.inactivity_scores.append(0)
|
||||
state.validators[index].activation_epoch = spec.FAR_FUTURE_EPOCH
|
||||
|
||||
churn_limit_0 = spec.get_validator_activation_churn_limit(state)
|
||||
|
||||
yield from run_process_registry_updates(spec, state)
|
||||
|
||||
# Half should churn in first run of registry update
|
||||
for i in range(mock_activations):
|
||||
index = validator_count_0 + i
|
||||
if index < validator_count_0 + churn_limit_0:
|
||||
# The eligible validators within the activation churn limit should have been activated
|
||||
assert state.validators[index].activation_epoch < spec.FAR_FUTURE_EPOCH
|
||||
else:
|
||||
assert state.validators[index].activation_epoch == spec.FAR_FUTURE_EPOCH
|
||||
|
||||
|
||||
@with_deneb_and_later
|
||||
@with_presets([MINIMAL],
|
||||
reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated")
|
||||
@spec_test
|
||||
@with_custom_state(balances_fn=scaled_churn_balances_exceed_activation_churn_limit,
|
||||
threshold_fn=lambda spec: spec.config.EJECTION_BALANCE)
|
||||
@single_phase
|
||||
def test_activation_churn_limit__greater_than_activation_limit(spec, state):
|
||||
assert spec.get_validator_activation_churn_limit(state) == spec.config.MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT
|
||||
assert spec.get_validator_churn_limit(state) > spec.config.MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT
|
||||
yield from run_test_activation_churn_limit(spec, state)
|
||||
|
||||
|
||||
@with_deneb_and_later
|
||||
@with_presets([MINIMAL],
|
||||
reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated")
|
||||
@spec_test
|
||||
@with_custom_state(balances_fn=scaled_churn_balances_equal_activation_churn_limit,
|
||||
threshold_fn=lambda spec: spec.config.EJECTION_BALANCE)
|
||||
@single_phase
|
||||
def test_activation_churn_limit__equal_to_activation_limit(spec, state):
|
||||
assert spec.get_validator_activation_churn_limit(state) == spec.config.MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT
|
||||
assert spec.get_validator_churn_limit(state) == spec.config.MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT
|
||||
yield from run_test_activation_churn_limit(spec, state)
|
||||
|
||||
|
||||
@with_deneb_and_later
|
||||
@with_presets([MINIMAL],
|
||||
reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated")
|
||||
@spec_state_test
|
||||
def test_activation_churn_limit__less_than_activation_limit(spec, state):
|
||||
assert spec.get_validator_activation_churn_limit(state) < spec.config.MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT
|
||||
assert spec.get_validator_churn_limit(state) < spec.config.MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT
|
||||
yield from run_test_activation_churn_limit(spec, state)
|
|
@ -3,7 +3,8 @@ from eth2spec.test.context import (
|
|||
spec_state_test,
|
||||
always_bls, with_all_phases, with_presets,
|
||||
spec_test, single_phase,
|
||||
with_custom_state, scaled_churn_balances,
|
||||
with_custom_state,
|
||||
scaled_churn_balances_min_churn_limit,
|
||||
)
|
||||
from eth2spec.test.helpers.keys import pubkey_to_privkey
|
||||
from eth2spec.test.helpers.voluntary_exits import (
|
||||
|
@ -102,7 +103,8 @@ def test_success_exit_queue__min_churn(spec, state):
|
|||
@with_presets([MINIMAL],
|
||||
reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated")
|
||||
@spec_test
|
||||
@with_custom_state(balances_fn=scaled_churn_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE)
|
||||
@with_custom_state(balances_fn=scaled_churn_balances_min_churn_limit,
|
||||
threshold_fn=lambda spec: spec.config.EJECTION_BALANCE)
|
||||
@single_phase
|
||||
def test_success_exit_queue__scaled_churn(spec, state):
|
||||
churn_limit = spec.get_validator_churn_limit(state)
|
||||
|
|
|
@ -5,7 +5,7 @@ from eth2spec.test.context import (
|
|||
spec_test, spec_state_test,
|
||||
with_all_phases, single_phase,
|
||||
with_custom_state, with_presets,
|
||||
scaled_churn_balances,
|
||||
scaled_churn_balances_min_churn_limit,
|
||||
)
|
||||
from eth2spec.test.helpers.epoch_processing import run_epoch_processing_with
|
||||
|
||||
|
@ -164,7 +164,8 @@ def test_activation_queue_efficiency_min(spec, state):
|
|||
@with_presets([MINIMAL],
|
||||
reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated")
|
||||
@spec_test
|
||||
@with_custom_state(balances_fn=scaled_churn_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE)
|
||||
@with_custom_state(balances_fn=scaled_churn_balances_min_churn_limit,
|
||||
threshold_fn=lambda spec: spec.config.EJECTION_BALANCE)
|
||||
@single_phase
|
||||
def test_activation_queue_efficiency_scaled(spec, state):
|
||||
assert spec.get_validator_churn_limit(state) > spec.config.MIN_PER_EPOCH_CHURN_LIMIT
|
||||
|
@ -227,7 +228,8 @@ def test_ejection_past_churn_limit_min(spec, state):
|
|||
@with_presets([MINIMAL],
|
||||
reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated")
|
||||
@spec_test
|
||||
@with_custom_state(balances_fn=scaled_churn_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE)
|
||||
@with_custom_state(balances_fn=scaled_churn_balances_min_churn_limit,
|
||||
threshold_fn=lambda spec: spec.config.EJECTION_BALANCE)
|
||||
@single_phase
|
||||
def test_ejection_past_churn_limit_scaled(spec, state):
|
||||
assert spec.get_validator_churn_limit(state) > spec.config.MIN_PER_EPOCH_CHURN_LIMIT
|
||||
|
@ -324,7 +326,8 @@ def test_activation_queue_activation_and_ejection__exceed_churn_limit(spec, stat
|
|||
@with_presets([MINIMAL],
|
||||
reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated")
|
||||
@spec_test
|
||||
@with_custom_state(balances_fn=scaled_churn_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE)
|
||||
@with_custom_state(balances_fn=scaled_churn_balances_min_churn_limit,
|
||||
threshold_fn=lambda spec: spec.config.EJECTION_BALANCE)
|
||||
@single_phase
|
||||
def test_activation_queue_activation_and_ejection__scaled_churn_limit(spec, state):
|
||||
churn_limit = spec.get_validator_churn_limit(state)
|
||||
|
@ -336,7 +339,8 @@ def test_activation_queue_activation_and_ejection__scaled_churn_limit(spec, stat
|
|||
@with_presets([MINIMAL],
|
||||
reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated")
|
||||
@spec_test
|
||||
@with_custom_state(balances_fn=scaled_churn_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE)
|
||||
@with_custom_state(balances_fn=scaled_churn_balances_min_churn_limit,
|
||||
threshold_fn=lambda spec: spec.config.EJECTION_BALANCE)
|
||||
@single_phase
|
||||
def test_activation_queue_activation_and_ejection__exceed_scaled_churn_limit(spec, state):
|
||||
churn_limit = spec.get_validator_churn_limit(state)
|
||||
|
|
|
@ -32,7 +32,10 @@ if __name__ == "__main__":
|
|||
]}
|
||||
capella_mods = combine_mods(_new_capella_mods, bellatrix_mods)
|
||||
|
||||
deneb_mods = capella_mods
|
||||
_new_deneb_mods = {key: 'eth2spec.test.deneb.epoch_processing.test_process_' + key for key in [
|
||||
'registry_updates',
|
||||
]}
|
||||
deneb_mods = combine_mods(_new_deneb_mods, capella_mods)
|
||||
|
||||
eip6110_mods = deneb_mods
|
||||
|
||||
|
|
Loading…
Reference in New Issue