add process_registry_updates tests for scaled churn limit

This commit is contained in:
Danny Ryan 2021-09-07 20:34:28 -06:00
parent 4faff4f899
commit 43e79a7ee0
No known key found for this signature in database
GPG Key ID: 2765A792E42CE07A
3 changed files with 147 additions and 43 deletions

View File

@ -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

View File

@ -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)

View File

@ -1,6 +1,11 @@
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.context import (
spec_test, spec_state_test,
with_all_phases, single_phase,
with_custom_state,
scaled_churn_balances,
)
from eth2spec.test.helpers.epoch_processing import run_epoch_processing_with
@ -112,9 +117,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 +131,43 @@ 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
@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 +188,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 +205,130 @@ 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
@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):
num_validators_per_status= spec.get_validator_churn_limit(state)
yield from run_test_activation_queue_activation_and_ejection(spec, state, num_validators_per_status)
@with_all_phases
@spec_state_test
def test_activation_queue_activation_and_ejection__exceed_churn_limit(spec, state):
num_validators_per_status = spec.get_validator_churn_limit(state) + 1
yield from run_test_activation_queue_activation_and_ejection(spec, state, num_validators_per_status)
@with_all_phases
@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
@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
num_validators_per_status = churn_limit * 2
yield from run_test_activation_queue_activation_and_ejection(spec, state, num_validators_per_status)