Merge pull request #2222 from ethereum/leak-scores

independent leak score feature
This commit is contained in:
Danny Ryan 2021-03-11 14:27:20 -07:00 committed by GitHub
commit ea9f3f184f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 189 additions and 98 deletions

View File

@ -18,6 +18,8 @@ ALTAIR_PROPORTIONAL_SLASHING_MULTIPLIER: 2
SYNC_COMMITTEE_SIZE: 1024 SYNC_COMMITTEE_SIZE: 1024
# 2**6 (=64) # 2**6 (=64)
SYNC_SUBCOMMITTEE_SIZE: 64 SYNC_SUBCOMMITTEE_SIZE: 64
# 2**2 (=4)
LEAK_SCORE_BIAS: 4
# Time parameters # Time parameters

View File

@ -18,6 +18,8 @@ ALTAIR_PROPORTIONAL_SLASHING_MULTIPLIER: 2
SYNC_COMMITTEE_SIZE: 32 SYNC_COMMITTEE_SIZE: 32
# [customized] # [customized]
SYNC_SUBCOMMITTEE_SIZE: 16 SYNC_SUBCOMMITTEE_SIZE: 16
# 2**2 (=4)
LEAK_SCORE_BIAS: 4
# Time parameters # Time parameters

View File

@ -18,7 +18,7 @@
- [Time parameters](#time-parameters) - [Time parameters](#time-parameters)
- [Domain types](#domain-types) - [Domain types](#domain-types)
- [Containers](#containers) - [Containers](#containers)
- [Extended containers](#extended-containers) - [Modified containers](#modified-containers)
- [`BeaconBlockBody`](#beaconblockbody) - [`BeaconBlockBody`](#beaconblockbody)
- [`BeaconState`](#beaconstate) - [`BeaconState`](#beaconstate)
- [New containers](#new-containers) - [New containers](#new-containers)
@ -41,10 +41,11 @@
- [New `slash_validator`](#new-slash_validator) - [New `slash_validator`](#new-slash_validator)
- [Block processing](#block-processing) - [Block processing](#block-processing)
- [Modified `process_attestation`](#modified-process_attestation) - [Modified `process_attestation`](#modified-process_attestation)
- [New `process_deposit`](#new-process_deposit) - [Modified `process_deposit`](#modified-process_deposit)
- [Sync committee processing](#sync-committee-processing) - [Sync committee processing](#sync-committee-processing)
- [Epoch processing](#epoch-processing) - [Epoch processing](#epoch-processing)
- [Justification and finalization](#justification-and-finalization) - [Justification and finalization](#justification-and-finalization)
- [Leak scores](#leak-scores)
- [Rewards and penalties](#rewards-and-penalties) - [Rewards and penalties](#rewards-and-penalties)
- [Slashings](#slashings) - [Slashings](#slashings)
- [Participation flags updates](#participation-flags-updates) - [Participation flags updates](#participation-flags-updates)
@ -118,6 +119,7 @@ This patch updates a few configuration values to move penalty constants toward t
| - | - | | - | - |
| `SYNC_COMMITTEE_SIZE` | `uint64(2**10)` (= 1,024) | | `SYNC_COMMITTEE_SIZE` | `uint64(2**10)` (= 1,024) |
| `SYNC_SUBCOMMITTEE_SIZE` | `uint64(2**6)` (= 64) | | `SYNC_SUBCOMMITTEE_SIZE` | `uint64(2**6)` (= 64) |
| `LEAK_SCORE_BIAS` | 4 |
### Time parameters ### Time parameters
@ -133,10 +135,7 @@ This patch updates a few configuration values to move penalty constants toward t
## Containers ## Containers
### Extended containers ### Modified containers
*Note*: Extended SSZ containers inherit all fields from the parent in the original
order and append any additional fields to the end.
#### `BeaconBlockBody` #### `BeaconBlockBody`
@ -182,16 +181,18 @@ class BeaconState(Container):
# Slashings # Slashings
slashings: Vector[Gwei, EPOCHS_PER_SLASHINGS_VECTOR] # Per-epoch sums of slashed effective balances slashings: Vector[Gwei, EPOCHS_PER_SLASHINGS_VECTOR] # Per-epoch sums of slashed effective balances
# Participation # Participation
previous_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] previous_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] # [New in Altair]
current_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] current_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] # [New in Altair]
# Finality # Finality
justification_bits: Bitvector[JUSTIFICATION_BITS_LENGTH] # Bit set for every recent justified epoch justification_bits: Bitvector[JUSTIFICATION_BITS_LENGTH] # Bit set for every recent justified epoch
previous_justified_checkpoint: Checkpoint previous_justified_checkpoint: Checkpoint
current_justified_checkpoint: Checkpoint current_justified_checkpoint: Checkpoint
finalized_checkpoint: Checkpoint finalized_checkpoint: Checkpoint
# Light client sync committees # Light client sync committees
current_sync_committee: SyncCommittee current_sync_committee: SyncCommittee # [New in Altair]
next_sync_committee: SyncCommittee next_sync_committee: SyncCommittee # [New in Altair]
# Leak
leak_scores: List[uint64, VALIDATOR_REGISTRY_LIMIT] # [New in Altair]
``` ```
### New containers ### New containers
@ -284,13 +285,10 @@ def get_sync_committee(state: BeaconState, epoch: Epoch) -> SyncCommittee:
Return the sync committee for a given state and epoch. Return the sync committee for a given state and epoch.
""" """
indices = get_sync_committee_indices(state, epoch) indices = get_sync_committee_indices(state, epoch)
validators = [state.validators[index] for index in indices] pubkeys = [state.validators[index].pubkey for index in indices]
pubkeys = [validator.pubkey for validator in validators] subcommitees = [pubkeys[i:i + SYNC_SUBCOMMITTEE_SIZE] for i in range(0, len(pubkeys), SYNC_SUBCOMMITTEE_SIZE)]
aggregates = [ pubkey_aggregates = [bls.AggregatePKs(subcommitee) for subcommitee in subcommitees]
bls.AggregatePKs(pubkeys[i:i + SYNC_SUBCOMMITTEE_SIZE]) return SyncCommittee(pubkeys=pubkeys, pubkey_aggregates=pubkey_aggregates)
for i in range(0, len(pubkeys), SYNC_SUBCOMMITTEE_SIZE)
]
return SyncCommittee(pubkeys=pubkeys, pubkey_aggregates=aggregates)
``` ```
#### `get_base_reward` #### `get_base_reward`
@ -366,8 +364,12 @@ 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 flags to determine who participated and who did not, applying the leak penalty globally and applying
compensatory rewards to participants. compensatory rewards to participants.
""" """
rewards = [Gwei(0) for _ in range(len(state.validators))]
penalties = [Gwei(0) for _ in range(len(state.validators))] penalties = [Gwei(0) for _ in range(len(state.validators))]
if is_in_inactivity_leak(state):
if not is_in_inactivity_leak(state):
return rewards, penalties
reward_numerator_sum = sum(numerator for (_, numerator) in get_flag_indices_and_numerators()) reward_numerator_sum = sum(numerator for (_, numerator) in get_flag_indices_and_numerators())
matching_target_attesting_indices = get_unslashed_participating_indices( matching_target_attesting_indices = get_unslashed_participating_indices(
state, TIMELY_TARGET_FLAG_INDEX, get_previous_epoch(state) state, TIMELY_TARGET_FLAG_INDEX, get_previous_epoch(state)
@ -375,14 +377,13 @@ def get_inactivity_penalty_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], S
for index in get_eligible_validator_indices(state): for index in get_eligible_validator_indices(state):
# If validator is performing optimally this cancels all attestation rewards for a neutral balance # 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) penalties[index] += Gwei(get_base_reward(state, index) * reward_numerator_sum // FLAG_DENOMINATOR)
if index not in matching_target_attesting_indices: if index not in matching_target_attesting_indices and state.leak_scores[index] >= LEAK_SCORE_BIAS:
effective_balance = state.validators[index].effective_balance effective_balance = state.validators[index].effective_balance
penalties[index] += Gwei( leak_penalty = Gwei(
effective_balance * get_finality_delay(state) effective_balance * state.leak_scores[index] // LEAK_SCORE_BIAS // ALTAIR_INACTIVITY_PENALTY_QUOTIENT
// ALTAIR_INACTIVITY_PENALTY_QUOTIENT
) )
penalties[index] += leak_penalty
rewards = [Gwei(0) for _ in range(len(state.validators))]
return rewards, penalties return rewards, penalties
``` ```
@ -483,9 +484,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 ```python
def process_deposit(state: BeaconState, deposit: Deposit) -> None: def process_deposit(state: BeaconState, deposit: Deposit) -> None:
@ -521,6 +522,7 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None:
state.balances.append(amount) state.balances.append(amount)
state.previous_epoch_participation.append(ParticipationFlags(0b0000_0000)) # [New in Altair] state.previous_epoch_participation.append(ParticipationFlags(0b0000_0000)) # [New in Altair]
state.current_epoch_participation.append(ParticipationFlags(0b0000_0000)) # [New in Altair] state.current_epoch_participation.append(ParticipationFlags(0b0000_0000)) # [New in Altair]
state.leak_scores.append(0) # [New in Altair]
else: else:
# Increase balance by deposit amount # Increase balance by deposit amount
index = ValidatorIndex(validator_pubkeys.index(pubkey)) index = ValidatorIndex(validator_pubkeys.index(pubkey))
@ -561,6 +563,7 @@ def process_sync_committee(state: BeaconState, body: BeaconBlockBody) -> None:
```python ```python
def process_epoch(state: BeaconState) -> None: def process_epoch(state: BeaconState) -> None:
process_justification_and_finalization(state) # [Modified in Altair] process_justification_and_finalization(state) # [Modified in Altair]
process_leak_updates(state) # [New in Altair]
process_rewards_and_penalties(state) # [Modified in Altair] process_rewards_and_penalties(state) # [Modified in Altair]
process_registry_updates(state) process_registry_updates(state)
process_slashings(state) # [Modified in Altair] process_slashings(state) # [Modified in Altair]
@ -619,6 +622,23 @@ def process_justification_and_finalization(state: BeaconState) -> None:
state.finalized_checkpoint = old_current_justified_checkpoint state.finalized_checkpoint = old_current_justified_checkpoint
``` ```
#### Leak scores
*Note*: The function `process_leak_updates` is new.
```python
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_scores[index] > 0:
state.leak_scores[index] -= 1
elif is_in_inactivity_leak(state):
state.leak_scores[index] += LEAK_SCORE_BIAS
```
#### Rewards and penalties #### Rewards and penalties
*Note*: The function `process_rewards_and_penalties` is modified to support the incentive reforms. *Note*: The function `process_rewards_and_penalties` is modified to support the incentive reforms.

View File

@ -42,6 +42,7 @@ After `process_slots` of Phase 0 finishes, if `state.slot == ALTAIR_FORK_SLOT`,
def upgrade_to_altair(pre: phase0.BeaconState) -> BeaconState: def upgrade_to_altair(pre: phase0.BeaconState) -> BeaconState:
epoch = get_current_epoch(pre) epoch = get_current_epoch(pre)
post = BeaconState( post = BeaconState(
# Versioning
genesis_time=pre.genesis_time, genesis_time=pre.genesis_time,
genesis_validators_root=pre.genesis_validators_root, genesis_validators_root=pre.genesis_validators_root,
slot=pre.slot, slot=pre.slot,
@ -67,13 +68,15 @@ def upgrade_to_altair(pre: phase0.BeaconState) -> BeaconState:
# Slashings # Slashings
slashings=pre.slashings, slashings=pre.slashings,
# Participation # Participation
previous_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(0) for _ in range(len(pre.validators))], current_epoch_participation=[ParticipationFlags(0b0000_0000) for _ in range(len(pre.validators))],
# Finality # Finality
justification_bits=pre.justification_bits, justification_bits=pre.justification_bits,
previous_justified_checkpoint=pre.previous_justified_checkpoint, previous_justified_checkpoint=pre.previous_justified_checkpoint,
current_justified_checkpoint=pre.current_justified_checkpoint, current_justified_checkpoint=pre.current_justified_checkpoint,
finalized_checkpoint=pre.finalized_checkpoint, finalized_checkpoint=pre.finalized_checkpoint,
# Leak
leak_scores=[0 for _ in range(len(pre.validators))],
) )
# Fill in sync committees # Fill in sync committees
post.current_sync_committee = get_sync_committee(post, get_current_epoch(post)) post.current_sync_committee = get_sync_committee(post, get_current_epoch(post))

View File

@ -2,9 +2,11 @@ import random
from eth2spec.test.helpers.state import ( from eth2spec.test.helpers.state import (
state_transition_and_sign_block, state_transition_and_sign_block,
next_epoch, next_epoch,
next_epoch_via_block,
) )
from eth2spec.test.helpers.block import ( from eth2spec.test.helpers.block import (
build_empty_block_for_next_slot, build_empty_block_for_next_slot,
build_empty_block,
) )
from eth2spec.test.helpers.sync_committee import ( from eth2spec.test.helpers.sync_committee import (
compute_aggregate_sync_committee_signature, compute_aggregate_sync_committee_signature,
@ -73,3 +75,25 @@ def test_half_sync_committee_committee_genesis(spec, state):
@spec_state_test @spec_state_test
def test_empty_sync_committee_committee_genesis(spec, state): def test_empty_sync_committee_committee_genesis(spec, state):
yield from run_sync_committee_sanity_test(spec, state, fraction_full=0.0) 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

View File

@ -34,6 +34,7 @@ def run_deltas(spec, state):
- source deltas ('source_deltas') - source deltas ('source_deltas')
- target deltas ('target_deltas') - target deltas ('target_deltas')
- head deltas ('head_deltas') - head deltas ('head_deltas')
- not if is_post_altair(spec)
- inclusion delay deltas ('inclusion_delay_deltas') - inclusion delay deltas ('inclusion_delay_deltas')
- inactivity penalty deltas ('inactivity_penalty_deltas') - inactivity penalty deltas ('inactivity_penalty_deltas')
""" """
@ -70,6 +71,7 @@ def run_deltas(spec, state):
spec.get_matching_head_attestations, spec.get_matching_head_attestations,
'head_deltas', 'head_deltas',
) )
if not is_post_altair(spec):
yield from run_get_inclusion_delay_deltas(spec, state) yield from run_get_inclusion_delay_deltas(spec, state)
yield from run_get_inactivity_penalty_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): def transition_state_to_leak(spec, state, epochs=None):
if epochs is 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 assert epochs >= spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY
for _ in range(epochs): for _ in range(epochs):

View File

@ -1,5 +1,9 @@
import random
from eth2spec.test.context import ( 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.attestations import sign_indexed_attestation
from eth2spec.test.helpers.attester_slashings import get_valid_attester_slashing, \ 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) proposer_index = spec.get_beacon_proposer_index(state)
pre_proposer_balance = get_balance(state, proposer_index) 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 = { pre_withdrawalable_epochs = {
slashed_index: state.validators[slashed_index].withdrawable_epoch slashed_index: state.validators[slashed_index].withdrawable_epoch
for slashed_index in slashed_indices for slashed_index in slashed_indices
} }
total_proposer_rewards = sum( total_proposer_rewards = sum(
balance // spec.WHISTLEBLOWER_REWARD_QUOTIENT effective_balance // spec.WHISTLEBLOWER_REWARD_QUOTIENT
for balance in pre_slashings.values() for effective_balance in pre_slashing_effectives.values()
) )
# Process slashing # 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 assert slashed_validator.withdrawable_epoch == expected_withdrawable_epoch
else: else:
assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH 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: if proposer_index not in slashed_indices:
# gained whistleblower reward # gained whistleblower reward
@ -71,7 +79,7 @@ def run_attester_slashing_processing(spec, state, attester_slashing, valid=True)
expected_balance = ( expected_balance = (
pre_proposer_balance pre_proposer_balance
+ total_proposer_rewards + 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 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) 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 @with_all_phases
@spec_state_test @spec_state_test
@always_bls @always_bls

View File

@ -1,11 +1,5 @@
from eth2spec.test.context import spec_state_test, with_all_phases from eth2spec.test.context import spec_state_test, with_all_phases
from eth2spec.test.helpers.epoch_processing import ( from eth2spec.test.helpers.epoch_processing import run_epoch_processing_to
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')
@with_all_phases @with_all_phases
@ -44,7 +38,9 @@ def test_effective_balance_hysteresis(spec, state):
state.validators[i].effective_balance = pre_eff state.validators[i].effective_balance = pre_eff
state.balances[i] = bal 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): for i, (_, _, post_eff, name) in enumerate(cases):
assert state.validators[i].effective_balance == post_eff, name assert state.validators[i].effective_balance == post_eff, name

View File

@ -27,6 +27,43 @@ def run_process_rewards_and_penalties(spec, state):
yield from run_epoch_processing_with(spec, state, 'process_rewards_and_penalties') 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_altair(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]
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 @with_all_phases
@spec_state_test @spec_state_test
def test_genesis_epoch_no_attestations_no_penalties(spec, state): def test_genesis_epoch_no_attestations_no_penalties(spec, state):
@ -100,19 +137,10 @@ def test_full_attestations_misc_balances(spec, state):
yield from run_process_rewards_and_penalties(spec, state) yield from run_process_rewards_and_penalties(spec, state)
attesting_indices = spec.get_unslashed_attesting_indices(state, attestations) validate_resulting_balances(spec, pre_state, 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]
# Check if base rewards are consistent with effective balance. # Check if base rewards are consistent with effective balance.
brs = {} brs = {}
attesting_indices = spec.get_unslashed_attesting_indices(state, attestations)
for index in attesting_indices: for index in attesting_indices:
br = spec.get_base_reward(state, index) br = spec.get_base_reward(state, index)
if br in brs: if br in brs:
@ -146,8 +174,7 @@ def test_no_attestations_all_penalties(spec, state):
yield from run_process_rewards_and_penalties(spec, state) yield from run_process_rewards_and_penalties(spec, state)
for index in range(len(pre_state.validators)): validate_resulting_balances(spec, pre_state, state, [])
assert state.balances[index] < pre_state.balances[index]
def run_with_participation(spec, state, participation_fn): def run_with_participation(spec, state, participation_fn):
@ -161,35 +188,12 @@ def run_with_participation(spec, state, participation_fn):
attestations = prepare_state_with_attestations(spec, state, participation_fn=participation_tracker) attestations = prepare_state_with_attestations(spec, state, participation_fn=participation_tracker)
pre_state = state.copy() pre_state = state.copy()
if not is_post_altair(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) yield from run_process_rewards_and_penalties(spec, state)
attesting_indices = spec.get_unslashed_attesting_indices(state, attestations) attesting_indices = spec.get_unslashed_attesting_indices(state, attestations)
assert len(attesting_indices) == len(participated) assert len(attesting_indices) == len(participated)
for index in range(len(pre_state.validators)): validate_resulting_balances(spec, pre_state, state, attestations)
if spec.is_in_inactivity_leak(state):
# Proposers can still make money during a leak before ALTAIR
if not is_post_altair(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_altair(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]
@with_all_phases @with_all_phases
@ -438,10 +442,4 @@ def test_attestations_some_slashed(spec, state):
attesting_indices = spec.get_unslashed_attesting_indices(state, attestations) attesting_indices = spec.get_unslashed_attesting_indices(state, attestations)
assert len(attesting_indices) > 0 assert len(attesting_indices) > 0
assert len(attesting_indices_before_slashings) - len(attesting_indices) == spec.MIN_PER_EPOCH_CHURN_LIMIT assert len(attesting_indices_before_slashings) - len(attesting_indices) == spec.MIN_PER_EPOCH_CHURN_LIMIT
for index in range(len(pre_state.validators)): validate_resulting_balances(spec, pre_state, state, attestations)
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]