remove global quotient penalties from fork spec [temp]

This commit is contained in:
Danny Ryan 2021-01-05 13:48:26 -07:00
parent 9c75c3819d
commit b8d3589b46
No known key found for this signature in database
GPG Key ID: 2765A792E42CE07A
2 changed files with 77 additions and 57 deletions

View File

@ -49,7 +49,8 @@
This is a patch implementing the first hard fork to the beacon chain, tentatively named HF1 pending a permanent name. It has three main features: This is a patch implementing the first hard fork to the beacon chain, tentatively named HF1 pending a permanent name. It has three main features:
* Light client support via sync committees * Light client support via sync committees
* Incentive accounting reforms, reducing spec complexity and reducing the cost of processing chains that have very little or zero participation for a long span of epochs * 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
* Fork choice rule changes to address weaknesses recently discovered in the existing fork choice * Fork choice rule changes to address weaknesses recently discovered in the existing fork choice
## Constants ## Constants
@ -78,9 +79,7 @@ The reward fractions add up to 7/8, leaving the remaining 1/8 for proposer rewar
| Name | Value | | Name | Value |
| - | - | | - | - |
| `PARTICIPATION_FLAGS_LENGTH` | `8` | | `PARTICIPATION_FLAGS_LENGTH` | `8` |
| `FLAGS_AND_NUMERATORS` | `((TIMELY_HEAD_FLAG, TIMELY_HEAD_NUMERATOR), (TIMELY_SOURCE_FLAG, TIMELY_SOURCE_NUMERATOR), (TIMELY_TARGET_FLAG, TIMELY_TARGET_NUMERATOR))` |
| `G2_POINT_AT_INFINITY` | `BLSSignature(b'\xc0' + b'\x00' * 95)` | | `G2_POINT_AT_INFINITY` | `BLSSignature(b'\xc0' + b'\x00' * 95)` |
| `GWEI_PER_ETH` | `10**9` |
## Configuration ## Configuration
@ -155,8 +154,6 @@ class BeaconState(Container):
# Light client sync committees # Light client sync committees
current_sync_committee: SyncCommittee current_sync_committee: SyncCommittee
next_sync_committee: SyncCommittee next_sync_committee: SyncCommittee
# Denominator to real-time-updated balances (NOT effective balances!)
balance_denominator: uint64
``` ```
### New containers ### New containers
@ -173,23 +170,6 @@ class SyncCommittee(Container):
### `Predicates` ### `Predicates`
#### `get_base_reward`
*Note*: The function `get_base_reward` is modified with the removal of `BASE_REWARDS_PER_EPOCH`. Additionally, it is split into `get_base_reward_per_eth` to allow penalties to be computed to the denominator.
```python
def get_base_reward(state: BeaconState, index: ValidatorIndex) -> Gwei:
return get_base_reward_per_eth(state) * state.validators[index].effective_balance // GWEI_PER_ETH
```
#### `get_base_reward_per_eth`
```python
def get_base_reward_per_eth(state: BeaconState) -> Gwei:
total_balance = get_total_active_balance(state)
return Gwei(GWEI_PER_ETH * BASE_REWARD_FACTOR // integer_squareroot(total_balance))
```
#### `eth2_fast_aggregate_verify` #### `eth2_fast_aggregate_verify`
```python ```python
@ -202,6 +182,21 @@ def eth2_fast_aggregate_verify(pubkeys: Sequence[BLSPubkey], message: Bytes32, s
return bls.FastAggregateVerify(pubkeys, message, signature) return bls.FastAggregateVerify(pubkeys, message, signature)
``` ```
### Misc
#### `flags_and_numerators`
```python
def get_flags_and_numerators() -> Sequence[Tuple[int, int]]:
return (
(TIMELY_HEAD_FLAG, TIMELY_HEAD_NUMERATOR),
(TIMELY_SOURCE_FLAG, TIMELY_SOURCE_NUMERATOR),
(TIMELY_TARGET_FLAG, TIMELY_TARGET_NUMERATOR)
)
```
### Beacon state accessors ### Beacon state accessors
#### `get_sync_committee_indices` #### `get_sync_committee_indices`
@ -246,6 +241,17 @@ def get_sync_committee(state: BeaconState, epoch: Epoch) -> SyncCommittee:
return SyncCommittee(pubkeys=pubkeys, pubkey_aggregates=aggregates) return SyncCommittee(pubkeys=pubkeys, pubkey_aggregates=aggregates)
``` ```
#### `get_base_reward`
*Note*: The function `get_base_reward` is modified with the removal of `BASE_REWARDS_PER_EPOCH`.
```python
def get_base_reward(state: BeaconState, index: ValidatorIndex) -> Gwei:
total_balance = get_total_active_balance(state)
effective_balance = state.validators[index].effective_balance
return Gwei(effective_balance * BASE_REWARD_FACTOR // integer_squareroot(total_balance))
```
#### `get_unslashed_participating_indices` #### `get_unslashed_participating_indices`
```python ```python
@ -255,20 +261,24 @@ def get_unslashed_participating_indices(state: BeaconState, flag: uint8, epoch:
epoch_participation = state.current_epoch_participation epoch_participation = state.current_epoch_participation
else: else:
epoch_participation = state.previous_epoch_participation epoch_participation = state.previous_epoch_participation
participating_indices = [index for index in get_active_validator_indices(state, epoch) participating_indices = [
if epoch_participation[index][flag]] index for index in get_active_validator_indices(state, epoch)
if epoch_participation[index][flag]
]
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_deltas`
```python ```python
def get_flag_deltas(state: BeaconState, flag: uint8, numerator: uint64) -> Tuple[Sequence[Gwei], Gwei]: def get_flag_deltas(state: BeaconState, flag: uint8, numerator: uint64) -> Tuple[Sequence[Gwei], Sequence[Gwei]]:
""" """
Computes the rewards and penalties associated with a particular duty, by scanning through the participation Computes 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. 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)
unslashed_participating_indices = get_unslashed_participating_indices(state, flag, get_previous_epoch(state)) unslashed_participating_indices = get_unslashed_participating_indices(state, flag, 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
@ -281,10 +291,12 @@ def get_flag_deltas(state: BeaconState, flag: uint8, numerator: uint64) -> Tuple
rewards[index] = base_reward * numerator // REWARD_DENOMINATOR rewards[index] = base_reward * numerator // REWARD_DENOMINATOR
else: else:
rewards[index] = ( rewards[index] = (
(base_reward * unslashed_participating_increments // active_increments + base_reward) (base_reward * numerator * unslashed_participating_increments)
* numerator // REWARD_DENOMINATOR // (active_increments * REWARD_DENOMINATOR)
) )
return rewards, get_base_reward_per_eth(state) * numerator // REWARD_DENOMINATOR else:
penalties[index] = base_reward * numerator // REWARD_DENOMINATOR
return rewards, penalties
``` ```
##### `get_inactivity_penalty_deltas` ##### `get_inactivity_penalty_deltas`
@ -298,17 +310,21 @@ def get_inactivity_penalty_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], S
flags to determine who participated and who did not, applying the leak penalty globally and applying flags to determine who participated and who did not, applying the leak penalty globally and applying
compensatory rewards to participants. compensatory rewards to participants.
""" """
rewards = [Gwei(0) for _ in range(len(state.validators))] penalties = [Gwei(0) for _ in range(len(state.validators))]
if is_in_inactivity_leak(state): if is_in_inactivity_leak(state):
reward_numerator_sum = sum(numerator for (_, numerator) in FLAGS_AND_NUMERATORS) reward_numerator_sum = sum(numerator for (_, numerator) in get_flags_and_numerators())
matching_target_attesting_indices = get_unslashed_participating_indices( matching_target_attesting_indices = get_unslashed_participating_indices(
state, TIMELY_TARGET_FLAG, get_previous_epoch(state) state, TIMELY_TARGET_FLAG, 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 validator is performing optimally this cancels all attestation rewards for a neutral balance
penalties[index] += Gwei(get_base_reward(state, index) * reward_numerator_sum // REWARD_DENOMINATOR)
if index not in matching_target_attesting_indices:
effective_balance = state.validators[index].effective_balance effective_balance = state.validators[index].effective_balance
rewards[index] += Gwei(effective_balance * get_finality_delay(state) // INACTIVITY_PENALTY_QUOTIENT) penalties[index] += Gwei(effective_balance * get_finality_delay(state) // INACTIVITY_PENALTY_QUOTIENT)
return rewards, Gwei(GWEI_PER_ETH * get_finality_delay(state) // INACTIVITY_PENALTY_QUOTIENT)
rewards = [Gwei(0) for _ in range(len(state.validators))]
return rewards, penalties
``` ```
### Block processing ### Block processing
@ -334,19 +350,26 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None:
assert data.target.epoch == compute_epoch_at_slot(data.slot) assert data.target.epoch == compute_epoch_at_slot(data.slot)
assert data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot <= data.slot + SLOTS_PER_EPOCH assert data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot <= data.slot + SLOTS_PER_EPOCH
assert data.index < get_committee_count_per_slot(state, data.target.epoch) assert data.index < get_committee_count_per_slot(state, data.target.epoch)
committee = get_beacon_committee(state, data.slot, data.index) committee = get_beacon_committee(state, data.slot, data.index)
assert len(attestation.aggregation_bits) == len(committee) assert len(attestation.aggregation_bits) == len(committee)
if data.target.epoch == get_current_epoch(state): if data.target.epoch == get_current_epoch(state):
epoch_participation = state.current_epoch_participation epoch_participation = state.current_epoch_participation
justified_checkpoint = state.current_justified_checkpoint justified_checkpoint = state.current_justified_checkpoint
else: else:
epoch_participation = state.previous_epoch_participation epoch_participation = state.previous_epoch_participation
justified_checkpoint = state.previous_justified_checkpoint justified_checkpoint = state.previous_justified_checkpoint
# Matching roots # Matching roots
is_matching_head = data.beacon_block_root == get_block_root_at_slot(state, data.slot) is_matching_head = data.beacon_block_root == get_block_root_at_slot(state, data.slot)
is_matching_source = data.source == justified_checkpoint is_matching_source = data.source == justified_checkpoint
is_matching_target = data.target.root == get_block_root(state, data.target.epoch) is_matching_target = data.target.root == get_block_root(state, data.target.epoch)
assert is_matching_source assert is_matching_source
# Verify signature
assert is_valid_indexed_attestation(state, get_indexed_attestation(state, attestation))
# Participation flags # Participation flags
participation_flags = [] participation_flags = []
if is_matching_head and state.slot <= data.slot + MIN_ATTESTATION_INCLUSION_DELAY: if is_matching_head and state.slot <= data.slot + MIN_ATTESTATION_INCLUSION_DELAY:
@ -355,18 +378,18 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None:
participation_flags.append(TIMELY_SOURCE_FLAG) participation_flags.append(TIMELY_SOURCE_FLAG)
if is_matching_target and state.slot <= data.slot + SLOTS_PER_EPOCH: if is_matching_target and state.slot <= data.slot + SLOTS_PER_EPOCH:
participation_flags.append(TIMELY_TARGET_FLAG) participation_flags.append(TIMELY_TARGET_FLAG)
# 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, numerator in FLAGS_AND_NUMERATORS: for flag, numerator in get_flags_and_numerators():
if flag in participation_flags and not epoch_participation[index][flag]: if flag in participation_flags and not epoch_participation[index][flag]:
epoch_participation[index][flag] = True epoch_participation[index][flag] = True
proposer_reward_numerator += get_base_reward(state, index) * numerator proposer_reward_numerator += get_base_reward(state, index) * numerator
# Reward proposer # Reward proposer
proposer_reward = Gwei(proposer_reward_numerator // (REWARD_DENOMINATOR * PROPOSER_REWARD_QUOTIENT)) proposer_reward = Gwei(proposer_reward_numerator // (REWARD_DENOMINATOR * PROPOSER_REWARD_QUOTIENT))
increase_balance(state, get_beacon_proposer_index(state), proposer_reward) increase_balance(state, get_beacon_proposer_index(state), proposer_reward)
# Verify signature
assert is_valid_indexed_attestation(state, get_indexed_attestation(state, attestation))
``` ```
@ -406,6 +429,7 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None:
# Add validator and balance entries # Add validator and balance entries
state.validators.append(get_validator_from_deposit(state, deposit)) state.validators.append(get_validator_from_deposit(state, deposit))
state.balances.append(amount) state.balances.append(amount)
# [Added in hf-1] Initialize empty participation flags for new validator
state.previous_epoch_participation.append(Bitvector[PARTICIPATION_FLAGS_LENGTH]()) state.previous_epoch_participation.append(Bitvector[PARTICIPATION_FLAGS_LENGTH]())
state.current_epoch_participation.append(Bitvector[PARTICIPATION_FLAGS_LENGTH]()) state.current_epoch_participation.append(Bitvector[PARTICIPATION_FLAGS_LENGTH]())
else: else:
@ -458,6 +482,7 @@ def process_justification_and_finalization(state: BeaconState) -> None:
current_epoch = get_current_epoch(state) current_epoch = get_current_epoch(state)
old_previous_justified_checkpoint = state.previous_justified_checkpoint old_previous_justified_checkpoint = state.previous_justified_checkpoint
old_current_justified_checkpoint = state.current_justified_checkpoint old_current_justified_checkpoint = state.current_justified_checkpoint
# Process justifications # Process justifications
state.previous_justified_checkpoint = state.current_justified_checkpoint state.previous_justified_checkpoint = state.current_justified_checkpoint
state.justification_bits[1:] = state.justification_bits[:JUSTIFICATION_BITS_LENGTH - 1] state.justification_bits[1:] = state.justification_bits[:JUSTIFICATION_BITS_LENGTH - 1]
@ -472,6 +497,7 @@ def process_justification_and_finalization(state: BeaconState) -> None:
state.current_justified_checkpoint = Checkpoint(epoch=current_epoch, state.current_justified_checkpoint = Checkpoint(epoch=current_epoch,
root=get_block_root(state, current_epoch)) root=get_block_root(state, current_epoch))
state.justification_bits[0] = 0b1 state.justification_bits[0] = 0b1
# Process finalizations # Process finalizations
bits = state.justification_bits bits = state.justification_bits
# The 2nd/3rd/4th most recent epochs are justified, the 2nd using the 4th as source # The 2nd/3rd/4th most recent epochs are justified, the 2nd using the 4th as source
@ -497,22 +523,17 @@ 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, numerator) for (flag, numerator) in FLAGS_AND_NUMERATORS] flag_deltas = [get_flag_deltas(state, flag, numerator) for (flag, numerator) in get_flags_and_numerators()]
deltas = flag_deltas + [get_inactivity_penalty_deltas(state)] deltas = flag_deltas + [get_inactivity_penalty_deltas(state)]
for (rewards, penalty) in deltas: for (rewards, penalties) in deltas:
for index in range(len(state.validators)): for index in range(len(state.validators)):
increase_balance(state, ValidatorIndex(index), rewards[index]) increase_balance(state, ValidatorIndex(index), rewards[index])
# Bounds-friendly expansion for `denom *= (1 + penalty // GWEI_PER_ETH)` decrease_balance(state, ValidatorIndex(index), penalties[index])
state.balance_denominator = (
state.balance_denominator +
penalty +
(state.balance_denominator - GWEI_PER_ETH) * penalty // GWEI_PER_ETH
)
``` ```
#### Final updates #### Final updates
*Note*: The function `process_final_updates` is modified to handle sync committee updates, replacement of `PendingAttestation`s with participation flags, and a sliding-denominator-aware hysteresis mechanism. *Note*: The function `process_final_updates` is modified to handle sync committee updates and with the replacement of `PendingAttestation`s with participation flags.
```python ```python
def process_final_updates(state: BeaconState) -> None: def process_final_updates(state: BeaconState) -> None:
@ -521,23 +542,21 @@ def process_final_updates(state: BeaconState) -> None:
# Reset eth1 data votes # Reset eth1 data votes
if next_epoch % EPOCHS_PER_ETH1_VOTING_PERIOD == 0: if next_epoch % EPOCHS_PER_ETH1_VOTING_PERIOD == 0:
state.eth1_data_votes = [] state.eth1_data_votes = []
# Update sync committees # [Added in hf-1] Update sync committees
if next_epoch % EPOCHS_PER_SYNC_COMMITTEE_PERIOD == 0: if next_epoch % EPOCHS_PER_SYNC_COMMITTEE_PERIOD == 0:
state.current_sync_committee = state.next_sync_committee state.current_sync_committee = state.next_sync_committee
state.next_sync_committee = get_sync_committee(state, next_epoch + EPOCHS_PER_SYNC_COMMITTEE_PERIOD) state.next_sync_committee = get_sync_committee(state, next_epoch + EPOCHS_PER_SYNC_COMMITTEE_PERIOD)
# Update effective balances with hysteresis # Update effective balances with hysteresis
for index, validator in enumerate(state.validators): for index, validator in enumerate(state.validators):
balance_increments = state.balances[index] * HYSTERESIS_QUOTIENT // state.balance_denominator balance = state.balances[index]
effective_increments = validator.effective_balance // GWEI_PER_ETH * HYSTERESIS_QUOTIENT HYSTERESIS_INCREMENT = uint64(EFFECTIVE_BALANCE_INCREMENT // HYSTERESIS_QUOTIENT)
DOWNWARD_THRESHOLD = HYSTERESIS_INCREMENT * HYSTERESIS_DOWNWARD_MULTIPLIER
UPWARD_THRESHOLD = HYSTERESIS_INCREMENT * HYSTERESIS_UPWARD_MULTIPLIER
if ( if (
balance_increments + HYSTERESIS_DOWNWARD_MULTIPLIER < effective_increments balance + DOWNWARD_THRESHOLD < validator.effective_balance
or effective_increments + HYSTERESIS_UPWARD_MULTIPLIER < balance_increments or validator.effective_balance + UPWARD_THRESHOLD < balance
or get_current_epoch(state) == validator.withdrawable_epoch
): ):
validator.effective_balance = (state.balances[index] // state.balance_denominator) * GWEI_PER_ETH validator.effective_balance = min(balance - balance % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE)
if state.balance_denominator >= 2 * GWEI_PER_ETH:
state.balances = [x // 2 for x in state.balances]
state.balance_denominator //= 2
# Reset slashings # Reset slashings
state.slashings[next_epoch % EPOCHS_PER_SLASHINGS_VECTOR] = Gwei(0) state.slashings[next_epoch % EPOCHS_PER_SLASHINGS_VECTOR] = Gwei(0)
# Set randao mix # Set randao mix
@ -546,6 +565,8 @@ def process_final_updates(state: BeaconState) -> None:
if next_epoch % (SLOTS_PER_HISTORICAL_ROOT // SLOTS_PER_EPOCH) == 0: if next_epoch % (SLOTS_PER_HISTORICAL_ROOT // SLOTS_PER_EPOCH) == 0:
historical_batch = HistoricalBatch(block_roots=state.block_roots, state_roots=state.state_roots) historical_batch = HistoricalBatch(block_roots=state.block_roots, state_roots=state.state_roots)
state.historical_roots.append(hash_tree_root(historical_batch)) state.historical_roots.append(hash_tree_root(historical_batch))
# Rotate current/previous epoch participation flags # [Added in hf-1] Rotate current/previous epoch participation flags
state.previous_epoch_participation = state.current_epoch_participation state.previous_epoch_participation = state.current_epoch_participation
state.current_epoch_participation = [Bitvector[PARTICIPATION_FLAGS_LENGTH]() for _ in range(len(state.validators))] state.current_epoch_participation = [Bitvector[PARTICIPATION_FLAGS_LENGTH]() for _ in range(len(state.validators))]
# [Removed in hf-1] Rotate current/previous epoch attestations
```

View File

@ -74,7 +74,6 @@ def upgrade_to_lightclient_patch(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,
balance_denominator=GWEI_PER_ETH,
) )
# 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))