Merge pull request #2236 from ethereum/justin_altair_cleanups
Altair cosmetic cleanups plus a couple substantive changes
This commit is contained in:
commit
b8bf7f0f80
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)):
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue