Merge pull request #2236 from ethereum/justin_altair_cleanups

Altair cosmetic cleanups plus a couple substantive changes
This commit is contained in:
Danny Ryan 2021-03-15 08:57:35 -06:00 committed by GitHub
commit b8bf7f0f80
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 91 additions and 114 deletions

View File

@ -4,7 +4,7 @@ CONFIG_NAME: "mainnet"
# Updated penalty values # Updated penalty values
# --------------------------------------------------------------- # ---------------------------------------------------------------
# 3 * 2**24) (= 50,331,648) # 3 * 2**24 (= 50,331,648)
ALTAIR_INACTIVITY_PENALTY_QUOTIENT: 50331648 ALTAIR_INACTIVITY_PENALTY_QUOTIENT: 50331648
# 2**6 (= 64) # 2**6 (= 64)
ALTAIR_MIN_SLASHING_PENALTY_QUOTIENT: 64 ALTAIR_MIN_SLASHING_PENALTY_QUOTIENT: 64
@ -14,12 +14,12 @@ ALTAIR_PROPORTIONAL_SLASHING_MULTIPLIER: 2
# Misc # Misc
# --------------------------------------------------------------- # ---------------------------------------------------------------
# 2**10 (=1,024) # 2**10 (= 1,024)
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) # 2**2 (= 4)
LEAK_SCORE_BIAS: 4 INACTIVITY_SCORE_BIAS: 4
# Time parameters # Time parameters

View File

@ -4,7 +4,7 @@ CONFIG_NAME: "minimal"
# Updated penalty values # Updated penalty values
# --------------------------------------------------------------- # ---------------------------------------------------------------
# 3 * 2**24) (= 50,331,648) # 3 * 2**24 (= 50,331,648)
ALTAIR_INACTIVITY_PENALTY_QUOTIENT: 50331648 ALTAIR_INACTIVITY_PENALTY_QUOTIENT: 50331648
# 2**6 (= 64) # 2**6 (= 64)
ALTAIR_MIN_SLASHING_PENALTY_QUOTIENT: 64 ALTAIR_MIN_SLASHING_PENALTY_QUOTIENT: 64
@ -18,8 +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) # 2**2 (= 4)
LEAK_SCORE_BIAS: 4 INACTIVITY_SCORE_BIAS: 4
# Time parameters # Time parameters

View File

