From fe13bab33803eea248385016825ae1a8c4ae27e6 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 23 Apr 2020 10:26:34 -0600 Subject: [PATCH 01/15] rework rewards/penalties to be more granular --- specs/phase0/beacon-chain.md | 101 +++++++++++++++++++++++++++-------- 1 file changed, 80 insertions(+), 21 deletions(-) diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index cdf38dc1e..00e1d3a73 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -1347,32 +1347,57 @@ def get_base_reward(state: BeaconState, index: ValidatorIndex) -> Gwei: ``` ```python -def get_attestation_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: +def get_eligible_validator_indices(state: BeaconState) -> Sequence[ValidatorIndex]: previous_epoch = get_previous_epoch(state) - total_balance = get_total_active_balance(state) - rewards = [Gwei(0) for _ in range(len(state.validators))] - penalties = [Gwei(0) for _ in range(len(state.validators))] - eligible_validator_indices = [ + return [ ValidatorIndex(index) for index, v in enumerate(state.validators) if is_active_validator(v, previous_epoch) or (v.slashed and previous_epoch + 1 < v.withdrawable_epoch) ] +``` - # Micro-incentives for matching FFG source, FFG target, and head - matching_source_attestations = get_matching_source_attestations(state, previous_epoch) - matching_target_attestations = get_matching_target_attestations(state, previous_epoch) - matching_head_attestations = get_matching_head_attestations(state, previous_epoch) - for attestations in (matching_source_attestations, matching_target_attestations, matching_head_attestations): - unslashed_attesting_indices = get_unslashed_attesting_indices(state, attestations) - attesting_balance = get_total_balance(state, unslashed_attesting_indices) - for index in eligible_validator_indices: - if index in unslashed_attesting_indices: - increment = EFFECTIVE_BALANCE_INCREMENT # Factored out from balance totals to avoid uint64 overflow - reward_numerator = get_base_reward(state, index) * (attesting_balance // increment) - rewards[index] += reward_numerator // (total_balance // increment) - else: - penalties[index] += get_base_reward(state, index) +```python +def compute_attestation_component_deltas(state: BeaconState, + attestations: Sequence[PendingAttestation] + ) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: + rewards = [Gwei(0) for _ in range(len(state.validators))] + penalties = [Gwei(0) for _ in range(len(state.validators))] + total_balance = get_total_active_balance(state) + unslashed_attesting_indices = get_unslashed_attesting_indices(state, attestations) + attesting_balance = get_total_balance(state, unslashed_attesting_indices) + for index in get_eligible_validator_indices(state): + if index in unslashed_attesting_indices: + increment = EFFECTIVE_BALANCE_INCREMENT # Factored out from balance totals to avoid uint64 overflow + reward_numerator = get_base_reward(state, index) * (attesting_balance // increment) + rewards[index] += reward_numerator // (total_balance // increment) + else: + penalties[index] += get_base_reward(state, index) + return rewards, penalties +``` +```python +def get_source_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: + matching_source_attestations = get_matching_source_attestations(state, get_previous_epoch(state)) + return compute_attestation_component_deltas(state, matching_source_attestations) +``` + +```python +def get_target_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: + matching_target_attestations = get_matching_source_attestations(state, get_previous_epoch(state)) + return compute_attestation_component_deltas(state, matching_target_attestations) +``` + +```python +def get_head_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: + matching_head_attestations = get_matching_source_attestations(state, get_previous_epoch(state)) + return compute_attestation_component_deltas(state, matching_head_attestations) +``` + +```python +def get_inclusion_delay_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: # Proposer and inclusion delay micro-rewards + rewards = [Gwei(0) for _ in range(len(state.validators))] + penalties = [Gwei(0) for _ in range(len(state.validators))] + matching_source_attestations = get_matching_source_attestations(state, get_previous_epoch(state)) for index in get_unslashed_attesting_indices(state, matching_source_attestations): attestation = min([ a for a in matching_source_attestations @@ -1382,16 +1407,50 @@ def get_attestation_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence rewards[attestation.proposer_index] += proposer_reward max_attester_reward = get_base_reward(state, index) - proposer_reward rewards[index] += Gwei(max_attester_reward // attestation.inclusion_delay) + return rewards, penalties +``` +```python +def get_inactivity_penalty_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: # Inactivity penalty - finality_delay = previous_epoch - state.finalized_checkpoint.epoch + rewards = [Gwei(0) for _ in range(len(state.validators))] + penalties = [Gwei(0) for _ in range(len(state.validators))] + finality_delay = get_previous_epoch(state) - state.finalized_checkpoint.epoch + if finality_delay > MIN_EPOCHS_TO_INACTIVITY_PENALTY: + matching_target_attestations = get_matching_source_attestations(state, get_previous_epoch(state)) matching_target_attesting_indices = get_unslashed_attesting_indices(state, matching_target_attestations) - for index in eligible_validator_indices: + for index in get_eligible_validator_indices(state): penalties[index] += Gwei(BASE_REWARDS_PER_EPOCH * get_base_reward(state, index)) if index not in matching_target_attesting_indices: effective_balance = state.validators[index].effective_balance penalties[index] += Gwei(effective_balance * finality_delay // INACTIVITY_PENALTY_QUOTIENT) + return rewards, penalties +``` + +```python +def get_attestation_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: + source_rewards, source_penalties = get_source_deltas(state) + target_rewards, target_penalties = get_target_deltas(state) + head_rewards, head_penalties = get_head_deltas(state) + inclusion_delay_rewards, _ = get_inclusion_delay_deltas(state) + _, inactivity_penalties = get_inactivity_penalty_deltas(state) + + rewards = [ + source_rewards[i] + + target_rewards[i] + + head_rewards[i] + + inclusion_delay_rewards[i] + for i in range(len(state.validators)) + ] + + penalties = [ + source_penalties[i] + + target_penalties[i] + + head_penalties[i] + + inactivity_penalties[i] + for i in range(len(state.validators)) + ] return rewards, penalties ``` From 7612667bbef59c7df5255d8bebde1ba6618ce18b Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 23 Apr 2020 11:13:09 -0600 Subject: [PATCH 02/15] minor feedback and fixes on rewards/penalites proposal Co-Authored-By: Hsiao-Wei Wang --- specs/phase0/beacon-chain.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index 00e1d3a73..2d9429cf1 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -1382,7 +1382,7 @@ def get_source_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei ```python def get_target_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: - matching_target_attestations = get_matching_source_attestations(state, get_previous_epoch(state)) + matching_target_attestations = get_matching_target_attestations(state, get_previous_epoch(state)) return compute_attestation_component_deltas(state, matching_target_attestations) ``` @@ -1394,7 +1394,9 @@ def get_head_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]] ```python def get_inclusion_delay_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: - # Proposer and inclusion delay micro-rewards + """ + Return proposer and inclusion delay micro-rewards. + """ rewards = [Gwei(0) for _ in range(len(state.validators))] penalties = [Gwei(0) for _ in range(len(state.validators))] matching_source_attestations = get_matching_source_attestations(state, get_previous_epoch(state)) @@ -1412,13 +1414,15 @@ def get_inclusion_delay_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequ ```python def get_inactivity_penalty_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: - # Inactivity penalty + """ + Return inactivity penalty. + """ rewards = [Gwei(0) for _ in range(len(state.validators))] penalties = [Gwei(0) for _ in range(len(state.validators))] finality_delay = get_previous_epoch(state) - state.finalized_checkpoint.epoch if finality_delay > MIN_EPOCHS_TO_INACTIVITY_PENALTY: - matching_target_attestations = get_matching_source_attestations(state, get_previous_epoch(state)) + matching_target_attestations = get_matching_target_attestations(state, get_previous_epoch(state)) matching_target_attesting_indices = get_unslashed_attesting_indices(state, matching_target_attestations) for index in get_eligible_validator_indices(state): penalties[index] += Gwei(BASE_REWARDS_PER_EPOCH * get_base_reward(state, index)) From 5f18dd778c1d44203140a27f088b9596767189d5 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 28 Apr 2020 19:26:14 -0600 Subject: [PATCH 03/15] add baseline get_target_deltas tests --- specs/phase0/beacon-chain.md | 24 ++++- .../eth2spec/test/helpers/attestations.py | 33 ++++++- .../rewards/test_get_target_deltas.py | 92 +++++++++++++++++++ .../test_process_rewards_and_penalties.py | 28 +----- 4 files changed, 147 insertions(+), 30 deletions(-) create mode 100644 tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/rewards/test_get_target_deltas.py diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index 33689ece9..f40f0096b 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -1341,6 +1341,8 @@ def process_justification_and_finalization(state: BeaconState) -> None: #### Rewards and penalties +##### Helpers + ```python def get_base_reward(state: BeaconState, index: ValidatorIndex) -> Gwei: total_balance = get_total_active_balance(state) @@ -1376,20 +1378,31 @@ def compute_attestation_component_deltas(state: BeaconState, return rewards, penalties ``` +##### Components of attestation deltas + ```python def get_source_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: + """ + Return attester micro-rewards/penalties for source-vote for each validator. + """ matching_source_attestations = get_matching_source_attestations(state, get_previous_epoch(state)) return compute_attestation_component_deltas(state, matching_source_attestations) ``` ```python def get_target_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: + """ + Return attester micro-rewards/penalties for target-vote for each validator. + """ matching_target_attestations = get_matching_target_attestations(state, get_previous_epoch(state)) return compute_attestation_component_deltas(state, matching_target_attestations) ``` ```python def get_head_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: + """ + Return attester micro-rewards/penalties for head-vote for each validator. + """ matching_head_attestations = get_matching_source_attestations(state, get_previous_epoch(state)) return compute_attestation_component_deltas(state, matching_head_attestations) ``` @@ -1397,7 +1410,7 @@ def get_head_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]] ```python def get_inclusion_delay_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: """ - Return proposer and inclusion delay micro-rewards. + Return proposer and inclusion delay micro-rewards/penalties for each validator. """ rewards = [Gwei(0) for _ in range(len(state.validators))] penalties = [Gwei(0) for _ in range(len(state.validators))] @@ -1417,7 +1430,7 @@ def get_inclusion_delay_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequ ```python def get_inactivity_penalty_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: """ - Return inactivity penalty. + Return inactivity reward/penalty deltas for each validator. """ rewards = [Gwei(0) for _ in range(len(state.validators))] penalties = [Gwei(0) for _ in range(len(state.validators))] @@ -1434,8 +1447,13 @@ def get_inactivity_penalty_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], S return rewards, penalties ``` +##### `get_attestation_deltas` + ```python def get_attestation_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: + """ + Return attestation reward/penalty deltas for each validator. + """ source_rewards, source_penalties = get_source_deltas(state) target_rewards, target_penalties = get_target_deltas(state) head_rewards, head_penalties = get_head_deltas(state) @@ -1461,6 +1479,8 @@ def get_attestation_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence return rewards, penalties ``` +##### `process_rewards_and_penalties` + ```python def process_rewards_and_penalties(state: BeaconState) -> None: if get_current_epoch(state) == GENESIS_EPOCH: diff --git a/tests/core/pyspec/eth2spec/test/helpers/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/attestations.py index 8215f5c5b..692e874de 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attestations.py @@ -1,7 +1,7 @@ from typing import List from eth2spec.test.context import expect_assertion_error, PHASE0 -from eth2spec.test.helpers.state import state_transition_and_sign_block +from eth2spec.test.helpers.state import state_transition_and_sign_block, next_epoch, next_slot from eth2spec.test.helpers.block import build_empty_block_for_next_slot from eth2spec.test.helpers.keys import privkeys from eth2spec.utils import bls @@ -278,3 +278,34 @@ def next_epoch_with_attestations(spec, signed_blocks.append(signed_block) return state, signed_blocks, post_state + + +def prepare_state_with_full_attestations(spec, state, empty=False): + """ + Fill ``state`` with maximally full attestations. + Move to the start of the next epoch to ensure full epoch worth. + """ + # Go to start of next epoch to ensure can have full participation + next_epoch(spec, state) + + start_slot = state.slot + start_epoch = spec.get_current_epoch(state) + next_epoch_start_slot = spec.compute_start_slot_at_epoch(start_epoch + 1) + attestations = [] + for _ in range(spec.SLOTS_PER_EPOCH + spec.MIN_ATTESTATION_INCLUSION_DELAY): + # create an attestation for each index in each slot in epoch + if state.slot < next_epoch_start_slot: + for committee_index in range(spec.get_committee_count_at_slot(state, state.slot)): + attestation = get_valid_attestation(spec, state, index=committee_index, empty=empty, signed=True) + attestations.append(attestation) + # fill each created slot in state after inclusion delay + if state.slot >= start_slot + spec.MIN_ATTESTATION_INCLUSION_DELAY: + inclusion_slot = state.slot - spec.MIN_ATTESTATION_INCLUSION_DELAY + include_attestations = [att for att in attestations if att.data.slot == inclusion_slot] + add_attestations_to_state(spec, state, include_attestations, state.slot) + next_slot(spec, state) + + assert state.slot == next_epoch_start_slot + spec.MIN_ATTESTATION_INCLUSION_DELAY + assert len(state.previous_epoch_attestations) == len(attestations) + + return attestations diff --git a/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/rewards/test_get_target_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/rewards/test_get_target_deltas.py new file mode 100644 index 000000000..c30865cf0 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/rewards/test_get_target_deltas.py @@ -0,0 +1,92 @@ +from eth2spec.test.context import with_all_phases, spec_state_test +from eth2spec.test.helpers.attestations import prepare_state_with_full_attestations + + +def run_get_target_deltas(spec, state): + """ + Run ``process_block_header``, yielding: + - pre-state ('pre') + - rewards ('rewards') + - penalties ('penalties') + """ + yield 'pre', state + + rewards, penalties = spec.get_target_deltas(state) + + yield 'rewards', rewards + yield 'penalties', penalties + + matching_target_attestations = spec.get_matching_target_attestations(state, spec.get_previous_epoch(state)) + matching_target_indices = spec.get_unslashed_attesting_indices(state, matching_target_attestations) + for index in spec.get_eligible_validator_indices(state): + if index in matching_target_indices and not state.validators[index].slashed: + assert rewards[index] > 0 + assert penalties[index] == 0 + else: + assert rewards[index] == 0 + assert penalties[index] > 0 + + +@with_all_phases +@spec_state_test +def test_empty(spec, state): + # Do not add any attestations to state + + yield from run_get_target_deltas(spec, state) + + +@with_all_phases +@spec_state_test +def test_full_all_correct(spec, state): + prepare_state_with_full_attestations(spec, state) + + yield from run_get_target_deltas(spec, state) + + +@with_all_phases +@spec_state_test +def test_full_half_correct(spec, state): + prepare_state_with_full_attestations(spec, state) + + # Make half of pending attestations have bad target + for pending_attestation in state.previous_epoch_attestations[:len(state.previous_epoch_attestations) // 2]: + pending_attestation.data.target.root = b'\x66'*32 + + yield from run_get_target_deltas(spec, state) + + +@with_all_phases +@spec_state_test +def test_half_full(spec, state): + prepare_state_with_full_attestations(spec, state) + + # Remove half of attestations + state.previous_epoch_attestations = state.previous_epoch_attestations[:len(state.previous_epoch_attestations) // 2] + + yield from run_get_target_deltas(spec, state) + + +@with_all_phases +@spec_state_test +def test_one_correct(spec, state): + prepare_state_with_full_attestations(spec, state) + + # Remove half of attestations + state.previous_epoch_attestations = state.previous_epoch_attestations[:1] + + yield from run_get_target_deltas(spec, state) + + +@with_all_phases +@spec_state_test +def test_with_slashed_validators(spec, state): + prepare_state_with_full_attestations(spec, state) + + # Slash half of validators + for validator in state.validators: + validator.slashed = True + + yield from run_get_target_deltas(spec, state) + +def test_some_zero_balances(spec, state): + diff --git a/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_rewards_and_penalties.py b/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_rewards_and_penalties.py index af695fe69..f1d86c373 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_rewards_and_penalties.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_rewards_and_penalties.py @@ -12,6 +12,7 @@ from eth2spec.test.helpers.state import ( from eth2spec.test.helpers.attestations import ( add_attestations_to_state, get_valid_attestation, + prepare_state_with_full_attestations, ) from eth2spec.test.helpers.attester_slashings import get_indexed_attestation_participants from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import run_epoch_processing_with @@ -21,33 +22,6 @@ def run_process_rewards_and_penalties(spec, state): yield from run_epoch_processing_with(spec, state, 'process_rewards_and_penalties') -def prepare_state_with_full_attestations(spec, state, empty=False): - # Go to start of next epoch to ensure can have full participation - next_epoch(spec, state) - - start_slot = state.slot - start_epoch = spec.get_current_epoch(state) - next_epoch_start_slot = spec.compute_start_slot_at_epoch(start_epoch + 1) - attestations = [] - for _ in range(spec.SLOTS_PER_EPOCH + spec.MIN_ATTESTATION_INCLUSION_DELAY): - # create an attestation for each index in each slot in epoch - if state.slot < next_epoch_start_slot: - for committee_index in range(spec.get_committee_count_at_slot(state, state.slot)): - attestation = get_valid_attestation(spec, state, index=committee_index, empty=empty, signed=True) - attestations.append(attestation) - # fill each created slot in state after inclusion delay - if state.slot >= start_slot + spec.MIN_ATTESTATION_INCLUSION_DELAY: - inclusion_slot = state.slot - spec.MIN_ATTESTATION_INCLUSION_DELAY - include_attestations = [att for att in attestations if att.data.slot == inclusion_slot] - add_attestations_to_state(spec, state, include_attestations, state.slot) - next_slot(spec, state) - - assert state.slot == next_epoch_start_slot + spec.MIN_ATTESTATION_INCLUSION_DELAY - assert len(state.previous_epoch_attestations) == len(attestations) - - return attestations - - @with_phases(['phase0']) @spec_state_test def test_genesis_epoch_no_attestations_no_penalties(spec, state): From cd27e5e045f8a2f5d3a073e496596126fcedfc3a Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 29 Apr 2020 09:56:48 -0600 Subject: [PATCH 04/15] add tests for source, target, head --- specs/phase0/beacon-chain.md | 6 +- .../pyspec/eth2spec/test/helpers/rewards.py | 117 ++++++++++++++++ .../rewards/test_get_head_deltas.py | 109 +++++++++++++++ .../rewards/test_get_source_deltas.py | 116 ++++++++++++++++ .../rewards/test_get_target_deltas.py | 125 ++++++++++-------- 5 files changed, 418 insertions(+), 55 deletions(-) create mode 100644 tests/core/pyspec/eth2spec/test/helpers/rewards.py create mode 100644 tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/rewards/test_get_head_deltas.py create mode 100644 tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/rewards/test_get_source_deltas.py diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index f40f0096b..95f06f125 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -110,6 +110,10 @@ - [Helper functions](#helper-functions-1) - [Justification and finalization](#justification-and-finalization) - [Rewards and penalties](#rewards-and-penalties-1) + - [Helpers](#helpers) + - [Components of attestation deltas](#components-of-attestation-deltas) + - [`get_attestation_deltas`](#get_attestation_deltas) + - [`process_rewards_and_penalties`](#process_rewards_and_penalties) - [Registry updates](#registry-updates) - [Slashings](#slashings) - [Final updates](#final-updates) @@ -1403,7 +1407,7 @@ def get_head_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]] """ Return attester micro-rewards/penalties for head-vote for each validator. """ - matching_head_attestations = get_matching_source_attestations(state, get_previous_epoch(state)) + matching_head_attestations = get_matching_head_attestations(state, get_previous_epoch(state)) return compute_attestation_component_deltas(state, matching_head_attestations) ``` diff --git a/tests/core/pyspec/eth2spec/test/helpers/rewards.py b/tests/core/pyspec/eth2spec/test/helpers/rewards.py new file mode 100644 index 000000000..41497fe60 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/helpers/rewards.py @@ -0,0 +1,117 @@ +from eth2spec.test.helpers.attestations import prepare_state_with_full_attestations + + +def run_attestation_component_deltas(spec, state, component_delta_fn, matching_att_fn): + """ + Run ``component_delta_fn``, yielding: + - pre-state ('pre') + - rewards ('rewards') + - penalties ('penalties') + """ + yield 'pre', state + + rewards, penalties = component_delta_fn(state) + + yield 'rewards', rewards + yield 'penalties', penalties + + matching_attestations = matching_att_fn(state, spec.get_previous_epoch(state)) + matching_indices = spec.get_unslashed_attesting_indices(state, matching_attestations) + for index in spec.get_eligible_validator_indices(state): + validator = state.validators[index] + enough_for_reward = ( + validator.effective_balance * spec.BASE_REWARD_FACTOR + > spec.integer_squareroot(spec.get_total_active_balance(state)) // spec.BASE_REWARDS_PER_EPOCH + ) + + if index in matching_indices and not validator.slashed: + if enough_for_reward: + assert rewards[index] > 0 + else: + assert rewards[index] == 0 + assert penalties[index] == 0 + else: + assert rewards[index] == 0 + if enough_for_reward: + assert penalties[index] > 0 + else: + assert penalties[index] == 0 + + +def test_empty(spec, state, runner): + # Do not add any attestations to state + + yield from runner(spec, state) + + +def test_full_all_correct(spec, state, runner): + prepare_state_with_full_attestations(spec, state) + + yield from runner(spec, state) + + +def test_half_full(spec, state, runner): + prepare_state_with_full_attestations(spec, state) + + # Remove half of attestations + state.previous_epoch_attestations = state.previous_epoch_attestations[:len(state.previous_epoch_attestations) // 2] + + yield from runner(spec, state) + + +def test_one_attestation_one_correct(spec, state, runner): + prepare_state_with_full_attestations(spec, state) + + # Remove half of attestations + state.previous_epoch_attestations = state.previous_epoch_attestations[:1] + + yield from runner(spec, state) + + +def test_with_slashed_validators(spec, state, runner): + prepare_state_with_full_attestations(spec, state) + + # Slash half of validators + for validator in state.validators[:len(state.validators) // 2]: + validator.slashed = True + + yield from runner(spec, state) + + +def test_some_zero_effective_balances_that_attested(spec, state, runner): + prepare_state_with_full_attestations(spec, state) + + # Set some balances to zero + state.validators[0].effective_balance = 0 + state.validators[1].effective_balance = 0 + + yield from runner(spec, state) + + +def test_some_zero_effective_balances_that_did_not_attest(spec, state, runner): + prepare_state_with_full_attestations(spec, state) + + # Set some balances to zero + attestation = state.previous_epoch_attestations[0] + # Remove attestation + state.previous_epoch_attestations = state.previous_epoch_attestations[1:] + # Set removed indices effective balance to zero + indices = spec.get_unslashed_attesting_indices(state, [attestation]) + for index in indices: + state.validators[index].effective_balance = 0 + + yield from runner(spec, state) + + +def test_full_fraction_incorrect(spec, state, correct_target, correct_head, fraction_incorrect, runner): + prepare_state_with_full_attestations(spec, state) + + # Make fraction_incorrect of pending attestations have bad target/head as specified + num_incorrect = int(fraction_incorrect * len(state.previous_epoch_attestations)) + for pending_attestation in state.previous_epoch_attestations[:num_incorrect]: + if not correct_target: + pending_attestation.data.target.root = b'\x55' * 32 + if not correct_head: + pending_attestation.data.beacon_block_root = b'\x66' * 32 + + yield from runner(spec, state) diff --git a/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/rewards/test_get_head_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/rewards/test_get_head_deltas.py new file mode 100644 index 000000000..24e3aaac5 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/rewards/test_get_head_deltas.py @@ -0,0 +1,109 @@ +from eth2spec.test.context import with_all_phases, spec_state_test +from eth2spec.test.helpers.rewards import run_attestation_component_deltas +import eth2spec.test.helpers.rewards as rewards_helpers + + +def run_get_head_deltas(spec, state): + """ + Run ``get_head_deltas``, yielding: + - pre-state ('pre') + - rewards ('rewards') + - penalties ('penalties') + """ + + yield from run_attestation_component_deltas( + spec, + state, + spec.get_head_deltas, + spec.get_matching_head_attestations, + ) + + +@with_all_phases +@spec_state_test +def test_empty(spec, state): + yield from rewards_helpers.test_empty(spec, state, run_get_head_deltas) + + +@with_all_phases +@spec_state_test +def test_full_all_correct(spec, state): + yield from rewards_helpers.test_full_all_correct(spec, state, run_get_head_deltas) + + +@with_all_phases +@spec_state_test +def test_half_full(spec, state): + yield from rewards_helpers.test_half_full(spec, state, run_get_head_deltas) + + +@with_all_phases +@spec_state_test +def test_one_attestation_one_correct(spec, state): + yield from rewards_helpers.test_one_attestation_one_correct(spec, state, run_get_head_deltas) + + +@with_all_phases +@spec_state_test +def test_with_slashed_validators(spec, state): + yield from rewards_helpers.test_with_slashed_validators(spec, state, run_get_head_deltas) + + +@with_all_phases +@spec_state_test +def test_some_zero_effective_balances_that_attested(spec, state): + yield from rewards_helpers.test_some_zero_effective_balances_that_attested(spec, state, run_get_head_deltas) + + +@with_all_phases +@spec_state_test +def test_some_zero_effective_balances_that_did_not_attest(spec, state): + yield from rewards_helpers.test_some_zero_effective_balances_that_did_not_attest(spec, state, run_get_head_deltas) + + +@with_all_phases +@spec_state_test +def test_full_half_correct_target_incorrect_head(spec, state): + yield from rewards_helpers.test_full_fraction_incorrect( + spec, state, + correct_target=True, + correct_head=False, + fraction_incorrect=0.5, + runner=run_get_head_deltas + ) + + +@with_all_phases +@spec_state_test +def test_full_correct_target_incorrect_head(spec, state): + yield from rewards_helpers.test_full_fraction_incorrect( + spec, state, + correct_target=True, + correct_head=False, + fraction_incorrect=1.0, + runner=run_get_head_deltas + ) + + +@with_all_phases +@spec_state_test +def test_full_half_incorrect_target_incorrect_head(spec, state): + yield from rewards_helpers.test_full_fraction_incorrect( + spec, state, + correct_target=False, + correct_head=False, + fraction_incorrect=0.5, + runner=run_get_head_deltas + ) + + +@with_all_phases +@spec_state_test +def test_full_half_incorrect_target_correct_head(spec, state): + yield from rewards_helpers.test_full_fraction_incorrect( + spec, state, + correct_target=False, + correct_head=True, + fraction_incorrect=0.5, + runner=run_get_head_deltas + ) diff --git a/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/rewards/test_get_source_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/rewards/test_get_source_deltas.py new file mode 100644 index 000000000..da47bd204 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/rewards/test_get_source_deltas.py @@ -0,0 +1,116 @@ +from eth2spec.test.context import with_all_phases, spec_state_test +from eth2spec.test.helpers.rewards import run_attestation_component_deltas +import eth2spec.test.helpers.rewards as rewards_helpers + + +def run_get_source_deltas(spec, state): + """ + Run ``get_source_deltas``, yielding: + - pre-state ('pre') + - rewards ('rewards') + - penalties ('penalties') + """ + + yield from run_attestation_component_deltas( + spec, + state, + spec.get_source_deltas, + spec.get_matching_source_attestations, + ) + + +@with_all_phases +@spec_state_test +def test_empty(spec, state): + yield from rewards_helpers.test_empty(spec, state, run_get_source_deltas) + + +@with_all_phases +@spec_state_test +def test_full_all_correct(spec, state): + yield from rewards_helpers.test_full_all_correct(spec, state, run_get_source_deltas) + + +@with_all_phases +@spec_state_test +def test_half_full(spec, state): + yield from rewards_helpers.test_half_full(spec, state, run_get_source_deltas) + + +@with_all_phases +@spec_state_test +def test_one_attestation_one_correct(spec, state): + yield from rewards_helpers.test_one_attestation_one_correct(spec, state, run_get_source_deltas) + + +@with_all_phases +@spec_state_test +def test_with_slashed_validators(spec, state): + yield from rewards_helpers.test_with_slashed_validators(spec, state, run_get_source_deltas) + + +@with_all_phases +@spec_state_test +def test_some_zero_effective_balances_that_attested(spec, state): + yield from rewards_helpers.test_some_zero_effective_balances_that_attested(spec, state, run_get_source_deltas) + + +@with_all_phases +@spec_state_test +def test_some_zero_effective_balances_that_did_not_attest(spec, state): + yield from rewards_helpers.test_some_zero_effective_balances_that_did_not_attest(spec, state, run_get_source_deltas) + + +# +# NOTE: No source incorrect tests +# All PendingAttestations in state have source validated +# We choose to keep this invariant in these tests to not force clients to test with degenerate states +# + + +@with_all_phases +@spec_state_test +def test_full_half_correct_target_incorrect_head(spec, state): + yield from rewards_helpers.test_full_fraction_incorrect( + spec, state, + correct_target=True, + correct_head=False, + fraction_incorrect=0.5, + runner=run_get_source_deltas + ) + + +@with_all_phases +@spec_state_test +def test_full_correct_target_incorrect_head(spec, state): + yield from rewards_helpers.test_full_fraction_incorrect( + spec, state, + correct_target=True, + correct_head=False, + fraction_incorrect=1.0, + runner=run_get_source_deltas + ) + + +@with_all_phases +@spec_state_test +def test_full_half_incorrect_target_incorrect_head(spec, state): + yield from rewards_helpers.test_full_fraction_incorrect( + spec, state, + correct_target=False, + correct_head=False, + fraction_incorrect=0.5, + runner=run_get_source_deltas + ) + + +@with_all_phases +@spec_state_test +def test_full_half_incorrect_target_correct_head(spec, state): + yield from rewards_helpers.test_full_fraction_incorrect( + spec, state, + correct_target=False, + correct_head=True, + fraction_incorrect=0.5, + runner=run_get_source_deltas + ) diff --git a/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/rewards/test_get_target_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/rewards/test_get_target_deltas.py index c30865cf0..406471c67 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/rewards/test_get_target_deltas.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/rewards/test_get_target_deltas.py @@ -1,92 +1,109 @@ from eth2spec.test.context import with_all_phases, spec_state_test -from eth2spec.test.helpers.attestations import prepare_state_with_full_attestations +from eth2spec.test.helpers.rewards import run_attestation_component_deltas +import eth2spec.test.helpers.rewards as rewards_helpers def run_get_target_deltas(spec, state): """ - Run ``process_block_header``, yielding: + Run ``get_target_deltas``, yielding: - pre-state ('pre') - rewards ('rewards') - penalties ('penalties') """ - yield 'pre', state - rewards, penalties = spec.get_target_deltas(state) - - yield 'rewards', rewards - yield 'penalties', penalties - - matching_target_attestations = spec.get_matching_target_attestations(state, spec.get_previous_epoch(state)) - matching_target_indices = spec.get_unslashed_attesting_indices(state, matching_target_attestations) - for index in spec.get_eligible_validator_indices(state): - if index in matching_target_indices and not state.validators[index].slashed: - assert rewards[index] > 0 - assert penalties[index] == 0 - else: - assert rewards[index] == 0 - assert penalties[index] > 0 + yield from run_attestation_component_deltas( + spec, + state, + spec.get_target_deltas, + spec.get_matching_target_attestations, + ) @with_all_phases @spec_state_test def test_empty(spec, state): - # Do not add any attestations to state - - yield from run_get_target_deltas(spec, state) + yield from rewards_helpers.test_empty(spec, state, run_get_target_deltas) @with_all_phases @spec_state_test def test_full_all_correct(spec, state): - prepare_state_with_full_attestations(spec, state) - - yield from run_get_target_deltas(spec, state) - - -@with_all_phases -@spec_state_test -def test_full_half_correct(spec, state): - prepare_state_with_full_attestations(spec, state) - - # Make half of pending attestations have bad target - for pending_attestation in state.previous_epoch_attestations[:len(state.previous_epoch_attestations) // 2]: - pending_attestation.data.target.root = b'\x66'*32 - - yield from run_get_target_deltas(spec, state) + yield from rewards_helpers.test_full_all_correct(spec, state, run_get_target_deltas) @with_all_phases @spec_state_test def test_half_full(spec, state): - prepare_state_with_full_attestations(spec, state) - - # Remove half of attestations - state.previous_epoch_attestations = state.previous_epoch_attestations[:len(state.previous_epoch_attestations) // 2] - - yield from run_get_target_deltas(spec, state) + yield from rewards_helpers.test_half_full(spec, state, run_get_target_deltas) @with_all_phases @spec_state_test -def test_one_correct(spec, state): - prepare_state_with_full_attestations(spec, state) - - # Remove half of attestations - state.previous_epoch_attestations = state.previous_epoch_attestations[:1] - - yield from run_get_target_deltas(spec, state) +def test_one_attestation_one_correct(spec, state): + yield from rewards_helpers.test_one_attestation_one_correct(spec, state, run_get_target_deltas) @with_all_phases @spec_state_test def test_with_slashed_validators(spec, state): - prepare_state_with_full_attestations(spec, state) + yield from rewards_helpers.test_with_slashed_validators(spec, state, run_get_target_deltas) - # Slash half of validators - for validator in state.validators: - validator.slashed = True - yield from run_get_target_deltas(spec, state) +@with_all_phases +@spec_state_test +def test_some_zero_effective_balances_that_attested(spec, state): + yield from rewards_helpers.test_some_zero_effective_balances_that_attested(spec, state, run_get_target_deltas) -def test_some_zero_balances(spec, state): +@with_all_phases +@spec_state_test +def test_some_zero_effective_balances_that_did_not_attest(spec, state): + yield from rewards_helpers.test_some_zero_effective_balances_that_did_not_attest(spec, state, run_get_target_deltas) + + +@with_all_phases +@spec_state_test +def test_full_half_correct_target_incorrect_head(spec, state): + yield from rewards_helpers.test_full_fraction_incorrect( + spec, state, + correct_target=True, + correct_head=False, + fraction_incorrect=0.5, + runner=run_get_target_deltas + ) + + +@with_all_phases +@spec_state_test +def test_full_correct_target_incorrect_head(spec, state): + yield from rewards_helpers.test_full_fraction_incorrect( + spec, state, + correct_target=True, + correct_head=False, + fraction_incorrect=1.0, + runner=run_get_target_deltas + ) + + +@with_all_phases +@spec_state_test +def test_full_half_incorrect_target_incorrect_head(spec, state): + yield from rewards_helpers.test_full_fraction_incorrect( + spec, state, + correct_target=False, + correct_head=False, + fraction_incorrect=0.5, + runner=run_get_target_deltas + ) + + +@with_all_phases +@spec_state_test +def test_full_half_incorrect_target_correct_head(spec, state): + yield from rewards_helpers.test_full_fraction_incorrect( + spec, state, + correct_target=False, + correct_head=True, + fraction_incorrect=0.5, + runner=run_get_target_deltas + ) From eda249957e1891a1211738e67133ee2524a6efb6 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Mon, 4 May 2020 13:20:32 -0600 Subject: [PATCH 05/15] basic generators work --- .../pyspec/eth2spec/test/helpers/rewards.py | 10 +++- .../eth2spec/test/phase_0/rewards/__init__.py | 0 .../rewards/test_get_head_deltas.py | 0 .../rewards/test_get_source_deltas.py | 0 .../rewards/test_get_target_deltas.py | 0 tests/formats/rewards/README.md | 47 +++++++++++++++++++ tests/generators/rewards/README.md | 8 ++++ tests/generators/rewards/main.py | 44 +++++++++++++++++ tests/generators/rewards/requirements.txt | 2 + 9 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 tests/core/pyspec/eth2spec/test/phase_0/rewards/__init__.py rename tests/core/pyspec/eth2spec/test/phase_0/{epoch_processing => }/rewards/test_get_head_deltas.py (100%) rename tests/core/pyspec/eth2spec/test/phase_0/{epoch_processing => }/rewards/test_get_source_deltas.py (100%) rename tests/core/pyspec/eth2spec/test/phase_0/{epoch_processing => }/rewards/test_get_target_deltas.py (100%) create mode 100644 tests/formats/rewards/README.md create mode 100644 tests/generators/rewards/README.md create mode 100644 tests/generators/rewards/main.py create mode 100644 tests/generators/rewards/requirements.txt diff --git a/tests/core/pyspec/eth2spec/test/helpers/rewards.py b/tests/core/pyspec/eth2spec/test/helpers/rewards.py index 41497fe60..a98aa3415 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/rewards.py +++ b/tests/core/pyspec/eth2spec/test/helpers/rewards.py @@ -1,4 +1,10 @@ from eth2spec.test.helpers.attestations import prepare_state_with_full_attestations +from eth2spec.utils.ssz.ssz_typing import Container, uint64, List + + +# HACK to get the generators outputting correctly +class Deltas(Container): + delta_list: List[uint64, 2**30] def run_attestation_component_deltas(spec, state, component_delta_fn, matching_att_fn): @@ -12,8 +18,8 @@ def run_attestation_component_deltas(spec, state, component_delta_fn, matching_a rewards, penalties = component_delta_fn(state) - yield 'rewards', rewards - yield 'penalties', penalties + yield 'rewards', Deltas(delta_list=rewards) + yield 'penalties', Deltas(delta_list=penalties) matching_attestations = matching_att_fn(state, spec.get_previous_epoch(state)) matching_indices = spec.get_unslashed_attesting_indices(state, matching_attestations) diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/__init__.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/rewards/test_get_head_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_head_deltas.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/rewards/test_get_head_deltas.py rename to tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_head_deltas.py diff --git a/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/rewards/test_get_source_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_source_deltas.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/rewards/test_get_source_deltas.py rename to tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_source_deltas.py diff --git a/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/rewards/test_get_target_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_target_deltas.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/rewards/test_get_target_deltas.py rename to tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_target_deltas.py diff --git a/tests/formats/rewards/README.md b/tests/formats/rewards/README.md new file mode 100644 index 000000000..dce2b5ac8 --- /dev/null +++ b/tests/formats/rewards/README.md @@ -0,0 +1,47 @@ +# Rewards tests + +The different rewards deltas sub-functions are testing individually with the test handlers, each returning the related `rewards`/`penalties`. +There is no "change" factor, the rewards/penalties outputs are pure functions with just the pre-state as input. +Hence, the format is shared between each test-handler. (See test condition documentation on how to run the tests.) + + +## Test case format + +### `meta.yaml` + +```yaml +description: string -- Optional description of test case, purely for debugging purposes. + Tests should use the directory name of the test case as identifier, not the description. +bls_setting: int -- see general test-format spec. +``` + +### `pre.yaml` + +A YAML-encoded `BeaconState`, the state before running the rewards sub-function. + +Also available as `pre.ssz`. + + +### `rewards.yaml` + +A YAML-encoded list of integers representing the 0th item in the return value (i.e. the rewards deltas) + +### `penalties.yaml` + +A YAML-encoded list of integers representing the 1st item in the return value (i.e. the penalties deltas) + +## Condition + +A handler of the `rewards` test-runner should process these cases, + calling the corresponding rewards deltas function (same name in spec). +This excludes all other parts of `process_rewards_and_penalties` + +The provided pre-state is ready to be input into the designated handler. + +The resulting `rewards`/`penalties` should match the return values of the +handler. Specifically the following must hold true: + +```python + rewards == handler(state)[0] + penalties == handler(state)[1] +``` diff --git a/tests/generators/rewards/README.md b/tests/generators/rewards/README.md new file mode 100644 index 000000000..60f106836 --- /dev/null +++ b/tests/generators/rewards/README.md @@ -0,0 +1,8 @@ +# Rewards + +Rewards covers the sub-functions of `process_rewards_and_penalties` for granular testing of components of the rewards function. + +A rewards test-runner can consume these sub-transition test-suites, + and handle different kinds of epoch sub-transitions by processing the cases using the specified test handler. + +Information on the format of the tests can be found in the [rewards test formats documentation](../../formats/rewards/README.md). diff --git a/tests/generators/rewards/main.py b/tests/generators/rewards/main.py new file mode 100644 index 000000000..caffc3bd1 --- /dev/null +++ b/tests/generators/rewards/main.py @@ -0,0 +1,44 @@ +from typing import Iterable + +from eth2spec.phase0 import spec as spec_phase0 +from eth2spec.phase1 import spec as spec_phase1 +from eth2spec.test.phase_0.rewards import ( + test_get_source_deltas, + test_get_target_deltas, + test_get_head_deltas, +) +from gen_base import gen_runner, gen_typing +from gen_from_tests.gen import generate_from_tests +from importlib import reload +from eth2spec.config import config_util +from eth2spec.test.context import PHASE0 + + +def create_provider(handler_name: str, tests_src, config_name: str) -> gen_typing.TestProvider: + + def prepare_fn(configs_path: str) -> str: + config_util.prepare_config(configs_path, config_name) + reload(spec_phase0) + reload(spec_phase1) + return config_name + + def cases_fn() -> Iterable[gen_typing.TestCase]: + return generate_from_tests( + runner_name='rewards', + handler_name=handler_name, + src=tests_src, + fork_name=PHASE0, + ) + + return gen_typing.TestProvider(prepare=prepare_fn, make_cases=cases_fn) + + +if __name__ == "__main__": + gen_runner.run_generator("epoch_processing", [ + create_provider('get_source_deltas', test_get_source_deltas, 'minimal'), + create_provider('get_source_deltas', test_get_source_deltas, 'mainnet'), + create_provider('get_target_deltas', test_get_target_deltas, 'minimal'), + create_provider('get_target_deltas', test_get_target_deltas, 'mainnet'), + create_provider('get_head_deltas', test_get_head_deltas, 'minimal'), + create_provider('get_head_deltas', test_get_head_deltas, 'mainnet'), + ]) diff --git a/tests/generators/rewards/requirements.txt b/tests/generators/rewards/requirements.txt new file mode 100644 index 000000000..b82314298 --- /dev/null +++ b/tests/generators/rewards/requirements.txt @@ -0,0 +1,2 @@ +../../core/gen_helpers +../../../ \ No newline at end of file From 5194c1f2d2dba0c1083b6d1cd075c9f41f0aba62 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Mon, 4 May 2020 16:53:29 -0600 Subject: [PATCH 06/15] add test_get_inclusion_delay_deltas --- .../pyspec/eth2spec/test/helpers/rewards.py | 66 ++++-- .../phase_0/rewards/test_get_head_deltas.py | 10 +- .../test_get_inclusion_delay_deltas.py | 208 ++++++++++++++++++ .../phase_0/rewards/test_get_source_deltas.py | 16 +- .../phase_0/rewards/test_get_target_deltas.py | 16 +- 5 files changed, 297 insertions(+), 19 deletions(-) create mode 100644 tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inclusion_delay_deltas.py diff --git a/tests/core/pyspec/eth2spec/test/helpers/rewards.py b/tests/core/pyspec/eth2spec/test/helpers/rewards.py index a98aa3415..e8cd950ce 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/rewards.py +++ b/tests/core/pyspec/eth2spec/test/helpers/rewards.py @@ -1,3 +1,5 @@ +from random import Random + from eth2spec.test.helpers.attestations import prepare_state_with_full_attestations from eth2spec.utils.ssz.ssz_typing import Container, uint64, List @@ -7,6 +9,13 @@ class Deltas(Container): delta_list: List[uint64, 2**30] +def has_enough_for_reward(spec, state, index): + return ( + state.validators[index].effective_balance * spec.BASE_REWARD_FACTOR + > spec.integer_squareroot(spec.get_total_active_balance(state)) // spec.BASE_REWARDS_PER_EPOCH + ) + + def run_attestation_component_deltas(spec, state, component_delta_fn, matching_att_fn): """ Run ``component_delta_fn``, yielding: @@ -25,11 +34,7 @@ def run_attestation_component_deltas(spec, state, component_delta_fn, matching_a matching_indices = spec.get_unslashed_attesting_indices(state, matching_attestations) for index in spec.get_eligible_validator_indices(state): validator = state.validators[index] - enough_for_reward = ( - validator.effective_balance * spec.BASE_REWARD_FACTOR - > spec.integer_squareroot(spec.get_total_active_balance(state)) // spec.BASE_REWARDS_PER_EPOCH - ) - + enough_for_reward = has_enough_for_reward(spec, state, index) if index in matching_indices and not validator.slashed: if enough_for_reward: assert rewards[index] > 0 @@ -56,15 +61,29 @@ def test_full_all_correct(spec, state, runner): yield from runner(spec, state) -def test_half_full(spec, state, runner): +def test_full_but_partial_participation(spec, state, runner, rng=Random(5522)): prepare_state_with_full_attestations(spec, state) - # Remove half of attestations - state.previous_epoch_attestations = state.previous_epoch_attestations[:len(state.previous_epoch_attestations) // 2] + for a in state.previous_epoch_attestations: + a.aggregation_bits = [rng.choice([True, False]) for _ in a.aggregation_bits] yield from runner(spec, state) +def test_partial(spec, state, fraction_filled, runner): + prepare_state_with_full_attestations(spec, state) + + # Remove portion of attestations + num_attestations = int(len(state.previous_epoch_attestations) * fraction_filled) + state.previous_epoch_attestations = state.previous_epoch_attestations[:num_attestations] + + yield from runner(spec, state) + + +def test_half_full(spec, state, runner): + yield from test_partial(spec, state, 0.5, runner) + + def test_one_attestation_one_correct(spec, state, runner): prepare_state_with_full_attestations(spec, state) @@ -84,12 +103,16 @@ def test_with_slashed_validators(spec, state, runner): yield from runner(spec, state) -def test_some_zero_effective_balances_that_attested(spec, state, runner): +def test_some_very_low_effective_balances_that_attested(spec, state, runner): + state.balances prepare_state_with_full_attestations(spec, state) - # Set some balances to zero + # Set some balances to be very low (including 0) state.validators[0].effective_balance = 0 - state.validators[1].effective_balance = 0 + state.validators[1].effective_balance = 2 + state.validators[2].effective_balance = 10 + state.validators[3].effective_balance = 100 + state.validators[4].effective_balance = 1000 yield from runner(spec, state) @@ -97,9 +120,8 @@ def test_some_zero_effective_balances_that_attested(spec, state, runner): def test_some_zero_effective_balances_that_did_not_attest(spec, state, runner): prepare_state_with_full_attestations(spec, state) - # Set some balances to zero - attestation = state.previous_epoch_attestations[0] # Remove attestation + attestation = state.previous_epoch_attestations[0] state.previous_epoch_attestations = state.previous_epoch_attestations[1:] # Set removed indices effective balance to zero indices = spec.get_unslashed_attesting_indices(state, [attestation]) @@ -121,3 +143,21 @@ def test_full_fraction_incorrect(spec, state, correct_target, correct_head, frac pending_attestation.data.beacon_block_root = b'\x66' * 32 yield from runner(spec, state) + + +def test_full_random(spec, state, runner, rng=Random(8020)): + prepare_state_with_full_attestations(spec, state) + + for pending_attestation in state.previous_epoch_attestations: + # 1/3 have bad target + if rng.randint(0, 2) == 0: + pending_attestation.data.target.root = b'\x55' * 32 + # 1/3 have bad head + if rng.randint(0, 2) == 0: + pending_attestation.data.beacon_block_root = b'\x66' * 32 + # ~50% participation + pending_attestation.aggregation_bits = [rng.choice([True, False]) for _ in pending_attestation.aggregation_bits] + # Random inclusion delay + pending_attestation.inclusion_delay = rng.randint(1, spec.SLOTS_PER_EPOCH) + + yield from runner(spec, state) diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_head_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_head_deltas.py index 24e3aaac5..b9aab92cb 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_head_deltas.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_head_deltas.py @@ -51,8 +51,8 @@ def test_with_slashed_validators(spec, state): @with_all_phases @spec_state_test -def test_some_zero_effective_balances_that_attested(spec, state): - yield from rewards_helpers.test_some_zero_effective_balances_that_attested(spec, state, run_get_head_deltas) +def test_some_very_low_effective_balances_that_attested(spec, state): + yield from rewards_helpers.test_some_very_low_effective_balances_that_attested(spec, state, run_get_head_deltas) @with_all_phases @@ -107,3 +107,9 @@ def test_full_half_incorrect_target_correct_head(spec, state): fraction_incorrect=0.5, runner=run_get_head_deltas ) + + +@with_all_phases +@spec_state_test +def test_full_random(spec, state): + yield from rewards_helpers.test_full_random(spec, state, run_get_head_deltas) diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inclusion_delay_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inclusion_delay_deltas.py new file mode 100644 index 000000000..b9ae9882c --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inclusion_delay_deltas.py @@ -0,0 +1,208 @@ +from random import Random + +from eth2spec.test.context import with_all_phases, spec_state_test +from eth2spec.test.helpers.attestations import prepare_state_with_full_attestations +from eth2spec.test.helpers.rewards import has_enough_for_reward +import eth2spec.test.helpers.rewards as rewards_helpers +from eth2spec.utils.ssz.ssz_typing import Container, uint64, List + + +# HACK to get the generators outputting correctly +class Deltas(Container): + delta_list: List[uint64, 2**30] + + +def run_get_inclusion_delay_deltas(spec, state): + """ + Run ``get_inclusion_delay_deltas``, yielding: + - pre-state ('pre') + - rewards ('rewards') + - penalties ('penalties') + """ + + yield 'pre', state + + rewards, penalties = spec.get_inclusion_delay_deltas(state) + + yield 'rewards', Deltas(delta_list=rewards) + yield 'penalties', Deltas(delta_list=penalties) + + eligible_attestations = spec.get_matching_source_attestations(state, spec.get_previous_epoch(state)) + attesting_indices = spec.get_unslashed_attesting_indices(state, eligible_attestations) + + rewarded_indices = set() + rewarded_proposer_indices = set() + # Ensure attesters with enough balance are rewarded for attestations + # Track those that are rewarded and track proposers that should be rewarded + for index in range(len(state.validators)): + if index in attesting_indices and has_enough_for_reward(spec, state, index): + assert rewards[index] > 0 + rewarded_indices.add(index) + + # Track proposer of earliest included attestation for the validator defined by index + earliest_attestation = min([ + a for a in eligible_attestations + if index in spec.get_attesting_indices(state, a.data, a.aggregation_bits) + ], key=lambda a: a.inclusion_delay) + rewarded_proposer_indices.add(earliest_attestation.proposer_index) + + # Ensure all expected proposers have been rewarded + # Track rewarde indices + proposing_indices = [a.proposer_index for a in eligible_attestations] + for index in proposing_indices: + if index in rewarded_proposer_indices: + assert rewards[index] > 0 + rewarded_indices.add(index) + + # Ensure all expected non-rewarded indices received no reward + for index in range(len(state.validators)): + assert penalties[index] == 0 + if index not in rewarded_indices: + assert rewards[index] == 0 + + +@with_all_phases +@spec_state_test +def test_empty(spec, state): + yield from rewards_helpers.test_empty(spec, state, run_get_inclusion_delay_deltas) + + +@with_all_phases +@spec_state_test +def test_full(spec, state): + yield from rewards_helpers.test_full_all_correct(spec, state, run_get_inclusion_delay_deltas) + + +@with_all_phases +@spec_state_test +def test_half_full(spec, state): + yield from rewards_helpers.test_half_full(spec, state, run_get_inclusion_delay_deltas) + + +@with_all_phases +@spec_state_test +def test_quarter_full(spec, state): + yield from rewards_helpers.test_partial(spec, state, 0.25, run_get_inclusion_delay_deltas) + + +@with_all_phases +@spec_state_test +def test_full_but_partial_participation(spec, state): + yield from rewards_helpers.test_full_but_partial_participation(spec, state, run_get_inclusion_delay_deltas) + + +@with_all_phases +@spec_state_test +def test_with_slashed_validators(spec, state): + yield from rewards_helpers.test_with_slashed_validators(spec, state, run_get_inclusion_delay_deltas) + + +@with_all_phases +@spec_state_test +def test_some_very_low_effective_balances_that_attested(spec, state): + yield from rewards_helpers.test_some_very_low_effective_balances_that_attested( + spec, + state, + run_get_inclusion_delay_deltas + ) + + +@with_all_phases +@spec_state_test +def test_full_random(spec, state): + yield from rewards_helpers.test_full_random(spec, state, run_get_inclusion_delay_deltas) + + +@with_all_phases +@spec_state_test +def test_full_delay_one_slot(spec, state): + prepare_state_with_full_attestations(spec, state) + for a in state.previous_epoch_attestations: + a.inclusion_delay += 1 + + yield from run_get_inclusion_delay_deltas(spec, state) + + +@with_all_phases +@spec_state_test +def test_full_delay_max_slots(spec, state): + prepare_state_with_full_attestations(spec, state) + for a in state.previous_epoch_attestations: + a.inclusion_delay += spec.SLOTS_PER_EPOCH + + yield from run_get_inclusion_delay_deltas(spec, state) + + +@with_all_phases +@spec_state_test +def test_full_mixed_delay(spec, state): + rng = Random(1234) + + prepare_state_with_full_attestations(spec, state) + for a in state.previous_epoch_attestations: + a.inclusion_delay = rng.randint(1, spec.SLOTS_PER_EPOCH) + + yield from run_get_inclusion_delay_deltas(spec, state) + + +@with_all_phases +@spec_state_test +def test_proposer_not_in_attestations(spec, state): + prepare_state_with_full_attestations(spec, state) + + # Get an attestation where the proposer is not in the committee + non_proposer_attestations = [] + for a in state.previous_epoch_attestations: + if a.proposer_index not in spec.get_unslashed_attesting_indices(state, [a]): + non_proposer_attestations.append(a) + + assert any(non_proposer_attestations) + state.previous_epoch_attestations = non_proposer_attestations + + yield from run_get_inclusion_delay_deltas(spec, state) + + +@with_all_phases +@spec_state_test +def test_duplicate_attestations_at_later_slots(spec, state): + prepare_state_with_full_attestations(spec, state) + + # Remove 2/3 of attestations to make it more interesting + num_attestations = int(len(state.previous_epoch_attestations) * 0.33) + state.previous_epoch_attestations = state.previous_epoch_attestations[:num_attestations] + + # Get map of the proposer at each slot to make valid-looking duplicate attestations + per_slot_proposers = { + (a.data.slot + a.inclusion_delay): a.proposer_index + for a in state.previous_epoch_attestations + } + max_slot = max([a.data.slot + a.inclusion_delay for a in state.previous_epoch_attestations]) + later_attestations = [] + for a in state.previous_epoch_attestations: + # Do not create later duplicate if goes into next epoch + if a.data.slot + a.inclusion_delay >= max_slot: + continue + later_a = a.copy() + later_a.inclusion_delay += 1 + later_a.proposer_index = per_slot_proposers[later_a.data.slot + later_a.inclusion_delay] + later_attestations.append(later_a) + + assert any(later_attestations) + + state.previous_epoch_attestations = sorted( + state.previous_epoch_attestations + later_attestations, + key=lambda a: a.data.slot + a.inclusion_delay + ) + + yield from run_get_inclusion_delay_deltas(spec, state) + + +@with_all_phases +@spec_state_test +def test_all_balances_too_low_for_reward(spec, state): + prepare_state_with_full_attestations(spec, state) + + for index in range(len(state.validators)): + state.validators[index].effective_balance = 10 + + yield from run_get_inclusion_delay_deltas(spec, state) diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_source_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_source_deltas.py index da47bd204..9063abfc6 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_source_deltas.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_source_deltas.py @@ -37,6 +37,12 @@ def test_half_full(spec, state): yield from rewards_helpers.test_half_full(spec, state, run_get_source_deltas) +@with_all_phases +@spec_state_test +def test_full_but_partial_participation(spec, state): + yield from rewards_helpers.test_full_but_partial_participation(spec, state, run_get_source_deltas) + + @with_all_phases @spec_state_test def test_one_attestation_one_correct(spec, state): @@ -51,8 +57,8 @@ def test_with_slashed_validators(spec, state): @with_all_phases @spec_state_test -def test_some_zero_effective_balances_that_attested(spec, state): - yield from rewards_helpers.test_some_zero_effective_balances_that_attested(spec, state, run_get_source_deltas) +def test_some_very_low_effective_balances_that_attested(spec, state): + yield from rewards_helpers.test_some_very_low_effective_balances_that_attested(spec, state, run_get_source_deltas) @with_all_phases @@ -114,3 +120,9 @@ def test_full_half_incorrect_target_correct_head(spec, state): fraction_incorrect=0.5, runner=run_get_source_deltas ) + + +@with_all_phases +@spec_state_test +def test_full_random(spec, state): + yield from rewards_helpers.test_full_random(spec, state, run_get_source_deltas) diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_target_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_target_deltas.py index 406471c67..ff20014c8 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_target_deltas.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_target_deltas.py @@ -37,6 +37,12 @@ def test_half_full(spec, state): yield from rewards_helpers.test_half_full(spec, state, run_get_target_deltas) +@with_all_phases +@spec_state_test +def test_full_but_partial_participation(spec, state): + yield from rewards_helpers.test_full_but_partial_participation(spec, state, run_get_target_deltas) + + @with_all_phases @spec_state_test def test_one_attestation_one_correct(spec, state): @@ -51,8 +57,8 @@ def test_with_slashed_validators(spec, state): @with_all_phases @spec_state_test -def test_some_zero_effective_balances_that_attested(spec, state): - yield from rewards_helpers.test_some_zero_effective_balances_that_attested(spec, state, run_get_target_deltas) +def test_some_very_low_effective_balances_that_attested(spec, state): + yield from rewards_helpers.test_some_very_low_effective_balances_that_attested(spec, state, run_get_target_deltas) @with_all_phases @@ -107,3 +113,9 @@ def test_full_half_incorrect_target_correct_head(spec, state): fraction_incorrect=0.5, runner=run_get_target_deltas ) + + +@with_all_phases +@spec_state_test +def test_full_random(spec, state): + yield from rewards_helpers.test_full_random(spec, state, run_get_target_deltas) From 8f569a8ddc05bc5d13c94a4dc7e2465c3eb1448a Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Mon, 4 May 2020 20:58:42 -0600 Subject: [PATCH 07/15] add inactivity penalty deltas tests --- .../pyspec/eth2spec/test/helpers/rewards.py | 14 +- .../phase_0/rewards/test_get_head_deltas.py | 14 +- .../test_get_inactivity_penalty_deltas.py | 204 ++++++++++++++++++ .../phase_0/rewards/test_get_source_deltas.py | 8 +- .../phase_0/rewards/test_get_target_deltas.py | 8 +- tests/generators/rewards/main.py | 6 + 6 files changed, 244 insertions(+), 10 deletions(-) create mode 100644 tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inactivity_penalty_deltas.py diff --git a/tests/core/pyspec/eth2spec/test/helpers/rewards.py b/tests/core/pyspec/eth2spec/test/helpers/rewards.py index e8cd950ce..09c99c3ac 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/rewards.py +++ b/tests/core/pyspec/eth2spec/test/helpers/rewards.py @@ -32,7 +32,13 @@ def run_attestation_component_deltas(spec, state, component_delta_fn, matching_a matching_attestations = matching_att_fn(state, spec.get_previous_epoch(state)) matching_indices = spec.get_unslashed_attesting_indices(state, matching_attestations) - for index in spec.get_eligible_validator_indices(state): + eligible_indices = spec.get_eligible_validator_indices(state) + for index in range(len(state.validators)): + if index not in eligible_indices: + assert rewards[index] == 0 + assert penalties[index] == 0 + continue + validator = state.validators[index] enough_for_reward = has_enough_for_reward(spec, state, index) if index in matching_indices and not validator.slashed: @@ -117,7 +123,7 @@ def test_some_very_low_effective_balances_that_attested(spec, state, runner): yield from runner(spec, state) -def test_some_zero_effective_balances_that_did_not_attest(spec, state, runner): +def test_some_very_low_effective_balances_that_did_not_attest(spec, state, runner): prepare_state_with_full_attestations(spec, state) # Remove attestation @@ -125,8 +131,8 @@ def test_some_zero_effective_balances_that_did_not_attest(spec, state, runner): state.previous_epoch_attestations = state.previous_epoch_attestations[1:] # Set removed indices effective balance to zero indices = spec.get_unslashed_attesting_indices(state, [attestation]) - for index in indices: - state.validators[index].effective_balance = 0 + for i, index in enumerate(indices): + state.validators[index].effective_balance = i yield from runner(spec, state) diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_head_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_head_deltas.py index b9aab92cb..237920732 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_head_deltas.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_head_deltas.py @@ -37,6 +37,12 @@ def test_half_full(spec, state): yield from rewards_helpers.test_half_full(spec, state, run_get_head_deltas) +@with_all_phases +@spec_state_test +def test_full_but_partial_participation(spec, state): + yield from rewards_helpers.test_full_but_partial_participation(spec, state, run_get_head_deltas) + + @with_all_phases @spec_state_test def test_one_attestation_one_correct(spec, state): @@ -57,8 +63,12 @@ def test_some_very_low_effective_balances_that_attested(spec, state): @with_all_phases @spec_state_test -def test_some_zero_effective_balances_that_did_not_attest(spec, state): - yield from rewards_helpers.test_some_zero_effective_balances_that_did_not_attest(spec, state, run_get_head_deltas) +def test_some_very_low_effective_balances_that_did_not_attest(spec, state): + yield from rewards_helpers.test_some_very_low_effective_balances_that_did_not_attest( + spec, + state, + run_get_head_deltas, + ) @with_all_phases diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inactivity_penalty_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inactivity_penalty_deltas.py new file mode 100644 index 000000000..a81451e38 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inactivity_penalty_deltas.py @@ -0,0 +1,204 @@ +from eth2spec.test.context import with_all_phases, spec_state_test +from eth2spec.test.helpers.rewards import has_enough_for_reward +from eth2spec.test.helpers.state import next_epoch +import eth2spec.test.helpers.rewards as rewards_helpers +from eth2spec.utils.ssz.ssz_typing import Container, uint64, List + + +# HACK to get the generators outputting correctly +class Deltas(Container): + delta_list: List[uint64, 2**30] + + +def run_get_inactivity_penalty_deltas(spec, state): + """ + Run ``get_inactivity_penalty_deltas``, yielding: + - pre-state ('pre') + - rewards ('rewards') + - penalties ('penalties') + """ + + yield 'pre', state + + rewards, penalties = spec.get_inactivity_penalty_deltas(state) + + yield 'rewards', Deltas(delta_list=rewards) + yield 'penalties', Deltas(delta_list=penalties) + + matching_attestations = spec.get_matching_target_attestations(state, spec.get_previous_epoch(state)) + matching_attesting_indices = spec.get_unslashed_attesting_indices(state, matching_attestations) + + finality_delay = spec.get_previous_epoch(state) - state.finalized_checkpoint.epoch + eligible_indices = spec.get_eligible_validator_indices(state) + for index in range(len(state.validators)): + assert rewards[index] == 0 + if index not in eligible_indices: + assert penalties[index] == 0 + continue + + if finality_delay > spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY: + base_penalty = spec.BASE_REWARDS_PER_EPOCH * spec.get_base_reward(state, index) + if not has_enough_for_reward(spec, state, index): + assert penalties[index] == 0 + elif index in matching_attesting_indices: + assert penalties[index] == base_penalty + else: + assert penalties[index] > base_penalty + else: + assert penalties[index] == 0 + + +def transition_state_to_leak(spec, state, epochs=None): + if epochs is None: + epochs = spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY + assert epochs >= spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY + + for _ in range(epochs): + next_epoch(spec, state) + + +@with_all_phases +@spec_state_test +def test_empty_no_leak(spec, state): + yield from rewards_helpers.test_empty(spec, state, run_get_inactivity_penalty_deltas) + + +@with_all_phases +@spec_state_test +def test_empty_leak(spec, state): + transition_state_to_leak(spec, state) + yield from rewards_helpers.test_empty(spec, state, run_get_inactivity_penalty_deltas) + + +@with_all_phases +@spec_state_test +def test_full_no_leak(spec, state): + yield from rewards_helpers.test_full_all_correct(spec, state, run_get_inactivity_penalty_deltas) + + +@with_all_phases +@spec_state_test +def test_full_leak(spec, state): + transition_state_to_leak(spec, state) + yield from rewards_helpers.test_full_all_correct(spec, state, run_get_inactivity_penalty_deltas) + + +@with_all_phases +@spec_state_test +def test_half_full_no_leak(spec, state): + yield from rewards_helpers.test_half_full(spec, state, run_get_inactivity_penalty_deltas) + + +@with_all_phases +@spec_state_test +def test_half_full_leak(spec, state): + transition_state_to_leak(spec, state) + yield from rewards_helpers.test_half_full(spec, state, run_get_inactivity_penalty_deltas) + + +@with_all_phases +@spec_state_test +def test_quarter_full_no_leak(spec, state): + yield from rewards_helpers.test_partial(spec, state, 0.25, run_get_inactivity_penalty_deltas) + + +@with_all_phases +@spec_state_test +def test_quarter_full_leak(spec, state): + transition_state_to_leak(spec, state) + yield from rewards_helpers.test_partial(spec, state, 0.25, run_get_inactivity_penalty_deltas) + + +@with_all_phases +@spec_state_test +def test_full_but_partial_participation_no_leak(spec, state): + yield from rewards_helpers.test_full_but_partial_participation(spec, state, run_get_inactivity_penalty_deltas) + + +@with_all_phases +@spec_state_test +def test_full_but_partial_participation_leak(spec, state): + transition_state_to_leak(spec, state) + yield from rewards_helpers.test_full_but_partial_participation(spec, state, run_get_inactivity_penalty_deltas) + + +@with_all_phases +@spec_state_test +def test_with_slashed_validators_no_leak(spec, state): + yield from rewards_helpers.test_with_slashed_validators(spec, state, run_get_inactivity_penalty_deltas) + + +@with_all_phases +@spec_state_test +def test_with_slashed_validators_leak(spec, state): + transition_state_to_leak(spec, state) + yield from rewards_helpers.test_with_slashed_validators(spec, state, run_get_inactivity_penalty_deltas) + + +@with_all_phases +@spec_state_test +def test_some_very_low_effective_balances_that_attested_no_leak(spec, state): + yield from rewards_helpers.test_some_very_low_effective_balances_that_attested( + spec, + state, + run_get_inactivity_penalty_deltas, + ) + + +@with_all_phases +@spec_state_test +def test_some_very_low_effective_balances_that_attested_leak(spec, state): + transition_state_to_leak(spec, state) + yield from rewards_helpers.test_some_very_low_effective_balances_that_attested( + spec, + state, + run_get_inactivity_penalty_deltas, + ) + + +@with_all_phases +@spec_state_test +def test_some_very_low_effective_balances_that_did_not_attest_no_leak(spec, state): + yield from rewards_helpers.test_some_very_low_effective_balances_that_did_not_attest( + spec, + state, + run_get_inactivity_penalty_deltas, + ) + + +@with_all_phases +@spec_state_test +def test_some_very_low_effective_balances_that_did_not_attest_leak(spec, state): + transition_state_to_leak(spec, state) + yield from rewards_helpers.test_some_very_low_effective_balances_that_did_not_attest( + spec, + state, + run_get_inactivity_penalty_deltas, + ) + + +@with_all_phases +@spec_state_test +def test_full_random_no_leak(spec, state): + yield from rewards_helpers.test_full_random(spec, state, run_get_inactivity_penalty_deltas) + + +@with_all_phases +@spec_state_test +def test_full_random_leak(spec, state): + transition_state_to_leak(spec, state) + yield from rewards_helpers.test_full_random(spec, state, run_get_inactivity_penalty_deltas) + + +@with_all_phases +@spec_state_test +def test_full_random_five_epoch_leak(spec, state): + transition_state_to_leak(spec, state, epochs=5) + yield from rewards_helpers.test_full_random(spec, state, run_get_inactivity_penalty_deltas) + + +@with_all_phases +@spec_state_test +def test_full_random_ten_epoch_leak(spec, state): + transition_state_to_leak(spec, state, epochs=10) + yield from rewards_helpers.test_full_random(spec, state, run_get_inactivity_penalty_deltas) diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_source_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_source_deltas.py index 9063abfc6..842728db8 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_source_deltas.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_source_deltas.py @@ -63,8 +63,12 @@ def test_some_very_low_effective_balances_that_attested(spec, state): @with_all_phases @spec_state_test -def test_some_zero_effective_balances_that_did_not_attest(spec, state): - yield from rewards_helpers.test_some_zero_effective_balances_that_did_not_attest(spec, state, run_get_source_deltas) +def test_some_very_low_effective_balances_that_did_not_attest(spec, state): + yield from rewards_helpers.test_some_very_low_effective_balances_that_did_not_attest( + spec, + state, + run_get_source_deltas, + ) # diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_target_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_target_deltas.py index ff20014c8..ad2a65d2a 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_target_deltas.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_target_deltas.py @@ -63,8 +63,12 @@ def test_some_very_low_effective_balances_that_attested(spec, state): @with_all_phases @spec_state_test -def test_some_zero_effective_balances_that_did_not_attest(spec, state): - yield from rewards_helpers.test_some_zero_effective_balances_that_did_not_attest(spec, state, run_get_target_deltas) +def test_some_very_low_effective_balances_that_did_not_attest(spec, state): + yield from rewards_helpers.test_some_very_low_effective_balances_that_did_not_attest( + spec, + state, + run_get_target_deltas, + ) @with_all_phases diff --git a/tests/generators/rewards/main.py b/tests/generators/rewards/main.py index caffc3bd1..fd95dcfaa 100644 --- a/tests/generators/rewards/main.py +++ b/tests/generators/rewards/main.py @@ -6,6 +6,8 @@ from eth2spec.test.phase_0.rewards import ( test_get_source_deltas, test_get_target_deltas, test_get_head_deltas, + test_get_inclusion_delay_deltas, + test_get_inactivity_penalty_deltas, ) from gen_base import gen_runner, gen_typing from gen_from_tests.gen import generate_from_tests @@ -41,4 +43,8 @@ if __name__ == "__main__": create_provider('get_target_deltas', test_get_target_deltas, 'mainnet'), create_provider('get_head_deltas', test_get_head_deltas, 'minimal'), create_provider('get_head_deltas', test_get_head_deltas, 'mainnet'), + create_provider('get_inclusion_delay_deltas', test_get_inclusion_delay_deltas, 'minimal'), + create_provider('get_inclusion_delay_deltas', test_get_inclusion_delay_deltas, 'mainnet'), + create_provider('get_inactivity_penalty_deltas', test_get_inactivity_penalty_deltas, 'minimal'), + create_provider('get_inactivity_penalty_deltas', test_get_inactivity_penalty_deltas, 'mainnet'), ]) From 3f250f7dd32296ae50a417424665b4279f9f588e Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Mon, 4 May 2020 21:05:10 -0600 Subject: [PATCH 08/15] PR feedback --- specs/phase0/beacon-chain.md | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index 95f06f125..82fbb2856 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -1364,9 +1364,12 @@ def get_eligible_validator_indices(state: BeaconState) -> Sequence[ValidatorInde ``` ```python -def compute_attestation_component_deltas(state: BeaconState, - attestations: Sequence[PendingAttestation] - ) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: +def get_attestation_component_deltas(state: BeaconState, + attestations: Sequence[PendingAttestation] + ) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: + """ + Helper with shared logic for use by get source, target, and head deltas functions + """ rewards = [Gwei(0) for _ in range(len(state.validators))] penalties = [Gwei(0) for _ in range(len(state.validators))] total_balance = get_total_active_balance(state) @@ -1390,7 +1393,7 @@ def get_source_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei Return attester micro-rewards/penalties for source-vote for each validator. """ matching_source_attestations = get_matching_source_attestations(state, get_previous_epoch(state)) - return compute_attestation_component_deltas(state, matching_source_attestations) + return get_attestation_component_deltas(state, matching_source_attestations) ``` ```python @@ -1399,7 +1402,7 @@ def get_target_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei Return attester micro-rewards/penalties for target-vote for each validator. """ matching_target_attestations = get_matching_target_attestations(state, get_previous_epoch(state)) - return compute_attestation_component_deltas(state, matching_target_attestations) + return get_attestation_component_deltas(state, matching_target_attestations) ``` ```python @@ -1408,7 +1411,7 @@ def get_head_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]] Return attester micro-rewards/penalties for head-vote for each validator. """ matching_head_attestations = get_matching_head_attestations(state, get_previous_epoch(state)) - return compute_attestation_component_deltas(state, matching_head_attestations) + return get_attestation_component_deltas(state, matching_head_attestations) ``` ```python @@ -1465,18 +1468,12 @@ def get_attestation_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence _, inactivity_penalties = get_inactivity_penalty_deltas(state) rewards = [ - source_rewards[i] - + target_rewards[i] - + head_rewards[i] - + inclusion_delay_rewards[i] + source_rewards[i] + target_rewards[i] + head_rewards[i] + inclusion_delay_rewards[i] for i in range(len(state.validators)) ] penalties = [ - source_penalties[i] - + target_penalties[i] - + head_penalties[i] - + inactivity_penalties[i] + source_penalties[i] + target_penalties[i] + head_penalties[i] + inactivity_penalties[i] for i in range(len(state.validators)) ] From f35106d9eec822dd02ca768dc5bc5918a6ad05ba Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 5 May 2020 09:43:25 -0600 Subject: [PATCH 09/15] add comment for helper -- has_enouh_for_reward --- tests/core/pyspec/eth2spec/test/helpers/rewards.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/core/pyspec/eth2spec/test/helpers/rewards.py b/tests/core/pyspec/eth2spec/test/helpers/rewards.py index 09c99c3ac..1b428891b 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/rewards.py +++ b/tests/core/pyspec/eth2spec/test/helpers/rewards.py @@ -10,6 +10,12 @@ class Deltas(Container): def has_enough_for_reward(spec, state, index): + """ + Check if base_reward will be non-zero. + + At very low balances, it is possible for a validator have a positive effective_balance + but a zero base reward. + """ return ( state.validators[index].effective_balance * spec.BASE_REWARD_FACTOR > spec.integer_squareroot(spec.get_total_active_balance(state)) // spec.BASE_REWARDS_PER_EPOCH From d26cfd2e599abc0f8f2691d21406fdaddf9de300 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 5 May 2020 13:08:41 -0600 Subject: [PATCH 10/15] Apply suggestions from code review from @hwwhww Co-authored-by: Hsiao-Wei Wang --- tests/core/pyspec/eth2spec/test/helpers/rewards.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/rewards.py b/tests/core/pyspec/eth2spec/test/helpers/rewards.py index 1b428891b..17849e7d1 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/rewards.py +++ b/tests/core/pyspec/eth2spec/test/helpers/rewards.py @@ -99,7 +99,7 @@ def test_half_full(spec, state, runner): def test_one_attestation_one_correct(spec, state, runner): prepare_state_with_full_attestations(spec, state) - # Remove half of attestations + # Remove all attestations except for the first one state.previous_epoch_attestations = state.previous_epoch_attestations[:1] yield from runner(spec, state) @@ -120,11 +120,9 @@ def test_some_very_low_effective_balances_that_attested(spec, state, runner): prepare_state_with_full_attestations(spec, state) # Set some balances to be very low (including 0) - state.validators[0].effective_balance = 0 - state.validators[1].effective_balance = 2 - state.validators[2].effective_balance = 10 - state.validators[3].effective_balance = 100 - state.validators[4].effective_balance = 1000 + assert len(state.validators) >= 5: + for i, index in enumerate(5): + state.validators[index].effective_balance = i yield from runner(spec, state) @@ -135,7 +133,7 @@ def test_some_very_low_effective_balances_that_did_not_attest(spec, state, runne # Remove attestation attestation = state.previous_epoch_attestations[0] state.previous_epoch_attestations = state.previous_epoch_attestations[1:] - # Set removed indices effective balance to zero + # Set removed indices effective balance to very low amount indices = spec.get_unslashed_attesting_indices(state, [attestation]) for i, index in enumerate(indices): state.validators[index].effective_balance = i From b2dfb6cebe9e40e2f2bef2cb4e03397893d76e1a Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 5 May 2020 13:33:44 -0600 Subject: [PATCH 11/15] PR feedback from @hwwhww --- .../pyspec/eth2spec/test/helpers/rewards.py | 32 +++++++-------- .../phase_0/rewards/test_get_head_deltas.py | 26 ++++++------ .../test_get_inactivity_penalty_deltas.py | 40 +++++++++---------- .../test_get_inclusion_delay_deltas.py | 19 ++++----- .../phase_0/rewards/test_get_source_deltas.py | 30 ++++++++------ .../phase_0/rewards/test_get_target_deltas.py | 30 ++++++++------ 6 files changed, 93 insertions(+), 84 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/rewards.py b/tests/core/pyspec/eth2spec/test/helpers/rewards.py index 17849e7d1..deee1b120 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/rewards.py +++ b/tests/core/pyspec/eth2spec/test/helpers/rewards.py @@ -61,19 +61,19 @@ def run_attestation_component_deltas(spec, state, component_delta_fn, matching_a assert penalties[index] == 0 -def test_empty(spec, state, runner): +def run_test_empty(spec, state, runner): # Do not add any attestations to state yield from runner(spec, state) -def test_full_all_correct(spec, state, runner): +def run_test_full_all_correct(spec, state, runner): prepare_state_with_full_attestations(spec, state) yield from runner(spec, state) -def test_full_but_partial_participation(spec, state, runner, rng=Random(5522)): +def run_test_full_but_partial_participation(spec, state, runner, rng=Random(5522)): prepare_state_with_full_attestations(spec, state) for a in state.previous_epoch_attestations: @@ -82,7 +82,7 @@ def test_full_but_partial_participation(spec, state, runner, rng=Random(5522)): yield from runner(spec, state) -def test_partial(spec, state, fraction_filled, runner): +def run_test_partial(spec, state, fraction_filled, runner): prepare_state_with_full_attestations(spec, state) # Remove portion of attestations @@ -92,11 +92,11 @@ def test_partial(spec, state, fraction_filled, runner): yield from runner(spec, state) -def test_half_full(spec, state, runner): - yield from test_partial(spec, state, 0.5, runner) +def run_test_half_full(spec, state, runner): + yield from run_test_partial(spec, state, 0.5, runner) -def test_one_attestation_one_correct(spec, state, runner): +def run_test_one_attestation_one_correct(spec, state, runner): prepare_state_with_full_attestations(spec, state) # Remove all attestations except for the first one @@ -105,7 +105,7 @@ def test_one_attestation_one_correct(spec, state, runner): yield from runner(spec, state) -def test_with_slashed_validators(spec, state, runner): +def run_test_with_slashed_validators(spec, state, runner): prepare_state_with_full_attestations(spec, state) # Slash half of validators @@ -115,19 +115,19 @@ def test_with_slashed_validators(spec, state, runner): yield from runner(spec, state) -def test_some_very_low_effective_balances_that_attested(spec, state, runner): +def run_test_some_very_low_effective_balances_that_attested(spec, state, runner): state.balances prepare_state_with_full_attestations(spec, state) # Set some balances to be very low (including 0) - assert len(state.validators) >= 5: - for i, index in enumerate(5): + assert len(state.validators) >= 5 + for i, index in enumerate(range(5)): state.validators[index].effective_balance = i yield from runner(spec, state) -def test_some_very_low_effective_balances_that_did_not_attest(spec, state, runner): +def run_test_some_very_low_effective_balances_that_did_not_attest(spec, state, runner): prepare_state_with_full_attestations(spec, state) # Remove attestation @@ -141,7 +141,7 @@ def test_some_very_low_effective_balances_that_did_not_attest(spec, state, runne yield from runner(spec, state) -def test_full_fraction_incorrect(spec, state, correct_target, correct_head, fraction_incorrect, runner): +def run_test_full_fraction_incorrect(spec, state, correct_target, correct_head, fraction_incorrect, runner): prepare_state_with_full_attestations(spec, state) # Make fraction_incorrect of pending attestations have bad target/head as specified @@ -155,14 +155,14 @@ def test_full_fraction_incorrect(spec, state, correct_target, correct_head, frac yield from runner(spec, state) -def test_full_random(spec, state, runner, rng=Random(8020)): +def run_test_full_random(spec, state, runner, rng=Random(8020)): prepare_state_with_full_attestations(spec, state) for pending_attestation in state.previous_epoch_attestations: - # 1/3 have bad target + # ~1/3 have bad target if rng.randint(0, 2) == 0: pending_attestation.data.target.root = b'\x55' * 32 - # 1/3 have bad head + # ~1/3 have bad head if rng.randint(0, 2) == 0: pending_attestation.data.beacon_block_root = b'\x66' * 32 # ~50% participation diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_head_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_head_deltas.py index 237920732..19a3e7191 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_head_deltas.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_head_deltas.py @@ -22,49 +22,49 @@ def run_get_head_deltas(spec, state): @with_all_phases @spec_state_test def test_empty(spec, state): - yield from rewards_helpers.test_empty(spec, state, run_get_head_deltas) + yield from rewards_helpers.run_test_empty(spec, state, run_get_head_deltas) @with_all_phases @spec_state_test def test_full_all_correct(spec, state): - yield from rewards_helpers.test_full_all_correct(spec, state, run_get_head_deltas) + yield from rewards_helpers.run_test_full_all_correct(spec, state, run_get_head_deltas) @with_all_phases @spec_state_test def test_half_full(spec, state): - yield from rewards_helpers.test_half_full(spec, state, run_get_head_deltas) + yield from rewards_helpers.run_test_half_full(spec, state, run_get_head_deltas) @with_all_phases @spec_state_test def test_full_but_partial_participation(spec, state): - yield from rewards_helpers.test_full_but_partial_participation(spec, state, run_get_head_deltas) + yield from rewards_helpers.run_test_full_but_partial_participation(spec, state, run_get_head_deltas) @with_all_phases @spec_state_test def test_one_attestation_one_correct(spec, state): - yield from rewards_helpers.test_one_attestation_one_correct(spec, state, run_get_head_deltas) + yield from rewards_helpers.run_test_one_attestation_one_correct(spec, state, run_get_head_deltas) @with_all_phases @spec_state_test def test_with_slashed_validators(spec, state): - yield from rewards_helpers.test_with_slashed_validators(spec, state, run_get_head_deltas) + yield from rewards_helpers.run_test_with_slashed_validators(spec, state, run_get_head_deltas) @with_all_phases @spec_state_test def test_some_very_low_effective_balances_that_attested(spec, state): - yield from rewards_helpers.test_some_very_low_effective_balances_that_attested(spec, state, run_get_head_deltas) + yield from rewards_helpers.run_test_some_very_low_effective_balances_that_attested(spec, state, run_get_head_deltas) @with_all_phases @spec_state_test def test_some_very_low_effective_balances_that_did_not_attest(spec, state): - yield from rewards_helpers.test_some_very_low_effective_balances_that_did_not_attest( + yield from rewards_helpers.run_test_some_very_low_effective_balances_that_did_not_attest( spec, state, run_get_head_deltas, @@ -74,7 +74,7 @@ def test_some_very_low_effective_balances_that_did_not_attest(spec, state): @with_all_phases @spec_state_test def test_full_half_correct_target_incorrect_head(spec, state): - yield from rewards_helpers.test_full_fraction_incorrect( + yield from rewards_helpers.run_test_full_fraction_incorrect( spec, state, correct_target=True, correct_head=False, @@ -86,7 +86,7 @@ def test_full_half_correct_target_incorrect_head(spec, state): @with_all_phases @spec_state_test def test_full_correct_target_incorrect_head(spec, state): - yield from rewards_helpers.test_full_fraction_incorrect( + yield from rewards_helpers.run_test_full_fraction_incorrect( spec, state, correct_target=True, correct_head=False, @@ -98,7 +98,7 @@ def test_full_correct_target_incorrect_head(spec, state): @with_all_phases @spec_state_test def test_full_half_incorrect_target_incorrect_head(spec, state): - yield from rewards_helpers.test_full_fraction_incorrect( + yield from rewards_helpers.run_test_full_fraction_incorrect( spec, state, correct_target=False, correct_head=False, @@ -110,7 +110,7 @@ def test_full_half_incorrect_target_incorrect_head(spec, state): @with_all_phases @spec_state_test def test_full_half_incorrect_target_correct_head(spec, state): - yield from rewards_helpers.test_full_fraction_incorrect( + yield from rewards_helpers.run_test_full_fraction_incorrect( spec, state, correct_target=False, correct_head=True, @@ -122,4 +122,4 @@ def test_full_half_incorrect_target_correct_head(spec, state): @with_all_phases @spec_state_test def test_full_random(spec, state): - yield from rewards_helpers.test_full_random(spec, state, run_get_head_deltas) + yield from rewards_helpers.run_test_full_random(spec, state, run_get_head_deltas) diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inactivity_penalty_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inactivity_penalty_deltas.py index a81451e38..fb24e09d8 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inactivity_penalty_deltas.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inactivity_penalty_deltas.py @@ -60,85 +60,85 @@ def transition_state_to_leak(spec, state, epochs=None): @with_all_phases @spec_state_test def test_empty_no_leak(spec, state): - yield from rewards_helpers.test_empty(spec, state, run_get_inactivity_penalty_deltas) + yield from rewards_helpers.run_test_empty(spec, state, run_get_inactivity_penalty_deltas) @with_all_phases @spec_state_test def test_empty_leak(spec, state): transition_state_to_leak(spec, state) - yield from rewards_helpers.test_empty(spec, state, run_get_inactivity_penalty_deltas) + yield from rewards_helpers.run_test_empty(spec, state, run_get_inactivity_penalty_deltas) @with_all_phases @spec_state_test def test_full_no_leak(spec, state): - yield from rewards_helpers.test_full_all_correct(spec, state, run_get_inactivity_penalty_deltas) + yield from rewards_helpers.run_test_full_all_correct(spec, state, run_get_inactivity_penalty_deltas) @with_all_phases @spec_state_test def test_full_leak(spec, state): transition_state_to_leak(spec, state) - yield from rewards_helpers.test_full_all_correct(spec, state, run_get_inactivity_penalty_deltas) + yield from rewards_helpers.run_test_full_all_correct(spec, state, run_get_inactivity_penalty_deltas) @with_all_phases @spec_state_test def test_half_full_no_leak(spec, state): - yield from rewards_helpers.test_half_full(spec, state, run_get_inactivity_penalty_deltas) + yield from rewards_helpers.run_test_half_full(spec, state, run_get_inactivity_penalty_deltas) @with_all_phases @spec_state_test def test_half_full_leak(spec, state): transition_state_to_leak(spec, state) - yield from rewards_helpers.test_half_full(spec, state, run_get_inactivity_penalty_deltas) + yield from rewards_helpers.run_test_half_full(spec, state, run_get_inactivity_penalty_deltas) @with_all_phases @spec_state_test def test_quarter_full_no_leak(spec, state): - yield from rewards_helpers.test_partial(spec, state, 0.25, run_get_inactivity_penalty_deltas) + yield from rewards_helpers.run_test_partial(spec, state, 0.25, run_get_inactivity_penalty_deltas) @with_all_phases @spec_state_test def test_quarter_full_leak(spec, state): transition_state_to_leak(spec, state) - yield from rewards_helpers.test_partial(spec, state, 0.25, run_get_inactivity_penalty_deltas) + yield from rewards_helpers.run_test_partial(spec, state, 0.25, run_get_inactivity_penalty_deltas) @with_all_phases @spec_state_test def test_full_but_partial_participation_no_leak(spec, state): - yield from rewards_helpers.test_full_but_partial_participation(spec, state, run_get_inactivity_penalty_deltas) + yield from rewards_helpers.run_test_full_but_partial_participation(spec, state, run_get_inactivity_penalty_deltas) @with_all_phases @spec_state_test def test_full_but_partial_participation_leak(spec, state): transition_state_to_leak(spec, state) - yield from rewards_helpers.test_full_but_partial_participation(spec, state, run_get_inactivity_penalty_deltas) + yield from rewards_helpers.run_test_full_but_partial_participation(spec, state, run_get_inactivity_penalty_deltas) @with_all_phases @spec_state_test def test_with_slashed_validators_no_leak(spec, state): - yield from rewards_helpers.test_with_slashed_validators(spec, state, run_get_inactivity_penalty_deltas) + yield from rewards_helpers.run_test_with_slashed_validators(spec, state, run_get_inactivity_penalty_deltas) @with_all_phases @spec_state_test def test_with_slashed_validators_leak(spec, state): transition_state_to_leak(spec, state) - yield from rewards_helpers.test_with_slashed_validators(spec, state, run_get_inactivity_penalty_deltas) + yield from rewards_helpers.run_test_with_slashed_validators(spec, state, run_get_inactivity_penalty_deltas) @with_all_phases @spec_state_test def test_some_very_low_effective_balances_that_attested_no_leak(spec, state): - yield from rewards_helpers.test_some_very_low_effective_balances_that_attested( + yield from rewards_helpers.run_test_some_very_low_effective_balances_that_attested( spec, state, run_get_inactivity_penalty_deltas, @@ -149,7 +149,7 @@ def test_some_very_low_effective_balances_that_attested_no_leak(spec, state): @spec_state_test def test_some_very_low_effective_balances_that_attested_leak(spec, state): transition_state_to_leak(spec, state) - yield from rewards_helpers.test_some_very_low_effective_balances_that_attested( + yield from rewards_helpers.run_test_some_very_low_effective_balances_that_attested( spec, state, run_get_inactivity_penalty_deltas, @@ -159,7 +159,7 @@ def test_some_very_low_effective_balances_that_attested_leak(spec, state): @with_all_phases @spec_state_test def test_some_very_low_effective_balances_that_did_not_attest_no_leak(spec, state): - yield from rewards_helpers.test_some_very_low_effective_balances_that_did_not_attest( + yield from rewards_helpers.run_test_some_very_low_effective_balances_that_did_not_attest( spec, state, run_get_inactivity_penalty_deltas, @@ -170,7 +170,7 @@ def test_some_very_low_effective_balances_that_did_not_attest_no_leak(spec, stat @spec_state_test def test_some_very_low_effective_balances_that_did_not_attest_leak(spec, state): transition_state_to_leak(spec, state) - yield from rewards_helpers.test_some_very_low_effective_balances_that_did_not_attest( + yield from rewards_helpers.run_test_some_very_low_effective_balances_that_did_not_attest( spec, state, run_get_inactivity_penalty_deltas, @@ -180,25 +180,25 @@ def test_some_very_low_effective_balances_that_did_not_attest_leak(spec, state): @with_all_phases @spec_state_test def test_full_random_no_leak(spec, state): - yield from rewards_helpers.test_full_random(spec, state, run_get_inactivity_penalty_deltas) + yield from rewards_helpers.run_test_full_random(spec, state, run_get_inactivity_penalty_deltas) @with_all_phases @spec_state_test def test_full_random_leak(spec, state): transition_state_to_leak(spec, state) - yield from rewards_helpers.test_full_random(spec, state, run_get_inactivity_penalty_deltas) + yield from rewards_helpers.run_test_full_random(spec, state, run_get_inactivity_penalty_deltas) @with_all_phases @spec_state_test def test_full_random_five_epoch_leak(spec, state): transition_state_to_leak(spec, state, epochs=5) - yield from rewards_helpers.test_full_random(spec, state, run_get_inactivity_penalty_deltas) + yield from rewards_helpers.run_test_full_random(spec, state, run_get_inactivity_penalty_deltas) @with_all_phases @spec_state_test def test_full_random_ten_epoch_leak(spec, state): transition_state_to_leak(spec, state, epochs=10) - yield from rewards_helpers.test_full_random(spec, state, run_get_inactivity_penalty_deltas) + yield from rewards_helpers.run_test_full_random(spec, state, run_get_inactivity_penalty_deltas) diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inclusion_delay_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inclusion_delay_deltas.py index b9ae9882c..9eda75290 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inclusion_delay_deltas.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inclusion_delay_deltas.py @@ -64,43 +64,43 @@ def run_get_inclusion_delay_deltas(spec, state): @with_all_phases @spec_state_test def test_empty(spec, state): - yield from rewards_helpers.test_empty(spec, state, run_get_inclusion_delay_deltas) + yield from rewards_helpers.run_test_empty(spec, state, run_get_inclusion_delay_deltas) @with_all_phases @spec_state_test def test_full(spec, state): - yield from rewards_helpers.test_full_all_correct(spec, state, run_get_inclusion_delay_deltas) + yield from rewards_helpers.run_test_full_all_correct(spec, state, run_get_inclusion_delay_deltas) @with_all_phases @spec_state_test def test_half_full(spec, state): - yield from rewards_helpers.test_half_full(spec, state, run_get_inclusion_delay_deltas) + yield from rewards_helpers.run_test_half_full(spec, state, run_get_inclusion_delay_deltas) @with_all_phases @spec_state_test def test_quarter_full(spec, state): - yield from rewards_helpers.test_partial(spec, state, 0.25, run_get_inclusion_delay_deltas) + yield from rewards_helpers.run_test_partial(spec, state, 0.25, run_get_inclusion_delay_deltas) @with_all_phases @spec_state_test def test_full_but_partial_participation(spec, state): - yield from rewards_helpers.test_full_but_partial_participation(spec, state, run_get_inclusion_delay_deltas) + yield from rewards_helpers.run_test_full_but_partial_participation(spec, state, run_get_inclusion_delay_deltas) @with_all_phases @spec_state_test def test_with_slashed_validators(spec, state): - yield from rewards_helpers.test_with_slashed_validators(spec, state, run_get_inclusion_delay_deltas) + yield from rewards_helpers.run_test_with_slashed_validators(spec, state, run_get_inclusion_delay_deltas) @with_all_phases @spec_state_test def test_some_very_low_effective_balances_that_attested(spec, state): - yield from rewards_helpers.test_some_very_low_effective_balances_that_attested( + yield from rewards_helpers.run_test_some_very_low_effective_balances_that_attested( spec, state, run_get_inclusion_delay_deltas @@ -110,7 +110,7 @@ def test_some_very_low_effective_balances_that_attested(spec, state): @with_all_phases @spec_state_test def test_full_random(spec, state): - yield from rewards_helpers.test_full_random(spec, state, run_get_inclusion_delay_deltas) + yield from rewards_helpers.run_test_full_random(spec, state, run_get_inclusion_delay_deltas) @with_all_phases @@ -179,7 +179,8 @@ def test_duplicate_attestations_at_later_slots(spec, state): max_slot = max([a.data.slot + a.inclusion_delay for a in state.previous_epoch_attestations]) later_attestations = [] for a in state.previous_epoch_attestations: - # Do not create later duplicate if goes into next epoch + # Only have proposers for previous epoch so do not create later + # duplicate if slot exceeds the max slot in previous_epoch_attestations if a.data.slot + a.inclusion_delay >= max_slot: continue later_a = a.copy() diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_source_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_source_deltas.py index 842728db8..efdc1fcb0 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_source_deltas.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_source_deltas.py @@ -22,49 +22,53 @@ def run_get_source_deltas(spec, state): @with_all_phases @spec_state_test def test_empty(spec, state): - yield from rewards_helpers.test_empty(spec, state, run_get_source_deltas) + yield from rewards_helpers.run_test_empty(spec, state, run_get_source_deltas) @with_all_phases @spec_state_test def test_full_all_correct(spec, state): - yield from rewards_helpers.test_full_all_correct(spec, state, run_get_source_deltas) + yield from rewards_helpers.run_test_full_all_correct(spec, state, run_get_source_deltas) @with_all_phases @spec_state_test def test_half_full(spec, state): - yield from rewards_helpers.test_half_full(spec, state, run_get_source_deltas) + yield from rewards_helpers.run_test_half_full(spec, state, run_get_source_deltas) @with_all_phases @spec_state_test def test_full_but_partial_participation(spec, state): - yield from rewards_helpers.test_full_but_partial_participation(spec, state, run_get_source_deltas) + yield from rewards_helpers.run_test_full_but_partial_participation(spec, state, run_get_source_deltas) @with_all_phases @spec_state_test def test_one_attestation_one_correct(spec, state): - yield from rewards_helpers.test_one_attestation_one_correct(spec, state, run_get_source_deltas) + yield from rewards_helpers.run_test_one_attestation_one_correct(spec, state, run_get_source_deltas) @with_all_phases @spec_state_test def test_with_slashed_validators(spec, state): - yield from rewards_helpers.test_with_slashed_validators(spec, state, run_get_source_deltas) + yield from rewards_helpers.run_test_with_slashed_validators(spec, state, run_get_source_deltas) @with_all_phases @spec_state_test def test_some_very_low_effective_balances_that_attested(spec, state): - yield from rewards_helpers.test_some_very_low_effective_balances_that_attested(spec, state, run_get_source_deltas) + yield from rewards_helpers.run_test_some_very_low_effective_balances_that_attested( + spec, + state, + run_get_source_deltas + ) @with_all_phases @spec_state_test def test_some_very_low_effective_balances_that_did_not_attest(spec, state): - yield from rewards_helpers.test_some_very_low_effective_balances_that_did_not_attest( + yield from rewards_helpers.run_test_some_very_low_effective_balances_that_did_not_attest( spec, state, run_get_source_deltas, @@ -81,7 +85,7 @@ def test_some_very_low_effective_balances_that_did_not_attest(spec, state): @with_all_phases @spec_state_test def test_full_half_correct_target_incorrect_head(spec, state): - yield from rewards_helpers.test_full_fraction_incorrect( + yield from rewards_helpers.run_test_full_fraction_incorrect( spec, state, correct_target=True, correct_head=False, @@ -93,7 +97,7 @@ def test_full_half_correct_target_incorrect_head(spec, state): @with_all_phases @spec_state_test def test_full_correct_target_incorrect_head(spec, state): - yield from rewards_helpers.test_full_fraction_incorrect( + yield from rewards_helpers.run_test_full_fraction_incorrect( spec, state, correct_target=True, correct_head=False, @@ -105,7 +109,7 @@ def test_full_correct_target_incorrect_head(spec, state): @with_all_phases @spec_state_test def test_full_half_incorrect_target_incorrect_head(spec, state): - yield from rewards_helpers.test_full_fraction_incorrect( + yield from rewards_helpers.run_test_full_fraction_incorrect( spec, state, correct_target=False, correct_head=False, @@ -117,7 +121,7 @@ def test_full_half_incorrect_target_incorrect_head(spec, state): @with_all_phases @spec_state_test def test_full_half_incorrect_target_correct_head(spec, state): - yield from rewards_helpers.test_full_fraction_incorrect( + yield from rewards_helpers.run_test_full_fraction_incorrect( spec, state, correct_target=False, correct_head=True, @@ -129,4 +133,4 @@ def test_full_half_incorrect_target_correct_head(spec, state): @with_all_phases @spec_state_test def test_full_random(spec, state): - yield from rewards_helpers.test_full_random(spec, state, run_get_source_deltas) + yield from rewards_helpers.run_test_full_random(spec, state, run_get_source_deltas) diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_target_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_target_deltas.py index ad2a65d2a..ad7198c84 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_target_deltas.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_target_deltas.py @@ -22,49 +22,53 @@ def run_get_target_deltas(spec, state): @with_all_phases @spec_state_test def test_empty(spec, state): - yield from rewards_helpers.test_empty(spec, state, run_get_target_deltas) + yield from rewards_helpers.run_test_empty(spec, state, run_get_target_deltas) @with_all_phases @spec_state_test def test_full_all_correct(spec, state): - yield from rewards_helpers.test_full_all_correct(spec, state, run_get_target_deltas) + yield from rewards_helpers.run_test_full_all_correct(spec, state, run_get_target_deltas) @with_all_phases @spec_state_test def test_half_full(spec, state): - yield from rewards_helpers.test_half_full(spec, state, run_get_target_deltas) + yield from rewards_helpers.run_test_half_full(spec, state, run_get_target_deltas) @with_all_phases @spec_state_test def test_full_but_partial_participation(spec, state): - yield from rewards_helpers.test_full_but_partial_participation(spec, state, run_get_target_deltas) + yield from rewards_helpers.run_test_full_but_partial_participation(spec, state, run_get_target_deltas) @with_all_phases @spec_state_test def test_one_attestation_one_correct(spec, state): - yield from rewards_helpers.test_one_attestation_one_correct(spec, state, run_get_target_deltas) + yield from rewards_helpers.run_test_one_attestation_one_correct(spec, state, run_get_target_deltas) @with_all_phases @spec_state_test def test_with_slashed_validators(spec, state): - yield from rewards_helpers.test_with_slashed_validators(spec, state, run_get_target_deltas) + yield from rewards_helpers.run_test_with_slashed_validators(spec, state, run_get_target_deltas) @with_all_phases @spec_state_test def test_some_very_low_effective_balances_that_attested(spec, state): - yield from rewards_helpers.test_some_very_low_effective_balances_that_attested(spec, state, run_get_target_deltas) + yield from rewards_helpers.run_test_some_very_low_effective_balances_that_attested( + spec, + state, + run_get_target_deltas + ) @with_all_phases @spec_state_test def test_some_very_low_effective_balances_that_did_not_attest(spec, state): - yield from rewards_helpers.test_some_very_low_effective_balances_that_did_not_attest( + yield from rewards_helpers.run_test_some_very_low_effective_balances_that_did_not_attest( spec, state, run_get_target_deltas, @@ -74,7 +78,7 @@ def test_some_very_low_effective_balances_that_did_not_attest(spec, state): @with_all_phases @spec_state_test def test_full_half_correct_target_incorrect_head(spec, state): - yield from rewards_helpers.test_full_fraction_incorrect( + yield from rewards_helpers.run_test_full_fraction_incorrect( spec, state, correct_target=True, correct_head=False, @@ -86,7 +90,7 @@ def test_full_half_correct_target_incorrect_head(spec, state): @with_all_phases @spec_state_test def test_full_correct_target_incorrect_head(spec, state): - yield from rewards_helpers.test_full_fraction_incorrect( + yield from rewards_helpers.run_test_full_fraction_incorrect( spec, state, correct_target=True, correct_head=False, @@ -98,7 +102,7 @@ def test_full_correct_target_incorrect_head(spec, state): @with_all_phases @spec_state_test def test_full_half_incorrect_target_incorrect_head(spec, state): - yield from rewards_helpers.test_full_fraction_incorrect( + yield from rewards_helpers.run_test_full_fraction_incorrect( spec, state, correct_target=False, correct_head=False, @@ -110,7 +114,7 @@ def test_full_half_incorrect_target_incorrect_head(spec, state): @with_all_phases @spec_state_test def test_full_half_incorrect_target_correct_head(spec, state): - yield from rewards_helpers.test_full_fraction_incorrect( + yield from rewards_helpers.run_test_full_fraction_incorrect( spec, state, correct_target=False, correct_head=True, @@ -122,4 +126,4 @@ def test_full_half_incorrect_target_correct_head(spec, state): @with_all_phases @spec_state_test def test_full_random(spec, state): - yield from rewards_helpers.test_full_random(spec, state, run_get_target_deltas) + yield from rewards_helpers.run_test_full_random(spec, state, run_get_target_deltas) From 4f401133e14fac50ea9fce42264b3c09f6ac39b0 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 5 May 2020 15:37:14 -0600 Subject: [PATCH 12/15] address PR feedback from @protolambda --- specs/phase0/beacon-chain.md | 4 +-- .../pyspec/eth2spec/test/helpers/rewards.py | 8 +++--- .../test_get_inactivity_penalty_deltas.py | 10 ++----- .../test_get_inclusion_delay_deltas.py | 11 ++------ tests/formats/rewards/README.md | 27 +++++++++++-------- 5 files changed, 26 insertions(+), 34 deletions(-) diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index 82fbb2856..fe8fcc9bd 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -1370,8 +1370,8 @@ def get_attestation_component_deltas(state: BeaconState, """ Helper with shared logic for use by get source, target, and head deltas functions """ - rewards = [Gwei(0) for _ in range(len(state.validators))] - penalties = [Gwei(0) for _ in range(len(state.validators))] + rewards = [Gwei(0)] * len(state.validators) + penalties = [Gwei(0)] * len(state.validators) total_balance = get_total_active_balance(state) unslashed_attesting_indices = get_unslashed_attesting_indices(state, attestations) attesting_balance = get_total_balance(state, unslashed_attesting_indices) diff --git a/tests/core/pyspec/eth2spec/test/helpers/rewards.py b/tests/core/pyspec/eth2spec/test/helpers/rewards.py index deee1b120..6e470d9e9 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/rewards.py +++ b/tests/core/pyspec/eth2spec/test/helpers/rewards.py @@ -1,12 +1,13 @@ from random import Random +from eth2spec.phase0 import spec as spec_phase0 from eth2spec.test.helpers.attestations import prepare_state_with_full_attestations from eth2spec.utils.ssz.ssz_typing import Container, uint64, List -# HACK to get the generators outputting correctly class Deltas(Container): - delta_list: List[uint64, 2**30] + rewards: List[uint64, spec_phase0.VALIDATOR_REGISTRY_LIMIT] + penalties: List[uint64, spec_phase0.VALIDATOR_REGISTRY_LIMIT] def has_enough_for_reward(spec, state, index): @@ -33,8 +34,7 @@ def run_attestation_component_deltas(spec, state, component_delta_fn, matching_a rewards, penalties = component_delta_fn(state) - yield 'rewards', Deltas(delta_list=rewards) - yield 'penalties', Deltas(delta_list=penalties) + yield 'deltas', Deltas(rewards=rewards, penalties=penalties) matching_attestations = matching_att_fn(state, spec.get_previous_epoch(state)) matching_indices = spec.get_unslashed_attesting_indices(state, matching_attestations) diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inactivity_penalty_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inactivity_penalty_deltas.py index fb24e09d8..657acbdbb 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inactivity_penalty_deltas.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inactivity_penalty_deltas.py @@ -1,13 +1,8 @@ from eth2spec.test.context import with_all_phases, spec_state_test from eth2spec.test.helpers.rewards import has_enough_for_reward from eth2spec.test.helpers.state import next_epoch +from eth2spec.test.helpers.rewards import Deltas import eth2spec.test.helpers.rewards as rewards_helpers -from eth2spec.utils.ssz.ssz_typing import Container, uint64, List - - -# HACK to get the generators outputting correctly -class Deltas(Container): - delta_list: List[uint64, 2**30] def run_get_inactivity_penalty_deltas(spec, state): @@ -22,8 +17,7 @@ def run_get_inactivity_penalty_deltas(spec, state): rewards, penalties = spec.get_inactivity_penalty_deltas(state) - yield 'rewards', Deltas(delta_list=rewards) - yield 'penalties', Deltas(delta_list=penalties) + yield 'deltas', Deltas(rewards=rewards, penalties=penalties) matching_attestations = spec.get_matching_target_attestations(state, spec.get_previous_epoch(state)) matching_attesting_indices = spec.get_unslashed_attesting_indices(state, matching_attestations) diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inclusion_delay_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inclusion_delay_deltas.py index 9eda75290..320f59a9d 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inclusion_delay_deltas.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inclusion_delay_deltas.py @@ -2,14 +2,8 @@ from random import Random from eth2spec.test.context import with_all_phases, spec_state_test from eth2spec.test.helpers.attestations import prepare_state_with_full_attestations -from eth2spec.test.helpers.rewards import has_enough_for_reward +from eth2spec.test.helpers.rewards import Deltas, has_enough_for_reward import eth2spec.test.helpers.rewards as rewards_helpers -from eth2spec.utils.ssz.ssz_typing import Container, uint64, List - - -# HACK to get the generators outputting correctly -class Deltas(Container): - delta_list: List[uint64, 2**30] def run_get_inclusion_delay_deltas(spec, state): @@ -24,8 +18,7 @@ def run_get_inclusion_delay_deltas(spec, state): rewards, penalties = spec.get_inclusion_delay_deltas(state) - yield 'rewards', Deltas(delta_list=rewards) - yield 'penalties', Deltas(delta_list=penalties) + yield 'deltas', Deltas(rewards=rewards, penalties=penalties) eligible_attestations = spec.get_matching_source_attestations(state, spec.get_previous_epoch(state)) attesting_indices = spec.get_unslashed_attesting_indices(state, eligible_attestations) diff --git a/tests/formats/rewards/README.md b/tests/formats/rewards/README.md index dce2b5ac8..bffe2e7de 100644 --- a/tests/formats/rewards/README.md +++ b/tests/formats/rewards/README.md @@ -4,7 +4,6 @@ The different rewards deltas sub-functions are testing individually with the tes There is no "change" factor, the rewards/penalties outputs are pure functions with just the pre-state as input. Hence, the format is shared between each test-handler. (See test condition documentation on how to run the tests.) - ## Test case format ### `meta.yaml` @@ -12,23 +11,29 @@ Hence, the format is shared between each test-handler. (See test condition docum ```yaml description: string -- Optional description of test case, purely for debugging purposes. Tests should use the directory name of the test case as identifier, not the description. -bls_setting: int -- see general test-format spec. ``` +_Note_: No signature verification happens within rewards sub-functions. These + tests can safely be run with or without BLS enabled. + ### `pre.yaml` A YAML-encoded `BeaconState`, the state before running the rewards sub-function. Also available as `pre.ssz`. +### `deltas.yaml` -### `rewards.yaml` +A YAML-encoded `Deltas` representing the rewards and penalties returned by the rewards sub-function -A YAML-encoded list of integers representing the 0th item in the return value (i.e. the rewards deltas) +Where `Deltas` is defined as: +```python +class Deltas(Container): + rewards: List[uint64, VALIDATOR_REGISTRY_LIMIT] + penalties: List[uint64, VALIDATOR_REGISTRY_LIMIT] +``` -### `penalties.yaml` - -A YAML-encoded list of integers representing the 1st item in the return value (i.e. the penalties deltas) +Also available as `rewards.ssz`. ## Condition @@ -38,10 +43,10 @@ This excludes all other parts of `process_rewards_and_penalties` The provided pre-state is ready to be input into the designated handler. -The resulting `rewards`/`penalties` should match the return values of the -handler. Specifically the following must hold true: +The provided `deltas` should match the return values of the + handler. Specifically the following must hold true: ```python - rewards == handler(state)[0] - penalties == handler(state)[1] + deltas.rewards == handler(state)[0] + deltas.penalties == handler(state)[1] ``` From f0742b2f2def1de421469919384faea05db953c5 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 5 May 2020 16:28:29 -0600 Subject: [PATCH 13/15] add exited tests for rewards. make some valiators exited/withdrawable in slashed tests --- .../pyspec/eth2spec/test/helpers/rewards.py | 44 +++++++++++++++++-- .../phase_0/rewards/test_get_head_deltas.py | 6 +++ .../test_get_inactivity_penalty_deltas.py | 13 ++++++ .../test_get_inclusion_delay_deltas.py | 6 +++ .../phase_0/rewards/test_get_source_deltas.py | 6 +++ 5 files changed, 71 insertions(+), 4 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/rewards.py b/tests/core/pyspec/eth2spec/test/helpers/rewards.py index 6e470d9e9..fa041dae4 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/rewards.py +++ b/tests/core/pyspec/eth2spec/test/helpers/rewards.py @@ -2,6 +2,7 @@ from random import Random from eth2spec.phase0 import spec as spec_phase0 from eth2spec.test.helpers.attestations import prepare_state_with_full_attestations +from eth2spec.test.helpers.state import next_epoch from eth2spec.utils.ssz.ssz_typing import Container, uint64, List @@ -61,6 +62,32 @@ def run_attestation_component_deltas(spec, state, component_delta_fn, matching_a assert penalties[index] == 0 +def exit_random_validators(spec, state, rng): + if spec.get_current_epoch(state) < 5: + # Move epochs forward to allow for some validators already exited/withdrawable + for _ in range(5): + next_epoch(spec, state) + + current_epoch = spec.get_current_epoch(state) + # Exit ~1/2 of validators + for validator in state.validators: + if rng.choice([True, False]): + continue + + validator.exit_epoch = rng.choice([current_epoch - 1, current_epoch - 2, current_epoch - 3]) + # ~1/2 are withdrawable + if rng.choice([True, False]): + validator.withdrawable_epoch = current_epoch + else: + validator.withdrawable_epoch = current_epoch + 1 + + +def slash_random_validators(spec, state, rng): + # Slash ~1/2 of validators + for validator in state.validators: + validator.slashed = rng.choice([True, False]) + + def run_test_empty(spec, state, runner): # Do not add any attestations to state @@ -105,12 +132,18 @@ def run_test_one_attestation_one_correct(spec, state, runner): yield from runner(spec, state) -def run_test_with_slashed_validators(spec, state, runner): +def run_test_with_exited_validators(spec, state, runner, rng=Random(1337)): + exit_random_validators(spec, state, rng) prepare_state_with_full_attestations(spec, state) - # Slash half of validators - for validator in state.validators[:len(state.validators) // 2]: - validator.slashed = True + yield from runner(spec, state) + + +def run_test_with_slashed_validators(spec, state, runner, rng=Random(3322)): + exit_random_validators(spec, state, rng) + slash_random_validators(spec, state, rng) + + prepare_state_with_full_attestations(spec, state) yield from runner(spec, state) @@ -156,6 +189,9 @@ def run_test_full_fraction_incorrect(spec, state, correct_target, correct_head, def run_test_full_random(spec, state, runner, rng=Random(8020)): + exit_random_validators(spec, state, rng) + slash_random_validators(spec, state, rng) + prepare_state_with_full_attestations(spec, state) for pending_attestation in state.previous_epoch_attestations: diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_head_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_head_deltas.py index 19a3e7191..ffaad15af 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_head_deltas.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_head_deltas.py @@ -49,6 +49,12 @@ def test_one_attestation_one_correct(spec, state): yield from rewards_helpers.run_test_one_attestation_one_correct(spec, state, run_get_head_deltas) +@with_all_phases +@spec_state_test +def test_with_exited_validators(spec, state): + yield from rewards_helpers.run_test_with_exited_validators(spec, state, run_get_head_deltas) + + @with_all_phases @spec_state_test def test_with_slashed_validators(spec, state): diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inactivity_penalty_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inactivity_penalty_deltas.py index 657acbdbb..52ad631ca 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inactivity_penalty_deltas.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inactivity_penalty_deltas.py @@ -116,6 +116,19 @@ def test_full_but_partial_participation_leak(spec, state): yield from rewards_helpers.run_test_full_but_partial_participation(spec, state, run_get_inactivity_penalty_deltas) +@with_all_phases +@spec_state_test +def test_with_exited_validators_no_leak(spec, state): + yield from rewards_helpers.run_test_with_exited_validators(spec, state, run_get_inactivity_penalty_deltas) + + +@with_all_phases +@spec_state_test +def test_with_exited_validators_leak(spec, state): + transition_state_to_leak(spec, state) + yield from rewards_helpers.run_test_with_exited_validators(spec, state, run_get_inactivity_penalty_deltas) + + @with_all_phases @spec_state_test def test_with_slashed_validators_no_leak(spec, state): diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inclusion_delay_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inclusion_delay_deltas.py index 320f59a9d..29ac10131 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inclusion_delay_deltas.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inclusion_delay_deltas.py @@ -84,6 +84,12 @@ def test_full_but_partial_participation(spec, state): yield from rewards_helpers.run_test_full_but_partial_participation(spec, state, run_get_inclusion_delay_deltas) +@with_all_phases +@spec_state_test +def test_with_exited_validators(spec, state): + yield from rewards_helpers.run_test_with_exited_validators(spec, state, run_get_inclusion_delay_deltas) + + @with_all_phases @spec_state_test def test_with_slashed_validators(spec, state): diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_source_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_source_deltas.py index efdc1fcb0..120260d82 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_source_deltas.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_source_deltas.py @@ -49,6 +49,12 @@ def test_one_attestation_one_correct(spec, state): yield from rewards_helpers.run_test_one_attestation_one_correct(spec, state, run_get_source_deltas) +@with_all_phases +@spec_state_test +def test_with_exited_validators(spec, state): + yield from rewards_helpers.run_test_with_exited_validators(spec, state, run_get_source_deltas) + + @with_all_phases @spec_state_test def test_with_slashed_validators(spec, state): From 4ffa0dba60a4d8670f1a50ddead4f7bce2a66351 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 5 May 2020 16:31:40 -0600 Subject: [PATCH 14/15] Apply suggestions from code review "rewards/penalties" -> "deltas" in throughout test comments/descriptions Co-authored-by: Diederik Loerakker --- tests/core/pyspec/eth2spec/test/helpers/rewards.py | 3 +-- .../eth2spec/test/phase_0/rewards/test_get_head_deltas.py | 3 +-- .../test/phase_0/rewards/test_get_inactivity_penalty_deltas.py | 3 +-- .../test/phase_0/rewards/test_get_inclusion_delay_deltas.py | 3 +-- .../eth2spec/test/phase_0/rewards/test_get_source_deltas.py | 3 +-- .../eth2spec/test/phase_0/rewards/test_get_target_deltas.py | 3 +-- tests/formats/rewards/README.md | 2 +- 7 files changed, 7 insertions(+), 13 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/rewards.py b/tests/core/pyspec/eth2spec/test/helpers/rewards.py index fa041dae4..eaeb9252e 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/rewards.py +++ b/tests/core/pyspec/eth2spec/test/helpers/rewards.py @@ -28,8 +28,7 @@ def run_attestation_component_deltas(spec, state, component_delta_fn, matching_a """ Run ``component_delta_fn``, yielding: - pre-state ('pre') - - rewards ('rewards') - - penalties ('penalties') + - deltas ('deltas') """ yield 'pre', state diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_head_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_head_deltas.py index ffaad15af..2e4b9dbbc 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_head_deltas.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_head_deltas.py @@ -7,8 +7,7 @@ def run_get_head_deltas(spec, state): """ Run ``get_head_deltas``, yielding: - pre-state ('pre') - - rewards ('rewards') - - penalties ('penalties') + - deltas ('deltas') """ yield from run_attestation_component_deltas( diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inactivity_penalty_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inactivity_penalty_deltas.py index 52ad631ca..4940cdc63 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inactivity_penalty_deltas.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inactivity_penalty_deltas.py @@ -9,8 +9,7 @@ def run_get_inactivity_penalty_deltas(spec, state): """ Run ``get_inactivity_penalty_deltas``, yielding: - pre-state ('pre') - - rewards ('rewards') - - penalties ('penalties') + - deltas ('deltas') """ yield 'pre', state diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inclusion_delay_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inclusion_delay_deltas.py index 29ac10131..526d135ed 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inclusion_delay_deltas.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inclusion_delay_deltas.py @@ -10,8 +10,7 @@ def run_get_inclusion_delay_deltas(spec, state): """ Run ``get_inclusion_delay_deltas``, yielding: - pre-state ('pre') - - rewards ('rewards') - - penalties ('penalties') + - deltas ('deltas') """ yield 'pre', state diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_source_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_source_deltas.py index 120260d82..54f8f3b5d 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_source_deltas.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_source_deltas.py @@ -7,8 +7,7 @@ def run_get_source_deltas(spec, state): """ Run ``get_source_deltas``, yielding: - pre-state ('pre') - - rewards ('rewards') - - penalties ('penalties') + - deltas ('deltas') """ yield from run_attestation_component_deltas( diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_target_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_target_deltas.py index ad7198c84..0ae985086 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_target_deltas.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_target_deltas.py @@ -7,8 +7,7 @@ def run_get_target_deltas(spec, state): """ Run ``get_target_deltas``, yielding: - pre-state ('pre') - - rewards ('rewards') - - penalties ('penalties') + - deltas ('deltas') """ yield from run_attestation_component_deltas( diff --git a/tests/formats/rewards/README.md b/tests/formats/rewards/README.md index bffe2e7de..f70a20f9c 100644 --- a/tests/formats/rewards/README.md +++ b/tests/formats/rewards/README.md @@ -33,7 +33,7 @@ class Deltas(Container): penalties: List[uint64, VALIDATOR_REGISTRY_LIMIT] ``` -Also available as `rewards.ssz`. +Also available as `deltas.ssz`. ## Condition From 7a130606ac73b72d033b6764285c3a617e322a67 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Fri, 8 May 2020 10:50:05 -0600 Subject: [PATCH 15/15] hww feedback --- specs/phase0/beacon-chain.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index fe8fcc9bd..5f04269e1 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -1420,7 +1420,6 @@ def get_inclusion_delay_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequ Return proposer and inclusion delay micro-rewards/penalties for each validator. """ rewards = [Gwei(0) for _ in range(len(state.validators))] - penalties = [Gwei(0) for _ in range(len(state.validators))] matching_source_attestations = get_matching_source_attestations(state, get_previous_epoch(state)) for index in get_unslashed_attesting_indices(state, matching_source_attestations): attestation = min([ @@ -1431,6 +1430,9 @@ def get_inclusion_delay_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequ rewards[attestation.proposer_index] += proposer_reward max_attester_reward = get_base_reward(state, index) - proposer_reward rewards[index] += Gwei(max_attester_reward // attestation.inclusion_delay) + + # No penalties associated with inclusion delay + penalties = [Gwei(0) for _ in range(len(state.validators))] return rewards, penalties ``` @@ -1439,7 +1441,6 @@ def get_inactivity_penalty_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], S """ Return inactivity reward/penalty deltas for each validator. """ - rewards = [Gwei(0) for _ in range(len(state.validators))] penalties = [Gwei(0) for _ in range(len(state.validators))] finality_delay = get_previous_epoch(state) - state.finalized_checkpoint.epoch @@ -1451,6 +1452,9 @@ def get_inactivity_penalty_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], S if index not in matching_target_attesting_indices: effective_balance = state.validators[index].effective_balance penalties[index] += Gwei(effective_balance * finality_delay // INACTIVITY_PENALTY_QUOTIENT) + + # No rewards associated with inactivity penalties + rewards = [Gwei(0) for _ in range(len(state.validators))] return rewards, penalties ```