mirror of
https://github.com/status-im/eth2.0-specs.git
synced 2025-01-31 21:05:24 +00:00
Slashing penalty calculation change (#1217)
If the exit queue is very long, then a validator may take many months to exit. With the code as currently written, however, self-slashing is a potentially lucrative route to get one's money out faster, because one can exit in 36 days. This PR changes it so that slashing can only extend your withdrawal time, not contract it. Also, instead of the slashed balances used to calculate one's slashing penalty being those in `[withdrawal - 54 days ... withdrawal - 18 days]`, we now run the penalization algorithm once every 36 days that a validator is slashed but not withdrawn, so that it covers the 36-day period where the validator was actually slashed. It also moves the minimum slashing penalty to the `slash_validator` function so that it is only applied once. We also simplify the `slashed_balances` logic to be per-epoch.
This commit is contained in:
parent
0ba933e088
commit
c764202a57
@ -76,7 +76,7 @@ MIN_EPOCHS_TO_INACTIVITY_PENALTY: 4
|
||||
# 2**16 (= 65,536) epochs ~0.8 years
|
||||
EPOCHS_PER_HISTORICAL_VECTOR: 65536
|
||||
# 2**13 (= 8,192) epochs ~36 days
|
||||
EPOCHS_PER_SLASHED_BALANCES_VECTOR: 8192
|
||||
EPOCHS_PER_SLASHINGS_VECTOR: 8192
|
||||
# 2**24 (= 16,777,216) historical roots, ~26,131 years
|
||||
HISTORICAL_ROOTS_LIMIT: 16777216
|
||||
# 2**40 (= 1,099,511,627,776) validator spots
|
||||
@ -88,7 +88,7 @@ VALIDATOR_REGISTRY_LIMIT: 1099511627776
|
||||
# 2**5 (= 32)
|
||||
BASE_REWARD_FACTOR: 32
|
||||
# 2**9 (= 512)
|
||||
WHISTLEBLOWING_REWARD_QUOTIENT: 512
|
||||
WHISTLEBLOWER_REWARD_QUOTIENT: 512
|
||||
# 2**3 (= 8)
|
||||
PROPOSER_REWARD_QUOTIENT: 8
|
||||
# 2**25 (= 33,554,432)
|
||||
|
@ -77,7 +77,7 @@ EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 4096
|
||||
# [customized] smaller state
|
||||
EPOCHS_PER_HISTORICAL_VECTOR: 64
|
||||
# [customized] smaller state
|
||||
EPOCHS_PER_SLASHED_BALANCES_VECTOR: 64
|
||||
EPOCHS_PER_SLASHINGS_VECTOR: 64
|
||||
# 2**24 (= 16,777,216) historical roots
|
||||
HISTORICAL_ROOTS_LIMIT: 16777216
|
||||
# 2**40 (= 1,099,511,627,776) validator spots
|
||||
@ -89,7 +89,7 @@ VALIDATOR_REGISTRY_LIMIT: 1099511627776
|
||||
# 2**5 (= 32)
|
||||
BASE_REWARD_FACTOR: 32
|
||||
# 2**9 (= 512)
|
||||
WHISTLEBLOWING_REWARD_QUOTIENT: 512
|
||||
WHISTLEBLOWER_REWARD_QUOTIENT: 512
|
||||
# 2**3 (= 8)
|
||||
PROPOSER_REWARD_QUOTIENT: 8
|
||||
# 2**25 (= 33,554,432)
|
||||
|
@ -234,7 +234,7 @@ The following values are (non-configurable) constants used throughout the specif
|
||||
| Name | Value | Unit | Duration |
|
||||
| - | - | :-: | :-: |
|
||||
| `EPOCHS_PER_HISTORICAL_VECTOR` | `2**16` (= 65,536) | epochs | ~0.8 years |
|
||||
| `EPOCHS_PER_SLASHED_BALANCES_VECTOR` | `2**13` (= 8,192) | epochs | ~36 days |
|
||||
| `EPOCHS_PER_SLASHINGS_VECTOR` | `2**13` (= 8,192) | epochs | ~36 days |
|
||||
| `HISTORICAL_ROOTS_LIMIT` | `2**24` (= 16,777,216) | historical roots | ~26,131 years |
|
||||
| `VALIDATOR_REGISTRY_LIMIT` | `2**40` (= 1,099,511,627,776) | validator spots | |
|
||||
|
||||
@ -243,7 +243,7 @@ The following values are (non-configurable) constants used throughout the specif
|
||||
| Name | Value |
|
||||
| - | - |
|
||||
| `BASE_REWARD_FACTOR` | `2**6` (= 64) |
|
||||
| `WHISTLEBLOWING_REWARD_QUOTIENT` | `2**9` (= 512) |
|
||||
| `WHISTLEBLOWER_REWARD_QUOTIENT` | `2**9` (= 512) |
|
||||
| `PROPOSER_REWARD_QUOTIENT` | `2**3` (= 8) |
|
||||
| `INACTIVITY_PENALTY_QUOTIENT` | `2**25` (= 33,554,432) |
|
||||
| `MIN_SLASHING_PENALTY_QUOTIENT` | `2**5` (= 32) |
|
||||
@ -520,7 +520,7 @@ class BeaconState(Container):
|
||||
randao_mixes: Vector[Hash, EPOCHS_PER_HISTORICAL_VECTOR]
|
||||
active_index_roots: Vector[Hash, EPOCHS_PER_HISTORICAL_VECTOR] # Active registry digests for light clients
|
||||
# Slashings
|
||||
slashed_balances: Vector[Gwei, EPOCHS_PER_SLASHED_BALANCES_VECTOR] # Sums of slashed effective balances
|
||||
slashings: Vector[Gwei, EPOCHS_PER_SLASHINGS_VECTOR] # Per-epoch sums of slashed effective balances
|
||||
# Attestations
|
||||
previous_epoch_attestations: List[PendingAttestation, MAX_ATTESTATIONS * SLOTS_PER_EPOCH]
|
||||
current_epoch_attestations: List[PendingAttestation, MAX_ATTESTATIONS * SLOTS_PER_EPOCH]
|
||||
@ -1097,21 +1097,22 @@ def slash_validator(state: BeaconState,
|
||||
"""
|
||||
Slash the validator with index ``slashed_index``.
|
||||
"""
|
||||
current_epoch = get_current_epoch(state)
|
||||
epoch = get_current_epoch(state)
|
||||
initiate_validator_exit(state, slashed_index)
|
||||
state.validators[slashed_index].slashed = True
|
||||
state.validators[slashed_index].withdrawable_epoch = Epoch(current_epoch + EPOCHS_PER_SLASHED_BALANCES_VECTOR)
|
||||
slashed_balance = state.validators[slashed_index].effective_balance
|
||||
state.slashed_balances[current_epoch % EPOCHS_PER_SLASHED_BALANCES_VECTOR] += slashed_balance
|
||||
validator = state.validators[slashed_index]
|
||||
validator.slashed = True
|
||||
validator.withdrawable_epoch = max(validator.withdrawable_epoch, Epoch(epoch + EPOCHS_PER_SLASHINGS_VECTOR))
|
||||
state.slashings[epoch % EPOCHS_PER_SLASHINGS_VECTOR] += validator.effective_balance
|
||||
decrease_balance(state, slashed_index, validator.effective_balance // MIN_SLASHING_PENALTY_QUOTIENT)
|
||||
|
||||
# Apply proposer and whistleblower rewards
|
||||
proposer_index = get_beacon_proposer_index(state)
|
||||
if whistleblower_index is None:
|
||||
whistleblower_index = proposer_index
|
||||
whistleblowing_reward = Gwei(slashed_balance // WHISTLEBLOWING_REWARD_QUOTIENT)
|
||||
proposer_reward = Gwei(whistleblowing_reward // PROPOSER_REWARD_QUOTIENT)
|
||||
whistleblower_reward = Gwei(validator.effective_balance // WHISTLEBLOWER_REWARD_QUOTIENT)
|
||||
proposer_reward = Gwei(whistleblower_reward // PROPOSER_REWARD_QUOTIENT)
|
||||
increase_balance(state, proposer_index, proposer_reward)
|
||||
increase_balance(state, whistleblower_index, whistleblowing_reward - proposer_reward)
|
||||
decrease_balance(state, slashed_index, whistleblowing_reward)
|
||||
increase_balance(state, whistleblower_index, whistleblower_reward - proposer_reward)
|
||||
```
|
||||
|
||||
## Genesis
|
||||
@ -1493,18 +1494,9 @@ def process_registry_updates(state: BeaconState) -> None:
|
||||
def process_slashings(state: BeaconState) -> None:
|
||||
epoch = get_current_epoch(state)
|
||||
total_balance = get_total_active_balance(state)
|
||||
|
||||
# Compute slashed balances in the current epoch
|
||||
total_at_start = state.slashed_balances[(epoch + 1) % EPOCHS_PER_SLASHED_BALANCES_VECTOR]
|
||||
total_at_end = state.slashed_balances[epoch % EPOCHS_PER_SLASHED_BALANCES_VECTOR]
|
||||
total_penalties = total_at_end - total_at_start
|
||||
|
||||
for index, validator in enumerate(state.validators):
|
||||
if validator.slashed and epoch + EPOCHS_PER_SLASHED_BALANCES_VECTOR // 2 == validator.withdrawable_epoch:
|
||||
penalty = max(
|
||||
validator.effective_balance * min(total_penalties * 3, total_balance) // total_balance,
|
||||
validator.effective_balance // MIN_SLASHING_PENALTY_QUOTIENT
|
||||
)
|
||||
if validator.slashed and epoch + EPOCHS_PER_SLASHINGS_VECTOR // 2 == validator.withdrawable_epoch:
|
||||
penalty = validator.effective_balance * min(sum(state.slashings) * 3, total_balance) // total_balance
|
||||
decrease_balance(state, ValidatorIndex(index), penalty)
|
||||
```
|
||||
|
||||
@ -1532,10 +1524,8 @@ def process_final_updates(state: BeaconState) -> None:
|
||||
get_active_validator_indices(state, Epoch(next_epoch + ACTIVATION_EXIT_DELAY))
|
||||
)
|
||||
)
|
||||
# Set total slashed balances
|
||||
state.slashed_balances[next_epoch % EPOCHS_PER_SLASHED_BALANCES_VECTOR] = (
|
||||
state.slashed_balances[current_epoch % EPOCHS_PER_SLASHED_BALANCES_VECTOR]
|
||||
)
|
||||
# Reset slashings
|
||||
state.slashings[next_epoch % EPOCHS_PER_SLASHINGS_VECTOR] = Gwei(0)
|
||||
# Set randao mix
|
||||
state.randao_mixes[next_epoch % EPOCHS_PER_HISTORICAL_VECTOR] = get_randao_mix(state, current_epoch)
|
||||
# Set historical root accumulator
|
||||
|
@ -453,7 +453,7 @@ def process_early_derived_secret_reveal(state: BeaconState,
|
||||
# Apply penalty
|
||||
proposer_index = get_beacon_proposer_index(state)
|
||||
whistleblower_index = reveal.masker_index
|
||||
whistleblowing_reward = Gwei(penalty // WHISTLEBLOWING_REWARD_QUOTIENT)
|
||||
whistleblowing_reward = Gwei(penalty // WHISTLEBLOWER_REWARD_QUOTIENT)
|
||||
proposer_reward = Gwei(whistleblowing_reward // PROPOSER_REWARD_QUOTIENT)
|
||||
increase_balance(state, proposer_index, proposer_reward)
|
||||
increase_balance(state, whistleblower_index, whistleblowing_reward - proposer_reward)
|
||||
|
@ -25,31 +25,56 @@ def run_attester_slashing_processing(spec, state, attester_slashing, valid=True)
|
||||
yield 'post', None
|
||||
return
|
||||
|
||||
slashed_index = attester_slashing.attestation_1.custody_bit_0_indices[0]
|
||||
pre_slashed_balance = get_balance(state, slashed_index)
|
||||
slashed_indices = (
|
||||
attester_slashing.attestation_1.custody_bit_0_indices
|
||||
+ attester_slashing.attestation_1.custody_bit_1_indices
|
||||
)
|
||||
|
||||
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_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()
|
||||
)
|
||||
|
||||
# Process slashing
|
||||
spec.process_attester_slashing(state, attester_slashing)
|
||||
|
||||
slashed_validator = state.validators[slashed_index]
|
||||
for slashed_index in slashed_indices:
|
||||
pre_withdrawalable_epoch = pre_withdrawalable_epochs[slashed_index]
|
||||
slashed_validator = state.validators[slashed_index]
|
||||
|
||||
# Check slashing
|
||||
assert slashed_validator.slashed
|
||||
assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH
|
||||
assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH
|
||||
# Check slashing
|
||||
assert slashed_validator.slashed
|
||||
assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH
|
||||
if pre_withdrawalable_epoch < spec.FAR_FUTURE_EPOCH:
|
||||
expected_withdrawable_epoch = max(
|
||||
pre_withdrawalable_epoch,
|
||||
spec.get_current_epoch(state) + spec.EPOCHS_PER_SLASHINGS_VECTOR
|
||||
)
|
||||
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]
|
||||
|
||||
if slashed_index != proposer_index:
|
||||
# lost whistleblower reward
|
||||
assert get_balance(state, slashed_index) < pre_slashed_balance
|
||||
if proposer_index not in slashed_indices:
|
||||
# gained whistleblower reward
|
||||
assert get_balance(state, proposer_index) > pre_proposer_balance
|
||||
assert get_balance(state, proposer_index) == pre_proposer_balance + total_proposer_rewards
|
||||
else:
|
||||
# gained rewards for all slashings, which may include others. And only lost that of themselves.
|
||||
# Netto at least 0, if more people where slashed, a balance increase.
|
||||
assert get_balance(state, slashed_index) >= pre_slashed_balance
|
||||
expected_balance = (
|
||||
pre_proposer_balance
|
||||
+ total_proposer_rewards
|
||||
- pre_slashings[proposer_index] // spec.MIN_SLASHING_PENALTY_QUOTIENT
|
||||
)
|
||||
|
||||
assert get_balance(state, proposer_index) == expected_balance
|
||||
|
||||
yield 'post', state
|
||||
|
||||
@ -82,6 +107,37 @@ def test_success_surround(spec, state):
|
||||
yield from run_attester_slashing_processing(spec, state, attester_slashing)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@always_bls
|
||||
@spec_state_test
|
||||
def test_success_already_exited_recent(spec, state):
|
||||
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True)
|
||||
slashed_indices = (
|
||||
attester_slashing.attestation_1.custody_bit_0_indices
|
||||
+ attester_slashing.attestation_1.custody_bit_1_indices
|
||||
)
|
||||
for index in slashed_indices:
|
||||
spec.initiate_validator_exit(state, index)
|
||||
|
||||
yield from run_attester_slashing_processing(spec, state, attester_slashing)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@always_bls
|
||||
@spec_state_test
|
||||
def test_success_already_exited_long_ago(spec, state):
|
||||
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True)
|
||||
slashed_indices = (
|
||||
attester_slashing.attestation_1.custody_bit_0_indices
|
||||
+ attester_slashing.attestation_1.custody_bit_1_indices
|
||||
)
|
||||
for index in slashed_indices:
|
||||
spec.initiate_validator_exit(state, index)
|
||||
state.validators[index].withdrawable_epoch = spec.get_current_epoch(state) + 2
|
||||
|
||||
yield from run_attester_slashing_processing(spec, state, attester_slashing)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@always_bls
|
||||
@spec_state_test
|
||||
|
Loading…
x
Reference in New Issue
Block a user