Merge pull request #2399 from ethereum/keep-inactivity-function

dankrad altair review -- extended and tested
This commit is contained in:
Danny Ryan 2021-05-12 12:38:26 -06:00 committed by GitHub
commit 51a5474e6d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 41 additions and 51 deletions

View File

@ -28,7 +28,6 @@
- [`Predicates`](#predicates) - [`Predicates`](#predicates)
- [`eth2_fast_aggregate_verify`](#eth2_fast_aggregate_verify) - [`eth2_fast_aggregate_verify`](#eth2_fast_aggregate_verify)
- [Misc](#misc-2) - [Misc](#misc-2)
- [`get_flag_indices_and_weights`](#get_flag_indices_and_weights)
- [`add_flag`](#add_flag) - [`add_flag`](#add_flag)
- [`has_flag`](#has_flag) - [`has_flag`](#has_flag)
- [Beacon state accessors](#beacon-state-accessors) - [Beacon state accessors](#beacon-state-accessors)
@ -99,6 +98,7 @@ Altair is the first beacon chain hard fork. Its main features are:
| Name | Value | | Name | Value |
| - | - | | - | - |
| `G2_POINT_AT_INFINITY` | `BLSSignature(b'\xc0' + b'\x00' * 95)` | | `G2_POINT_AT_INFINITY` | `BLSSignature(b'\xc0' + b'\x00' * 95)` |
| `PARTICIPATION_FLAG_WEIGHTS` | `[TIMELY_HEAD_WEIGHT, TIMELY_SOURCE_WEIGHT, TIMELY_TARGET_WEIGHT]` |
## Configuration ## Configuration
@ -232,20 +232,6 @@ def eth2_fast_aggregate_verify(pubkeys: Sequence[BLSPubkey], message: Bytes32, s
### Misc ### Misc
#### `get_flag_indices_and_weights`
```python
def get_flag_indices_and_weights() -> Sequence[Tuple[int, uint64]]:
"""
Return paired tuples of participation flag indices along with associated incentivization weights.
"""
return (
(TIMELY_HEAD_FLAG_INDEX, TIMELY_HEAD_WEIGHT),
(TIMELY_SOURCE_FLAG_INDEX, TIMELY_SOURCE_WEIGHT),
(TIMELY_TARGET_FLAG_INDEX, TIMELY_TARGET_WEIGHT),
)
```
#### `add_flag` #### `add_flag`
```python ```python
@ -277,6 +263,8 @@ def get_next_sync_committee_indices(state: BeaconState) -> Sequence[ValidatorInd
""" """
Return the sequence of sync committee indices (which may include duplicate indices) Return the sequence of sync committee indices (which may include duplicate indices)
for the next sync committee, given a ``state`` at a sync committee period boundary. for the next sync committee, given a ``state`` at a sync committee period boundary.
Note: Committee can contain duplicate indices for small validator sets (< SYNC_COMMITTEE_SIZE + 128)
""" """
epoch = Epoch(get_current_epoch(state) + 1) epoch = Epoch(get_current_epoch(state) + 1)
@ -291,7 +279,7 @@ def get_next_sync_committee_indices(state: BeaconState) -> Sequence[ValidatorInd
candidate_index = active_validator_indices[shuffled_index] candidate_index = active_validator_indices[shuffled_index]
random_byte = hash(seed + uint_to_bytes(uint64(i // 32)))[i % 32] random_byte = hash(seed + uint_to_bytes(uint64(i // 32)))[i % 32]
effective_balance = state.validators[candidate_index].effective_balance effective_balance = state.validators[candidate_index].effective_balance
if effective_balance * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE * random_byte: # Sample with replacement if effective_balance * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE * random_byte:
sync_committee_indices.append(candidate_index) sync_committee_indices.append(candidate_index)
i += 1 i += 1
return sync_committee_indices return sync_committee_indices
@ -312,8 +300,8 @@ def get_next_sync_committee(state: BeaconState) -> SyncCommittee:
returns duplicate indices. Implementations must take care when handling returns duplicate indices. Implementations must take care when handling
optimizations relating to aggregation and verification in the presence of duplicates. optimizations relating to aggregation and verification in the presence of duplicates.
Note: This function should only be called at sync committee period boundaries, as Note: This function should only be called at sync committee period boundaries by ``process_sync_committee_updates``
``get_next_sync_committee_indices`` is not stable within a given period. as ``get_next_sync_committee_indices`` is not stable within a given period.
""" """
indices = get_next_sync_committee_indices(state) indices = get_next_sync_committee_indices(state)
pubkeys = [state.validators[index].pubkey for index in indices] pubkeys = [state.validators[index].pubkey for index in indices]
@ -365,35 +353,31 @@ def get_unslashed_participating_indices(state: BeaconState, flag_index: int, epo
#### `get_flag_index_deltas` #### `get_flag_index_deltas`
```python ```python
def get_flag_index_deltas(state: BeaconState, flag_index: int, weight: uint64) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: def get_flag_index_deltas(state: BeaconState, flag_index: int) -> Tuple[Sequence[Gwei], Sequence[Gwei]]:
""" """
Return the deltas for a given ``flag_index`` scaled by ``weight`` by scanning through the participation flags. Return the deltas for a given ``flag_index`` scaled by ``weight`` by scanning through the participation flags.
""" """
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)) previous_epoch = get_previous_epoch(state)
increment = EFFECTIVE_BALANCE_INCREMENT # Factored out from balances to avoid uint64 overflow unslashed_participating_indices = get_unslashed_participating_indices(state, flag_index, previous_epoch)
unslashed_participating_increments = get_total_balance(state, unslashed_participating_indices) // increment weight = PARTICIPATION_FLAG_WEIGHTS[flag_index]
active_increments = get_total_active_balance(state) // increment unslashed_participating_balance = get_total_balance(state, unslashed_participating_indices)
unslashed_participating_increments = unslashed_participating_balance // EFFECTIVE_BALANCE_INCREMENT
active_increments = get_total_active_balance(state) // EFFECTIVE_BALANCE_INCREMENT
for index in get_eligible_validator_indices(state): for index in get_eligible_validator_indices(state):
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 not is_in_inactivity_leak(state):
# This flag reward cancels the inactivity penalty corresponding to the flag index
rewards[index] += Gwei(base_reward * weight // WEIGHT_DENOMINATOR)
else:
reward_numerator = base_reward * weight * unslashed_participating_increments reward_numerator = base_reward * weight * unslashed_participating_increments
rewards[index] += Gwei(reward_numerator // (active_increments * WEIGHT_DENOMINATOR)) rewards[index] += Gwei(reward_numerator // (active_increments * WEIGHT_DENOMINATOR))
else: elif flag_index != TIMELY_HEAD_FLAG_INDEX:
penalties[index] += Gwei(base_reward * weight // WEIGHT_DENOMINATOR) penalties[index] += Gwei(base_reward * weight // WEIGHT_DENOMINATOR)
return rewards, penalties return rewards, penalties
``` ```
#### Modified `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`.
```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]]:
""" """
@ -405,9 +389,6 @@ def get_inactivity_penalty_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], S
previous_epoch = get_previous_epoch(state) previous_epoch = get_previous_epoch(state)
matching_target_indices = get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, previous_epoch) matching_target_indices = get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, previous_epoch)
for index in get_eligible_validator_indices(state): for index in get_eligible_validator_indices(state):
for (_, weight) in get_flag_indices_and_weights():
# This inactivity penalty cancels the flag reward corresponding to the flag index
penalties[index] += Gwei(get_base_reward(state, index) * weight // WEIGHT_DENOMINATOR)
if index not in matching_target_indices: if index not in matching_target_indices:
penalty_numerator = state.validators[index].effective_balance * state.inactivity_scores[index] penalty_numerator = state.validators[index].effective_balance * state.inactivity_scores[index]
penalty_denominator = INACTIVITY_SCORE_BIAS * INACTIVITY_PENALTY_QUOTIENT_ALTAIR penalty_denominator = INACTIVITY_SCORE_BIAS * INACTIVITY_PENALTY_QUOTIENT_ALTAIR
@ -501,7 +482,7 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None:
# Update epoch participation flags # Update epoch participation flags
proposer_reward_numerator = 0 proposer_reward_numerator = 0
for index in get_attesting_indices(state, data, attestation.aggregation_bits): for index in get_attesting_indices(state, data, attestation.aggregation_bits):
for flag_index, weight in get_flag_indices_and_weights(): for flag_index, weight in enumerate(PARTICIPATION_FLAG_WEIGHTS):
if flag_index in participation_flag_indices and not has_flag(epoch_participation[index], flag_index): if flag_index in participation_flag_indices and not has_flag(epoch_participation[index], flag_index):
epoch_participation[index] = add_flag(epoch_participation[index], flag_index) epoch_participation[index] = add_flag(epoch_participation[index], flag_index)
proposer_reward_numerator += get_base_reward(state, index) * weight proposer_reward_numerator += get_base_reward(state, index) * weight
@ -643,8 +624,7 @@ def process_rewards_and_penalties(state: BeaconState) -> None:
if get_current_epoch(state) == GENESIS_EPOCH: if get_current_epoch(state) == GENESIS_EPOCH:
return return
flag_indices_and_numerators = get_flag_indices_and_weights() flag_deltas = [get_flag_index_deltas(state, flag_index) for flag_index in range(len(PARTICIPATION_FLAG_WEIGHTS))]
flag_deltas = [get_flag_index_deltas(state, index, numerator) for (index, numerator) in flag_indices_and_numerators]
deltas = flag_deltas + [get_inactivity_penalty_deltas(state)] deltas = flag_deltas + [get_inactivity_penalty_deltas(state)]
for (rewards, penalties) in deltas: for (rewards, penalties) in deltas:
for index in range(len(state.validators)): for index in range(len(state.validators)):

View File

@ -62,13 +62,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_index_deltas(state, spec.TIMELY_SOURCE_FLAG_INDEX, spec.TIMELY_SOURCE_WEIGHT) return spec.get_flag_index_deltas(state, spec.TIMELY_SOURCE_FLAG_INDEX)
def get_head_deltas(state): def get_head_deltas(state):
return spec.get_flag_index_deltas(state, spec.TIMELY_HEAD_FLAG_INDEX, spec.TIMELY_HEAD_WEIGHT) return spec.get_flag_index_deltas(state, spec.TIMELY_HEAD_FLAG_INDEX)
def get_target_deltas(state): def get_target_deltas(state):
return spec.get_flag_index_deltas(state, spec.TIMELY_TARGET_FLAG_INDEX, spec.TIMELY_TARGET_WEIGHT) return spec.get_flag_index_deltas(state, spec.TIMELY_TARGET_FLAG_INDEX)
yield from run_attestation_component_deltas( yield from run_attestation_component_deltas(
spec, spec,
@ -133,14 +133,23 @@ def run_attestation_component_deltas(spec, state, component_delta_fn, matching_a
validator = state.validators[index] validator = state.validators[index]
enough_for_reward = has_enough_for_reward(spec, state, index) enough_for_reward = has_enough_for_reward(spec, state, index)
if index in matching_indices and not validator.slashed: if index in matching_indices and not validator.slashed:
if enough_for_reward: if is_post_altair(spec):
assert rewards[index] > 0 if not spec.is_in_inactivity_leak(state) and enough_for_reward:
assert rewards[index] > 0
else:
assert rewards[index] == 0
else: else:
assert rewards[index] == 0 if enough_for_reward:
assert rewards[index] > 0
else:
assert rewards[index] == 0
assert penalties[index] == 0 assert penalties[index] == 0
else: else:
assert rewards[index] == 0 assert rewards[index] == 0
if enough_for_reward: if is_post_altair(spec) and 'head' in deltas_name:
assert penalties[index] == 0
elif enough_for_reward:
assert penalties[index] > 0 assert penalties[index] > 0
else: else:
assert penalties[index] == 0 assert penalties[index] == 0
@ -225,18 +234,19 @@ def run_get_inactivity_penalty_deltas(spec, state):
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_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:
base_penalty = sum(
base_reward * numerator // spec.WEIGHT_DENOMINATOR
for (_, numerator) in spec.get_flag_indices_and_weights()
)
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
elif index in matching_attesting_indices or not has_enough_for_leak_penalty(spec, state, index): elif index in matching_attesting_indices or not has_enough_for_leak_penalty(spec, state, index):
assert penalties[index] == base_penalty if is_post_altair(spec):
assert penalties[index] == 0
else:
assert penalties[index] == base_penalty
else: else:
assert penalties[index] > base_penalty if is_post_altair(spec):
assert penalties[index] > 0
else:
assert penalties[index] > base_penalty
else: else:
assert penalties[index] == 0 assert penalties[index] == 0