mirror of
https://github.com/status-im/eth2.0-specs.git
synced 2025-01-24 01:20:56 +00:00
Merge pull request #2586 from ethereum/churn-test
Add churn tests for when churn limit scales with v-set size
This commit is contained in:
commit
b660892ca3
@ -58,8 +58,8 @@ INACTIVITY_SCORE_RECOVERY_RATE: 16
|
||||
EJECTION_BALANCE: 16000000000
|
||||
# 2**2 (= 4)
|
||||
MIN_PER_EPOCH_CHURN_LIMIT: 4
|
||||
# 2**16 (= 65,536)
|
||||
CHURN_LIMIT_QUOTIENT: 65536
|
||||
# [customized] scale queue churn at much lower validator counts for testing
|
||||
CHURN_LIMIT_QUOTIENT: 32
|
||||
|
||||
|
||||
# Deposit contract
|
||||
|
@ -126,6 +126,17 @@ def default_balances(spec):
|
||||
return [spec.MAX_EFFECTIVE_BALANCE] * num_validators
|
||||
|
||||
|
||||
def scaled_churn_balances(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, ...)`
|
||||
"""
|
||||
num_validators = spec.config.CHURN_LIMIT_QUOTIENT * (2 + spec.config.MIN_PER_EPOCH_CHURN_LIMIT)
|
||||
return [spec.MAX_EFFECTIVE_BALANCE] * num_validators
|
||||
|
||||
|
||||
with_state = with_custom_state(default_balances, default_activation_threshold)
|
||||
|
||||
|
||||
|
@ -1,4 +1,10 @@
|
||||
from eth2spec.test.context import spec_state_test, expect_assertion_error, always_bls, with_all_phases
|
||||
from eth2spec.test.helpers.constants import MINIMAL
|
||||
from eth2spec.test.context import (
|
||||
spec_state_test, expect_assertion_error,
|
||||
always_bls, with_all_phases, with_presets,
|
||||
spec_test, single_phase,
|
||||
with_custom_state, scaled_churn_balances,
|
||||
)
|
||||
from eth2spec.test.helpers.keys import pubkey_to_privkey
|
||||
from eth2spec.test.helpers.voluntary_exits import sign_voluntary_exit
|
||||
|
||||
@ -68,9 +74,7 @@ def test_invalid_signature(spec, state):
|
||||
yield from run_voluntary_exit_processing(spec, state, signed_voluntary_exit, False)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_success_exit_queue(spec, state):
|
||||
def run_test_success_exit_queue(spec, state):
|
||||
# move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit
|
||||
state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
|
||||
|
||||
@ -106,10 +110,29 @@ def test_success_exit_queue(spec, state):
|
||||
# when processing an additional exit, it results in an exit in a later epoch
|
||||
yield from run_voluntary_exit_processing(spec, state, signed_voluntary_exit)
|
||||
|
||||
assert (
|
||||
state.validators[validator_index].exit_epoch ==
|
||||
state.validators[initial_indices[0]].exit_epoch + 1
|
||||
)
|
||||
for index in initial_indices:
|
||||
assert (
|
||||
state.validators[validator_index].exit_epoch ==
|
||||
state.validators[index].exit_epoch + 1
|
||||
)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_success_exit_queue__min_churn(spec, state):
|
||||
yield from run_test_success_exit_queue(spec, state)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@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)
|
||||
@single_phase
|
||||
def test_success_exit_queue__scaled_churn(spec, state):
|
||||
churn_limit = spec.get_validator_churn_limit(state)
|
||||
assert churn_limit > spec.config.MIN_PER_EPOCH_CHURN_LIMIT
|
||||
yield from run_test_success_exit_queue(spec, state)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
|
@ -1,6 +1,12 @@
|
||||
from eth2spec.test.helpers.deposits import mock_deposit
|
||||
from eth2spec.test.helpers.state import next_epoch, next_slots
|
||||
from eth2spec.test.context import spec_state_test, with_all_phases
|
||||
from eth2spec.test.helpers.constants import MINIMAL
|
||||
from eth2spec.test.context import (
|
||||
spec_test, spec_state_test,
|
||||
with_all_phases, single_phase,
|
||||
with_custom_state, with_presets,
|
||||
scaled_churn_balances,
|
||||
)
|
||||
from eth2spec.test.helpers.epoch_processing import run_epoch_processing_with
|
||||
|
||||
|
||||
@ -112,9 +118,7 @@ def test_activation_queue_sorting(spec, state):
|
||||
assert state.validators[churn_limit - 1].activation_epoch != spec.FAR_FUTURE_EPOCH
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_activation_queue_efficiency(spec, state):
|
||||
def run_test_activation_queue_efficiency(spec, state):
|
||||
churn_limit = spec.get_validator_churn_limit(state)
|
||||
mock_activations = churn_limit * 2
|
||||
|
||||
@ -128,23 +132,45 @@ def test_activation_queue_efficiency(spec, state):
|
||||
|
||||
state.finalized_checkpoint.epoch = epoch + 1
|
||||
|
||||
# Churn limit could have changed given the active vals removed via `mock_deposit`
|
||||
churn_limit_0 = spec.get_validator_churn_limit(state)
|
||||
|
||||
# Run first registry update. Do not yield test vectors
|
||||
for _ in run_process_registry_updates(spec, state):
|
||||
pass
|
||||
|
||||
# Half should churn in first run of registry update
|
||||
for i in range(mock_activations):
|
||||
if i < mock_activations // 2:
|
||||
if i < churn_limit_0:
|
||||
assert state.validators[i].activation_epoch < spec.FAR_FUTURE_EPOCH
|
||||
else:
|
||||
assert state.validators[i].activation_epoch == spec.FAR_FUTURE_EPOCH
|
||||
|
||||
# Second half should churn in second run of registry update
|
||||
churn_limit_1 = spec.get_validator_churn_limit(state)
|
||||
yield from run_process_registry_updates(spec, state)
|
||||
for i in range(mock_activations):
|
||||
for i in range(churn_limit_0 + churn_limit_1):
|
||||
assert state.validators[i].activation_epoch < spec.FAR_FUTURE_EPOCH
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_activation_queue_efficiency_min(spec, state):
|
||||
assert spec.get_validator_churn_limit(state) == spec.config.MIN_PER_EPOCH_CHURN_LIMIT
|
||||
yield from run_test_activation_queue_efficiency(spec, state)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@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)
|
||||
@single_phase
|
||||
def test_activation_queue_efficiency_scaled(spec, state):
|
||||
assert spec.get_validator_churn_limit(state) > spec.config.MIN_PER_EPOCH_CHURN_LIMIT
|
||||
yield from run_test_activation_queue_efficiency(spec, state)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_ejection(spec, state):
|
||||
@ -165,9 +191,7 @@ def test_ejection(spec, state):
|
||||
)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_ejection_past_churn_limit(spec, state):
|
||||
def run_test_ejection_past_churn_limit(spec, state):
|
||||
churn_limit = spec.get_validator_churn_limit(state)
|
||||
|
||||
# try to eject more than per-epoch churn limit
|
||||
@ -184,58 +208,137 @@ def test_ejection_past_churn_limit(spec, state):
|
||||
# first third ejected in normal speed
|
||||
if i < mock_ejections // 3:
|
||||
assert state.validators[i].exit_epoch == expected_ejection_epoch
|
||||
# second thirdgets delayed by 1 epoch
|
||||
# second third gets delayed by 1 epoch
|
||||
elif mock_ejections // 3 <= i < mock_ejections * 2 // 3:
|
||||
assert state.validators[i].exit_epoch == expected_ejection_epoch + 1
|
||||
# second thirdgets delayed by 2 epochs
|
||||
# final third gets delayed by 2 epochs
|
||||
else:
|
||||
assert state.validators[i].exit_epoch == expected_ejection_epoch + 2
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_activation_queue_activation_and_ejection(spec, state):
|
||||
def test_ejection_past_churn_limit_min(spec, state):
|
||||
assert spec.get_validator_churn_limit(state) == spec.config.MIN_PER_EPOCH_CHURN_LIMIT
|
||||
yield from run_test_ejection_past_churn_limit(spec, state)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@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)
|
||||
@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
|
||||
yield from run_test_ejection_past_churn_limit(spec, state)
|
||||
|
||||
|
||||
def run_test_activation_queue_activation_and_ejection(spec, state, num_per_status):
|
||||
# move past first two irregular epochs wrt finality
|
||||
next_epoch(spec, state)
|
||||
next_epoch(spec, state)
|
||||
|
||||
# ready for entrance into activation queue
|
||||
activation_queue_index = 0
|
||||
mock_deposit(spec, state, activation_queue_index)
|
||||
activation_queue_start_index = 0
|
||||
activation_queue_indices = list(range(activation_queue_start_index, activation_queue_start_index + num_per_status))
|
||||
for validator_index in activation_queue_indices:
|
||||
mock_deposit(spec, state, validator_index)
|
||||
|
||||
# ready for activation
|
||||
activation_index = 1
|
||||
mock_deposit(spec, state, activation_index)
|
||||
state.finalized_checkpoint.epoch = spec.get_current_epoch(state) - 1
|
||||
state.validators[activation_index].activation_eligibility_epoch = state.finalized_checkpoint.epoch
|
||||
activation_start_index = num_per_status
|
||||
activation_indices = list(range(activation_start_index, activation_start_index + num_per_status))
|
||||
for validator_index in activation_indices:
|
||||
mock_deposit(spec, state, validator_index)
|
||||
state.validators[validator_index].activation_eligibility_epoch = state.finalized_checkpoint.epoch
|
||||
|
||||
# ready for ejection
|
||||
ejection_index = 2
|
||||
state.validators[ejection_index].effective_balance = spec.config.EJECTION_BALANCE
|
||||
ejection_start_index = num_per_status * 2
|
||||
ejection_indices = list(range(ejection_start_index, ejection_start_index + num_per_status))
|
||||
for validator_index in ejection_indices:
|
||||
state.validators[validator_index].effective_balance = spec.config.EJECTION_BALANCE
|
||||
|
||||
churn_limit = spec.get_validator_churn_limit(state)
|
||||
yield from run_process_registry_updates(spec, state)
|
||||
|
||||
# validator moved into activation queue
|
||||
validator = state.validators[activation_queue_index]
|
||||
assert validator.activation_eligibility_epoch != spec.FAR_FUTURE_EPOCH
|
||||
assert validator.activation_epoch == spec.FAR_FUTURE_EPOCH
|
||||
assert not spec.is_active_validator(validator, spec.get_current_epoch(state))
|
||||
# all eligible validators moved into activation queue
|
||||
for validator_index in activation_queue_indices:
|
||||
validator = state.validators[validator_index]
|
||||
assert validator.activation_eligibility_epoch != spec.FAR_FUTURE_EPOCH
|
||||
assert validator.activation_epoch == spec.FAR_FUTURE_EPOCH
|
||||
assert not spec.is_active_validator(validator, spec.get_current_epoch(state))
|
||||
|
||||
# validator activated for future epoch
|
||||
validator = state.validators[activation_index]
|
||||
assert validator.activation_eligibility_epoch != spec.FAR_FUTURE_EPOCH
|
||||
assert validator.activation_epoch != spec.FAR_FUTURE_EPOCH
|
||||
assert not spec.is_active_validator(validator, spec.get_current_epoch(state))
|
||||
assert spec.is_active_validator(
|
||||
validator,
|
||||
spec.compute_activation_exit_epoch(spec.get_current_epoch(state))
|
||||
)
|
||||
# up to churn limit validators get activated for future epoch from the queue
|
||||
for validator_index in activation_indices[:churn_limit]:
|
||||
validator = state.validators[validator_index]
|
||||
assert validator.activation_eligibility_epoch != spec.FAR_FUTURE_EPOCH
|
||||
assert validator.activation_epoch != spec.FAR_FUTURE_EPOCH
|
||||
assert not spec.is_active_validator(validator, spec.get_current_epoch(state))
|
||||
assert spec.is_active_validator(
|
||||
validator,
|
||||
spec.compute_activation_exit_epoch(spec.get_current_epoch(state))
|
||||
)
|
||||
|
||||
# validator ejected for future epoch
|
||||
validator = state.validators[ejection_index]
|
||||
assert validator.exit_epoch != spec.FAR_FUTURE_EPOCH
|
||||
assert spec.is_active_validator(validator, spec.get_current_epoch(state))
|
||||
assert not spec.is_active_validator(
|
||||
validator,
|
||||
spec.compute_activation_exit_epoch(spec.get_current_epoch(state))
|
||||
)
|
||||
# any remaining validators do not exit the activation queue
|
||||
for validator_index in activation_indices[churn_limit:]:
|
||||
validator = state.validators[validator_index]
|
||||
assert validator.activation_eligibility_epoch != spec.FAR_FUTURE_EPOCH
|
||||
assert validator.activation_epoch == spec.FAR_FUTURE_EPOCH
|
||||
|
||||
# all ejection balance validators ejected for a future epoch
|
||||
for i, validator_index in enumerate(ejection_indices):
|
||||
validator = state.validators[validator_index]
|
||||
assert validator.exit_epoch != spec.FAR_FUTURE_EPOCH
|
||||
assert spec.is_active_validator(validator, spec.get_current_epoch(state))
|
||||
queue_offset = i // churn_limit
|
||||
assert not spec.is_active_validator(
|
||||
validator,
|
||||
spec.compute_activation_exit_epoch(spec.get_current_epoch(state)) + queue_offset
|
||||
)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_activation_queue_activation_and_ejection__1(spec, state):
|
||||
yield from run_test_activation_queue_activation_and_ejection(spec, state, 1)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_activation_queue_activation_and_ejection__churn_limit(spec, state):
|
||||
churn_limit = spec.get_validator_churn_limit(state)
|
||||
assert churn_limit == spec.config.MIN_PER_EPOCH_CHURN_LIMIT
|
||||
yield from run_test_activation_queue_activation_and_ejection(spec, state, churn_limit)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_activation_queue_activation_and_ejection__exceed_churn_limit(spec, state):
|
||||
churn_limit = spec.get_validator_churn_limit(state)
|
||||
assert churn_limit == spec.config.MIN_PER_EPOCH_CHURN_LIMIT
|
||||
yield from run_test_activation_queue_activation_and_ejection(spec, state, churn_limit + 1)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@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)
|
||||
@single_phase
|
||||
def test_activation_queue_activation_and_ejection__scaled_churn_limit(spec, state):
|
||||
churn_limit = spec.get_validator_churn_limit(state)
|
||||
assert churn_limit > spec.config.MIN_PER_EPOCH_CHURN_LIMIT
|
||||
yield from run_test_activation_queue_activation_and_ejection(spec, state, churn_limit)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@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)
|
||||
@single_phase
|
||||
def test_activation_queue_activation_and_ejection__exceed_scaled_churn_limit(spec, state):
|
||||
churn_limit = spec.get_validator_churn_limit(state)
|
||||
assert churn_limit > spec.config.MIN_PER_EPOCH_CHURN_LIMIT
|
||||
yield from run_test_activation_queue_activation_and_ejection(spec, state, churn_limit * 2)
|
||||
|
Loading…
x
Reference in New Issue
Block a user