Simplify get_justification_and_finalization_deltas
Cosmetic changes related to `get_justification_and_finalization_deltas`: * Review naming of misc helper functions and variables * Abstract away common logic and rework for readability * Add `MAX_FINALITY_LOOKBACK` and `BASE_REWARDS_PER_EPOCH` constants * Rescale `INACTIVITY_PENALTY_QUOTIENT` Substantive changes: * Make logic relative to `previous_epoch` throughout (as opposed to mixing `current_epoch` and `previous_epoch`) * Replace inclusion delay bonus by an inclusion delay penalty
This commit is contained in:
parent
587193076e
commit
6f56c379d6
|
@ -185,6 +185,7 @@ These configurations are updated for releases, but may be out of sync during `de
|
||||||
| `MAX_ATTESTATION_PARTICIPANTS` | `2**12` (= 4,096) |
|
| `MAX_ATTESTATION_PARTICIPANTS` | `2**12` (= 4,096) |
|
||||||
| `MIN_PER_EPOCH_CHURN_LIMIT` | `2**2` (= 4) |
|
| `MIN_PER_EPOCH_CHURN_LIMIT` | `2**2` (= 4) |
|
||||||
| `CHURN_LIMIT_QUOTIENT` | `2**16` (= 65,536) |
|
| `CHURN_LIMIT_QUOTIENT` | `2**16` (= 65,536) |
|
||||||
|
| `BASE_REWARDS_PER_EPOCH` | `5` |
|
||||||
| `SHUFFLE_ROUND_COUNT` | 90 |
|
| `SHUFFLE_ROUND_COUNT` | 90 |
|
||||||
|
|
||||||
* For the safety of crosslinks `TARGET_COMMITTEE_SIZE` exceeds [the recommended minimum committee size of 111](https://vitalik.ca/files/Ithaca201807_Sharding.pdf); with sufficient active validators (at least `SLOTS_PER_EPOCH * TARGET_COMMITTEE_SIZE`), the shuffling algorithm ensures committee sizes of at least `TARGET_COMMITTEE_SIZE`. (Unbiasable randomness with a Verifiable Delay Function (VDF) will improve committee robustness and lower the safe minimum committee size.)
|
* For the safety of crosslinks `TARGET_COMMITTEE_SIZE` exceeds [the recommended minimum committee size of 111](https://vitalik.ca/files/Ithaca201807_Sharding.pdf); with sufficient active validators (at least `SLOTS_PER_EPOCH * TARGET_COMMITTEE_SIZE`), the shuffling algorithm ensures committee sizes of at least `TARGET_COMMITTEE_SIZE`. (Unbiasable randomness with a Verifiable Delay Function (VDF) will improve committee robustness and lower the safe minimum committee size.)
|
||||||
|
@ -234,6 +235,7 @@ These configurations are updated for releases, but may be out of sync during `de
|
||||||
| `MIN_VALIDATOR_WITHDRAWABILITY_DELAY` | `2**8` (= 256) | epochs | ~27 hours |
|
| `MIN_VALIDATOR_WITHDRAWABILITY_DELAY` | `2**8` (= 256) | epochs | ~27 hours |
|
||||||
| `PERSISTENT_COMMITTEE_PERIOD` | `2**11` (= 2,048) | epochs | 9 days |
|
| `PERSISTENT_COMMITTEE_PERIOD` | `2**11` (= 2,048) | epochs | 9 days |
|
||||||
| `MAX_CROSSLINK_EPOCHS` | `2**6` (= 64) | epochs | ~7 hours |
|
| `MAX_CROSSLINK_EPOCHS` | `2**6` (= 64) | epochs | ~7 hours |
|
||||||
|
| `MAX_FINALITY_LOOKBACK` | `2**2` (= 4) | epochs | 25.6 minutes |
|
||||||
|
|
||||||
* `MAX_CROSSLINK_EPOCHS` should be a small constant times `SHARD_COUNT // SLOTS_PER_EPOCH`
|
* `MAX_CROSSLINK_EPOCHS` should be a small constant times `SHARD_COUNT // SLOTS_PER_EPOCH`
|
||||||
|
|
||||||
|
@ -252,7 +254,7 @@ These configurations are updated for releases, but may be out of sync during `de
|
||||||
| `BASE_REWARD_QUOTIENT` | `2**5` (= 32) |
|
| `BASE_REWARD_QUOTIENT` | `2**5` (= 32) |
|
||||||
| `WHISTLEBLOWING_REWARD_QUOTIENT` | `2**9` (= 512) |
|
| `WHISTLEBLOWING_REWARD_QUOTIENT` | `2**9` (= 512) |
|
||||||
| `PROPOSER_REWARD_QUOTIENT` | `2**3` (= 8) |
|
| `PROPOSER_REWARD_QUOTIENT` | `2**3` (= 8) |
|
||||||
| `INACTIVITY_PENALTY_QUOTIENT` | `2**24` (= 16,777,216) |
|
| `INACTIVITY_PENALTY_QUOTIENT` | `2**25` (= 33,554,432) |
|
||||||
| `MIN_PENALTY_QUOTIENT` | `2**5` (= 32) |
|
| `MIN_PENALTY_QUOTIENT` | `2**5` (= 32) |
|
||||||
|
|
||||||
* The `BASE_REWARD_QUOTIENT` parameter dictates the per-epoch reward. It corresponds to ~2.54% annual interest assuming 10 million participating ETH in every epoch.
|
* The `BASE_REWARD_QUOTIENT` parameter dictates the per-epoch reward. It corresponds to ~2.54% annual interest assuming 10 million participating ETH in every epoch.
|
||||||
|
@ -875,7 +877,7 @@ def compute_committee(validator_indices: List[ValidatorIndex],
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
**Note**: this definition and the next few definitions are highly inefficient as algorithms, as they re-calculate many sub-expressions. Production implementations are expected to appropriately use caching/memoization to avoid redoing work.
|
Note: this definition and the next few definitions are highly inefficient as algorithms, as they re-calculate many sub-expressions. Production implementations are expected to appropriately use caching/memoization to avoid redoing work.
|
||||||
|
|
||||||
### `get_crosslink_committees_at_slot`
|
### `get_crosslink_committees_at_slot`
|
||||||
|
|
||||||
|
@ -1582,7 +1584,7 @@ Transition section notes:
|
||||||
|
|
||||||
Beacon blocks that trigger unhandled Python exceptions (e.g. out-of-range list accesses) and failed `assert`s during the state transition are considered invalid.
|
Beacon blocks that trigger unhandled Python exceptions (e.g. out-of-range list accesses) and failed `assert`s during the state transition are considered invalid.
|
||||||
|
|
||||||
_Note_: If there are skipped slots between a block and its parent block, run the steps in the [state-root](#state-caching), [per-epoch](#per-epoch-processing), and [per-slot](#per-slot-processing) sections once for each skipped slot and then once for the slot containing the new block.
|
Note: If there are skipped slots between a block and its parent block, run the steps in the [state-root](#state-caching), [per-epoch](#per-epoch-processing), and [per-slot](#per-slot-processing) sections once for each skipped slot and then once for the slot containing the new block.
|
||||||
|
|
||||||
### State caching
|
### State caching
|
||||||
|
|
||||||
|
@ -1612,30 +1614,20 @@ The steps below happen when `state.slot > GENESIS_SLOT and (state.slot + 1) % SL
|
||||||
We define epoch transition helper functions:
|
We define epoch transition helper functions:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def get_current_total_balance(state: BeaconState) -> Gwei:
|
def get_previous_epoch_total_balance(state: BeaconState) -> Gwei:
|
||||||
return get_total_balance(state, get_active_validator_indices(state, get_current_epoch(state)))
|
|
||||||
```
|
|
||||||
|
|
||||||
```python
|
|
||||||
def get_previous_total_balance(state: BeaconState) -> Gwei:
|
|
||||||
return get_total_balance(state, get_active_validator_indices(state, get_previous_epoch(state)))
|
return get_total_balance(state, get_active_validator_indices(state, get_previous_epoch(state)))
|
||||||
```
|
```
|
||||||
|
|
||||||
```python
|
Note: The balance computed by `get_previous_epoch_total_balance` may be different to the actual total balance during the previous epoch transition. Due to the bounds on per-epoch validator churn and per-epoch rewards/penalties, the maximum balance difference is low and only marginally affects consensus safety.
|
||||||
def get_unslashed_attesting_indices(state: BeaconState, attestations: List[PendingAttestation]) -> List[ValidatorIndex]:
|
|
||||||
output = set()
|
|
||||||
for a in attestations:
|
|
||||||
output = output.union(get_attestation_participants(state, a.data, a.aggregation_bitfield))
|
|
||||||
return sorted(filter(lambda index: not state.validator_registry[index].slashed, list(output)))
|
|
||||||
```
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def get_attesting_balance(state: BeaconState, attestations: List[PendingAttestation]) -> Gwei:
|
def get_current_epoch_total_balance(state: BeaconState) -> Gwei:
|
||||||
return get_total_balance(state, get_unslashed_attesting_indices(state, attestations))
|
return get_total_balance(state, get_active_validator_indices(state, get_current_epoch(state)))
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def get_current_epoch_boundary_attestations(state: BeaconState) -> List[PendingAttestation]:
|
def get_current_epoch_matching_target_attestations(state: BeaconState) -> List[PendingAttestation]:
|
||||||
return [
|
return [
|
||||||
a for a in state.current_epoch_attestations
|
a for a in state.current_epoch_attestations
|
||||||
if a.data.target_root == get_block_root(state, get_epoch_start_slot(get_current_epoch(state)))
|
if a.data.target_root == get_block_root(state, get_epoch_start_slot(get_current_epoch(state)))
|
||||||
|
@ -1643,7 +1635,7 @@ def get_current_epoch_boundary_attestations(state: BeaconState) -> List[PendingA
|
||||||
```
|
```
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def get_previous_epoch_boundary_attestations(state: BeaconState) -> List[PendingAttestation]:
|
def get_previous_epoch_matching_target_attestations(state: BeaconState) -> List[PendingAttestation]:
|
||||||
return [
|
return [
|
||||||
a for a in state.previous_epoch_attestations
|
a for a in state.previous_epoch_attestations
|
||||||
if a.data.target_root == get_block_root(state, get_epoch_start_slot(get_previous_epoch(state)))
|
if a.data.target_root == get_block_root(state, get_epoch_start_slot(get_previous_epoch(state)))
|
||||||
|
@ -1658,7 +1650,18 @@ def get_previous_epoch_matching_head_attestations(state: BeaconState) -> List[Pe
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
**Note**: Total balances computed for the previous epoch might be marginally different than the actual total balances during the previous epoch transition. Due to the tight bound on validator churn each epoch and small per-epoch rewards/penalties, the potential balance difference is very low and only marginally affects consensus safety.
|
```python
|
||||||
|
def get_unslashed_attesting_indices(state: BeaconState, attestations: List[PendingAttestation]) -> List[ValidatorIndex]:
|
||||||
|
output = set()
|
||||||
|
for a in attestations:
|
||||||
|
output = output.union(get_attestation_participants(state, a.data, a.aggregation_bitfield))
|
||||||
|
return sorted(filter(lambda index: not state.validator_registry[index].slashed, list(output)))
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get_attesting_balance(state: BeaconState, attestations: List[PendingAttestation]) -> Gwei:
|
||||||
|
return get_total_balance(state, get_unslashed_attesting_indices(state, attestations))
|
||||||
|
```
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def get_winning_root_and_participants(state: BeaconState, shard: Shard) -> Tuple[Bytes32, List[ValidatorIndex]]:
|
def get_winning_root_and_participants(state: BeaconState, shard: Shard) -> Tuple[Bytes32, List[ValidatorIndex]]:
|
||||||
|
@ -1701,13 +1704,13 @@ def update_justification_and_finalization(state: BeaconState) -> None:
|
||||||
state.previous_justified_epoch = state.current_justified_epoch
|
state.previous_justified_epoch = state.current_justified_epoch
|
||||||
state.previous_justified_root = state.current_justified_root
|
state.previous_justified_root = state.current_justified_root
|
||||||
state.justification_bitfield = (state.justification_bitfield << 1) % 2**64
|
state.justification_bitfield = (state.justification_bitfield << 1) % 2**64
|
||||||
previous_boundary_attesting_balance = get_attesting_balance(state, get_previous_epoch_boundary_attestations(state))
|
previous_epoch_matching_target_balance = get_attesting_balance(state, get_previous_epoch_matching_target_attestations(state))
|
||||||
if previous_boundary_attesting_balance * 3 >= get_previous_total_balance(state) * 2:
|
if previous_epoch_matching_target_balance * 3 >= get_previous_epoch_total_balance(state) * 2:
|
||||||
state.current_justified_epoch = get_previous_epoch(state)
|
state.current_justified_epoch = get_previous_epoch(state)
|
||||||
state.current_justified_root = get_block_root(state, get_epoch_start_slot(state.current_justified_epoch))
|
state.current_justified_root = get_block_root(state, get_epoch_start_slot(state.current_justified_epoch))
|
||||||
state.justification_bitfield |= (1 << 1)
|
state.justification_bitfield |= (1 << 1)
|
||||||
current_boundary_attesting_balance = get_attesting_balance(state, get_current_epoch_boundary_attestations(state))
|
current_epoch_matching_target_balance = get_attesting_balance(state, get_current_epoch_matching_target_attestations(state))
|
||||||
if current_boundary_attesting_balance * 3 >= get_current_total_balance(state) * 2:
|
if current_epoch_matching_target_balance * 3 >= get_current_epoch_total_balance(state) * 2:
|
||||||
state.current_justified_epoch = get_current_epoch(state)
|
state.current_justified_epoch = get_current_epoch(state)
|
||||||
state.current_justified_root = get_block_root(state, get_epoch_start_slot(state.current_justified_epoch))
|
state.current_justified_root = get_block_root(state, get_epoch_start_slot(state.current_justified_epoch))
|
||||||
state.justification_bitfield |= (1 << 0)
|
state.justification_bitfield |= (1 << 0)
|
||||||
|
@ -1771,80 +1774,58 @@ def maybe_reset_eth1_period(state: BeaconState) -> None:
|
||||||
|
|
||||||
#### Rewards and penalties
|
#### Rewards and penalties
|
||||||
|
|
||||||
First, we define some additional helpers:
|
We first define a helper:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def get_base_reward_from_total_balance(state: BeaconState, total_balance: Gwei, index: ValidatorIndex) -> Gwei:
|
def get_base_reward(state: BeaconState, total_balance: Gwei, index: ValidatorIndex) -> Gwei:
|
||||||
if total_balance == 0:
|
if total_balance == 0:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
adjusted_quotient = integer_squareroot(total_balance) // BASE_REWARD_QUOTIENT
|
adjusted_quotient = integer_squareroot(total_balance) // BASE_REWARD_QUOTIENT
|
||||||
return get_effective_balance(state, index) // adjusted_quotient // 5
|
return get_effective_balance(state, index) // adjusted_quotient // BASE_REWARDS_PER_EPOCH
|
||||||
```
|
```
|
||||||
|
|
||||||
```python
|
|
||||||
def get_base_reward(state: BeaconState, index: ValidatorIndex) -> Gwei:
|
|
||||||
return get_base_reward_from_total_balance(state, get_previous_total_balance(state), index)
|
|
||||||
```
|
|
||||||
|
|
||||||
```python
|
|
||||||
def get_inactivity_penalty(state: BeaconState, index: ValidatorIndex, epochs_since_finality: int) -> Gwei:
|
|
||||||
if epochs_since_finality <= 4:
|
|
||||||
extra_penalty = 0
|
|
||||||
else:
|
|
||||||
extra_penalty = get_effective_balance(state, index) * epochs_since_finality // INACTIVITY_PENALTY_QUOTIENT // 2
|
|
||||||
return get_base_reward(state, index) + extra_penalty
|
|
||||||
```
|
|
||||||
|
|
||||||
Note: When applying penalties in the following balance recalculations, implementers should make sure the `uint64` does not underflow.
|
|
||||||
|
|
||||||
##### Justification and finalization
|
##### Justification and finalization
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def get_justification_and_finalization_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]:
|
def get_justification_and_finalization_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]:
|
||||||
current_epoch = get_current_epoch(state)
|
previous_epoch = get_previous_epoch(state)
|
||||||
epochs_since_finality = current_epoch + 1 - state.finalized_epoch
|
|
||||||
rewards = [0 for index in range(len(state.validator_registry))]
|
|
||||||
penalties = [0 for index in range(len(state.validator_registry))]
|
|
||||||
# Some helper variables
|
|
||||||
boundary_attestations = get_previous_epoch_boundary_attestations(state)
|
|
||||||
boundary_attesting_balance = get_attesting_balance(state, boundary_attestations)
|
|
||||||
total_balance = get_previous_total_balance(state)
|
|
||||||
total_attesting_balance = get_attesting_balance(state, state.previous_epoch_attestations)
|
|
||||||
matching_head_attestations = get_previous_epoch_matching_head_attestations(state)
|
|
||||||
matching_head_balance = get_attesting_balance(state, matching_head_attestations)
|
|
||||||
eligible_validators = [
|
eligible_validators = [
|
||||||
index for index, validator in enumerate(state.validator_registry)
|
index for index, validator in enumerate(state.validator_registry)
|
||||||
if (
|
if (
|
||||||
is_active_validator(validator, current_epoch) or
|
is_active_validator(validator, previous_epoch) or
|
||||||
(validator.slashed and current_epoch < validator.withdrawable_epoch)
|
(validator.slashed and previous_epoch < validator.withdrawable_epoch)
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
# Process rewards or penalties for all validators
|
rewards = [0 for index in range(len(state.validator_registry))]
|
||||||
|
penalties = [0 for index in range(len(state.validator_registry))]
|
||||||
for index in eligible_validators:
|
for index in eligible_validators:
|
||||||
base_reward = get_base_reward(state, index)
|
base_reward = get_base_reward(state, get_previous_epoch_total_balance(state), index)
|
||||||
# Expected FFG source
|
|
||||||
|
# Micro-incentives for matching FFG source, matching FFG target, and matching head
|
||||||
|
for attestations in (
|
||||||
|
state.previous_epoch_attestations, # Matching FFG source
|
||||||
|
get_previous_epoch_matching_target_attestations(state), # Matching FFG target
|
||||||
|
get_previous_epoch_matching_head_attestations(state), # Matching head
|
||||||
|
):
|
||||||
|
if index in get_unslashed_attesting_indices(state, attestations):
|
||||||
|
rewards[index] += base_reward * get_attesting_balance(state, attestations) // get_previous_epoch_total_balance(state)
|
||||||
|
else:
|
||||||
|
penalties[index] += base_reward
|
||||||
|
|
||||||
|
# Inclusion delay micro-penalty
|
||||||
if index in get_unslashed_attesting_indices(state, state.previous_epoch_attestations):
|
if index in get_unslashed_attesting_indices(state, state.previous_epoch_attestations):
|
||||||
rewards[index] += base_reward * total_attesting_balance // total_balance
|
|
||||||
# Inclusion speed bonus
|
|
||||||
earliest_attestation = get_earliest_attestation(state, state.previous_epoch_attestations, index)
|
earliest_attestation = get_earliest_attestation(state, state.previous_epoch_attestations, index)
|
||||||
inclusion_delay = earliest_attestation.inclusion_slot - earliest_attestation.data.slot
|
inclusion_delay = earliest_attestation.inclusion_slot - earliest_attestation.data.slot
|
||||||
rewards[index] += base_reward * MIN_ATTESTATION_INCLUSION_DELAY // inclusion_delay
|
penalties[index] += base_reward * (1 - MIN_ATTESTATION_INCLUSION_DELAY // inclusion_delay)
|
||||||
else:
|
|
||||||
penalties[index] += base_reward
|
# Inactivity penalty
|
||||||
# Expected FFG target
|
epochs_since_finality = previous_epoch + 1 - state.finalized_epoch
|
||||||
if index in get_unslashed_attesting_indices(state, boundary_attestations):
|
if epochs_since_finality > MAX_FINALITY_LOOKBACK:
|
||||||
rewards[index] += base_reward * boundary_attesting_balance // total_balance
|
penalties[index] += BASE_REWARDS_PER_EPOCH * base_reward
|
||||||
else:
|
if index not in get_unslashed_attesting_indices(state, get_previous_epoch_matching_target_attestations(state)):
|
||||||
penalties[index] += get_inactivity_penalty(state, index, epochs_since_finality)
|
penalties[index] += get_effective_balance(state, index) * epochs_since_finality // INACTIVITY_PENALTY_QUOTIENT
|
||||||
# Expected head
|
|
||||||
if index in get_unslashed_attesting_indices(state, matching_head_attestations):
|
|
||||||
rewards[index] += base_reward * matching_head_balance // total_balance
|
|
||||||
else:
|
|
||||||
penalties[index] += base_reward
|
|
||||||
# Take away max rewards if we're not finalizing
|
|
||||||
if epochs_since_finality > 4:
|
|
||||||
penalties[index] += base_reward * 4
|
|
||||||
return [rewards, penalties]
|
return [rewards, penalties]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -1862,10 +1843,11 @@ def get_crosslink_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]:
|
||||||
participating_balance = get_total_balance(state, participants)
|
participating_balance = get_total_balance(state, participants)
|
||||||
total_balance = get_total_balance(state, crosslink_committee)
|
total_balance = get_total_balance(state, crosslink_committee)
|
||||||
for index in crosslink_committee:
|
for index in crosslink_committee:
|
||||||
|
base_reward = get_base_reward(state, get_previous_epoch_total_balance(state), index)
|
||||||
if index in participants:
|
if index in participants:
|
||||||
rewards[index] += get_base_reward(state, index) * participating_balance // total_balance
|
rewards[index] += base_reward * participating_balance // total_balance
|
||||||
else:
|
else:
|
||||||
penalties[index] += get_base_reward(state, index)
|
penalties[index] += base_reward
|
||||||
return [rewards, penalties]
|
return [rewards, penalties]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -2176,7 +2158,7 @@ def process_proposer_attestation_rewards(state: BeaconState) -> None:
|
||||||
for pending_attestations in (state.previous_epoch_attestations, state.current_epoch_attestations):
|
for pending_attestations in (state.previous_epoch_attestations, state.current_epoch_attestations):
|
||||||
for index in get_unslashed_attesting_indices(state, pending_attestations):
|
for index in get_unslashed_attesting_indices(state, pending_attestations):
|
||||||
if get_earliest_attestation(state, pending_attestations, index).inclusion_slot == state.slot:
|
if get_earliest_attestation(state, pending_attestations, index).inclusion_slot == state.slot:
|
||||||
base_reward = get_base_reward_from_total_balance(state, get_current_total_balance(state), index)
|
base_reward = get_base_reward(state, get_current_epoch_total_balance(state), index)
|
||||||
increase_balance(state, proposer_index, base_reward // PROPOSER_REWARD_QUOTIENT)
|
increase_balance(state, proposer_index, base_reward // PROPOSER_REWARD_QUOTIENT)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue