Skip to content
This repository has been archived by the owner on Jul 1, 2021. It is now read-only.

Commit

Permalink
Add process_slashings (#362)
Browse files Browse the repository at this point in the history
* Add `process_slashings`

* PR feedback, thanks to NIC
  • Loading branch information
hwwhww authored Mar 7, 2019
1 parent 3896ae8 commit 2e239ef
Show file tree
Hide file tree
Showing 5 changed files with 224 additions and 1 deletion.
1 change: 1 addition & 0 deletions eth2/beacon/configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
('WHISTLEBLOWER_REWARD_QUOTIENT', int),
('ATTESTATION_INCLUSION_REWARD_QUOTIENT', int),
('INACTIVITY_PENALTY_QUOTIENT', int),
('MIN_PENALTY_QUOTIENT', int),
# Max operations per block
('MAX_PROPOSER_SLASHINGS', int),
('MAX_ATTESTER_SLASHINGS', int),
Expand Down
1 change: 1 addition & 0 deletions eth2/beacon/state_machines/forks/serenity/configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
WHISTLEBLOWER_REWARD_QUOTIENT=2**9, # (= 512)
ATTESTATION_INCLUSION_REWARD_QUOTIENT=2**3, # (= 8)
INACTIVITY_PENALTY_QUOTIENT=2**24, # (= 16,777,216)
MIN_PENALTY_QUOTIENT=2**5,
# Max operations per block
MAX_PROPOSER_SLASHINGS=2**4, # (= 16)
MAX_ATTESTER_SLASHINGS=2**0, # (= 1)
Expand Down
74 changes: 73 additions & 1 deletion eth2/beacon/state_machines/forks/serenity/epoch_processing.py
Original file line number Diff line number Diff line change
Expand Up @@ -1085,7 +1085,7 @@ def process_validator_registry(state: BeaconState,

state = validator_registry_transition(state, config)

# TODO: state = process_slashings(state, config)
state = process_slashings(state, config)

# TODO: state = process_exit_queue(state, config)

Expand Down Expand Up @@ -1130,6 +1130,78 @@ def _update_latest_active_index_roots(state: BeaconState,
)


def _compute_total_penalties(state: BeaconState,
config: BeaconConfig,
current_epoch: Epoch) -> Gwei:
epoch_index = current_epoch % config.LATEST_SLASHED_EXIT_LENGTH
start_index_in_latest_slashed_balances = (
(epoch_index + 1) % config.LATEST_SLASHED_EXIT_LENGTH
)
total_at_start = state.latest_slashed_balances[start_index_in_latest_slashed_balances]
total_at_end = state.latest_slashed_balances[epoch_index]
return Gwei(total_at_end - total_at_start)


def _compute_individual_penalty(state: BeaconState,
config: BeaconConfig,
validator_index: ValidatorIndex,
total_penalties: Gwei,
total_balance: Gwei) -> Gwei:
effective_balance = get_effective_balance(
state.validator_balances,
validator_index,
config.MAX_DEPOSIT_AMOUNT,
)
return Gwei(
max(
effective_balance * min(total_penalties * 3, total_balance) // total_balance,
effective_balance // config.MIN_PENALTY_QUOTIENT,
)
)


def process_slashings(state: BeaconState,
config: BeaconConfig) -> BeaconState:
"""
Process the slashings.
"""
latest_slashed_exit_length = config.LATEST_SLASHED_EXIT_LENGTH
max_deposit_amount = config.MAX_DEPOSIT_AMOUNT

current_epoch = state.current_epoch(config.SLOTS_PER_EPOCH)
active_validator_indices = get_active_validator_indices(state.validator_registry, current_epoch)
total_balance = Gwei(
sum(
get_effective_balance(state.validator_balances, i, max_deposit_amount)
for i in active_validator_indices
)
)
total_penalties = _compute_total_penalties(
state,
config,
current_epoch,
)

for validator_index, validator in enumerate(state.validator_registry):
validator_index = ValidatorIndex(validator_index)
is_halfway_to_withdrawable_epoch = (
current_epoch == validator.withdrawable_epoch - latest_slashed_exit_length // 2
)
if validator.slashed and is_halfway_to_withdrawable_epoch:
penalty = _compute_individual_penalty(
state=state,
config=config,
validator_index=validator_index,
total_penalties=total_penalties,
total_balance=total_balance,
)
state = state.update_validator_balance(
validator_index=validator_index,
balance=state.validator_balances[validator_index] - penalty,
)
return state


def process_final_updates(state: BeaconState,
config: BeaconConfig) -> BeaconState:
current_epoch = state.current_epoch(config.SLOTS_PER_EPOCH)
Expand Down
7 changes: 7 additions & 0 deletions tests/eth2/beacon/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,11 @@ def inactivity_penalty_quotient():
return SERENITY_CONFIG.INACTIVITY_PENALTY_QUOTIENT


@pytest.fixture
def min_penalty_quotient():
return SERENITY_CONFIG.MIN_PENALTY_QUOTIENT


@pytest.fixture
def max_proposer_slashings():
return SERENITY_CONFIG.MAX_PROPOSER_SLASHINGS
Expand Down Expand Up @@ -706,6 +711,7 @@ def config(
whistleblower_reward_quotient,
attestation_inclusion_reward_quotient,
inactivity_penalty_quotient,
min_penalty_quotient,
max_proposer_slashings,
max_attester_slashings,
max_attestations,
Expand Down Expand Up @@ -744,6 +750,7 @@ def config(
WHISTLEBLOWER_REWARD_QUOTIENT=whistleblower_reward_quotient,
ATTESTATION_INCLUSION_REWARD_QUOTIENT=attestation_inclusion_reward_quotient,
INACTIVITY_PENALTY_QUOTIENT=inactivity_penalty_quotient,
MIN_PENALTY_QUOTIENT=min_penalty_quotient,
MAX_PROPOSER_SLASHINGS=max_proposer_slashings,
MAX_ATTESTER_SLASHINGS=max_attester_slashings,
MAX_ATTESTATIONS=max_attestations,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@
from eth2.beacon.types.pending_attestation_records import PendingAttestationRecord
from eth2.beacon.state_machines.forks.serenity.epoch_processing import (
_check_if_update_validator_registry,
_compute_individual_penalty,
_compute_total_penalties,
_current_previous_epochs_justifiable,
_get_finalized_epoch,
_process_rewards_and_penalties_for_attestation_inclusion,
Expand All @@ -64,6 +66,7 @@
process_ejections,
process_final_updates,
process_justification,
process_slashings,
process_validator_registry,
update_validator_registry,
)
Expand Down Expand Up @@ -1145,6 +1148,145 @@ def mock_generate_seed(state,
assert result_state.current_shuffling_seed != new_seed


@pytest.mark.parametrize(
(
'slots_per_epoch',
'genesis_slot',
'current_epoch',
'latest_slashed_exit_length',
'latest_slashed_balances',
'expected_total_penalties',
),
[
(4, 8, 8, 8, (30, 10) + (0,) * 6, 30 - 10)
]
)
def test_compute_total_penalties(genesis_state,
config,
slots_per_epoch,
current_epoch,
latest_slashed_balances,
expected_total_penalties):
state = genesis_state.copy(
slot=get_epoch_start_slot(current_epoch, slots_per_epoch),
latest_slashed_balances=latest_slashed_balances,
)
total_penalties = _compute_total_penalties(
state,
config,
current_epoch,
)
assert total_penalties == expected_total_penalties


@pytest.mark.parametrize(
(
'num_validators',
'slots_per_epoch',
'genesis_slot',
'current_epoch',
'latest_slashed_exit_length',
),
[
(
10, 4, 8, 8, 8,
)
]
)
@pytest.mark.parametrize(
(
'total_penalties',
'total_balance',
'min_penalty_quotient',
'expected_penalty',
),
[
(
10**9, # 1 ETH
(32 * 10**9 * 10),
2**5,
# effective_balance // MIN_PENALTY_QUOTIENT,
32 * 10**9 // 2**5,
),
(
10**9, # 1 ETH
(32 * 10**9 * 10),
2**10, # Make MIN_PENALTY_QUOTIENT greater
# effective_balance * min(total_penalties * 3, total_balance) // total_balance,
32 * 10**9 * min(10**9 * 3, (32 * 10**9 * 10)) // (32 * 10**9 * 10),
),
]
)
def test_compute_individual_penalty(genesis_state,
config,
slots_per_epoch,
current_epoch,
latest_slashed_exit_length,
total_penalties,
total_balance,
expected_penalty):
state = genesis_state.copy(
slot=get_epoch_start_slot(current_epoch, slots_per_epoch),
)
validator_index = 0
penalty = _compute_individual_penalty(
state=state,
config=config,
validator_index=validator_index,
total_penalties=total_penalties,
total_balance=total_balance,
)
assert penalty == expected_penalty


@pytest.mark.parametrize(
(
'num_validators',
'slots_per_epoch',
'genesis_slot',
'current_epoch',
'latest_slashed_exit_length',
'latest_slashed_balances',
'expected_penalty',
),
[
(
10,
4,
8,
8,
8,
(2 * 10**9, 10**9) + (0,) * 6,
32 * 10**9 // 2**5,
),
]
)
def test_process_slashings(genesis_state,
config,
current_epoch,
latest_slashed_balances,
slots_per_epoch,
latest_slashed_exit_length,
expected_penalty):
state = genesis_state.copy(
slot=get_epoch_start_slot(current_epoch, slots_per_epoch),
latest_slashed_balances=latest_slashed_balances,
)
slashing_validator_index = 0
validator = state.validator_registry[slashing_validator_index].copy(
slashed=True,
withdrawable_epoch=current_epoch + latest_slashed_exit_length // 2
)
state = state.update_validator_registry(slashing_validator_index, validator)

result_state = process_slashings(state, config)
penalty = (
state.validator_balances[slashing_validator_index] -
result_state.validator_balances[slashing_validator_index]
)
assert penalty == expected_penalty


#
# Final updates
#
Expand Down

0 comments on commit 2e239ef

Please sign in to comment.