From 5f18dd778c1d44203140a27f088b9596767189d5 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 28 Apr 2020 19:26:14 -0600 Subject: [PATCH] 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):