@ -35,17 +35,17 @@
- [`get_sync_committee`](#get_sync_committee) - [`get_sync_committee`](#get_sync_committee)
- [`get_base_reward`](#get_base_reward) - [`get_base_reward`](#get_base_reward)
- [`get_unslashed_participating_indices`](#get_unslashed_participating_indices) - [`get_unslashed_participating_indices`](#get_unslashed_participating_indices)
- [`get_flag_deltas`](#get_flag_deltas) - [`get_flag_index_deltas`](#get_flag_index_deltas)
- [New `get_inactivity_penalty_deltas`](#new-get_inactivity_penalty_deltas) - [Modified `get_inactivity_penalty_deltas`](#modified-get_inactivity_penalty_deltas)
- [Beacon state mutators](#beacon-state-mutators) - [Beacon state mutators](#beacon-state-mutators)
- [New `slash_validator`](#new-slash_validator) - [Modified `slash_validator`](#modified-slash_validator)
- [Block processing](#block-processing) - [Block processing](#block-processing)
- [Modified `process_attestation`](#modified-process_attestation) - [Modified `process_attestation`](#modified-process_attestation)
- [Modified `process_deposit`](#modified-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) - [Inactivity scores](#inactivity-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)
@ -56,20 +56,17 @@
## Introduction ## Introduction
Altair is a patch implementing the first hard fork to the beacon chain. Altair is the first beacon chain hard fork. Its main features are:
It has four main features:
* Light client support via sync committees * sync committees to support light clients
* Incentive accounting reforms, reducing spec complexity * incentive accounting reforms to reduce spec complexity
and [TODO] reducing the cost of processing chains that have very little or zero participation for a long span of epochs * penalty parameter updates to move them to their planned maximally punitive configuration
* Update penalty configuration values, moving them toward their planned maximally punitive configuration
* Fork choice rule changes to address weaknesses recently discovered in the existing fork choice
## Custom types ## Custom types
| Name | SSZ equivalent | Description | | Name | SSZ equivalent | Description |
| - | - | - | | - | - | - |
| `ParticipationFlags` | `uint8` | A succinct representation of 8 boolean participation flags | | `ParticipationFlags` | `uint8` | a succinct representation of 8 boolean participation flags |
## Constants ## Constants
@ -90,8 +87,7 @@ It has four main features:
| `TIMELY_TARGET_FLAG_NUMERATOR` | `32` | | `TIMELY_TARGET_FLAG_NUMERATOR` | `32` |
| `FLAG_DENOMINATOR` | `64` | | `FLAG_DENOMINATOR` | `64` |
**Note**: The participatition flag fractions add up to 7/8. **Note**: The sum of the participatition flag fractions (7/8) plus the proposer reward fraction (1/8) equals 1.
The remaining 1/8 is for proposer incentives and other future micro-incentives.
### Misc ### Misc
@ -103,14 +99,14 @@ The remaining 1/8 is for proposer incentives and other future micro-incentives.
### Updated penalty values ### Updated penalty values
This patch updates a few configuration values to move penalty constants toward their final, maxmium security values. This patch updates a few configuration values to move penalty parameters toward their final, maxmium security values.
*Note*: The spec does *not* override previous configuration values but instead creates new values and replaces usage throughout. *Note*: The spec does *not* override previous configuration values but instead creates new values and replaces usage throughout.
| Name | Value | | Name | Value |
| - | - | | - | - |
| `ALTAIR_INACTIVITY_PENALTY_QUOTIENT` | `uint64(3 * 2**24)` (= 50,331,648) | | `ALTAIR_INACTIVITY_PENALTY_QUOTIENT` | `uint64(3 * 2**24)` (= 50,331,648) |
| `ALTAIR_MIN_SLASHING_PENALTY_QUOTIENT` | `uint64(2**6)` (=64) | | `ALTAIR_MIN_SLASHING_PENALTY_QUOTIENT` | `uint64(2**6)` (= 64) |
| `ALTAIR_PROPORTIONAL_SLASHING_MULTIPLIER` | `uint64(2)` | | `ALTAIR_PROPORTIONAL_SLASHING_MULTIPLIER` | `uint64(2)` |
### Misc ### Misc
@ -119,7 +115,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 | | `INACTIVITY_SCORE_BIAS` | `uint64(4)` |
### Time parameters ### Time parameters
@ -181,18 +177,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] # [New in Altair] previous_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] # [Modified in Altair]
current_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] # [New in Altair] current_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] # [Modified 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 # Inactivity
inactivity_scores: List[uint64, VALIDATOR_REGISTRY_LIMIT] # [New in Altair]
# Sync
current_sync_committee: SyncCommittee # [New in Altair] current_sync_committee: SyncCommittee # [New in Altair]
next_sync_committee: SyncCommittee # [New in Altair] next_sync_committee: SyncCommittee # [New in Altair]
# Leak
leak_scores: List[uint64, VALIDATOR_REGISTRY_LIMIT] # [New in Altair]
``` ```
### New containers ### New containers
@ -307,7 +303,7 @@ def get_base_reward(state: BeaconState, index: ValidatorIndex) -> Gwei:
```python ```python
def get_unslashed_participating_indices(state: BeaconState, flag_index: int, epoch: Epoch) -> Set[ValidatorIndex]: def get_unslashed_participating_indices(state: BeaconState, flag_index: int, epoch: Epoch) -> Set[ValidatorIndex]:
""" """
Retrieve the active and unslashed validator indices for the given epoch and flag index. Return the active and unslashed validator indices for the given epoch and flag index.
""" """
assert epoch in (get_previous_epoch(state), get_current_epoch(state)) assert epoch in (get_previous_epoch(state), get_current_epoch(state))
if epoch == get_current_epoch(state): if epoch == get_current_epoch(state):
@ -319,19 +315,17 @@ def get_unslashed_participating_indices(state: BeaconState, flag_index: int, epo
return set(filter(lambda index: not state.validators[index].slashed, participating_indices)) return set(filter(lambda index: not state.validators[index].slashed, participating_indices))
``` ```
#### `get_flag_deltas` #### `get_flag_index_deltas`
```python ```python
def get_flag_deltas(state: BeaconState, def get_flag_index_deltas(state: BeaconState,
flag_index: int, flag_index: int,
numerator: uint64) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: numerator: uint64) -> Tuple[Sequence[Gwei], Sequence[Gwei]]:
""" """
Compute the rewards and penalties associated with a particular duty, by scanning through the participation Return the deltas for a given flag index by scanning through the participation flags.
flags to determine who participated and who did not and assigning them the appropriate rewards and penalties.
""" """
rewards = [Gwei(0)] * len(state.validators) rewards = [Gwei(0)] * len(state.validators)
penalties = [Gwei(0)] * len(state.validators) penalties = [Gwei(0)] * len(state.validators)
unslashed_participating_indices = get_unslashed_participating_indices(state, flag_index, get_previous_epoch(state)) unslashed_participating_indices = get_unslashed_participating_indices(state, flag_index, get_previous_epoch(state))
increment = EFFECTIVE_BALANCE_INCREMENT # Factored out from balances to avoid uint64 overflow increment = EFFECTIVE_BALANCE_INCREMENT # Factored out from balances to avoid uint64 overflow
unslashed_participating_increments = get_total_balance(state, unslashed_participating_indices) // increment unslashed_participating_increments = get_total_balance(state, unslashed_participating_indices) // increment
@ -340,19 +334,17 @@ def get_flag_deltas(state: BeaconState,
base_reward = get_base_reward(state, index) base_reward = get_base_reward(state, index)
if index in unslashed_participating_indices: if index in unslashed_participating_indices:
if is_in_inactivity_leak(state): if is_in_inactivity_leak(state):
# Optimal participation is fully rewarded to cancel the inactivity penalty # This flag reward cancels the inactivity penalty corresponding to the flag index
rewards[index] = base_reward * numerator // FLAG_DENOMINATOR rewards[index] += Gwei(base_reward * numerator // FLAG_DENOMINATOR)
else: else:
rewards[index] = ( reward_numerator = base_reward * numerator * unslashed_participating_increments
(base_reward * numerator * unslashed_participating_increments) rewards[index] += Gwei(reward_numerator // (active_increments * FLAG_DENOMINATOR))
// (active_increments * FLAG_DENOMINATOR)
)
else: else:
penalties[index] = base_reward * numerator // FLAG_DENOMINATOR penalties[index] += Gwei(base_reward * numerator // FLAG_DENOMINATOR)
return rewards, penalties return rewards, penalties
``` ```
#### New `get_inactivity_penalty_deltas` #### Modified `get_inactivity_penalty_deltas`
*Note*: The function `get_inactivity_penalty_deltas` is modified in the selection of matching target indices *Note*: The function `get_inactivity_penalty_deltas` is modified in the selection of matching target indices
and the removal of `BASE_REWARDS_PER_EPOCH`. and the removal of `BASE_REWARDS_PER_EPOCH`.
@ -360,39 +352,29 @@ and the removal of `BASE_REWARDS_PER_EPOCH`.
```python ```python
def get_inactivity_penalty_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: def get_inactivity_penalty_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]:
""" """
Compute the penalties associated with the inactivity leak, by scanning through the participation Return the inactivity penalty deltas by considering timely target participation flags and inactivity scores.
flags to determine who participated and who did not, applying the leak penalty globally and applying
compensatory rewards to participants.
""" """
rewards = [Gwei(0) for _ in range(len(state.validators))] 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): previous_epoch = get_previous_epoch(state)
return rewards, penalties matching_target_indices = get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, previous_epoch)
for index in get_eligible_validator_indices(state):
reward_numerator_sum = sum(numerator for (_, numerator) in get_flag_indices_and_numerators()) for (_, numerator) in get_flag_indices_and_numerators():
matching_target_attesting_indices = get_unslashed_participating_indices( # This inactivity penalty cancels the flag reward corresponding to the flag index
state, TIMELY_TARGET_FLAG_INDEX, get_previous_epoch(state) penalties[index] += Gwei(get_base_reward(state, index) * numerator // FLAG_DENOMINATOR)
) if index not in matching_target_indices:
for index in get_eligible_validator_indices(state): penalty_numerator = state.validators[index].effective_balance * state.inactivity_scores[index]
# If validator is performing optimally this cancels all attestation rewards for a neutral balance penalty_denominator = INACTIVITY_SCORE_BIAS * ALTAIR_INACTIVITY_PENALTY_QUOTIENT
penalties[index] += Gwei(get_base_reward(state, index) * reward_numerator_sum // FLAG_DENOMINATOR) penalties[index] += Gwei(penalty_numerator // penalty_denominator)
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_scores[index] // LEAK_SCORE_BIAS // ALTAIR_INACTIVITY_PENALTY_QUOTIENT
)
penalties[index] += leak_penalty
return rewards, penalties return rewards, penalties
``` ```
### Beacon state mutators ### Beacon state mutators
#### New `slash_validator` #### Modified `slash_validator`
*Note*: The function `slash_validator` is modified *Note*: The function `slash_validator` is modified to use `ALTAIR_MIN_SLASHING_PENALTY_QUOTIENT`.
with the substitution of `MIN_SLASHING_PENALTY_QUOTIENT` with `ALTAIR_MIN_SLASHING_PENALTY_QUOTIENT`.
```python ```python
def slash_validator(state: BeaconState, def slash_validator(state: BeaconState,
@ -483,10 +465,9 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None:
increase_balance(state, get_beacon_proposer_index(state), proposer_reward) increase_balance(state, get_beacon_proposer_index(state), proposer_reward)
``` ```
#### Modified `process_deposit` #### Modified `process_deposit`
*Note*: The function `process_deposit` is modified to initialize `leak_scores`, `previous_epoch_participation`, `current_epoch_participation`. *Note*: The function `process_deposit` is modified to initialize `inactivity_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:
@ -504,7 +485,7 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None:
pubkey = deposit.data.pubkey pubkey = deposit.data.pubkey
amount = deposit.data.amount amount = deposit.data.amount
validator_pubkeys = [v.pubkey for v in state.validators] validator_pubkeys = [validator.pubkey for validator in state.validators]
if pubkey not in validator_pubkeys: if pubkey not in validator_pubkeys:
# Verify the deposit signature (proof of possession) which is not checked by the deposit contract # Verify the deposit signature (proof of possession) which is not checked by the deposit contract
deposit_message = DepositMessage( deposit_message = DepositMessage(
@ -514,15 +495,13 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None:
) )
domain = compute_domain(DOMAIN_DEPOSIT) # Fork-agnostic domain since deposits are valid across forks domain = compute_domain(DOMAIN_DEPOSIT) # Fork-agnostic domain since deposits are valid across forks
signing_root = compute_signing_root(deposit_message, domain) signing_root = compute_signing_root(deposit_message, domain)
if not bls.Verify(pubkey, signing_root, deposit.data.signature): # Initialize validator if the deposit signature is valid
return if bls.Verify(pubkey, signing_root, deposit.data.signature):
state.validators.append(get_validator_from_deposit(state, deposit))
# Add validator and balance entries state.balances.append(amount)
state.validators.append(get_validator_from_deposit(state, deposit)) state.previous_epoch_participation.append(ParticipationFlags(0b0000_0000))
state.balances.append(amount) state.current_epoch_participation.append(ParticipationFlags(0b0000_0000))
state.previous_epoch_participation.append(ParticipationFlags(0b0000_0000)) # [New in Altair] state.inactivity_scores.append(0)
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))
@ -563,7 +542,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_inactivity_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]
@ -622,36 +601,32 @@ def process_justification_and_finalization(state: BeaconState) -> None:
state.finalized_checkpoint = old_current_justified_checkpoint state.finalized_checkpoint = old_current_justified_checkpoint
``` ```
#### Leak scores #### Inactivity scores
*Note*: The function `process_leak_updates` is new. *Note*: The function `process_inactivity_updates` is new.
```python ```python
def process_leak_updates(state: BeaconState) -> None: def process_inactivity_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): for index in get_eligible_validator_indices(state):
if index in matching_target_attesting_indices: if index in get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, get_previous_epoch(state)):
if state.leak_scores[index] > 0: if state.inactivity_scores[index] > 0:
state.leak_scores[index] -= 1 state.inactivity_scores[index] -= 1
elif is_in_inactivity_leak(state): elif is_in_inactivity_leak(state):
state.leak_scores[index] += LEAK_SCORE_BIAS state.inactivity_scores[index] += INACTIVITY_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 accounting reforms.
```python ```python
def process_rewards_and_penalties(state: BeaconState) -> None: def process_rewards_and_penalties(state: BeaconState) -> None:
# No rewards are applied at the end of `GENESIS_EPOCH` because rewards are for work done in the previous epoch # No rewards are applied at the end of `GENESIS_EPOCH` because rewards are for work done in the previous epoch
if get_current_epoch(state) == GENESIS_EPOCH: if get_current_epoch(state) == GENESIS_EPOCH:
return return
flag_deltas = [
get_flag_deltas(state, flag_index, flag_numerator) flag_indices_and_numerators = get_flag_indices_and_numerators()
for (flag_index, flag_numerator) in get_flag_indices_and_numerators() flag_deltas = [get_flag_index_deltas(state, index, numerator) for (index, numerator) in flag_indices_and_numerators]
]
deltas = flag_deltas + [get_inactivity_penalty_deltas(state)] deltas = flag_deltas + [get_inactivity_penalty_deltas(state)]
for (rewards, penalties) in deltas: for (rewards, penalties) in deltas:
for index in range(len(state.validators)): for index in range(len(state.validators)):

View File

@ -75,8 +75,8 @@ def upgrade_to_altair(pre: phase0.BeaconState) -> BeaconState:
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 # Inactivity
leak_scores=[0 for _ in range(len(pre.validators))], inactivity_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

@ -257,7 +257,7 @@ The following values are (non-configurable) constants used throughout the specif
| `WHISTLEBLOWER_REWARD_QUOTIENT` | `uint64(2**9)` (= 512) | | `WHISTLEBLOWER_REWARD_QUOTIENT` | `uint64(2**9)` (= 512) |
| `PROPOSER_REWARD_QUOTIENT` | `uint64(2**3)` (= 8) | | `PROPOSER_REWARD_QUOTIENT` | `uint64(2**3)` (= 8) |
| `INACTIVITY_PENALTY_QUOTIENT` | `uint64(2**26)` (= 67,108,864) | | `INACTIVITY_PENALTY_QUOTIENT` | `uint64(2**26)` (= 67,108,864) |
| `MIN_SLASHING_PENALTY_QUOTIENT` | `uint64(2**7)` (=128) | | `MIN_SLASHING_PENALTY_QUOTIENT` | `uint64(2**7)` (= 128) |
| `PROPORTIONAL_SLASHING_MULTIPLIER` | `uint64(1)` | | `PROPORTIONAL_SLASHING_MULTIPLIER` | `uint64(1)` |
- The `INACTIVITY_PENALTY_QUOTIENT` equals `INVERSE_SQRT_E_DROP_TIME**2` where `INVERSE_SQRT_E_DROP_TIME := 2**13` epochs (about 36 days) is the time it takes the inactivity penalty to reduce the balance of non-participating validators to about `1/sqrt(e) ~= 60.6%`. Indeed, the balance retained by offline validators after `n` epochs is about `(1 - 1/INACTIVITY_PENALTY_QUOTIENT)**(n**2/2)`; so after `INVERSE_SQRT_E_DROP_TIME` epochs, it is roughly `(1 - 1/INACTIVITY_PENALTY_QUOTIENT)**(INACTIVITY_PENALTY_QUOTIENT/2) ~= 1/sqrt(e)`. Note this value will be upgraded to `2**24` after Phase 0 mainnet stabilizes to provide a faster recovery in the event of an inactivity leak. - The `INACTIVITY_PENALTY_QUOTIENT` equals `INVERSE_SQRT_E_DROP_TIME**2` where `INVERSE_SQRT_E_DROP_TIME := 2**13` epochs (about 36 days) is the time it takes the inactivity penalty to reduce the balance of non-participating validators to about `1/sqrt(e) ~= 60.6%`. Indeed, the balance retained by offline validators after `n` epochs is about `(1 - 1/INACTIVITY_PENALTY_QUOTIENT)**(n**2/2)`; so after `INVERSE_SQRT_E_DROP_TIME` epochs, it is roughly `(1 - 1/INACTIVITY_PENALTY_QUOTIENT)**(INACTIVITY_PENALTY_QUOTIENT/2) ~= 1/sqrt(e)`. Note this value will be upgraded to `2**24` after Phase 0 mainnet stabilizes to provide a faster recovery in the event of an inactivity leak.

View File

@ -647,5 +647,5 @@ A validator client should be considered standalone and should consider the beaco
1) Private keys -- private keys should be protected from being exported accidentally or by an attacker. 1) Private keys -- private keys should be protected from being exported accidentally or by an attacker.
2) Slashing -- before a validator client signs a message it should validate the data, check it against a local slashing database (do not sign a slashable attestation or block) and update its internal slashing database with the newly signed object. 2) Slashing -- before a validator client signs a message it should validate the data, check it against a local slashing database (do not sign a slashable attestation or block) and update its internal slashing database with the newly signed object.
3) Recovered validator -- Recovering a validator from a private key will result in an empty local slashing db. Best practice is to import (from a trusted source) that validator's attestation history. See [EIP 3076](https://github.com/ethereum/EIPs/pull/3076/files) for a standard slashing interchange format. 3) Recovered validator -- Recovering a validator from a private key will result in an empty local slashing db. Best practice is to import (from a trusted source) that validator's attestation history. See [EIP 3076](https://github.com/ethereum/EIPs/pull/3076/files) for a standard slashing interchange format.
4) Far future signing requests -- A validator client can be requested to sign a far into the future attestation, resulting in a valid non-slashable request. If the validator client signs this message, it will result in it blocking itself from attesting any other attestation until the beacon-chain reaches that far into the future epoch. This will result in an inactivity leak and potential ejection due to low balance. 4) Far future signing requests -- A validator client can be requested to sign a far into the future attestation, resulting in a valid non-slashable request. If the validator client signs this message, it will result in it blocking itself from attesting any other attestation until the beacon-chain reaches that far into the future epoch. This will result in an inactivity penalty and potential ejection due to low balance.
A validator client should prevent itself from signing such requests by: a) keeping a local time clock if possible and following best practices to stop time server attacks and b) refusing to sign, by default, any message that has a large (>6h) gap from the current slashing protection database indicated a time "jump" or a long offline event. The administrator can manually override this protection to restart the validator after a genuine long offline event. A validator client should prevent itself from signing such requests by: a) keeping a local time clock if possible and following best practices to stop time server attacks and b) refusing to sign, by default, any message that has a large (>6h) gap from the current slashing protection database indicated a time "jump" or a long offline event. The administrator can manually override this protection to restart the validator after a genuine long offline event.

View File

@ -79,12 +79,12 @@ def test_empty_sync_committee_committee_genesis(spec, state):
@with_all_phases_except([PHASE0, PHASE1]) @with_all_phases_except([PHASE0, PHASE1])
@spec_state_test @spec_state_test
def test_leak_scores(spec, state): def test_inactivity_scores(spec, state):
for _ in range(spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY + 2): for _ in range(spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY + 2):
next_epoch_via_block(spec, state) next_epoch_via_block(spec, state)
assert spec.is_in_inactivity_leak(state) assert spec.is_in_inactivity_leak(state)
previous_leak_scores = state.leak_scores.copy() previous_inactivity_scores = state.inactivity_scores.copy()
yield 'pre', state yield 'pre', state
@ -95,5 +95,5 @@ def test_leak_scores(spec, state):
yield 'blocks', [signed_block] yield 'blocks', [signed_block]
yield 'post', state yield 'post', state
for pre, post in zip(previous_leak_scores, state.leak_scores): for pre, post in zip(previous_inactivity_scores, state.inactivity_scores):
assert post == pre + spec.LEAK_SCORE_BIAS assert post == pre + spec.INACTIVITY_SCORE_BIAS

View File

@ -42,13 +42,13 @@ def run_deltas(spec, state):
if is_post_altair(spec): if is_post_altair(spec):
def get_source_deltas(state): def get_source_deltas(state):
return spec.get_flag_deltas(state, spec.TIMELY_SOURCE_FLAG_INDEX, spec.TIMELY_SOURCE_FLAG_NUMERATOR) return spec.get_flag_index_deltas(state, spec.TIMELY_SOURCE_FLAG_INDEX, spec.TIMELY_SOURCE_FLAG_NUMERATOR)
def get_head_deltas(state): def get_head_deltas(state):
return spec.get_flag_deltas(state, spec.TIMELY_HEAD_FLAG_INDEX, spec.TIMELY_HEAD_FLAG_NUMERATOR) return spec.get_flag_index_deltas(state, spec.TIMELY_HEAD_FLAG_INDEX, spec.TIMELY_HEAD_FLAG_NUMERATOR)
def get_target_deltas(state): def get_target_deltas(state):
return spec.get_flag_deltas(state, spec.TIMELY_TARGET_FLAG_INDEX, spec.TIMELY_TARGET_FLAG_NUMERATOR) return spec.get_flag_index_deltas(state, spec.TIMELY_TARGET_FLAG_INDEX, spec.TIMELY_TARGET_FLAG_NUMERATOR)
yield from run_attestation_component_deltas( yield from run_attestation_component_deltas(
spec, spec,
@ -191,7 +191,6 @@ def run_get_inactivity_penalty_deltas(spec, state):
matching_attesting_indices = spec.get_unslashed_participating_indices( matching_attesting_indices = spec.get_unslashed_participating_indices(
state, spec.TIMELY_TARGET_FLAG_INDEX, spec.get_previous_epoch(state) state, spec.TIMELY_TARGET_FLAG_INDEX, spec.get_previous_epoch(state)
) )
reward_numerator_sum = sum(numerator for (_, numerator) in spec.get_flag_indices_and_numerators())
eligible_indices = spec.get_eligible_validator_indices(state) eligible_indices = spec.get_eligible_validator_indices(state)
for index in range(len(state.validators)): for index in range(len(state.validators)):
@ -202,12 +201,15 @@ def run_get_inactivity_penalty_deltas(spec, state):
if spec.is_in_inactivity_leak(state): if spec.is_in_inactivity_leak(state):
# Compute base_penalty # Compute base_penalty
base_reward = spec.get_base_reward(state, index)
if not is_post_altair(spec): if not is_post_altair(spec):
cancel_base_rewards_per_epoch = spec.BASE_REWARDS_PER_EPOCH cancel_base_rewards_per_epoch = spec.BASE_REWARDS_PER_EPOCH
base_reward = spec.get_base_reward(state, index)
base_penalty = cancel_base_rewards_per_epoch * base_reward - spec.get_proposer_reward(state, index) base_penalty = cancel_base_rewards_per_epoch * base_reward - spec.get_proposer_reward(state, index)
else: else:
base_penalty = spec.get_base_reward(state, index) * reward_numerator_sum // spec.FLAG_DENOMINATOR base_penalty = sum(
base_reward * numerator // spec.FLAG_DENOMINATOR
for (_, numerator) in spec.get_flag_indices_and_numerators()
)
if not has_enough_for_reward(spec, state, index): if not has_enough_for_reward(spec, state, index):
assert penalties[index] == 0 assert penalties[index] == 0
@ -221,7 +223,7 @@ 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:
# +1 to trigger leak_score transitions # +1 to trigger inactivity_score transitions
epochs = spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY + 1 epochs = spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY + 1
assert epochs >= spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY assert epochs >= spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY