mirror of
https://github.com/status-im/eth2.0-specs.git
synced 2025-01-10 02:35:41 +00:00
212eb00fc1
This adds documentation about the unit and actual value of light client specific constants, consistently with the rest of the spec.
729 lines
30 KiB
Markdown
729 lines
30 KiB
Markdown
# Altair -- The Beacon Chain
|
|
|
|
## Table of contents
|
|
|
|
<!-- TOC -->
|
|
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
|
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
|
|
|
- [Introduction](#introduction)
|
|
- [Custom types](#custom-types)
|
|
- [Constants](#constants)
|
|
- [Participation flag indices](#participation-flag-indices)
|
|
- [Incentivization weights](#incentivization-weights)
|
|
- [Domain types](#domain-types)
|
|
- [Misc](#misc)
|
|
- [Preset](#preset)
|
|
- [Updated penalty values](#updated-penalty-values)
|
|
- [Sync committee](#sync-committee)
|
|
- [Configuration](#configuration)
|
|
- [Inactivity penalties](#inactivity-penalties)
|
|
- [Containers](#containers)
|
|
- [Modified containers](#modified-containers)
|
|
- [`BeaconBlockBody`](#beaconblockbody)
|
|
- [`BeaconState`](#beaconstate)
|
|
- [New containers](#new-containers)
|
|
- [`SyncAggregate`](#syncaggregate)
|
|
- [`SyncCommittee`](#synccommittee)
|
|
- [Helper functions](#helper-functions)
|
|
- [Crypto](#crypto)
|
|
- [Misc](#misc-1)
|
|
- [`add_flag`](#add_flag)
|
|
- [`has_flag`](#has_flag)
|
|
- [Beacon state accessors](#beacon-state-accessors)
|
|
- [`get_next_sync_committee_indices`](#get_next_sync_committee_indices)
|
|
- [`get_next_sync_committee`](#get_next_sync_committee)
|
|
- [`get_base_reward_per_increment`](#get_base_reward_per_increment)
|
|
- [`get_base_reward`](#get_base_reward)
|
|
- [`get_unslashed_participating_indices`](#get_unslashed_participating_indices)
|
|
- [`get_attestation_participation_flag_indices`](#get_attestation_participation_flag_indices)
|
|
- [`get_flag_index_deltas`](#get_flag_index_deltas)
|
|
- [Modified `get_inactivity_penalty_deltas`](#modified-get_inactivity_penalty_deltas)
|
|
- [Beacon state mutators](#beacon-state-mutators)
|
|
- [Modified `slash_validator`](#modified-slash_validator)
|
|
- [Block processing](#block-processing)
|
|
- [Modified `process_attestation`](#modified-process_attestation)
|
|
- [Modified `process_deposit`](#modified-process_deposit)
|
|
- [Sync aggregate processing](#sync-aggregate-processing)
|
|
- [Epoch processing](#epoch-processing)
|
|
- [Justification and finalization](#justification-and-finalization)
|
|
- [Inactivity scores](#inactivity-scores)
|
|
- [Rewards and penalties](#rewards-and-penalties)
|
|
- [Slashings](#slashings)
|
|
- [Participation flags updates](#participation-flags-updates)
|
|
- [Sync committee updates](#sync-committee-updates)
|
|
- [Initialize state for pure Altair testnets and test vectors](#initialize-state-for-pure-altair-testnets-and-test-vectors)
|
|
|
|
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
|
<!-- /TOC -->
|
|
|
|
## Introduction
|
|
|
|
Altair is the first beacon chain hard fork. Its main features are:
|
|
|
|
* sync committees to support light clients
|
|
* incentive accounting reforms to reduce spec complexity
|
|
* penalty parameter updates towards their planned maximally punitive values
|
|
|
|
## Custom types
|
|
|
|
| Name | SSZ equivalent | Description |
|
|
| - | - | - |
|
|
| `ParticipationFlags` | `uint8` | a succinct representation of 8 boolean participation flags |
|
|
|
|
## Constants
|
|
|
|
### Participation flag indices
|
|
|
|
| Name | Value |
|
|
| - | - |
|
|
| `TIMELY_SOURCE_FLAG_INDEX` | `0` |
|
|
| `TIMELY_TARGET_FLAG_INDEX` | `1` |
|
|
| `TIMELY_HEAD_FLAG_INDEX` | `2` |
|
|
|
|
### Incentivization weights
|
|
|
|
| Name | Value |
|
|
| - | - |
|
|
| `TIMELY_SOURCE_WEIGHT` | `uint64(14)` |
|
|
| `TIMELY_TARGET_WEIGHT` | `uint64(26)` |
|
|
| `TIMELY_HEAD_WEIGHT` | `uint64(14)` |
|
|
| `SYNC_REWARD_WEIGHT` | `uint64(2)` |
|
|
| `PROPOSER_WEIGHT` | `uint64(8)` |
|
|
| `WEIGHT_DENOMINATOR` | `uint64(64)` |
|
|
|
|
*Note*: The sum of the weights equal `WEIGHT_DENOMINATOR`.
|
|
|
|
### Domain types
|
|
|
|
| Name | Value |
|
|
| - | - |
|
|
| `DOMAIN_SYNC_COMMITTEE` | `DomainType('0x07000000')` |
|
|
| `DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF` | `DomainType('0x08000000')` |
|
|
| `DOMAIN_CONTRIBUTION_AND_PROOF` | `DomainType('0x09000000')` |
|
|
|
|
### Misc
|
|
|
|
| Name | Value |
|
|
| - | - |
|
|
| `PARTICIPATION_FLAG_WEIGHTS` | `[TIMELY_SOURCE_WEIGHT, TIMELY_TARGET_WEIGHT, TIMELY_HEAD_WEIGHT]` |
|
|
|
|
## Preset
|
|
|
|
### Updated penalty values
|
|
|
|
This patch updates a few configuration values to move penalty parameters closer to their final, maximum security values.
|
|
|
|
*Note*: The spec does *not* override previous configuration values but instead creates new values and replaces usage throughout.
|
|
|
|
| Name | Value |
|
|
| - | - |
|
|
| `INACTIVITY_PENALTY_QUOTIENT_ALTAIR` | `uint64(3 * 2**24)` (= 50,331,648) |
|
|
| `MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR` | `uint64(2**6)` (= 64) |
|
|
| `PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR` | `uint64(2)` |
|
|
|
|
### Sync committee
|
|
|
|
| Name | Value | Unit | Duration |
|
|
| - | - | - | - |
|
|
| `SYNC_COMMITTEE_SIZE` | `uint64(2**9)` (= 512) | validators | |
|
|
| `EPOCHS_PER_SYNC_COMMITTEE_PERIOD` | `uint64(2**8)` (= 256) | epochs | ~27 hours |
|
|
|
|
## Configuration
|
|
|
|
### Inactivity penalties
|
|
|
|
| Name | Value | Description |
|
|
| - | - | - |
|
|
| `INACTIVITY_SCORE_BIAS` | `uint64(2**2)` (= 4) | score points per inactive epoch |
|
|
| `INACTIVITY_SCORE_RECOVERY_RATE` | `uint64(2**4)` (= 16) | score points per leak-free epoch |
|
|
|
|
## Containers
|
|
|
|
### Modified containers
|
|
|
|
#### `BeaconBlockBody`
|
|
|
|
```python
|
|
class BeaconBlockBody(Container):
|
|
randao_reveal: BLSSignature
|
|
eth1_data: Eth1Data # Eth1 data vote
|
|
graffiti: Bytes32 # Arbitrary data
|
|
# Operations
|
|
proposer_slashings: List[ProposerSlashing, MAX_PROPOSER_SLASHINGS]
|
|
attester_slashings: List[AttesterSlashing, MAX_ATTESTER_SLASHINGS]
|
|
attestations: List[Attestation, MAX_ATTESTATIONS]
|
|
deposits: List[Deposit, MAX_DEPOSITS]
|
|
voluntary_exits: List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS]
|
|
sync_aggregate: SyncAggregate # [New in Altair]
|
|
```
|
|
|
|
#### `BeaconState`
|
|
|
|
```python
|
|
class BeaconState(Container):
|
|
# Versioning
|
|
genesis_time: uint64
|
|
genesis_validators_root: Root
|
|
slot: Slot
|
|
fork: Fork
|
|
# History
|
|
latest_block_header: BeaconBlockHeader
|
|
block_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT]
|
|
state_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT]
|
|
historical_roots: List[Root, HISTORICAL_ROOTS_LIMIT]
|
|
# Eth1
|
|
eth1_data: Eth1Data
|
|
eth1_data_votes: List[Eth1Data, EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH]
|
|
eth1_deposit_index: uint64
|
|
# Registry
|
|
validators: List[Validator, VALIDATOR_REGISTRY_LIMIT]
|
|
balances: List[Gwei, VALIDATOR_REGISTRY_LIMIT]
|
|
# Randomness
|
|
randao_mixes: Vector[Bytes32, EPOCHS_PER_HISTORICAL_VECTOR]
|
|
# Slashings
|
|
slashings: Vector[Gwei, EPOCHS_PER_SLASHINGS_VECTOR] # Per-epoch sums of slashed effective balances
|
|
# Participation
|
|
previous_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] # [Modified in Altair]
|
|
current_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] # [Modified in Altair]
|
|
# Finality
|
|
justification_bits: Bitvector[JUSTIFICATION_BITS_LENGTH] # Bit set for every recent justified epoch
|
|
previous_justified_checkpoint: Checkpoint
|
|
current_justified_checkpoint: Checkpoint
|
|
finalized_checkpoint: Checkpoint
|
|
# Inactivity
|
|
inactivity_scores: List[uint64, VALIDATOR_REGISTRY_LIMIT] # [New in Altair]
|
|
# Sync
|
|
current_sync_committee: SyncCommittee # [New in Altair]
|
|
next_sync_committee: SyncCommittee # [New in Altair]
|
|
```
|
|
|
|
### New containers
|
|
|
|
#### `SyncAggregate`
|
|
|
|
```python
|
|
class SyncAggregate(Container):
|
|
sync_committee_bits: Bitvector[SYNC_COMMITTEE_SIZE]
|
|
sync_committee_signature: BLSSignature
|
|
```
|
|
|
|
#### `SyncCommittee`
|
|
|
|
```python
|
|
class SyncCommittee(Container):
|
|
pubkeys: Vector[BLSPubkey, SYNC_COMMITTEE_SIZE]
|
|
aggregate_pubkey: BLSPubkey
|
|
```
|
|
|
|
## Helper functions
|
|
|
|
### Crypto
|
|
|
|
Refer to the definitions in the [phase 0 document regarding BLS signatures](../phase0/beacon-chain.md#bls-signatures)
|
|
and the extensions defined in the [Altair BLS document](./bls.md). This specification assumes knowledge of
|
|
the functionality described in those documents.
|
|
|
|
### Misc
|
|
|
|
#### `add_flag`
|
|
|
|
```python
|
|
def add_flag(flags: ParticipationFlags, flag_index: int) -> ParticipationFlags:
|
|
"""
|
|
Return a new ``ParticipationFlags`` adding ``flag_index`` to ``flags``.
|
|
"""
|
|
flag = ParticipationFlags(2**flag_index)
|
|
return flags | flag
|
|
```
|
|
|
|
#### `has_flag`
|
|
|
|
```python
|
|
def has_flag(flags: ParticipationFlags, flag_index: int) -> bool:
|
|
"""
|
|
Return whether ``flags`` has ``flag_index`` set.
|
|
"""
|
|
flag = ParticipationFlags(2**flag_index)
|
|
return flags & flag == flag
|
|
```
|
|
|
|
### Beacon state accessors
|
|
|
|
#### `get_next_sync_committee_indices`
|
|
|
|
```python
|
|
def get_next_sync_committee_indices(state: BeaconState) -> Sequence[ValidatorIndex]:
|
|
"""
|
|
Return the sync committee indices, with possible duplicates, for the next sync committee.
|
|
"""
|
|
epoch = Epoch(get_current_epoch(state) + 1)
|
|
|
|
MAX_RANDOM_BYTE = 2**8 - 1
|
|
active_validator_indices = get_active_validator_indices(state, epoch)
|
|
active_validator_count = uint64(len(active_validator_indices))
|
|
seed = get_seed(state, epoch, DOMAIN_SYNC_COMMITTEE)
|
|
i = 0
|
|
sync_committee_indices: List[ValidatorIndex] = []
|
|
while len(sync_committee_indices) < SYNC_COMMITTEE_SIZE:
|
|
shuffled_index = compute_shuffled_index(uint64(i % active_validator_count), active_validator_count, seed)
|
|
candidate_index = active_validator_indices[shuffled_index]
|
|
random_byte = hash(seed + uint_to_bytes(uint64(i // 32)))[i % 32]
|
|
effective_balance = state.validators[candidate_index].effective_balance
|
|
if effective_balance * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE * random_byte:
|
|
sync_committee_indices.append(candidate_index)
|
|
i += 1
|
|
return sync_committee_indices
|
|
```
|
|
|
|
#### `get_next_sync_committee`
|
|
|
|
*Note*: The function `get_next_sync_committee` should only be called at sync committee period boundaries and when [upgrading state to Altair](./fork.md#upgrading-the-state).
|
|
|
|
```python
|
|
def get_next_sync_committee(state: BeaconState) -> SyncCommittee:
|
|
"""
|
|
Return the next sync committee, with possible pubkey duplicates.
|
|
"""
|
|
indices = get_next_sync_committee_indices(state)
|
|
pubkeys = [state.validators[index].pubkey for index in indices]
|
|
aggregate_pubkey = eth_aggregate_pubkeys(pubkeys)
|
|
return SyncCommittee(pubkeys=pubkeys, aggregate_pubkey=aggregate_pubkey)
|
|
```
|
|
|
|
#### `get_base_reward_per_increment`
|
|
|
|
```python
|
|
def get_base_reward_per_increment(state: BeaconState) -> Gwei:
|
|
return Gwei(EFFECTIVE_BALANCE_INCREMENT * BASE_REWARD_FACTOR // integer_squareroot(get_total_active_balance(state)))
|
|
```
|
|
|
|
#### `get_base_reward`
|
|
|
|
*Note*: The function `get_base_reward` is modified with the removal of `BASE_REWARDS_PER_EPOCH` and the use of increment based accounting.
|
|
|
|
*Note*: On average an optimally performing validator earns one base reward per epoch.
|
|
|
|
```python
|
|
def get_base_reward(state: BeaconState, index: ValidatorIndex) -> Gwei:
|
|
"""
|
|
Return the base reward for the validator defined by ``index`` with respect to the current ``state``.
|
|
"""
|
|
increments = state.validators[index].effective_balance // EFFECTIVE_BALANCE_INCREMENT
|
|
return Gwei(increments * get_base_reward_per_increment(state))
|
|
```
|
|
|
|
#### `get_unslashed_participating_indices`
|
|
|
|
```python
|
|
def get_unslashed_participating_indices(state: BeaconState, flag_index: int, epoch: Epoch) -> Set[ValidatorIndex]:
|
|
"""
|
|
Return the set of validator indices that are both active and unslashed for the given ``flag_index`` and ``epoch``.
|
|
"""
|
|
assert epoch in (get_previous_epoch(state), get_current_epoch(state))
|
|
if epoch == get_current_epoch(state):
|
|
epoch_participation = state.current_epoch_participation
|
|
else:
|
|
epoch_participation = state.previous_epoch_participation
|
|
active_validator_indices = get_active_validator_indices(state, epoch)
|
|
participating_indices = [i for i in active_validator_indices if has_flag(epoch_participation[i], flag_index)]
|
|
return set(filter(lambda index: not state.validators[index].slashed, participating_indices))
|
|
```
|
|
|
|
#### `get_attestation_participation_flag_indices`
|
|
|
|
```python
|
|
def get_attestation_participation_flag_indices(state: BeaconState,
|
|
data: AttestationData,
|
|
inclusion_delay: uint64) -> Sequence[int]:
|
|
"""
|
|
Return the flag indices that are satisfied by an attestation.
|
|
"""
|
|
if data.target.epoch == get_current_epoch(state):
|
|
justified_checkpoint = state.current_justified_checkpoint
|
|
else:
|
|
justified_checkpoint = state.previous_justified_checkpoint
|
|
|
|
# Matching roots
|
|
is_matching_source = data.source == justified_checkpoint
|
|
is_matching_target = is_matching_source and data.target.root == get_block_root(state, data.target.epoch)
|
|
is_matching_head = is_matching_target and data.beacon_block_root == get_block_root_at_slot(state, data.slot)
|
|
assert is_matching_source
|
|
|
|
participation_flag_indices = []
|
|
if is_matching_source and inclusion_delay <= integer_squareroot(SLOTS_PER_EPOCH):
|
|
participation_flag_indices.append(TIMELY_SOURCE_FLAG_INDEX)
|
|
if is_matching_target and inclusion_delay <= SLOTS_PER_EPOCH:
|
|
participation_flag_indices.append(TIMELY_TARGET_FLAG_INDEX)
|
|
if is_matching_head and inclusion_delay == MIN_ATTESTATION_INCLUSION_DELAY:
|
|
participation_flag_indices.append(TIMELY_HEAD_FLAG_INDEX)
|
|
|
|
return participation_flag_indices
|
|
```
|
|
|
|
#### `get_flag_index_deltas`
|
|
|
|
```python
|
|
def get_flag_index_deltas(state: BeaconState, flag_index: int) -> Tuple[Sequence[Gwei], Sequence[Gwei]]:
|
|
"""
|
|
Return the deltas for a given ``flag_index`` by scanning through the participation flags.
|
|
"""
|
|
rewards = [Gwei(0)] * len(state.validators)
|
|
penalties = [Gwei(0)] * len(state.validators)
|
|
previous_epoch = get_previous_epoch(state)
|
|
unslashed_participating_indices = get_unslashed_participating_indices(state, flag_index, previous_epoch)
|
|
weight = PARTICIPATION_FLAG_WEIGHTS[flag_index]
|
|
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):
|
|
base_reward = get_base_reward(state, index)
|
|
if index in unslashed_participating_indices:
|
|
if not is_in_inactivity_leak(state):
|
|
reward_numerator = base_reward * weight * unslashed_participating_increments
|
|
rewards[index] += Gwei(reward_numerator // (active_increments * WEIGHT_DENOMINATOR))
|
|
elif flag_index != TIMELY_HEAD_FLAG_INDEX:
|
|
penalties[index] += Gwei(base_reward * weight // WEIGHT_DENOMINATOR)
|
|
return rewards, penalties
|
|
```
|
|
|
|
#### Modified `get_inactivity_penalty_deltas`
|
|
|
|
```python
|
|
def get_inactivity_penalty_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]:
|
|
"""
|
|
Return the inactivity penalty deltas by considering timely target participation flags and inactivity scores.
|
|
"""
|
|
rewards = [Gwei(0) for _ in range(len(state.validators))]
|
|
penalties = [Gwei(0) for _ in range(len(state.validators))]
|
|
previous_epoch = get_previous_epoch(state)
|
|
matching_target_indices = get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, previous_epoch)
|
|
for index in get_eligible_validator_indices(state):
|
|
if index not in matching_target_indices:
|
|
penalty_numerator = state.validators[index].effective_balance * state.inactivity_scores[index]
|
|
penalty_denominator = INACTIVITY_SCORE_BIAS * INACTIVITY_PENALTY_QUOTIENT_ALTAIR
|
|
penalties[index] += Gwei(penalty_numerator // penalty_denominator)
|
|
return rewards, penalties
|
|
```
|
|
|
|
### Beacon state mutators
|
|
|
|
#### Modified `slash_validator`
|
|
|
|
*Note*: The function `slash_validator` is modified to use `MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR`
|
|
and use `PROPOSER_WEIGHT` when calculating the proposer reward.
|
|
|
|
```python
|
|
def slash_validator(state: BeaconState,
|
|
slashed_index: ValidatorIndex,
|
|
whistleblower_index: ValidatorIndex=None) -> None:
|
|
"""
|
|
Slash the validator with index ``slashed_index``.
|
|
"""
|
|
epoch = get_current_epoch(state)
|
|
initiate_validator_exit(state, slashed_index)
|
|
validator = state.validators[slashed_index]
|
|
validator.slashed = True
|
|
validator.withdrawable_epoch = max(validator.withdrawable_epoch, Epoch(epoch + EPOCHS_PER_SLASHINGS_VECTOR))
|
|
state.slashings[epoch % EPOCHS_PER_SLASHINGS_VECTOR] += validator.effective_balance
|
|
decrease_balance(state, slashed_index, validator.effective_balance // MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR)
|
|
|
|
# Apply proposer and whistleblower rewards
|
|
proposer_index = get_beacon_proposer_index(state)
|
|
if whistleblower_index is None:
|
|
whistleblower_index = proposer_index
|
|
whistleblower_reward = Gwei(validator.effective_balance // WHISTLEBLOWER_REWARD_QUOTIENT)
|
|
proposer_reward = Gwei(whistleblower_reward * PROPOSER_WEIGHT // WEIGHT_DENOMINATOR)
|
|
increase_balance(state, proposer_index, proposer_reward)
|
|
increase_balance(state, whistleblower_index, Gwei(whistleblower_reward - proposer_reward))
|
|
```
|
|
|
|
### Block processing
|
|
|
|
```python
|
|
def process_block(state: BeaconState, block: BeaconBlock) -> None:
|
|
process_block_header(state, block)
|
|
process_randao(state, block.body)
|
|
process_eth1_data(state, block.body)
|
|
process_operations(state, block.body) # [Modified in Altair]
|
|
process_sync_aggregate(state, block.body.sync_aggregate) # [New in Altair]
|
|
```
|
|
|
|
#### Modified `process_attestation`
|
|
|
|
*Note*: The function `process_attestation` is modified to do incentive accounting with epoch participation flags.
|
|
|
|
```python
|
|
def process_attestation(state: BeaconState, attestation: Attestation) -> None:
|
|
data = attestation.data
|
|
assert data.target.epoch in (get_previous_epoch(state), get_current_epoch(state))
|
|
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)
|
|
|
|
# Participation flag indices
|
|
participation_flag_indices = get_attestation_participation_flag_indices(state, data, state.slot - data.slot)
|
|
|
|
# Verify signature
|
|
assert is_valid_indexed_attestation(state, get_indexed_attestation(state, attestation))
|
|
|
|
# Update epoch participation flags
|
|
if data.target.epoch == get_current_epoch(state):
|
|
epoch_participation = state.current_epoch_participation
|
|
else:
|
|
epoch_participation = state.previous_epoch_participation
|
|
|
|
proposer_reward_numerator = 0
|
|
for index in get_attesting_indices(state, data, attestation.aggregation_bits):
|
|
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):
|
|
epoch_participation[index] = add_flag(epoch_participation[index], flag_index)
|
|
proposer_reward_numerator += get_base_reward(state, index) * weight
|
|
|
|
# Reward proposer
|
|
proposer_reward_denominator = (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT) * WEIGHT_DENOMINATOR // PROPOSER_WEIGHT
|
|
proposer_reward = Gwei(proposer_reward_numerator // proposer_reward_denominator)
|
|
increase_balance(state, get_beacon_proposer_index(state), proposer_reward)
|
|
```
|
|
|
|
#### Modified `process_deposit`
|
|
|
|
*Note*: The function `process_deposit` is modified to initialize `inactivity_scores`, `previous_epoch_participation`, and `current_epoch_participation`.
|
|
|
|
```python
|
|
def process_deposit(state: BeaconState, deposit: Deposit) -> None:
|
|
# Verify the Merkle branch
|
|
assert is_valid_merkle_branch(
|
|
leaf=hash_tree_root(deposit.data),
|
|
branch=deposit.proof,
|
|
depth=DEPOSIT_CONTRACT_TREE_DEPTH + 1, # Add 1 for the List length mix-in
|
|
index=state.eth1_deposit_index,
|
|
root=state.eth1_data.deposit_root,
|
|
)
|
|
|
|
# Deposits must be processed in order
|
|
state.eth1_deposit_index += 1
|
|
|
|
pubkey = deposit.data.pubkey
|
|
amount = deposit.data.amount
|
|
validator_pubkeys = [validator.pubkey for validator in state.validators]
|
|
if pubkey not in validator_pubkeys:
|
|
# Verify the deposit signature (proof of possession) which is not checked by the deposit contract
|
|
deposit_message = DepositMessage(
|
|
pubkey=deposit.data.pubkey,
|
|
withdrawal_credentials=deposit.data.withdrawal_credentials,
|
|
amount=deposit.data.amount,
|
|
)
|
|
domain = compute_domain(DOMAIN_DEPOSIT) # Fork-agnostic domain since deposits are valid across forks
|
|
signing_root = compute_signing_root(deposit_message, domain)
|
|
# Initialize validator if the deposit signature is valid
|
|
if bls.Verify(pubkey, signing_root, deposit.data.signature):
|
|
state.validators.append(get_validator_from_deposit(state, deposit))
|
|
state.balances.append(amount)
|
|
state.previous_epoch_participation.append(ParticipationFlags(0b0000_0000))
|
|
state.current_epoch_participation.append(ParticipationFlags(0b0000_0000))
|
|
state.inactivity_scores.append(uint64(0))
|
|
else:
|
|
# Increase balance by deposit amount
|
|
index = ValidatorIndex(validator_pubkeys.index(pubkey))
|
|
increase_balance(state, index, amount)
|
|
```
|
|
|
|
#### Sync aggregate processing
|
|
|
|
*Note*: The function `process_sync_aggregate` is new.
|
|
|
|
```python
|
|
def process_sync_aggregate(state: BeaconState, sync_aggregate: SyncAggregate) -> None:
|
|
# Verify sync committee aggregate signature signing over the previous slot block root
|
|
committee_pubkeys = state.current_sync_committee.pubkeys
|
|
participant_pubkeys = [pubkey for pubkey, bit in zip(committee_pubkeys, sync_aggregate.sync_committee_bits) if bit]
|
|
previous_slot = max(state.slot, Slot(1)) - Slot(1)
|
|
domain = get_domain(state, DOMAIN_SYNC_COMMITTEE, compute_epoch_at_slot(previous_slot))
|
|
signing_root = compute_signing_root(get_block_root_at_slot(state, previous_slot), domain)
|
|
assert eth_fast_aggregate_verify(participant_pubkeys, signing_root, sync_aggregate.sync_committee_signature)
|
|
|
|
# Compute participant and proposer rewards
|
|
total_active_increments = get_total_active_balance(state) // EFFECTIVE_BALANCE_INCREMENT
|
|
total_base_rewards = Gwei(get_base_reward_per_increment(state) * total_active_increments)
|
|
max_participant_rewards = Gwei(total_base_rewards * SYNC_REWARD_WEIGHT // WEIGHT_DENOMINATOR // SLOTS_PER_EPOCH)
|
|
participant_reward = Gwei(max_participant_rewards // SYNC_COMMITTEE_SIZE)
|
|
proposer_reward = Gwei(participant_reward * PROPOSER_WEIGHT // (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT))
|
|
|
|
# Apply participant and proposer rewards
|
|
all_pubkeys = [v.pubkey for v in state.validators]
|
|
committee_indices = [ValidatorIndex(all_pubkeys.index(pubkey)) for pubkey in state.current_sync_committee.pubkeys]
|
|
for participant_index, participation_bit in zip(committee_indices, sync_aggregate.sync_committee_bits):
|
|
if participation_bit:
|
|
increase_balance(state, participant_index, participant_reward)
|
|
increase_balance(state, get_beacon_proposer_index(state), proposer_reward)
|
|
else:
|
|
decrease_balance(state, participant_index, participant_reward)
|
|
```
|
|
|
|
### Epoch processing
|
|
|
|
```python
|
|
def process_epoch(state: BeaconState) -> None:
|
|
process_justification_and_finalization(state) # [Modified in Altair]
|
|
process_inactivity_updates(state) # [New in Altair]
|
|
process_rewards_and_penalties(state) # [Modified in Altair]
|
|
process_registry_updates(state)
|
|
process_slashings(state) # [Modified in Altair]
|
|
process_eth1_data_reset(state)
|
|
process_effective_balance_updates(state)
|
|
process_slashings_reset(state)
|
|
process_randao_mixes_reset(state)
|
|
process_historical_roots_update(state)
|
|
process_participation_flag_updates(state) # [New in Altair]
|
|
process_sync_committee_updates(state) # [New in Altair]
|
|
```
|
|
|
|
#### Justification and finalization
|
|
|
|
*Note*: The function `process_justification_and_finalization` is modified to adapt to the new participation records.
|
|
|
|
```python
|
|
def process_justification_and_finalization(state: BeaconState) -> None:
|
|
# Initial FFG checkpoint values have a `0x00` stub for `root`.
|
|
# Skip FFG updates in the first two epochs to avoid corner cases that might result in modifying this stub.
|
|
if get_current_epoch(state) <= GENESIS_EPOCH + 1:
|
|
return
|
|
previous_indices = get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, get_previous_epoch(state))
|
|
current_indices = get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, get_current_epoch(state))
|
|
total_active_balance = get_total_active_balance(state)
|
|
previous_target_balance = get_total_balance(state, previous_indices)
|
|
current_target_balance = get_total_balance(state, current_indices)
|
|
weigh_justification_and_finalization(state, total_active_balance, previous_target_balance, current_target_balance)
|
|
```
|
|
|
|
#### Inactivity scores
|
|
|
|
*Note*: The function `process_inactivity_updates` is new.
|
|
|
|
```python
|
|
def process_inactivity_updates(state: BeaconState) -> None:
|
|
# Skip the genesis epoch as score updates are based on the previous epoch participation
|
|
if get_current_epoch(state) == GENESIS_EPOCH:
|
|
return
|
|
|
|
for index in get_eligible_validator_indices(state):
|
|
# Increase the inactivity score of inactive validators
|
|
if index in get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, get_previous_epoch(state)):
|
|
state.inactivity_scores[index] -= min(1, state.inactivity_scores[index])
|
|
else:
|
|
state.inactivity_scores[index] += INACTIVITY_SCORE_BIAS
|
|
# Decrease the inactivity score of all eligible validators during a leak-free epoch
|
|
if not is_in_inactivity_leak(state):
|
|
state.inactivity_scores[index] -= min(INACTIVITY_SCORE_RECOVERY_RATE, state.inactivity_scores[index])
|
|
```
|
|
|
|
#### Rewards and penalties
|
|
|
|
*Note*: The function `process_rewards_and_penalties` is modified to support the incentive accounting reforms.
|
|
|
|
```python
|
|
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_index_deltas(state, flag_index) for flag_index in range(len(PARTICIPATION_FLAG_WEIGHTS))]
|
|
deltas = flag_deltas + [get_inactivity_penalty_deltas(state)]
|
|
for (rewards, penalties) in deltas:
|
|
for index in range(len(state.validators)):
|
|
increase_balance(state, ValidatorIndex(index), rewards[index])
|
|
decrease_balance(state, ValidatorIndex(index), penalties[index])
|
|
```
|
|
|
|
#### Slashings
|
|
|
|
*Note*: The function `process_slashings` is modified to use `PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR`.
|
|
|
|
```python
|
|
def process_slashings(state: BeaconState) -> None:
|
|
epoch = get_current_epoch(state)
|
|
total_balance = get_total_active_balance(state)
|
|
adjusted_total_slashing_balance = min(sum(state.slashings) * PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR, total_balance)
|
|
for index, validator in enumerate(state.validators):
|
|
if validator.slashed and epoch + EPOCHS_PER_SLASHINGS_VECTOR // 2 == validator.withdrawable_epoch:
|
|
increment = EFFECTIVE_BALANCE_INCREMENT # Factored out from penalty numerator to avoid uint64 overflow
|
|
penalty_numerator = validator.effective_balance // increment * adjusted_total_slashing_balance
|
|
penalty = penalty_numerator // total_balance * increment
|
|
decrease_balance(state, ValidatorIndex(index), penalty)
|
|
```
|
|
|
|
#### Participation flags updates
|
|
|
|
*Note*: The function `process_participation_flag_updates` is new.
|
|
|
|
```python
|
|
def process_participation_flag_updates(state: BeaconState) -> None:
|
|
state.previous_epoch_participation = state.current_epoch_participation
|
|
state.current_epoch_participation = [ParticipationFlags(0b0000_0000) for _ in range(len(state.validators))]
|
|
```
|
|
|
|
#### Sync committee updates
|
|
|
|
*Note*: The function `process_sync_committee_updates` is new.
|
|
|
|
```python
|
|
def process_sync_committee_updates(state: BeaconState) -> None:
|
|
next_epoch = get_current_epoch(state) + Epoch(1)
|
|
if next_epoch % EPOCHS_PER_SYNC_COMMITTEE_PERIOD == 0:
|
|
state.current_sync_committee = state.next_sync_committee
|
|
state.next_sync_committee = get_next_sync_committee(state)
|
|
```
|
|
|
|
## Initialize state for pure Altair testnets and test vectors
|
|
|
|
This helper function is only for initializing the state for pure Altair testnets and tests.
|
|
|
|
*Note*: The function `initialize_beacon_state_from_eth1` is modified: (1) using `ALTAIR_FORK_VERSION` as the current fork version, (2) utilizing the Altair `BeaconBlockBody` when constructing the initial `latest_block_header`, and (3) adding initial sync committees.
|
|
|
|
```python
|
|
def initialize_beacon_state_from_eth1(eth1_block_hash: Hash32,
|
|
eth1_timestamp: uint64,
|
|
deposits: Sequence[Deposit]) -> BeaconState:
|
|
fork = Fork(
|
|
previous_version=ALTAIR_FORK_VERSION, # [Modified in Altair] for testing only
|
|
current_version=ALTAIR_FORK_VERSION, # [Modified in Altair]
|
|
epoch=GENESIS_EPOCH,
|
|
)
|
|
state = BeaconState(
|
|
genesis_time=eth1_timestamp + GENESIS_DELAY,
|
|
fork=fork,
|
|
eth1_data=Eth1Data(block_hash=eth1_block_hash, deposit_count=uint64(len(deposits))),
|
|
latest_block_header=BeaconBlockHeader(body_root=hash_tree_root(BeaconBlockBody())),
|
|
randao_mixes=[eth1_block_hash] * EPOCHS_PER_HISTORICAL_VECTOR, # Seed RANDAO with Eth1 entropy
|
|
)
|
|
|
|
# Process deposits
|
|
leaves = list(map(lambda deposit: deposit.data, deposits))
|
|
for index, deposit in enumerate(deposits):
|
|
deposit_data_list = List[DepositData, 2**DEPOSIT_CONTRACT_TREE_DEPTH](*leaves[:index + 1])
|
|
state.eth1_data.deposit_root = hash_tree_root(deposit_data_list)
|
|
process_deposit(state, deposit)
|
|
|
|
# Process activations
|
|
for index, validator in enumerate(state.validators):
|
|
balance = state.balances[index]
|
|
validator.effective_balance = min(balance - balance % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE)
|
|
if validator.effective_balance == MAX_EFFECTIVE_BALANCE:
|
|
validator.activation_eligibility_epoch = GENESIS_EPOCH
|
|
validator.activation_epoch = GENESIS_EPOCH
|
|
|
|
# Set genesis validators root for domain separation and chain versioning
|
|
state.genesis_validators_root = hash_tree_root(state.validators)
|
|
|
|
# [New in Altair] Fill in sync committees
|
|
# Note: A duplicate committee is assigned for the current and next committee at genesis
|
|
state.current_sync_committee = get_next_sync_committee(state)
|
|
state.next_sync_committee = get_next_sync_committee(state)
|
|
|
|
return state
|
|
```
|