Altair cosmetic cleanups plus a couple substantive changes
This commit is contained in:
parent
ea9f3f184f
commit
e7ebd08d69
|
@ -4,7 +4,7 @@ CONFIG_NAME: "mainnet"
|
|||
|
||||
# Updated penalty values
|
||||
# ---------------------------------------------------------------
|
||||
# 3 * 2**24) (= 50,331,648)
|
||||
# 3 * 2**24 (= 50,331,648)
|
||||
ALTAIR_INACTIVITY_PENALTY_QUOTIENT: 50331648
|
||||
# 2**6 (= 64)
|
||||
ALTAIR_MIN_SLASHING_PENALTY_QUOTIENT: 64
|
||||
|
@ -14,12 +14,12 @@ ALTAIR_PROPORTIONAL_SLASHING_MULTIPLIER: 2
|
|||
|
||||
# Misc
|
||||
# ---------------------------------------------------------------
|
||||
# 2**10 (=1,024)
|
||||
# 2**10 (= 1,024)
|
||||
SYNC_COMMITTEE_SIZE: 1024
|
||||
# 2**6 (=64)
|
||||
# 2**6 (= 64)
|
||||
SYNC_SUBCOMMITTEE_SIZE: 64
|
||||
# 2**2 (=4)
|
||||
LEAK_SCORE_BIAS: 4
|
||||
# 2**2 (= 4)
|
||||
INACTIVITY_SCORE_BIAS: 4
|
||||
|
||||
|
||||
# Time parameters
|
||||
|
|
|
@ -4,7 +4,7 @@ CONFIG_NAME: "minimal"
|
|||
|
||||
# Updated penalty values
|
||||
# ---------------------------------------------------------------
|
||||
# 3 * 2**24) (= 50,331,648)
|
||||
# 3 * 2**24 (= 50,331,648)
|
||||
ALTAIR_INACTIVITY_PENALTY_QUOTIENT: 50331648
|
||||
# 2**6 (= 64)
|
||||
ALTAIR_MIN_SLASHING_PENALTY_QUOTIENT: 64
|
||||
|
@ -18,8 +18,8 @@ ALTAIR_PROPORTIONAL_SLASHING_MULTIPLIER: 2
|
|||
SYNC_COMMITTEE_SIZE: 32
|
||||
# [customized]
|
||||
SYNC_SUBCOMMITTEE_SIZE: 16
|
||||
# 2**2 (=4)
|
||||
LEAK_SCORE_BIAS: 4
|
||||
# 2**2 (= 4)
|
||||
INACTIVITY_SCORE_BIAS: 4
|
||||
|
||||
|
||||
# Time parameters
|
||||
|
|
|
@ -35,17 +35,17 @@
|
|||
- [`get_sync_committee`](#get_sync_committee)
|
||||
- [`get_base_reward`](#get_base_reward)
|
||||
- [`get_unslashed_participating_indices`](#get_unslashed_participating_indices)
|
||||
- [`get_flag_deltas`](#get_flag_deltas)
|
||||
- [New `get_inactivity_penalty_deltas`](#new-get_inactivity_penalty_deltas)
|
||||
- [`get_flag_index_deltas`](#get_flag_index_deltas)
|
||||
- [Modified `get_inactivity_penalty_deltas`](#modified-get_inactivity_penalty_deltas)
|
||||
- [Beacon state mutators](#beacon-state-mutators)
|
||||
- [New `slash_validator`](#new-slash_validator)
|
||||
- [Modified `slash_validator`](#modified-slash_validator)
|
||||
- [Block processing](#block-processing)
|
||||
- [Modified `process_attestation`](#modified-process_attestation)
|
||||
- [Modified `process_deposit`](#modified-process_deposit)
|
||||
- [Sync committee processing](#sync-committee-processing)
|
||||
- [Epoch processing](#epoch-processing)
|
||||
- [Justification and finalization](#justification-and-finalization)
|
||||
- [Leak scores](#leak-scores)
|
||||
- [Inactivity scores](#inactivity-scores)
|
||||
- [Rewards and penalties](#rewards-and-penalties)
|
||||
- [Slashings](#slashings)
|
||||
- [Participation flags updates](#participation-flags-updates)
|
||||
|
@ -56,20 +56,17 @@
|
|||
|
||||
## Introduction
|
||||
|
||||
Altair is a patch implementing the first hard fork to the beacon chain.
|
||||
It has four main features:
|
||||
Altair is the first beacon chain hard fork. Its main features are:
|
||||
|
||||
* Light client support via sync committees
|
||||
* Incentive accounting reforms, reducing spec complexity
|
||||
and [TODO] reducing the cost of processing chains that have very little or zero participation for a long span of epochs
|
||||
* 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
|
||||
* sync committees to support light clients
|
||||
* incentive accounting reforms to reduce spec complexity
|
||||
* penalty parameter updates to move them to their planned maximally punitive configuration
|
||||
|
||||
## Custom types
|
||||
|
||||
| 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
|
||||
|
||||
|
@ -90,8 +87,7 @@ It has four main features:
|
|||
| `TIMELY_TARGET_FLAG_NUMERATOR` | `32` |
|
||||
| `FLAG_DENOMINATOR` | `64` |
|
||||
|
||||
**Note**: The participatition flag fractions add up to 7/8.
|
||||
The remaining 1/8 is for proposer incentives and other future micro-incentives.
|
||||
**Note**: The sum of the participatition flag fractions (7/8) plus the proposer reward fraction (1/8) equals 1.
|
||||
|
||||
### Misc
|
||||
|
||||
|
@ -103,23 +99,23 @@ The remaining 1/8 is for proposer incentives and other future micro-incentives.
|
|||
|
||||
### 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.
|
||||
|
||||
| Name | Value |
|
||||
| - | - |
|
||||
| `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)` |
|
||||
|
||||
### Misc
|
||||
|
||||
| Name | Value |
|
||||
| - | - |
|
||||
| - | - |
|
||||
| `SYNC_COMMITTEE_SIZE` | `uint64(2**10)` (= 1,024) |
|
||||
| `SYNC_SUBCOMMITTEE_SIZE` | `uint64(2**6)` (= 64) |
|
||||
| `LEAK_SCORE_BIAS` | 4 |
|
||||
| `INACTIVITY_SCORE_BIAS` | `uint64(4)` |
|
||||
|
||||
### Time parameters
|
||||
|
||||
|
@ -181,18 +177,18 @@ class BeaconState(Container):
|
|||
# Slashings
|
||||
slashings: Vector[Gwei, EPOCHS_PER_SLASHINGS_VECTOR] # Per-epoch sums of slashed effective balances
|
||||
# Participation
|
||||
previous_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] # [New in Altair]
|
||||
current_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] # [Modofied in Altair]
|
||||
# Finality
|
||||
justification_bits: Bitvector[JUSTIFICATION_BITS_LENGTH] # Bit set for every recent justified epoch
|
||||
previous_justified_checkpoint: Checkpoint
|
||||
current_justified_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]
|
||||
next_sync_committee: SyncCommittee # [New in Altair]
|
||||
# Leak
|
||||
leak_scores: List[uint64, VALIDATOR_REGISTRY_LIMIT] # [New in Altair]
|
||||
```
|
||||
|
||||
### New containers
|
||||
|
@ -307,7 +303,7 @@ def get_base_reward(state: BeaconState, index: ValidatorIndex) -> Gwei:
|
|||
```python
|
||||
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))
|
||||
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))
|
||||
```
|
||||
|
||||
#### `get_flag_deltas`
|
||||
#### `get_flag_index_deltas`
|
||||
|
||||
```python
|
||||
def get_flag_deltas(state: BeaconState,
|
||||
flag_index: int,
|
||||
numerator: uint64) -> Tuple[Sequence[Gwei], Sequence[Gwei]]:
|
||||
def get_flag_index_deltas(state: BeaconState,
|
||||
flag_index: int,
|
||||
numerator: uint64) -> Tuple[Sequence[Gwei], Sequence[Gwei]]:
|
||||
"""
|
||||
Compute the rewards and penalties associated with a particular duty, by scanning through the participation
|
||||
flags to determine who participated and who did not and assigning them the appropriate rewards and penalties.
|
||||
Return the deltas for a given flag index by scanning through the participation flags.
|
||||
"""
|
||||
rewards = [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))
|
||||
increment = EFFECTIVE_BALANCE_INCREMENT # Factored out from balances to avoid uint64 overflow
|
||||
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)
|
||||
if index in unslashed_participating_indices:
|
||||
if is_in_inactivity_leak(state):
|
||||
# Optimal participation is fully rewarded to cancel the inactivity penalty
|
||||
rewards[index] = base_reward * numerator // FLAG_DENOMINATOR
|
||||
# This flag reward cancels the inactivity penalty corresponding to the flag index
|
||||
rewards[index] += Gwei(base_reward * numerator // FLAG_DENOMINATOR)
|
||||
else:
|
||||
rewards[index] = (
|
||||
(base_reward * numerator * unslashed_participating_increments)
|
||||
// (active_increments * FLAG_DENOMINATOR)
|
||||
)
|
||||
reward_numerator = base_reward * numerator * unslashed_participating_increments
|
||||
rewards[index] += Gwei(reward_numerator // (active_increments * FLAG_DENOMINATOR))
|
||||
else:
|
||||
penalties[index] = base_reward * numerator // FLAG_DENOMINATOR
|
||||
penalties[index] += Gwei(base_reward * numerator // FLAG_DENOMINATOR)
|
||||
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
|
||||
and the removal of `BASE_REWARDS_PER_EPOCH`.
|
||||
|
@ -360,39 +352,29 @@ and the removal of `BASE_REWARDS_PER_EPOCH`.
|
|||
```python
|
||||
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
|
||||
flags to determine who participated and who did not, applying the leak penalty globally and applying
|
||||
compensatory rewards to participants.
|
||||
Return the inactivity penalty deltas by considering timely target participation flags and inactivity scores.
|
||||
"""
|
||||
rewards = [Gwei(0) for _ in range(len(state.validators))]
|
||||
penalties = [Gwei(0) for _ in range(len(state.validators))]
|
||||
|
||||
if not is_in_inactivity_leak(state):
|
||||
return rewards, penalties
|
||||
|
||||
reward_numerator_sum = sum(numerator for (_, numerator) in get_flag_indices_and_numerators())
|
||||
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 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)
|
||||
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
|
||||
|
||||
if is_in_inactivity_leak(state):
|
||||
previous_epoch = get_previous_epoch(state)
|
||||
matching_target_indices = get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, previous_epoch)
|
||||
for index in get_eligible_validator_indices(state):
|
||||
for (_, numerator) in get_flag_indices_and_numerators():
|
||||
# This inactivity penalty cancels the flag reward corresponding to the flag index
|
||||
penalties[index] += Gwei(get_base_reward(state, index) * numerator // FLAG_DENOMINATOR)
|
||||
if index not in matching_target_indices:
|
||||
penalty_numerator = state.validators[index].effective_balance * state.inactivity_scores[index]
|
||||
penalty_denominator = INACTIVITY_SCORE_BIAS * ALTAIR_INACTIVITY_PENALTY_QUOTIENT
|
||||
penalties[index] += Gwei(penalty_numerator // penalty_denominator)
|
||||
return rewards, penalties
|
||||
```
|
||||
|
||||
### Beacon state mutators
|
||||
|
||||
#### New `slash_validator`
|
||||
#### Modified `slash_validator`
|
||||
|
||||
*Note*: The function `slash_validator` is modified
|
||||
with the substitution of `MIN_SLASHING_PENALTY_QUOTIENT` with `ALTAIR_MIN_SLASHING_PENALTY_QUOTIENT`.
|
||||
*Note*: The function `slash_validator` is modified to use `ALTAIR_MIN_SLASHING_PENALTY_QUOTIENT`.
|
||||
|
||||
```python
|
||||
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)
|
||||
```
|
||||
|
||||
|
||||
#### 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
|
||||
def process_deposit(state: BeaconState, deposit: Deposit) -> None:
|
||||
|
@ -504,7 +485,7 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None:
|
|||
|
||||
pubkey = deposit.data.pubkey
|
||||
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:
|
||||
# Verify the deposit signature (proof of possession) which is not checked by the deposit contract
|
||||
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
|
||||
signing_root = compute_signing_root(deposit_message, domain)
|
||||
if not bls.Verify(pubkey, signing_root, deposit.data.signature):
|
||||
return
|
||||
|
||||
# Add validator and balance entries
|
||||
state.validators.append(get_validator_from_deposit(state, deposit))
|
||||
state.balances.append(amount)
|
||||
state.previous_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]
|
||||
# Initialize validator if the deposit signature is valid
|
||||
if bls.Verify(pubkey, signing_root, deposit.data.signature):
|
||||
state.validators.append(get_validator_from_deposit(state, deposit))
|
||||
state.balances.append(amount)
|
||||
state.previous_epoch_participation.append(ParticipationFlags(0b0000_0000))
|
||||
state.current_epoch_participation.append(ParticipationFlags(0b0000_0000))
|
||||
state.inactivity_scores.append(0)
|
||||
else:
|
||||
# Increase balance by deposit amount
|
||||
index = ValidatorIndex(validator_pubkeys.index(pubkey))
|
||||
|
@ -563,7 +542,7 @@ def process_sync_committee(state: BeaconState, body: BeaconBlockBody) -> None:
|
|||
```python
|
||||
def process_epoch(state: BeaconState) -> None:
|
||||
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_registry_updates(state)
|
||||
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
|
||||
```
|
||||
|
||||
#### Leak scores
|
||||
#### Inactivity scores
|
||||
|
||||
*Note*: The function `process_leak_updates` is new.
|
||||
*Note*: The function `process_inactivity_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)
|
||||
)
|
||||
def process_inactivity_updates(state: BeaconState) -> None:
|
||||
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
|
||||
if index in get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, get_previous_epoch(state)):
|
||||
if state.inactivity_scores[index] > 0:
|
||||
state.inactivity_scores[index] -= 1
|
||||
elif is_in_inactivity_leak(state):
|
||||
state.leak_scores[index] += LEAK_SCORE_BIAS
|
||||
state.inactivity_scores[index] += INACTIVITY_SCORE_BIAS
|
||||
```
|
||||
|
||||
#### 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
|
||||
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
|
||||
if get_current_epoch(state) == GENESIS_EPOCH:
|
||||
return
|
||||
flag_deltas = [
|
||||
get_flag_deltas(state, flag_index, flag_numerator)
|
||||
for (flag_index, flag_numerator) in get_flag_indices_and_numerators()
|
||||
]
|
||||
|
||||
flag_indices_and_numerators = 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)]
|
||||
for (rewards, penalties) in deltas:
|
||||
for index in range(len(state.validators)):
|
||||
|
|
|
@ -75,8 +75,8 @@ def upgrade_to_altair(pre: phase0.BeaconState) -> BeaconState:
|
|||
previous_justified_checkpoint=pre.previous_justified_checkpoint,
|
||||
current_justified_checkpoint=pre.current_justified_checkpoint,
|
||||
finalized_checkpoint=pre.finalized_checkpoint,
|
||||
# Leak
|
||||
leak_scores=[0 for _ in range(len(pre.validators))],
|
||||
# Inactivity
|
||||
inactivity_scores=[0 for _ in range(len(pre.validators))],
|
||||
)
|
||||
# Fill in sync committees
|
||||
post.current_sync_committee = get_sync_committee(post, get_current_epoch(post))
|
||||
|
|
|
@ -257,7 +257,7 @@ The following values are (non-configurable) constants used throughout the specif
|
|||
| `WHISTLEBLOWER_REWARD_QUOTIENT` | `uint64(2**9)` (= 512) |
|
||||
| `PROPOSER_REWARD_QUOTIENT` | `uint64(2**3)` (= 8) |
|
||||
| `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)` |
|
||||
|
||||
- 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.
|
||||
|
|
|
@ -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.
|
||||
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.
|
||||
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.
|
||||
|
|
|
@ -79,12 +79,12 @@ def test_empty_sync_committee_committee_genesis(spec, state):
|
|||
|
||||
@with_all_phases_except([PHASE0, PHASE1])
|
||||
@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):
|
||||
next_epoch_via_block(spec, 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
|
||||
|
||||
|
@ -95,5 +95,5 @@ def test_leak_scores(spec, state):
|
|||
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
|
||||
for pre, post in zip(previous_inactivity_scores, state.inactivity_scores):
|
||||
assert post == pre + spec.INACTIVITY_SCORE_BIAS
|
||||
|
|
|
@ -42,13 +42,13 @@ def run_deltas(spec, state):
|
|||
|
||||
if is_post_altair(spec):
|
||||
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):
|
||||
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):
|
||||
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(
|
||||
spec,
|
||||
|
@ -191,7 +191,6 @@ def run_get_inactivity_penalty_deltas(spec, state):
|
|||
matching_attesting_indices = spec.get_unslashed_participating_indices(
|
||||
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)
|
||||
for index in range(len(state.validators)):
|
||||
|
@ -207,7 +206,7 @@ def run_get_inactivity_penalty_deltas(spec, state):
|
|||
base_reward = spec.get_base_reward(state, index)
|
||||
base_penalty = cancel_base_rewards_per_epoch * base_reward - spec.get_proposer_reward(state, index)
|
||||
else:
|
||||
base_penalty = spec.get_base_reward(state, index) * reward_numerator_sum // spec.FLAG_DENOMINATOR
|
||||
base_penalty = sum(spec.get_base_reward(state, index) * numerator // spec.FLAG_DENOMINATOR for (_, numerator) in spec.get_flag_indices_and_numerators())
|
||||
|
||||
if not has_enough_for_reward(spec, state, index):
|
||||
assert penalties[index] == 0
|
||||
|
@ -221,7 +220,7 @@ def run_get_inactivity_penalty_deltas(spec, state):
|
|||
|
||||
def transition_state_to_leak(spec, state, epochs=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
|
||||
assert epochs >= spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY
|
||||
|
||||
|
@ -232,7 +231,7 @@ def transition_state_to_leak(spec, state, epochs=None):
|
|||
_cache_dict = LRU(size=10)
|
||||
|
||||
|
||||
def leaking(epochs=None):
|
||||
def inactivity_penalty_active(epochs=None):
|
||||
|
||||
def deco(fn):
|
||||
def entry(*args, spec, state, **kw):
|
||||
|
|
|
@ -17,7 +17,7 @@ from eth2spec.test.helpers.attestations import (
|
|||
sign_attestation,
|
||||
prepare_state_with_attestations,
|
||||
)
|
||||
from eth2spec.test.helpers.rewards import leaking
|
||||
from eth2spec.test.helpers.rewards import inactivity_penalty_active
|
||||
from eth2spec.test.helpers.attester_slashings import get_indexed_attestation_participants
|
||||
from eth2spec.test.helpers.epoch_processing import run_epoch_processing_with
|
||||
from random import Random
|
||||
|
@ -205,7 +205,7 @@ def test_almost_empty_attestations(spec, state):
|
|||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
@leaking()
|
||||
@inactivity_penalty_active()
|
||||
def test_almost_empty_attestations_with_leak(spec, state):
|
||||
rng = Random(1234)
|
||||
yield from run_with_participation(spec, state, lambda slot, comm_index, comm: rng.sample(comm, 1))
|
||||
|
@ -220,7 +220,7 @@ def test_random_fill_attestations(spec, state):
|
|||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
@leaking()
|
||||
@inactivity_penalty_active()
|
||||
def test_random_fill_attestations_with_leak(spec, state):
|
||||
rng = Random(4567)
|
||||
yield from run_with_participation(spec, state, lambda slot, comm_index, comm: rng.sample(comm, len(comm) // 3))
|
||||
|
@ -235,7 +235,7 @@ def test_almost_full_attestations(spec, state):
|
|||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
@leaking()
|
||||
@inactivity_penalty_active()
|
||||
def test_almost_full_attestations_with_leak(spec, state):
|
||||
rng = Random(8901)
|
||||
yield from run_with_participation(spec, state, lambda slot, comm_index, comm: rng.sample(comm, len(comm) - 1))
|
||||
|
@ -249,7 +249,7 @@ def test_full_attestation_participation(spec, state):
|
|||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
@leaking()
|
||||
@inactivity_penalty_active()
|
||||
def test_full_attestation_participation_with_leak(spec, state):
|
||||
yield from run_with_participation(spec, state, lambda slot, comm_index, comm: comm)
|
||||
|
||||
|
|
|
@ -1,81 +1,81 @@
|
|||
from eth2spec.test.context import PHASE0, PHASE1, with_all_phases, with_phases, spec_state_test
|
||||
from eth2spec.test.helpers.rewards import leaking
|
||||
from eth2spec.test.helpers.rewards import inactivity_penalty_active
|
||||
import eth2spec.test.helpers.rewards as rewards_helpers
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
@leaking()
|
||||
@inactivity_penalty_active()
|
||||
def test_empty_leak(spec, state):
|
||||
yield from rewards_helpers.run_test_empty(spec, state)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
@leaking()
|
||||
@inactivity_penalty_active()
|
||||
def test_full_leak(spec, state):
|
||||
yield from rewards_helpers.run_test_full_all_correct(spec, state)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
@leaking()
|
||||
@inactivity_penalty_active()
|
||||
def test_half_full_leak(spec, state):
|
||||
yield from rewards_helpers.run_test_half_full(spec, state)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
@leaking()
|
||||
@inactivity_penalty_active()
|
||||
def test_quarter_full_leak(spec, state):
|
||||
yield from rewards_helpers.run_test_partial(spec, state, 0.25)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
@leaking()
|
||||
@inactivity_penalty_active()
|
||||
def test_full_but_partial_participation_leak(spec, state):
|
||||
yield from rewards_helpers.run_test_full_but_partial_participation(spec, state)
|
||||
|
||||
|
||||
@with_phases([PHASE0, PHASE1])
|
||||
@spec_state_test
|
||||
@leaking()
|
||||
@inactivity_penalty_active()
|
||||
def test_one_attestation_one_correct_leak(spec, state):
|
||||
yield from rewards_helpers.run_test_one_attestation_one_correct(spec, state)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
@leaking()
|
||||
@inactivity_penalty_active()
|
||||
def test_with_not_yet_activated_validators_leak(spec, state):
|
||||
yield from rewards_helpers.run_test_with_not_yet_activated_validators(spec, state)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
@leaking()
|
||||
@inactivity_penalty_active()
|
||||
def test_with_exited_validators_leak(spec, state):
|
||||
yield from rewards_helpers.run_test_with_exited_validators(spec, state)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
@leaking()
|
||||
@inactivity_penalty_active()
|
||||
def test_with_slashed_validators_leak(spec, state):
|
||||
yield from rewards_helpers.run_test_with_slashed_validators(spec, state)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
@leaking()
|
||||
@inactivity_penalty_active()
|
||||
def test_some_very_low_effective_balances_that_attested_leak(spec, state):
|
||||
yield from rewards_helpers.run_test_some_very_low_effective_balances_that_attested(spec, state)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
@leaking()
|
||||
@inactivity_penalty_active()
|
||||
def test_some_very_low_effective_balances_that_did_not_attest_leak(spec, state):
|
||||
yield from rewards_helpers.run_test_some_very_low_effective_balances_that_did_not_attest(spec, state)
|
||||
|
||||
|
@ -89,7 +89,7 @@ def test_some_very_low_effective_balances_that_did_not_attest_leak(spec, state):
|
|||
|
||||
@with_phases([PHASE0, PHASE1])
|
||||
@spec_state_test
|
||||
@leaking()
|
||||
@inactivity_penalty_active()
|
||||
def test_full_half_correct_target_incorrect_head_leak(spec, state):
|
||||
yield from rewards_helpers.run_test_full_fraction_incorrect(
|
||||
spec, state,
|
||||
|
@ -101,7 +101,7 @@ def test_full_half_correct_target_incorrect_head_leak(spec, state):
|
|||
|
||||
@with_phases([PHASE0, PHASE1])
|
||||
@spec_state_test
|
||||
@leaking()
|
||||
@inactivity_penalty_active()
|
||||
def test_full_correct_target_incorrect_head_leak(spec, state):
|
||||
yield from rewards_helpers.run_test_full_fraction_incorrect(
|
||||
spec, state,
|
||||
|
@ -113,7 +113,7 @@ def test_full_correct_target_incorrect_head_leak(spec, state):
|
|||
|
||||
@with_phases([PHASE0, PHASE1])
|
||||
@spec_state_test
|
||||
@leaking()
|
||||
@inactivity_penalty_active()
|
||||
def test_full_half_incorrect_target_incorrect_head_leak(spec, state):
|
||||
yield from rewards_helpers.run_test_full_fraction_incorrect(
|
||||
spec, state,
|
||||
|
@ -125,7 +125,7 @@ def test_full_half_incorrect_target_incorrect_head_leak(spec, state):
|
|||
|
||||
@with_phases([PHASE0, PHASE1])
|
||||
@spec_state_test
|
||||
@leaking()
|
||||
@inactivity_penalty_active()
|
||||
def test_full_half_incorrect_target_correct_head_leak(spec, state):
|
||||
yield from rewards_helpers.run_test_full_fraction_incorrect(
|
||||
spec, state,
|
||||
|
@ -137,20 +137,20 @@ def test_full_half_incorrect_target_correct_head_leak(spec, state):
|
|||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
@leaking()
|
||||
@inactivity_penalty_active()
|
||||
def test_full_random_leak(spec, state):
|
||||
yield from rewards_helpers.run_test_full_random(spec, state)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
@leaking(epochs=5)
|
||||
@inactivity_penalty_active(epochs=5)
|
||||
def test_full_random_five_epoch_leak(spec, state):
|
||||
yield from rewards_helpers.run_test_full_random(spec, state)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
@leaking(epochs=10)
|
||||
@inactivity_penalty_active(epochs=10)
|
||||
def test_full_random_ten_epoch_leak(spec, state):
|
||||
yield from rewards_helpers.run_test_full_random(spec, state)
|
||||
|
|
Loading…
Reference in New Issue