From 43e79a7ee03d89568371c25e3c7e5d2b9cc049c3 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 7 Sep 2021 20:34:28 -0600 Subject: [PATCH 1/4] 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) From 6784025d645f71fd16e54cb77983e608ac918776 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 7 Sep 2021 20:49:54 -0600 Subject: [PATCH 2/4] add scaled churn limit tests for voluntary exits --- .../test_process_voluntary_exit.py | 36 ++++++++++++++----- .../test_process_registry_updates.py | 2 +- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_voluntary_exit.py b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_voluntary_exit.py index f713d1792..1b6b40580 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_voluntary_exit.py +++ b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_voluntary_exit.py @@ -1,4 +1,9 @@ -from eth2spec.test.context import spec_state_test, expect_assertion_error, always_bls, with_all_phases +from eth2spec.test.context import ( + spec_state_test, expect_assertion_error, + always_bls, with_all_phases, + 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 +73,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 +109,27 @@ 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 +@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 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 e3f1f2093..6b1d7bce9 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 @@ -302,7 +302,7 @@ def test_activation_queue_activation_and_ejection__1(spec, state): @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) + 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) From 8220f7dd44fa983d3d40d8a72d758c8eb3bbc506 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 7 Sep 2021 20:55:47 -0600 Subject: [PATCH 3/4] ensure new dynamic queue tests don't run for mainnet cofig --- .../block_processing/test_process_voluntary_exit.py | 5 ++++- .../epoch_processing/test_process_registry_updates.py | 11 ++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_voluntary_exit.py b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_voluntary_exit.py index 1b6b40580..9e209f23e 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_voluntary_exit.py +++ b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_voluntary_exit.py @@ -1,6 +1,7 @@ +from eth2spec.test.helpers.constants import MINIMAL from eth2spec.test.context import ( spec_state_test, expect_assertion_error, - always_bls, with_all_phases, + always_bls, with_all_phases, with_presets, spec_test, single_phase, with_custom_state, scaled_churn_balances, ) @@ -123,6 +124,8 @@ def test_success_exit_queue__min_churn(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 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 6b1d7bce9..5bcf3a82b 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,9 +1,10 @@ from eth2spec.test.helpers.deposits import mock_deposit from eth2spec.test.helpers.state import next_epoch, next_slots +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_custom_state, with_presets, scaled_churn_balances, ) from eth2spec.test.helpers.epoch_processing import run_epoch_processing_with @@ -160,6 +161,8 @@ def test_activation_queue_efficiency_min(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 @@ -221,6 +224,8 @@ def test_ejection_past_churn_limit_min(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 @@ -314,6 +319,8 @@ def test_activation_queue_activation_and_ejection__exceed_churn_limit(spec, stat @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 @@ -324,6 +331,8 @@ def test_activation_queue_activation_and_ejection__scaled_churn_limit(spec, stat @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 From a47ade3ba6eb96e6b51957cfeb2ecc34910f3740 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 9 Sep 2021 15:05:52 -0600 Subject: [PATCH 4/4] pr feedback --- .../test_process_registry_updates.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) 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 5bcf3a82b..6539dc92d 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 @@ -307,15 +307,17 @@ def test_activation_queue_activation_and_ejection__1(spec, state): @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) + 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): - 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) + 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 @@ -339,5 +341,4 @@ def test_activation_queue_activation_and_ejection__scaled_churn_limit(spec, stat 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) + yield from run_test_activation_queue_activation_and_ejection(spec, state, churn_limit * 2)