From 43e79a7ee03d89568371c25e3c7e5d2b9cc049c3 Mon Sep 17 00:00:00 2001 From: Danny Ryan <dannyjryan@gmail.com> Date: Tue, 7 Sep 2021 20:34:28 -0600 Subject: [PATCH] add process_registry_updates tests for scaled churn limit --- configs/minimal.yaml | 4 +- tests/core/pyspec/eth2spec/test/context.py | 11 ++ .../test_process_registry_updates.py | 175 ++++++++++++++---- 3 files changed, 147 insertions(+), 43 deletions(-) diff --git a/configs/minimal.yaml b/configs/minimal.yaml index 37a428b50..b067f222f 100644 --- a/configs/minimal.yaml +++ b/configs/minimal.yaml @@ -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 diff --git a/tests/core/pyspec/eth2spec/test/context.py b/tests/core/pyspec/eth2spec/test/context.py index 346cdc8f1..ef92efade 100644 --- a/tests/core/pyspec/eth2spec/test/context.py +++ b/tests/core/pyspec/eth2spec/test/context.py @@ -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) diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py index 6e7784aa9..e3f1f2093 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py @@ -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)