From d59f1945e61a832cba2b8aab617b1e39ae5819fb Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 9 Mar 2021 12:52:04 -0700 Subject: [PATCH 1/5] port leak-score feature without 64-epoch --- specs/lightclient/beacon-chain.md | 60 +++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 19 deletions(-) diff --git a/specs/lightclient/beacon-chain.md b/specs/lightclient/beacon-chain.md index bc070908a..5736c22a2 100644 --- a/specs/lightclient/beacon-chain.md +++ b/specs/lightclient/beacon-chain.md @@ -118,6 +118,7 @@ This patch updates a few configuration values to move penalty constants toward t | - | - | | `SYNC_COMMITTEE_SIZE` | `uint64(2**10)` (= 1,024) | | `SYNC_SUBCOMMITTEE_SIZE` | `uint64(2**6)` (= 64) | +| `LEAK_SCORE_BIAS` | 4 | ### Time parameters @@ -192,6 +193,8 @@ class BeaconState(Container): # Light client sync committees current_sync_committee: SyncCommittee next_sync_committee: SyncCommittee + # is online in an inactivity leak, inactivity leak penalties are proportional to this value + leak_score: List[uint64, VALIDATOR_REGISTRY_LIMIT] ``` ### New containers @@ -366,23 +369,26 @@ def get_inactivity_penalty_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], S flags to determine who participated and who did not, applying the leak penalty globally and applying compensatory rewards to participants. """ - penalties = [Gwei(0) for _ in range(len(state.validators))] - if is_in_inactivity_leak(state): - reward_numerator_sum = sum(numerator for (_, numerator) in get_flag_indices_and_numerators()) - matching_target_attesting_indices = get_unslashed_participating_indices( - state, TIMELY_TARGET_FLAG_INDEX, get_previous_epoch(state) - ) - for index in get_eligible_validator_indices(state): - # If validator is performing optimally this cancels all attestation rewards for a neutral balance - penalties[index] += Gwei(get_base_reward(state, index) * reward_numerator_sum // FLAG_DENOMINATOR) - if index not in matching_target_attesting_indices: - effective_balance = state.validators[index].effective_balance - penalties[index] += Gwei( - effective_balance * get_finality_delay(state) - // HF1_INACTIVITY_PENALTY_QUOTIENT - ) - rewards = [Gwei(0) for _ in range(len(state.validators))] + penalties = [Gwei(0) for _ in range(len(state.validators))] + + if not is_in_inactivity_leak(state): + return rewards, penalties + + reward_numerator_sum = sum(numerator for (_, numerator) in get_flag_indices_and_numerators()) + matching_target_attesting_indices = get_unslashed_participating_indices( + state, TIMELY_TARGET_FLAG_INDEX, get_previous_epoch(state) + ) + for index in get_eligible_validator_indices(state): + # If validator is performing optimally this cancels all attestation rewards for a neutral balance + penalties[index] += Gwei(get_base_reward(state, index) * reward_numerator_sum // FLAG_DENOMINATOR) + if index not in matching_target_attesting_indices and state.leak_score[index] >= LEAK_SCORE_BIAS: + effective_balance = state.validators[index].effective_balance + leak_penalty = Gwei( + effective_balance * state.leak_score[index] // LEAK_SCORE_BIAS // HF1_INACTIVITY_PENALTY_QUOTIENT + ) + penalties[index] += leak_penalty + return rewards, penalties ``` @@ -519,9 +525,9 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None: # Add validator and balance entries state.validators.append(get_validator_from_deposit(state, deposit)) state.balances.append(amount) - # [Added in hf-1] Initialize empty participation flags for new validator - state.previous_epoch_participation.append(ParticipationFlags(0b0000_0000)) - state.current_epoch_participation.append(ParticipationFlags(0b0000_0000)) + state.previous_epoch_participation.append(ParticipationFlags(0b0000_0000)) # New in HF1 + state.current_epoch_participation.append(ParticipationFlags(0b0000_0000)) # New in HF1 + state.leak_score.append(0) # New in HF1 else: # Increase balance by deposit amount index = ValidatorIndex(validator_pubkeys.index(pubkey)) @@ -562,6 +568,7 @@ def process_sync_committee(state: BeaconState, body: BeaconBlockBody) -> None: ```python def process_epoch(state: BeaconState) -> None: process_justification_and_finalization(state) # [Modified in HF1] + process_leak_score_updates(state) # [New in HF1] process_rewards_and_penalties(state) # [Modified in HF1] process_registry_updates(state) process_slashings(state) # [Modified in HF1] @@ -620,6 +627,21 @@ def process_justification_and_finalization(state: BeaconState) -> None: state.finalized_checkpoint = old_current_justified_checkpoint ``` +#### Leak scores + +```python +def process_leak_score_updates(state: BeaconState) -> None: + matching_target_attesting_indices = get_unslashed_participating_indices( + state, TIMELY_TARGET_FLAG_INDEX, get_previous_epoch(state) + ) + for index in get_eligible_validator_indices(state): + if index in matching_target_attesting_indices: + if state.leak_score[index] > 0: + state.leak_score[index] -= 1 + elif is_in_inactivity_leak(state): + state.leak_score[index] += LEAK_SCORE_BIAS +``` + #### Rewards and penalties *Note*: The function `process_rewards_and_penalties` is modified to support the incentive reforms. From 70a28340582dee52ad91d9d12d6f08364c76760c Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 9 Mar 2021 13:21:16 -0700 Subject: [PATCH 2/5] incorporate justin's pr --- specs/lightclient/beacon-chain.md | 44 +++++++++++++++---------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/specs/lightclient/beacon-chain.md b/specs/lightclient/beacon-chain.md index 5736c22a2..c49e34c6c 100644 --- a/specs/lightclient/beacon-chain.md +++ b/specs/lightclient/beacon-chain.md @@ -41,10 +41,11 @@ - [New `slash_validator`](#new-slash_validator) - [Block processing](#block-processing) - [Modified `process_attestation`](#modified-process_attestation) - - [New `process_deposit`](#new-process_deposit) + - [Modified `process_deposit`](#modified-process_deposit) - [Sync committee processing](#sync-committee-processing) - [Epoch processing](#epoch-processing) - [Justification and finalization](#justification-and-finalization) + - [Leak scores](#leak-scores) - [Rewards and penalties](#rewards-and-penalties) - [Slashings](#slashings) - [Participation flags updates](#participation-flags-updates) @@ -183,8 +184,8 @@ class BeaconState(Container): # Slashings slashings: Vector[Gwei, EPOCHS_PER_SLASHINGS_VECTOR] # Per-epoch sums of slashed effective balances # Participation - previous_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] - current_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] + previous_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] # [New in HF1] + current_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] # [New in HF1] # Finality justification_bits: Bitvector[JUSTIFICATION_BITS_LENGTH] # Bit set for every recent justified epoch previous_justified_checkpoint: Checkpoint @@ -193,8 +194,8 @@ class BeaconState(Container): # Light client sync committees current_sync_committee: SyncCommittee next_sync_committee: SyncCommittee - # is online in an inactivity leak, inactivity leak penalties are proportional to this value - leak_score: List[uint64, VALIDATOR_REGISTRY_LIMIT] + # Leak + leak_scores: List[uint64, VALIDATOR_REGISTRY_LIMIT] ``` ### New containers @@ -287,13 +288,10 @@ def get_sync_committee(state: BeaconState, epoch: Epoch) -> SyncCommittee: Return the sync committee for a given state and epoch. """ indices = get_sync_committee_indices(state, epoch) - validators = [state.validators[index] for index in indices] - pubkeys = [validator.pubkey for validator in validators] - aggregates = [ - bls.AggregatePKs(pubkeys[i:i + SYNC_SUBCOMMITTEE_SIZE]) - for i in range(0, len(pubkeys), SYNC_SUBCOMMITTEE_SIZE) - ] - return SyncCommittee(pubkeys=pubkeys, pubkey_aggregates=aggregates) + pubkeys = [state.validators[index].pubkey for index in indices] + subcommitees = [pubkeys[i:i + SYNC_SUBCOMMITTEE_SIZE] for i in range(0, len(pubkeys), SYNC_SUBCOMMITTEE_SIZE)] + pubkey_aggregates = [bls.AggregatePKs(subcommitee) for subcommitee in subcommitees] + return SyncCommittee(pubkeys=pubkeys, pubkey_aggregates=pubkey_aggregates) ``` #### `get_base_reward` @@ -382,10 +380,10 @@ def get_inactivity_penalty_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], S for index in get_eligible_validator_indices(state): # If validator is performing optimally this cancels all attestation rewards for a neutral balance penalties[index] += Gwei(get_base_reward(state, index) * reward_numerator_sum // FLAG_DENOMINATOR) - if index not in matching_target_attesting_indices and state.leak_score[index] >= LEAK_SCORE_BIAS: + if index not in matching_target_attesting_indices and state.leak_scores[index] >= LEAK_SCORE_BIAS: effective_balance = state.validators[index].effective_balance leak_penalty = Gwei( - effective_balance * state.leak_score[index] // LEAK_SCORE_BIAS // HF1_INACTIVITY_PENALTY_QUOTIENT + effective_balance * state.leak_scores[index] // LEAK_SCORE_BIAS // HF1_INACTIVITY_PENALTY_QUOTIENT ) penalties[index] += leak_penalty @@ -489,9 +487,9 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None: ``` -#### New `process_deposit` +#### Modified `process_deposit` -*Note*: The function `process_deposit` is modified to initialize `previous_epoch_participation` and `current_epoch_participation`. +*Note*: The function `process_deposit` is modified to initialize `leak_scores`, `previous_epoch_participation`, `current_epoch_participation`. ```python def process_deposit(state: BeaconState, deposit: Deposit) -> None: @@ -527,7 +525,7 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None: state.balances.append(amount) state.previous_epoch_participation.append(ParticipationFlags(0b0000_0000)) # New in HF1 state.current_epoch_participation.append(ParticipationFlags(0b0000_0000)) # New in HF1 - state.leak_score.append(0) # New in HF1 + state.leak_scores.append(0) # New in HF1 else: # Increase balance by deposit amount index = ValidatorIndex(validator_pubkeys.index(pubkey)) @@ -568,7 +566,7 @@ def process_sync_committee(state: BeaconState, body: BeaconBlockBody) -> None: ```python def process_epoch(state: BeaconState) -> None: process_justification_and_finalization(state) # [Modified in HF1] - process_leak_score_updates(state) # [New in HF1] + process_leak_updates(state) # [New in HF1] process_rewards_and_penalties(state) # [Modified in HF1] process_registry_updates(state) process_slashings(state) # [Modified in HF1] @@ -629,17 +627,19 @@ def process_justification_and_finalization(state: BeaconState) -> None: #### Leak scores +*Note*: The function `process_leak_updates` is new. + ```python -def process_leak_score_updates(state: BeaconState) -> None: +def process_leak_updates(state: BeaconState) -> None: matching_target_attesting_indices = get_unslashed_participating_indices( state, TIMELY_TARGET_FLAG_INDEX, get_previous_epoch(state) ) for index in get_eligible_validator_indices(state): if index in matching_target_attesting_indices: - if state.leak_score[index] > 0: - state.leak_score[index] -= 1 + if state.leak_scores[index] > 0: + state.leak_scores[index] -= 1 elif is_in_inactivity_leak(state): - state.leak_score[index] += LEAK_SCORE_BIAS + state.leak_scores[index] += LEAK_SCORE_BIAS ``` #### Rewards and penalties From e2abdb74ae6340ecf2be5f871bd070254dc05e37 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 9 Mar 2021 15:16:26 -0700 Subject: [PATCH 3/5] port testing --- configs/mainnet/lightclient_patch.yaml | 2 + configs/minimal/lightclient_patch.yaml | 2 + specs/lightclient/beacon-chain.md | 9 +-- specs/lightclient/lightclient-fork.md | 7 +- .../pyspec/eth2spec/test/helpers/rewards.py | 9 ++- .../lightclient_patch/sanity/test_blocks.py | 24 ++++++ .../test_process_attester_slashing.py | 55 ++++++++++++-- .../test_process_effective_balance_updates.py | 12 +-- .../test_process_rewards_and_penalties.py | 74 ++++++++----------- 9 files changed, 125 insertions(+), 69 deletions(-) diff --git a/configs/mainnet/lightclient_patch.yaml b/configs/mainnet/lightclient_patch.yaml index a9ddc16f6..b151f5cdb 100644 --- a/configs/mainnet/lightclient_patch.yaml +++ b/configs/mainnet/lightclient_patch.yaml @@ -18,6 +18,8 @@ HF1_PROPORTIONAL_SLASHING_MULTIPLIER: 2 SYNC_COMMITTEE_SIZE: 1024 # 2**6 (=64) SYNC_SUBCOMMITTEE_SIZE: 64 +# 2**2 (=4) +LEAK_SCORE_BIAS: 4 # Time parameters diff --git a/configs/minimal/lightclient_patch.yaml b/configs/minimal/lightclient_patch.yaml index 56ce34591..cbdbaf8a7 100644 --- a/configs/minimal/lightclient_patch.yaml +++ b/configs/minimal/lightclient_patch.yaml @@ -18,6 +18,8 @@ HF1_PROPORTIONAL_SLASHING_MULTIPLIER: 2 SYNC_COMMITTEE_SIZE: 32 # [customized] SYNC_SUBCOMMITTEE_SIZE: 16 +# 2**2 (=4) +LEAK_SCORE_BIAS: 4 # Time parameters diff --git a/specs/lightclient/beacon-chain.md b/specs/lightclient/beacon-chain.md index c49e34c6c..5bebc8836 100644 --- a/specs/lightclient/beacon-chain.md +++ b/specs/lightclient/beacon-chain.md @@ -18,7 +18,7 @@ - [Time parameters](#time-parameters) - [Domain types](#domain-types) - [Containers](#containers) - - [Extended containers](#extended-containers) + - [Modified containers](#modified-containers) - [`BeaconBlockBody`](#beaconblockbody) - [`BeaconState`](#beaconstate) - [New containers](#new-containers) @@ -135,10 +135,7 @@ This patch updates a few configuration values to move penalty constants toward t ## Containers -### Extended containers - -*Note*: Extended SSZ containers inherit all fields from the parent in the original -order and append any additional fields to the end. +### Modified containers #### `BeaconBlockBody` @@ -195,7 +192,7 @@ class BeaconState(Container): current_sync_committee: SyncCommittee next_sync_committee: SyncCommittee # Leak - leak_scores: List[uint64, VALIDATOR_REGISTRY_LIMIT] + leak_scores: List[uint64, VALIDATOR_REGISTRY_LIMIT] # [New in HF1] ``` ### New containers diff --git a/specs/lightclient/lightclient-fork.md b/specs/lightclient/lightclient-fork.md index 157e67dc9..17a65793a 100644 --- a/specs/lightclient/lightclient-fork.md +++ b/specs/lightclient/lightclient-fork.md @@ -42,6 +42,7 @@ After `process_slots` of Phase 0 finishes, if `state.slot == LIGHTCLIENT_PATCH_F def upgrade_to_lightclient_patch(pre: phase0.BeaconState) -> BeaconState: epoch = get_current_epoch(pre) post = BeaconState( + # Versioning genesis_time=pre.genesis_time, genesis_validators_root=pre.genesis_validators_root, slot=pre.slot, @@ -67,13 +68,15 @@ def upgrade_to_lightclient_patch(pre: phase0.BeaconState) -> BeaconState: # Slashings slashings=pre.slashings, # Participation - previous_epoch_participation=[ParticipationFlags(0) for _ in range(len(pre.validators))], - current_epoch_participation=[ParticipationFlags(0) for _ in range(len(pre.validators))], + previous_epoch_participation=[ParticipationFlags(0b0000_0000) for _ in range(len(pre.validators))], + current_epoch_participation=[ParticipationFlags(0b0000_0000) for _ in range(len(pre.validators))], # Finality justification_bits=pre.justification_bits, previous_justified_checkpoint=pre.previous_justified_checkpoint, current_justified_checkpoint=pre.current_justified_checkpoint, finalized_checkpoint=pre.finalized_checkpoint, + # Leak + leak_scores=[0 for _ in range(len(pre.validators))], ) # Fill in sync committees post.current_sync_committee = get_sync_committee(post, get_current_epoch(post)) diff --git a/tests/core/pyspec/eth2spec/test/helpers/rewards.py b/tests/core/pyspec/eth2spec/test/helpers/rewards.py index 19ed3f691..a92a59458 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/rewards.py +++ b/tests/core/pyspec/eth2spec/test/helpers/rewards.py @@ -34,7 +34,8 @@ def run_deltas(spec, state): - source deltas ('source_deltas') - target deltas ('target_deltas') - head deltas ('head_deltas') - - inclusion delay deltas ('inclusion_delay_deltas') + - not if is_post_lightclient_patch(spec) + - inclusion delay deltas ('inclusion_delay_deltas') - inactivity penalty deltas ('inactivity_penalty_deltas') """ yield 'pre', state @@ -70,7 +71,8 @@ def run_deltas(spec, state): spec.get_matching_head_attestations, 'head_deltas', ) - yield from run_get_inclusion_delay_deltas(spec, state) + if not is_post_lightclient_patch(spec): + yield from run_get_inclusion_delay_deltas(spec, state) yield from run_get_inactivity_penalty_deltas(spec, state) @@ -219,7 +221,8 @@ def run_get_inactivity_penalty_deltas(spec, state): def transition_state_to_leak(spec, state, epochs=None): if epochs is None: - epochs = spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY + # +1 to trigger leak_score transitions + epochs = spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY + 1 assert epochs >= spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY for _ in range(epochs): diff --git a/tests/core/pyspec/eth2spec/test/lightclient_patch/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/lightclient_patch/sanity/test_blocks.py index 9033a0f15..3689c0783 100644 --- a/tests/core/pyspec/eth2spec/test/lightclient_patch/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/lightclient_patch/sanity/test_blocks.py @@ -2,9 +2,11 @@ import random from eth2spec.test.helpers.state import ( state_transition_and_sign_block, next_epoch, + next_epoch_via_block, ) from eth2spec.test.helpers.block import ( build_empty_block_for_next_slot, + build_empty_block, ) from eth2spec.test.helpers.sync_committee import ( compute_aggregate_sync_committee_signature, @@ -73,3 +75,25 @@ def test_half_sync_committee_committee_genesis(spec, state): @spec_state_test def test_empty_sync_committee_committee_genesis(spec, state): yield from run_sync_committee_sanity_test(spec, state, fraction_full=0.0) + + +@with_all_phases_except([PHASE0, PHASE1]) +@spec_state_test +def test_leak_scores(spec, state): + for _ in range(spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY + 2): + next_epoch_via_block(spec, state) + + assert spec.is_in_inactivity_leak(state) + previous_leak_scores = state.leak_scores.copy() + + yield 'pre', state + + # Block transition to next epoch + block = build_empty_block(spec, state, slot=state.slot + spec.SLOTS_PER_EPOCH) + signed_block = state_transition_and_sign_block(spec, state, block) + + yield 'blocks', [signed_block] + yield 'post', state + + for pre, post in zip(previous_leak_scores, state.leak_scores): + assert post == pre + spec.LEAK_SCORE_BIAS diff --git a/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attester_slashing.py b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attester_slashing.py index 21d9363f7..7345e62ba 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attester_slashing.py +++ b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attester_slashing.py @@ -1,5 +1,9 @@ +import random + from eth2spec.test.context import ( - spec_state_test, expect_assertion_error, always_bls, with_all_phases + spec_state_test, expect_assertion_error, always_bls, with_all_phases, + with_custom_state, spec_test, single_phase, + low_balances, misc_balances, ) from eth2spec.test.helpers.attestations import sign_indexed_attestation from eth2spec.test.helpers.attester_slashings import get_valid_attester_slashing, \ @@ -32,15 +36,19 @@ def run_attester_slashing_processing(spec, state, attester_slashing, valid=True) proposer_index = spec.get_beacon_proposer_index(state) pre_proposer_balance = get_balance(state, proposer_index) - pre_slashings = {slashed_index: get_balance(state, slashed_index) for slashed_index in slashed_indices} + pre_slashing_balances = {slashed_index: get_balance(state, slashed_index) for slashed_index in slashed_indices} + pre_slashing_effectives = { + slashed_index: state.validators[slashed_index].effective_balance + for slashed_index in slashed_indices + } pre_withdrawalable_epochs = { slashed_index: state.validators[slashed_index].withdrawable_epoch for slashed_index in slashed_indices } total_proposer_rewards = sum( - balance // spec.WHISTLEBLOWER_REWARD_QUOTIENT - for balance in pre_slashings.values() + effective_balance // spec.WHISTLEBLOWER_REWARD_QUOTIENT + for effective_balance in pre_slashing_effectives.values() ) # Process slashing @@ -61,7 +69,7 @@ def run_attester_slashing_processing(spec, state, attester_slashing, valid=True) assert slashed_validator.withdrawable_epoch == expected_withdrawable_epoch else: assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH - assert get_balance(state, slashed_index) < pre_slashings[slashed_index] + assert get_balance(state, slashed_index) < pre_slashing_balances[slashed_index] if proposer_index not in slashed_indices: # gained whistleblower reward @@ -71,7 +79,7 @@ def run_attester_slashing_processing(spec, state, attester_slashing, valid=True) expected_balance = ( pre_proposer_balance + total_proposer_rewards - - pre_slashings[proposer_index] // get_min_slashing_penalty_quotient(spec) + - pre_slashing_effectives[proposer_index] // get_min_slashing_penalty_quotient(spec) ) assert get_balance(state, proposer_index) == expected_balance @@ -118,6 +126,41 @@ def test_success_already_exited_recent(spec, state): yield from run_attester_slashing_processing(spec, state, attester_slashing) +@with_all_phases +@with_custom_state(balances_fn=low_balances, threshold_fn=lambda spec: spec.EJECTION_BALANCE) +@spec_test +@single_phase +def test_success_low_balances(spec, state): + attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True) + + yield from run_attester_slashing_processing(spec, state, attester_slashing) + + +@with_all_phases +@with_custom_state(balances_fn=misc_balances, threshold_fn=lambda spec: spec.EJECTION_BALANCE) +@spec_test +@single_phase +def test_success_misc_balances(spec, state): + attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True) + + yield from run_attester_slashing_processing(spec, state, attester_slashing) + + +@with_all_phases +@with_custom_state(balances_fn=misc_balances, threshold_fn=lambda spec: spec.EJECTION_BALANCE) +@spec_test +@single_phase +def test_success_with_effective_balance_disparity(spec, state): + # Jitter balances to be different from effective balances + for i in range(len(state.balances)): + pre = int(state.balances[i]) + state.balances[i] += random.randrange(max(pre - 5000, 0), pre + 5000) + + attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True) + + yield from run_attester_slashing_processing(spec, state, attester_slashing) + + @with_all_phases @spec_state_test @always_bls diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_effective_balance_updates.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_effective_balance_updates.py index 93411f657..dc4c047a2 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_effective_balance_updates.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_effective_balance_updates.py @@ -1,11 +1,5 @@ from eth2spec.test.context import spec_state_test, with_all_phases -from eth2spec.test.helpers.epoch_processing import ( - run_epoch_processing_with, run_epoch_processing_to -) - - -def run_process_effective_balance_updates(spec, state): - yield from run_epoch_processing_with(spec, state, 'process_effective_balance_updates') +from eth2spec.test.helpers.epoch_processing import run_epoch_processing_to @with_all_phases @@ -44,7 +38,9 @@ def test_effective_balance_hysteresis(spec, state): state.validators[i].effective_balance = pre_eff state.balances[i] = bal - yield from run_process_effective_balance_updates(spec, state) + yield 'pre', state + spec.process_effective_balance_updates(state) + yield 'post', state for i, (_, _, post_eff, name) in enumerate(cases): assert state.validators[i].effective_balance == post_eff, name diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_rewards_and_penalties.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_rewards_and_penalties.py index 7bb86b45e..2de054505 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_rewards_and_penalties.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_rewards_and_penalties.py @@ -27,6 +27,31 @@ def run_process_rewards_and_penalties(spec, state): yield from run_epoch_processing_with(spec, state, 'process_rewards_and_penalties') +def validate_resulting_balances(spec, pre_state, post_state, attestations): + attesting_indices = spec.get_unslashed_attesting_indices(post_state, attestations) + current_epoch = spec.get_current_epoch(post_state) + + for index in range(len(pre_state.validators)): + if not spec.is_active_validator(pre_state.validators[index], current_epoch): + assert post_state.balances[index] == pre_state.balances[index] + elif not is_post_lightclient_patch(spec): + proposer_indices = [a.proposer_index for a in post_state.previous_epoch_attestations] + if spec.is_in_inactivity_leak(post_state): + # Proposers can still make money during a leak before LIGHTCLIENT_PATCH + if index in proposer_indices and index in attesting_indices: + assert post_state.balances[index] > pre_state.balances[index] + elif index in attesting_indices: + # If not proposer but participated optimally, should have exactly neutral balance + assert post_state.balances[index] == pre_state.balances[index] + else: + assert post_state.balances[index] < pre_state.balances[index] + else: + if index in attesting_indices: + assert post_state.balances[index] > pre_state.balances[index] + else: + assert post_state.balances[index] < pre_state.balances[index] + + @with_all_phases @spec_state_test def test_genesis_epoch_no_attestations_no_penalties(spec, state): @@ -100,19 +125,10 @@ def test_full_attestations_misc_balances(spec, state): yield from run_process_rewards_and_penalties(spec, state) - attesting_indices = spec.get_unslashed_attesting_indices(state, attestations) - assert len(attesting_indices) > 0 - assert len(attesting_indices) != len(pre_state.validators) - assert any(v.effective_balance != spec.MAX_EFFECTIVE_BALANCE for v in state.validators) - for index in range(len(pre_state.validators)): - if index in attesting_indices: - assert state.balances[index] > pre_state.balances[index] - elif spec.is_active_validator(pre_state.validators[index], spec.compute_epoch_at_slot(state.slot)): - assert state.balances[index] < pre_state.balances[index] - else: - assert state.balances[index] == pre_state.balances[index] + validate_resulting_balances(spec, pre_state, state, attestations) # Check if base rewards are consistent with effective balance. brs = {} + attesting_indices = spec.get_unslashed_attesting_indices(state, attestations) for index in attesting_indices: br = spec.get_base_reward(state, index) if br in brs: @@ -146,8 +162,7 @@ def test_no_attestations_all_penalties(spec, state): yield from run_process_rewards_and_penalties(spec, state) - for index in range(len(pre_state.validators)): - assert state.balances[index] < pre_state.balances[index] + validate_resulting_balances(spec, pre_state, state, []) def run_with_participation(spec, state, participation_fn): @@ -161,35 +176,12 @@ def run_with_participation(spec, state, participation_fn): attestations = prepare_state_with_attestations(spec, state, participation_fn=participation_tracker) pre_state = state.copy() - if not is_post_lightclient_patch(spec): - proposer_indices = [a.proposer_index for a in state.previous_epoch_attestations] - else: - sync_committee_indices = spec.get_sync_committee_indices(state, spec.get_current_epoch(state)) - yield from run_process_rewards_and_penalties(spec, state) attesting_indices = spec.get_unslashed_attesting_indices(state, attestations) assert len(attesting_indices) == len(participated) - for index in range(len(pre_state.validators)): - if spec.is_in_inactivity_leak(state): - # Proposers can still make money during a leak before LIGHTCLIENT_PATCH - if not is_post_lightclient_patch(spec) and index in proposer_indices and index in participated: - assert state.balances[index] > pre_state.balances[index] - elif index in attesting_indices: - if is_post_lightclient_patch(spec) and index in sync_committee_indices: - # The sync committee reward has not been canceled, so the sync committee participants still earn it - assert state.balances[index] >= pre_state.balances[index] - else: - # If not proposer but participated optimally, should have exactly neutral balance - assert state.balances[index] == pre_state.balances[index] - else: - assert state.balances[index] < pre_state.balances[index] - else: - if index in participated: - assert state.balances[index] > pre_state.balances[index] - else: - assert state.balances[index] < pre_state.balances[index] + validate_resulting_balances(spec, pre_state, state, attestations) @with_all_phases @@ -438,10 +430,4 @@ def test_attestations_some_slashed(spec, state): attesting_indices = spec.get_unslashed_attesting_indices(state, attestations) assert len(attesting_indices) > 0 assert len(attesting_indices_before_slashings) - len(attesting_indices) == spec.MIN_PER_EPOCH_CHURN_LIMIT - for index in range(len(pre_state.validators)): - if index in attesting_indices: - # non-slashed attester should gain reward - assert state.balances[index] > pre_state.balances[index] - else: - # Slashed non-proposer attester should have penalty - assert state.balances[index] < pre_state.balances[index] + validate_resulting_balances(spec, pre_state, state, attestations) From 37c49ffcdcc7eba5df5a62b5b885f9618468b616 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 10 Mar 2021 13:11:03 -0700 Subject: [PATCH 4/5] ensure rewards are tested properly post altair fork --- .../test_process_rewards_and_penalties.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_rewards_and_penalties.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_rewards_and_penalties.py index 2de054505..6d05a498e 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_rewards_and_penalties.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_rewards_and_penalties.py @@ -50,6 +50,18 @@ def validate_resulting_balances(spec, pre_state, post_state, attestations): assert post_state.balances[index] > pre_state.balances[index] else: assert post_state.balances[index] < pre_state.balances[index] + else: + if spec.is_in_inactivity_leak(post_state): + if index in attesting_indices: + # If not proposer but participated optimally, should have exactly neutral balance + assert post_state.balances[index] == pre_state.balances[index] + else: + assert post_state.balances[index] < pre_state.balances[index] + else: + if index in attesting_indices: + assert post_state.balances[index] > pre_state.balances[index] + else: + assert post_state.balances[index] < pre_state.balances[index] @with_all_phases From b2a172ab21ec8cde863f7a06c1078063af994ec4 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 10 Mar 2021 13:12:48 -0700 Subject: [PATCH 5/5] @hwwhww review Co-authored-by: Hsiao-Wei Wang --- specs/lightclient/beacon-chain.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/lightclient/beacon-chain.md b/specs/lightclient/beacon-chain.md index 5bebc8836..040d00eae 100644 --- a/specs/lightclient/beacon-chain.md +++ b/specs/lightclient/beacon-chain.md @@ -189,8 +189,8 @@ class BeaconState(Container): current_justified_checkpoint: Checkpoint finalized_checkpoint: Checkpoint # Light client sync committees - current_sync_committee: SyncCommittee - next_sync_committee: SyncCommittee + current_sync_committee: SyncCommittee # [New in HF1] + next_sync_committee: SyncCommittee # [New in HF1] # Leak leak_scores: List[uint64, VALIDATOR_REGISTRY_LIMIT] # [New in HF1] ```