def process_attester_slashings(state: BeaconState, block: BaseBeaconBlock, config: Eth2Config) -> BeaconState: if len(block.body.attester_slashings) > config.MAX_ATTESTER_SLASHINGS: raise ValidationError( f"The block ({block}) has too many attester slashings:\n" f"\tFound {len(block.body.attester_slashings)} attester slashings, " f"maximum: {config.MAX_ATTESTER_SLASHINGS}") current_epoch = state.current_epoch(config.SLOTS_PER_EPOCH) for attester_slashing in block.body.attester_slashings: validate_attester_slashing( state, attester_slashing, config.MAX_VALIDATORS_PER_COMMITTEE, config.SLOTS_PER_EPOCH, ) slashed_any = False attestation_1 = attester_slashing.attestation_1 attestation_2 = attester_slashing.attestation_2 sorted_attesting_indices = sorted( set(attestation_1.attesting_indices).intersection( attestation_2.attesting_indices)) for index in sorted_attesting_indices: validator = state.validators[index] if validator.is_slashable(current_epoch): state = slash_validator(state, index, config) slashed_any = True validate_some_slashing(slashed_any, attester_slashing) return state
def process_attester_slashings(state: BeaconState, block: BaseBeaconBlock, config: Eth2Config) -> BeaconState: if len(block.body.attester_slashings) > config.MAX_ATTESTER_SLASHINGS: raise ValidationError( f"The block ({block}) has too many attester slashings:\n" f"\tFound {len(block.body.attester_slashings)} attester slashings, " f"maximum: {config.MAX_ATTESTER_SLASHINGS}" ) for attester_slashing in block.body.attester_slashings: validate_attester_slashing( state, attester_slashing, config.MAX_INDICES_PER_SLASHABLE_VOTE, config.SLOTS_PER_EPOCH, ) slashable_indices = _get_slashable_indices(state, config, attester_slashing) validate_slashable_indices(slashable_indices) for index in slashable_indices: state = slash_validator( state=state, index=index, latest_slashed_exit_length=config.LATEST_SLASHED_EXIT_LENGTH, whistleblower_reward_quotient=config.WHISTLEBLOWER_REWARD_QUOTIENT, max_deposit_amount=config.MAX_DEPOSIT_AMOUNT, committee_config=CommitteeConfig(config), ) return state
def test_slash_validator(monkeypatch, num_validators, committee, n_validators_state, genesis_epoch, slots_per_epoch, latest_slashed_exit_length, whistleblower_reward_quotient, activation_exit_delay, max_deposit_amount, target_committee_size, shard_count, committee_config): from eth2.beacon import committee_helpers def mock_get_crosslink_committees_at_slot(state, slot, committee_config, registry_change=False): return (( committee, 1, ), ) monkeypatch.setattr(committee_helpers, 'get_crosslink_committees_at_slot', mock_get_crosslink_committees_at_slot) state = n_validators_state index = 1 result_state = slash_validator( state=state, index=index, latest_slashed_exit_length=latest_slashed_exit_length, whistleblower_reward_quotient=whistleblower_reward_quotient, max_deposit_amount=max_deposit_amount, committee_config=committee_config, ) # Just check if `prepare_validator_for_withdrawal` applied these two functions expected_state = exit_validator(state, index, slots_per_epoch, activation_exit_delay) expected_state = _settle_penality_to_validator_and_whistleblower( state=expected_state, validator_index=index, latest_slashed_exit_length=latest_slashed_exit_length, whistleblower_reward_quotient=whistleblower_reward_quotient, max_deposit_amount=max_deposit_amount, committee_config=committee_config, ) current_epoch = state.current_epoch(slots_per_epoch) validator = state.validator_registry[index].copy( slashed=False, withdrawable_epoch=current_epoch + latest_slashed_exit_length, ) expected_state.update_validator_registry(index, validator) assert result_state == expected_state
def process_proposer_slashings( state: BeaconState, block: BaseBeaconBlock, config: Eth2Config ) -> BeaconState: if len(block.body.proposer_slashings) > config.MAX_PROPOSER_SLASHINGS: raise ValidationError( f"The block ({block}) has too many proposer slashings:\n" f"\tFound {len(block.body.proposer_slashings)} proposer slashings, " f"maximum: {config.MAX_PROPOSER_SLASHINGS}" ) for proposer_slashing in block.body.proposer_slashings: validate_proposer_slashing(state, proposer_slashing, config.SLOTS_PER_EPOCH) state = slash_validator(state, proposer_slashing.proposer_index, config) return state
def process_proposer_slashings(state: BeaconState, block: BaseBeaconBlock, config: BeaconConfig) -> BeaconState: if len(block.body.proposer_slashings) > config.MAX_PROPOSER_SLASHINGS: raise ValidationError( f"The block ({block}) has too many proposer slashings:\n" f"\tFound {len(block.body.proposer_slashings)} proposer slashings, " f"maximum: {config.MAX_PROPOSER_SLASHINGS}") for proposer_slashing in block.body.proposer_slashings: validate_proposer_slashing(state, proposer_slashing, config.SLOTS_PER_EPOCH) state = slash_validator( state=state, index=proposer_slashing.proposer_index, latest_slashed_exit_length=config.LATEST_SLASHED_EXIT_LENGTH, whistleblower_reward_quotient=config.WHISTLEBLOWER_REWARD_QUOTIENT, max_deposit_amount=config.MAX_DEPOSIT_AMOUNT, committee_config=CommitteeConfig(config), ) return state
def test_slash_validator(genesis_state, config): some_epoch = (config.GENESIS_EPOCH + random.randrange(1, 2**32) + config.EPOCHS_PER_SLASHINGS_VECTOR) earliest_slashable_epoch = some_epoch - config.EPOCHS_PER_SLASHINGS_VECTOR slashable_range = range(earliest_slashable_epoch, some_epoch) sampling_quotient = 4 state = genesis_state.copy(slot=compute_start_slot_at_epoch( earliest_slashable_epoch, config.SLOTS_PER_EPOCH)) validator_count_to_slash = len(state.validators) // sampling_quotient assert validator_count_to_slash > 1 validator_indices_to_slash = random.sample(range(len(state.validators)), validator_count_to_slash) # ensure case w/ one slashing in an epoch # by ignoring the first set_of_colluding_validators = validator_indices_to_slash[1:] # simulate multiple slashings in an epoch validators_grouped_by_coalition = groupby( lambda index: index % sampling_quotient, set_of_colluding_validators) coalition_count = len(validators_grouped_by_coalition) slashings = { epoch: coalition for coalition, epoch in zip( validators_grouped_by_coalition.values(), random.sample(slashable_range, coalition_count), ) } another_slashing_epoch = first(random.sample(slashable_range, 1)) while another_slashing_epoch in slashings: another_slashing_epoch += 1 slashings[another_slashing_epoch] = (validator_indices_to_slash[0], ) expected_slashings = {} expected_individual_penalties = {} for epoch, coalition in slashings.items(): for index in coalition: validator = state.validators[index] assert validator.is_active(earliest_slashable_epoch) assert validator.exit_epoch == FAR_FUTURE_EPOCH assert validator.withdrawable_epoch == FAR_FUTURE_EPOCH expected_slashings = update_in( expected_slashings, [epoch], lambda balance: balance + state.validators[index]. effective_balance, default=0, ) expected_individual_penalties = update_in( expected_individual_penalties, [index], lambda penalty: (penalty + (state.validators[index].effective_balance // config .MIN_SLASHING_PENALTY_QUOTIENT)), default=0, ) # emulate slashings across the current slashable range expected_proposer_rewards = {} for epoch, coalition in slashings.items(): state = state.copy( slot=compute_start_slot_at_epoch(epoch, config.SLOTS_PER_EPOCH)) expected_total_slashed_balance = expected_slashings[epoch] proposer_index = get_beacon_proposer_index(state, CommitteeConfig(config)) expected_proposer_rewards = update_in( expected_proposer_rewards, [proposer_index], lambda reward: reward + (expected_total_slashed_balance // config. WHISTLEBLOWER_REWARD_QUOTIENT), default=0, ) for index in coalition: state = slash_validator(state, index, config) state = state.copy( slot=compute_start_slot_at_epoch(some_epoch, config.SLOTS_PER_EPOCH)) # verify result for epoch, coalition in slashings.items(): for index in coalition: validator = state.validators[index] assert validator.exit_epoch != FAR_FUTURE_EPOCH assert validator.slashed assert validator.withdrawable_epoch == max( validator.exit_epoch + config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY, epoch + config.EPOCHS_PER_SLASHINGS_VECTOR, ) slashed_epoch_index = epoch % config.EPOCHS_PER_SLASHINGS_VECTOR slashed_balance = state.slashings[slashed_epoch_index] assert slashed_balance == expected_slashings[epoch] assert state.balances[index] == ( config.MAX_EFFECTIVE_BALANCE - expected_individual_penalties[index] + expected_proposer_rewards.get(index, 0)) for proposer_index, total_reward in expected_proposer_rewards.items(): assert state.balances[proposer_index] == ( total_reward + config.MAX_EFFECTIVE_BALANCE - expected_individual_penalties.get(proposer_index, 0))