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:
* 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
## Constants
@ -78,9 +79,7 @@ The reward fractions add up to 7/8, leaving the remaining 1/8 for proposer rewar
| Name | Value |
| - | - |
| `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)` |
| `GWEI_PER_ETH` | `10**9` |
## Configuration
@ -155,8 +154,6 @@ class BeaconState(Container):
# Light client sync committees
current_sync_committee: SyncCommittee
next_sync_committee: SyncCommittee
# Denominator to real-time-updated balances (NOT effective balances!)
balance_denominator: uint64
```
### New containers
@ -173,23 +170,6 @@ class SyncCommittee(Container):
### `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`
```python
@ -202,6 +182,21 @@ def eth2_fast_aggregate_verify(pubkeys: Sequence[BLSPubkey], message: Bytes32, s
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
#### `get_sync_committee_indices`
@ -246,6 +241,17 @@ def get_sync_committee(state: BeaconState, epoch: Epoch) -> SyncCommittee:
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`
```python
@ -255,20 +261,24 @@ def get_unslashed_participating_indices(state: BeaconState, flag: uint8, epoch:
epoch_participation = state.current_epoch_participation
else:
epoch_participation = state.previous_epoch_participation
participating_indices = [index for index in get_active_validator_indices(state, epoch)
if epoch_participation[index][flag]]
participating_indices = [
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))
```
#### `get_flag_deltas`
```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
flags to determine who participated and who did not and assigning them the appropriate rewards and penalties.
"""
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))
increment = EFFECTIVE_BALANCE_INCREMENT # Factored out from balances to avoid uint64 overflow
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
else:
rewards[index] = (
(base_reward * unslashed_participating_increments // active_increments + base_reward)
* numerator // REWARD_DENOMINATOR
(base_reward * numerator * unslashed_participating_increments)
// (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`
@ -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
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):
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(
state, TIMELY_TARGET_FLAG, get_previous_epoch(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
rewards[index] += Gwei(effective_balance * get_finality_delay(state) // INACTIVITY_PENALTY_QUOTIENT)
return rewards, Gwei(GWEI_PER_ETH * get_finality_delay(state) // INACTIVITY_PENALTY_QUOTIENT)
penalties[index] += Gwei(effective_balance * get_finality_delay(state) // INACTIVITY_PENALTY_QUOTIENT)
rewards = [Gwei(0) for _ in range(len(state.validators))]
return rewards, penalties
```
### 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.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot <= data.slot + SLOTS_PER_EPOCH
assert data.index < get_committee_count_per_slot(state, data.target.epoch)
committee = get_beacon_committee(state, data.slot, data.index)
assert len(attestation.aggregation_bits) == len(committee)
if data.target.epoch == get_current_epoch(state):
epoch_participation = state.current_epoch_participation
justified_checkpoint = state.current_justified_checkpoint
else:
epoch_participation = state.previous_epoch_participation
justified_checkpoint = state.previous_justified_checkpoint
# Matching roots
is_matching_head = data.beacon_block_root == get_block_root_at_slot(state, data.slot)
is_matching_source = data.source == justified_checkpoint
is_matching_target = data.target.root == get_block_root(state, data.target.epoch)
assert is_matching_source
# Verify signature
assert is_valid_indexed_attestation(state, get_indexed_attestation(state, attestation))
# Participation flags
participation_flags = []
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)
if is_matching_target and state.slot <= data.slot + SLOTS_PER_EPOCH:
participation_flags.append(TIMELY_TARGET_FLAG)
# Update epoch participation flags
proposer_reward_numerator = 0
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]:
epoch_participation[index][flag] = True
proposer_reward_numerator += get_base_reward(state, index) * numerator
# Reward proposer
proposer_reward = Gwei(proposer_reward_numerator // (REWARD_DENOMINATOR * PROPOSER_REWARD_QUOTIENT))
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
state.validators.append(get_validator_from_deposit(state, deposit))
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.current_epoch_participation.append(Bitvector[PARTICIPATION_FLAGS_LENGTH]())
else:
@ -458,6 +482,7 @@ def process_justification_and_finalization(state: BeaconState) -> None:
current_epoch = get_current_epoch(state)
old_previous_justified_checkpoint = state.previous_justified_checkpoint
old_current_justified_checkpoint = state.current_justified_checkpoint
# Process justifications
state.previous_justified_checkpoint = state.current_justified_checkpoint
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,
root=get_block_root(state, current_epoch))
state.justification_bits[0] = 0b1
# Process finalizations
bits = state.justification_bits
# 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
if get_current_epoch(state) == GENESIS_EPOCH:
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)]
for (rewards, penalty) in deltas:
for (rewards, penalties) in deltas:
for index in range(len(state.validators)):
increase_balance(state, ValidatorIndex(index), rewards[index])
# Bounds-friendly expansion for `denom *= (1 + penalty // GWEI_PER_ETH)`
state.balance_denominator = (
state.balance_denominator +
penalty +
(state.balance_denominator - GWEI_PER_ETH) * penalty // GWEI_PER_ETH
)
decrease_balance(state, ValidatorIndex(index), penalties[index])
```
#### 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
def process_final_updates(state: BeaconState) -> None:
@ -521,23 +542,21 @@ def process_final_updates(state: BeaconState) -> None:
# Reset eth1 data votes
if next_epoch % EPOCHS_PER_ETH1_VOTING_PERIOD == 0:
state.eth1_data_votes = []
# Update sync committees
# [Added in hf-1] Update sync committees
if next_epoch % EPOCHS_PER_SYNC_COMMITTEE_PERIOD == 0:
state.current_sync_committee = state.next_sync_committee
state.next_sync_committee = get_sync_committee(state, next_epoch + EPOCHS_PER_SYNC_COMMITTEE_PERIOD)
# Update effective balances with hysteresis
for index, validator in enumerate(state.validators):
balance_increments = state.balances[index] * HYSTERESIS_QUOTIENT // state.balance_denominator
effective_increments = validator.effective_balance // GWEI_PER_ETH * HYSTERESIS_QUOTIENT
balance = state.balances[index]
HYSTERESIS_INCREMENT = uint64(EFFECTIVE_BALANCE_INCREMENT // HYSTERESIS_QUOTIENT)
DOWNWARD_THRESHOLD = HYSTERESIS_INCREMENT * HYSTERESIS_DOWNWARD_MULTIPLIER
UPWARD_THRESHOLD = HYSTERESIS_INCREMENT * HYSTERESIS_UPWARD_MULTIPLIER
if (
balance_increments + HYSTERESIS_DOWNWARD_MULTIPLIER < effective_increments
or effective_increments + HYSTERESIS_UPWARD_MULTIPLIER < balance_increments
or get_current_epoch(state) == validator.withdrawable_epoch
balance + DOWNWARD_THRESHOLD < validator.effective_balance
or validator.effective_balance + UPWARD_THRESHOLD < balance
):
validator.effective_balance = (state.balances[index] // state.balance_denominator) * GWEI_PER_ETH
if state.balance_denominator >= 2 * GWEI_PER_ETH:
state.balances = [x // 2 for x in state.balances]
state.balance_denominator //= 2
validator.effective_balance = min(balance - balance % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE)
# Reset slashings
state.slashings[next_epoch % EPOCHS_PER_SLASHINGS_VECTOR] = Gwei(0)
# 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:
historical_batch = HistoricalBatch(block_roots=state.block_roots, state_roots=state.state_roots)
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.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,
current_justified_checkpoint=pre.current_justified_checkpoint,
finalized_checkpoint=pre.finalized_checkpoint,
balance_denominator=GWEI_PER_ETH,
)
# Fill in sync committees
post.current_sync_committee = get_sync_committee(post, get_current_epoch(post))