remove global quotient penalties from fork spec [temp]
This commit is contained in:
parent
9c75c3819d
commit
b8d3589b46
|
@ -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
|
||||
```
|
||||
|
|
|
@ -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))
|
||||
|
|
Loading…
Reference in New Issue