diff --git a/test_libs/pyspec/eth2spec/test/phase_0/epoch_processing/run_epoch_process_base.py b/test_libs/pyspec/eth2spec/test/phase_0/epoch_processing/run_epoch_process_base.py index c69160fb0..5b2a2ece4 100644 --- a/test_libs/pyspec/eth2spec/test/phase_0/epoch_processing/run_epoch_process_base.py +++ b/test_libs/pyspec/eth2spec/test/phase_0/epoch_processing/run_epoch_process_base.py @@ -12,13 +12,9 @@ process_calls = [ ] -def run_epoch_processing_to(spec, state, process_name: str, exclusive=False): +def run_epoch_processing_to(spec, state, process_name: str): """ - Run the epoch processing functions up to ``process_name``. - If ``exclusive`` is True, the process itself will not be ran. - If ``exclusive`` is False (default), this function yields: - - pre-state ('pre'), state before calling ``process_name`` - - post-state ('post'), state after calling ``process_name`` + Processes to the next epoch transition, up to, but not including, the sub-transition named ``process_name`` """ slot = state.slot + (spec.SLOTS_PER_EPOCH - state.slot % spec.SLOTS_PER_EPOCH) @@ -36,7 +32,14 @@ def run_epoch_processing_to(spec, state, process_name: str, exclusive=False): if hasattr(spec, name): getattr(spec, name)(state) - if not exclusive: - yield 'pre', state - getattr(spec, process_name)(state) - yield 'post', state + +def run_epoch_processing_with(spec, state, process_name: str): + """ + Processes to the next epoch transition, up to and including the sub-transition named ``process_name`` + - pre-state ('pre'), state before calling ``process_name`` + - post-state ('post'), state after calling ``process_name`` + """ + run_epoch_processing_to(spec, state, process_name) + yield 'pre', state + getattr(spec, process_name)(state) + yield 'post', state diff --git a/test_libs/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_crosslinks.py b/test_libs/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_crosslinks.py index 058c93733..599fde8e7 100644 --- a/test_libs/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_crosslinks.py +++ b/test_libs/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_crosslinks.py @@ -12,11 +12,11 @@ from eth2spec.test.helpers.attestations import ( get_valid_attestation, sign_attestation, ) -from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import run_epoch_processing_to +from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import run_epoch_processing_with def run_process_crosslinks(spec, state): - yield from run_epoch_processing_to(spec, state, 'process_crosslinks') + yield from run_epoch_processing_with(spec, state, 'process_crosslinks') @with_all_phases diff --git a/test_libs/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_final_updates.py b/test_libs/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_final_updates.py index d1af7d396..58882a44f 100644 --- a/test_libs/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_final_updates.py +++ b/test_libs/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_final_updates.py @@ -1,9 +1,11 @@ from eth2spec.test.context import spec_state_test, with_all_phases -from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import run_epoch_processing_to +from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import ( + run_epoch_processing_with, run_epoch_processing_to +) def run_process_final_updates(spec, state): - yield from run_epoch_processing_to(spec, state, 'process_final_updates') + yield from run_epoch_processing_with(spec, state, 'process_final_updates') @with_all_phases diff --git a/test_libs/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_registry_updates.py b/test_libs/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_registry_updates.py index ae76e95c2..19500d4ab 100644 --- a/test_libs/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_registry_updates.py +++ b/test_libs/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_registry_updates.py @@ -1,10 +1,10 @@ from eth2spec.test.helpers.state import next_epoch from eth2spec.test.context import spec_state_test, with_all_phases -from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import run_epoch_processing_to +from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import run_epoch_processing_with def run_process_registry_updates(spec, state): - yield from run_epoch_processing_to(spec, state, 'process_registry_updates') + yield from run_epoch_processing_with(spec, state, 'process_registry_updates') def mock_deposit(spec, state, index): diff --git a/test_libs/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_slashings.py b/test_libs/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_slashings.py new file mode 100644 index 000000000..f1a23326b --- /dev/null +++ b/test_libs/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_slashings.py @@ -0,0 +1,121 @@ +from eth2spec.test.context import spec_state_test, with_all_phases +from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import ( + run_epoch_processing_with, run_epoch_processing_to +) + + +def run_process_slashings(spec, state): + yield from run_epoch_processing_with(spec, state, 'process_slashings') + + +def slash_validators(spec, state, indices, out_epochs): + total_slashed_balance = 0 + for i, out_epoch in zip(indices, out_epochs): + v = state.validators[i] + v.slashed = True + spec.initiate_validator_exit(state, i) + v.withdrawable_epoch = out_epoch + total_slashed_balance += v.effective_balance + + state.slashed_balances[ + spec.get_current_epoch(state) % spec.EPOCHS_PER_SLASHED_BALANCES_VECTOR + ] = total_slashed_balance + + +@with_all_phases +@spec_state_test +def test_max_penalties(spec, state): + slashed_count = (len(state.validators) // 3) + 1 + out_epoch = spec.get_current_epoch(state) + (spec.EPOCHS_PER_SLASHED_BALANCES_VECTOR // 2) + + slashed_indices = list(range(slashed_count)) + slash_validators(spec, state, slashed_indices, [out_epoch] * slashed_count) + + total_balance = spec.get_total_active_balance(state) + total_penalties = state.slashed_balances[spec.get_current_epoch(state) % spec.EPOCHS_PER_SLASHED_BALANCES_VECTOR] + + assert total_balance // 3 <= total_penalties + + yield from run_process_slashings(spec, state) + + for i in slashed_indices: + assert state.balances[i] == 0 + + +@with_all_phases +@spec_state_test +def test_min_penalties(spec, state): + # run_epoch_processing_to(spec, state, 'process_slashings', exclusive=True) + + # Just the bare minimum for this one validator + pre_balance = state.balances[0] = state.validators[0].effective_balance = spec.EJECTION_BALANCE + # All the other validators get the maximum. + for i in range(1, len(state.validators)): + state.validators[i].effective_balance = state.balances[i] = spec.MAX_EFFECTIVE_BALANCE + + out_epoch = spec.get_current_epoch(state) + (spec.EPOCHS_PER_SLASHED_BALANCES_VECTOR // 2) + + slash_validators(spec, state, [0], [out_epoch]) + + total_balance = spec.get_total_active_balance(state) + total_penalties = state.slashed_balances[spec.get_current_epoch(state) % spec.EPOCHS_PER_SLASHED_BALANCES_VECTOR] + + # we are testing the minimum here, i.e. get slashed (effective_balance / MIN_SLASHING_PENALTY_QUOTIENT) + assert total_penalties * 3 / total_balance < 1 / spec.MIN_SLASHING_PENALTY_QUOTIENT + + yield from run_process_slashings(spec, state) + + assert state.balances[0] == pre_balance - (pre_balance // spec.MIN_SLASHING_PENALTY_QUOTIENT) + + +@with_all_phases +@spec_state_test +def test_scaled_penalties(spec, state): + # skip to next epoch + state.slot = spec.SLOTS_PER_EPOCH + + # Also mock some previous slashings, so that we test to have the delta in the penalties computation. + for i in range(spec.EPOCHS_PER_SLASHED_BALANCES_VECTOR): + state.slashed_balances[i] = spec.MAX_EFFECTIVE_BALANCE * 3 + + # Mock the very last one (which is to be used for the delta balance computation) to be different. + # To enforce the client test runner to correctly get this one from the array, not the others. + prev_penalties = state.slashed_balances[ + (spec.get_current_epoch(state) + 1) % spec.EPOCHS_PER_SLASHED_BALANCES_VECTOR + ] = spec.MAX_EFFECTIVE_BALANCE * 2 + + slashed_count = len(state.validators) // 4 + + assert slashed_count > 10 + + # make the balances non-uniform. + # Otherwise it would just be a simple 3/4 balance slashing. Test the per-validator scaled penalties. + for i in range(10): + state.validators[i].effective_balance += spec.EFFECTIVE_BALANCE_INCREMENT * 4 + state.balances[i] += spec.EFFECTIVE_BALANCE_INCREMENT * 4 + + total_balance = spec.get_total_active_balance(state) + + out_epoch = spec.get_current_epoch(state) + (spec.EPOCHS_PER_SLASHED_BALANCES_VECTOR // 2) + + slashed_indices = list(range(slashed_count)) + + # Process up to the sub-transition, then Hi-jack and get the balances. + # We just want to test the slashings. + # But we are not interested in the other balance changes during the same epoch transition. + run_epoch_processing_to(spec, state, 'process_slashings') + pre_slash_balances = list(state.balances) + + slash_validators(spec, state, slashed_indices, [out_epoch] * slashed_count) + + yield 'pre', state + spec.process_slashings(state) + yield 'post', state + + total_penalties = state.slashed_balances[spec.get_current_epoch(state) % spec.EPOCHS_PER_SLASHED_BALANCES_VECTOR] + total_penalties -= prev_penalties + + for i in slashed_indices: + v = state.validators[i] + penalty = v.effective_balance * total_penalties * 3 // total_balance + assert state.balances[i] == pre_slash_balances[i] - penalty