1929 lines
71 KiB
Markdown
1929 lines
71 KiB
Markdown
# Phase 0 -- 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)
|
|
- [Notation](#notation)
|
|
- [Custom types](#custom-types)
|
|
- [Constants](#constants)
|
|
- [Misc](#misc)
|
|
- [Withdrawal prefixes](#withdrawal-prefixes)
|
|
- [Domain types](#domain-types)
|
|
- [Preset](#preset)
|
|
- [Misc](#misc-1)
|
|
- [Gwei values](#gwei-values)
|
|
- [Time parameters](#time-parameters)
|
|
- [State list lengths](#state-list-lengths)
|
|
- [Rewards and penalties](#rewards-and-penalties)
|
|
- [Max operations per block](#max-operations-per-block)
|
|
- [Configuration](#configuration)
|
|
- [Genesis settings](#genesis-settings)
|
|
- [Time parameters](#time-parameters-1)
|
|
- [Validator cycle](#validator-cycle)
|
|
- [Containers](#containers)
|
|
- [Misc dependencies](#misc-dependencies)
|
|
- [`Fork`](#fork)
|
|
- [`ForkData`](#forkdata)
|
|
- [`Checkpoint`](#checkpoint)
|
|
- [`Validator`](#validator)
|
|
- [`AttestationData`](#attestationdata)
|
|
- [`IndexedAttestation`](#indexedattestation)
|
|
- [`PendingAttestation`](#pendingattestation)
|
|
- [`Eth1Data`](#eth1data)
|
|
- [`HistoricalBatch`](#historicalbatch)
|
|
- [`DepositMessage`](#depositmessage)
|
|
- [`DepositData`](#depositdata)
|
|
- [`BeaconBlockHeader`](#beaconblockheader)
|
|
- [`SigningData`](#signingdata)
|
|
- [Beacon operations](#beacon-operations)
|
|
- [`ProposerSlashing`](#proposerslashing)
|
|
- [`AttesterSlashing`](#attesterslashing)
|
|
- [`Attestation`](#attestation)
|
|
- [`Deposit`](#deposit)
|
|
- [`VoluntaryExit`](#voluntaryexit)
|
|
- [Beacon blocks](#beacon-blocks)
|
|
- [`BeaconBlockBody`](#beaconblockbody)
|
|
- [`BeaconBlock`](#beaconblock)
|
|
- [Beacon state](#beacon-state)
|
|
- [`BeaconState`](#beaconstate)
|
|
- [Signed envelopes](#signed-envelopes)
|
|
- [`SignedVoluntaryExit`](#signedvoluntaryexit)
|
|
- [`SignedBeaconBlock`](#signedbeaconblock)
|
|
- [`SignedBeaconBlockHeader`](#signedbeaconblockheader)
|
|
- [Helper functions](#helper-functions)
|
|
- [Math](#math)
|
|
- [`integer_squareroot`](#integer_squareroot)
|
|
- [`xor`](#xor)
|
|
- [`uint_to_bytes`](#uint_to_bytes)
|
|
- [`bytes_to_uint64`](#bytes_to_uint64)
|
|
- [Crypto](#crypto)
|
|
- [`hash`](#hash)
|
|
- [`hash_tree_root`](#hash_tree_root)
|
|
- [BLS signatures](#bls-signatures)
|
|
- [Predicates](#predicates)
|
|
- [`is_active_validator`](#is_active_validator)
|
|
- [`is_eligible_for_activation_queue`](#is_eligible_for_activation_queue)
|
|
- [`is_eligible_for_activation`](#is_eligible_for_activation)
|
|
- [`is_slashable_validator`](#is_slashable_validator)
|
|
- [`is_slashable_attestation_data`](#is_slashable_attestation_data)
|
|
- [`is_valid_indexed_attestation`](#is_valid_indexed_attestation)
|
|
- [`is_valid_merkle_branch`](#is_valid_merkle_branch)
|
|
- [Misc](#misc-2)
|
|
- [`compute_shuffled_index`](#compute_shuffled_index)
|
|
- [`compute_proposer_index`](#compute_proposer_index)
|
|
- [`compute_committee`](#compute_committee)
|
|
- [`compute_epoch_at_slot`](#compute_epoch_at_slot)
|
|
- [`compute_start_slot_at_epoch`](#compute_start_slot_at_epoch)
|
|
- [`compute_activation_exit_epoch`](#compute_activation_exit_epoch)
|
|
- [`compute_fork_data_root`](#compute_fork_data_root)
|
|
- [`compute_fork_digest`](#compute_fork_digest)
|
|
- [`compute_domain`](#compute_domain)
|
|
- [`compute_signing_root`](#compute_signing_root)
|
|
- [Beacon state accessors](#beacon-state-accessors)
|
|
- [`get_current_epoch`](#get_current_epoch)
|
|
- [`get_previous_epoch`](#get_previous_epoch)
|
|
- [`get_block_root`](#get_block_root)
|
|
- [`get_block_root_at_slot`](#get_block_root_at_slot)
|
|
- [`get_randao_mix`](#get_randao_mix)
|
|
- [`get_active_validator_indices`](#get_active_validator_indices)
|
|
- [`get_validator_churn_limit`](#get_validator_churn_limit)
|
|
- [`get_seed`](#get_seed)
|
|
- [`get_committee_count_per_slot`](#get_committee_count_per_slot)
|
|
- [`get_beacon_committee`](#get_beacon_committee)
|
|
- [`get_beacon_proposer_index`](#get_beacon_proposer_index)
|
|
- [`get_total_balance`](#get_total_balance)
|
|
- [`get_total_active_balance`](#get_total_active_balance)
|
|
- [`get_domain`](#get_domain)
|
|
- [`get_indexed_attestation`](#get_indexed_attestation)
|
|
- [`get_attesting_indices`](#get_attesting_indices)
|
|
- [Beacon state mutators](#beacon-state-mutators)
|
|
- [`increase_balance`](#increase_balance)
|
|
- [`decrease_balance`](#decrease_balance)
|
|
- [`initiate_validator_exit`](#initiate_validator_exit)
|
|
- [`slash_validator`](#slash_validator)
|
|
- [Genesis](#genesis)
|
|
- [Genesis state](#genesis-state)
|
|
- [Genesis block](#genesis-block)
|
|
- [Beacon chain state transition function](#beacon-chain-state-transition-function)
|
|
- [Epoch processing](#epoch-processing)
|
|
- [Helper functions](#helper-functions-1)
|
|
- [Justification and finalization](#justification-and-finalization)
|
|
- [Rewards and penalties](#rewards-and-penalties-1)
|
|
- [Helpers](#helpers)
|
|
- [Components of attestation deltas](#components-of-attestation-deltas)
|
|
- [`get_attestation_deltas`](#get_attestation_deltas)
|
|
- [`process_rewards_and_penalties`](#process_rewards_and_penalties)
|
|
- [Registry updates](#registry-updates)
|
|
- [Slashings](#slashings)
|
|
- [Eth1 data votes updates](#eth1-data-votes-updates)
|
|
- [Effective balances updates](#effective-balances-updates)
|
|
- [Slashings balances updates](#slashings-balances-updates)
|
|
- [Randao mixes updates](#randao-mixes-updates)
|
|
- [Historical roots updates](#historical-roots-updates)
|
|
- [Participation records rotation](#participation-records-rotation)
|
|
- [Block processing](#block-processing)
|
|
- [Block header](#block-header)
|
|
- [RANDAO](#randao)
|
|
- [Eth1 data](#eth1-data)
|
|
- [Operations](#operations)
|
|
- [Proposer slashings](#proposer-slashings)
|
|
- [Attester slashings](#attester-slashings)
|
|
- [Attestations](#attestations)
|
|
- [Deposits](#deposits)
|
|
- [Voluntary exits](#voluntary-exits)
|
|
|
|
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
|
<!-- /TOC -->
|
|
|
|
## Introduction
|
|
|
|
This document represents the specification for Phase 0 -- The Beacon Chain.
|
|
|
|
At the core of Ethereum proof-of-stake is a system chain called the "beacon chain". The beacon chain stores and manages the registry of validators. In the initial deployment phases of proof-of-stake, the only mechanism to become a validator is to make a one-way ETH transaction to a deposit contract on the Ethereum proof-of-work chain. Activation as a validator happens when deposit receipts are processed by the beacon chain, the activation balance is reached, and a queuing process is completed. Exit is either voluntary or done forcibly as a penalty for misbehavior.
|
|
The primary source of load on the beacon chain is "attestations". Attestations are simultaneously availability votes for a shard block (in a later upgrade) and proof-of-stake votes for a beacon block (Phase 0).
|
|
|
|
## Notation
|
|
|
|
Code snippets appearing in `this style` are to be interpreted as Python 3 code.
|
|
|
|
## Custom types
|
|
|
|
We define the following Python custom types for type hinting and readability:
|
|
|
|
| Name | SSZ equivalent | Description |
|
|
| - | - | - |
|
|
| `Slot` | `uint64` | a slot number |
|
|
| `Epoch` | `uint64` | an epoch number |
|
|
| `CommitteeIndex` | `uint64` | a committee index at a slot |
|
|
| `ValidatorIndex` | `uint64` | a validator registry index |
|
|
| `Gwei` | `uint64` | an amount in Gwei |
|
|
| `Root` | `Bytes32` | a Merkle root |
|
|
| `Hash32` | `Bytes32` | a 256-bit hash |
|
|
| `Version` | `Bytes4` | a fork version number |
|
|
| `DomainType` | `Bytes4` | a domain type |
|
|
| `ForkDigest` | `Bytes4` | a digest of the current fork data |
|
|
| `Domain` | `Bytes32` | a signature domain |
|
|
| `BLSPubkey` | `Bytes48` | a BLS12-381 public key |
|
|
| `BLSSignature` | `Bytes96` | a BLS12-381 signature |
|
|
|
|
## Constants
|
|
|
|
The following values are (non-configurable) constants used throughout the specification.
|
|
|
|
### Misc
|
|
|
|
| Name | Value |
|
|
| - | - |
|
|
| `GENESIS_SLOT` | `Slot(0)` |
|
|
| `GENESIS_EPOCH` | `Epoch(0)` |
|
|
| `FAR_FUTURE_EPOCH` | `Epoch(2**64 - 1)` |
|
|
| `BASE_REWARDS_PER_EPOCH` | `uint64(4)` |
|
|
| `DEPOSIT_CONTRACT_TREE_DEPTH` | `uint64(2**5)` (= 32) |
|
|
| `JUSTIFICATION_BITS_LENGTH` | `uint64(4)` |
|
|
| `ENDIANNESS` | `'little'` |
|
|
|
|
### Withdrawal prefixes
|
|
|
|
| Name | Value |
|
|
| - | - |
|
|
| `BLS_WITHDRAWAL_PREFIX` | `Bytes1('0x00')` |
|
|
| `ETH1_ADDRESS_WITHDRAWAL_PREFIX` | `Bytes1('0x01')` |
|
|
|
|
### Domain types
|
|
|
|
| Name | Value |
|
|
| - | - |
|
|
| `DOMAIN_BEACON_PROPOSER` | `DomainType('0x00000000')` |
|
|
| `DOMAIN_BEACON_ATTESTER` | `DomainType('0x01000000')` |
|
|
| `DOMAIN_RANDAO` | `DomainType('0x02000000')` |
|
|
| `DOMAIN_DEPOSIT` | `DomainType('0x03000000')` |
|
|
| `DOMAIN_VOLUNTARY_EXIT` | `DomainType('0x04000000')` |
|
|
| `DOMAIN_SELECTION_PROOF` | `DomainType('0x05000000')` |
|
|
| `DOMAIN_AGGREGATE_AND_PROOF` | `DomainType('0x06000000')` |
|
|
| `DOMAIN_APPLICATION_MASK` | `DomainType('0x00000001')` |
|
|
|
|
*Note*: `DOMAIN_APPLICATION_MASK` reserves the rest of the bitspace in `DomainType` for application usage. This means for some `DomainType` `DOMAIN_SOME_APPLICATION`, `DOMAIN_SOME_APPLICATION & DOMAIN_APPLICATION_MASK` **MUST** be non-zero. This expression for any other `DomainType` in the consensus specs **MUST** be zero.
|
|
|
|
## Preset
|
|
|
|
*Note*: The below configuration is bundled as a preset: a bundle of configuration variables which are expected to differ
|
|
between different modes of operation, e.g. testing, but not generally between different networks.
|
|
Additional preset configurations can be found in the [`configs`](../../configs) directory.
|
|
|
|
### Misc
|
|
|
|
| Name | Value |
|
|
| - | - |
|
|
| `MAX_COMMITTEES_PER_SLOT` | `uint64(2**6)` (= 64) |
|
|
| `TARGET_COMMITTEE_SIZE` | `uint64(2**7)` (= 128) |
|
|
| `MAX_VALIDATORS_PER_COMMITTEE` | `uint64(2**11)` (= 2,048) |
|
|
| `SHUFFLE_ROUND_COUNT` | `uint64(90)` |
|
|
| `HYSTERESIS_QUOTIENT` | `uint64(4)` |
|
|
| `HYSTERESIS_DOWNWARD_MULTIPLIER` | `uint64(1)` |
|
|
| `HYSTERESIS_UPWARD_MULTIPLIER` | `uint64(5)` |
|
|
|
|
- For the safety of committees, `TARGET_COMMITTEE_SIZE` exceeds [the recommended minimum committee size of 111](http://web.archive.org/web/20190504131341/https://vitalik.ca/files/Ithaca201807_Sharding.pdf); with sufficient active validators (at least `SLOTS_PER_EPOCH * TARGET_COMMITTEE_SIZE`), the shuffling algorithm ensures committee sizes of at least `TARGET_COMMITTEE_SIZE`. (Unbiasable randomness with a Verifiable Delay Function (VDF) will improve committee robustness and lower the safe minimum committee size.)
|
|
|
|
### Gwei values
|
|
|
|
| Name | Value |
|
|
| - | - |
|
|
| `MIN_DEPOSIT_AMOUNT` | `Gwei(2**0 * 10**9)` (= 1,000,000,000) |
|
|
| `MAX_EFFECTIVE_BALANCE` | `Gwei(2**5 * 10**9)` (= 32,000,000,000) |
|
|
| `EFFECTIVE_BALANCE_INCREMENT` | `Gwei(2**0 * 10**9)` (= 1,000,000,000) |
|
|
|
|
### Time parameters
|
|
|
|
| Name | Value | Unit | Duration |
|
|
| - | - | :-: | :-: |
|
|
| `MIN_ATTESTATION_INCLUSION_DELAY` | `uint64(2**0)` (= 1) | slots | 12 seconds |
|
|
| `SLOTS_PER_EPOCH` | `uint64(2**5)` (= 32) | slots | 6.4 minutes |
|
|
| `MIN_SEED_LOOKAHEAD` | `uint64(2**0)` (= 1) | epochs | 6.4 minutes |
|
|
| `MAX_SEED_LOOKAHEAD` | `uint64(2**2)` (= 4) | epochs | 25.6 minutes |
|
|
| `MIN_EPOCHS_TO_INACTIVITY_PENALTY` | `uint64(2**2)` (= 4) | epochs | 25.6 minutes |
|
|
| `EPOCHS_PER_ETH1_VOTING_PERIOD` | `uint64(2**6)` (= 64) | epochs | ~6.8 hours |
|
|
| `SLOTS_PER_HISTORICAL_ROOT` | `uint64(2**13)` (= 8,192) | slots | ~27 hours |
|
|
|
|
### State list lengths
|
|
|
|
| Name | Value | Unit | Duration |
|
|
| - | - | :-: | :-: |
|
|
| `EPOCHS_PER_HISTORICAL_VECTOR` | `uint64(2**16)` (= 65,536) | epochs | ~0.8 years |
|
|
| `EPOCHS_PER_SLASHINGS_VECTOR` | `uint64(2**13)` (= 8,192) | epochs | ~36 days |
|
|
| `HISTORICAL_ROOTS_LIMIT` | `uint64(2**24)` (= 16,777,216) | historical roots | ~52,262 years |
|
|
| `VALIDATOR_REGISTRY_LIMIT` | `uint64(2**40)` (= 1,099,511,627,776) | validators |
|
|
|
|
### Rewards and penalties
|
|
|
|
| Name | Value |
|
|
| - | - |
|
|
| `BASE_REWARD_FACTOR` | `uint64(2**6)` (= 64) |
|
|
| `WHISTLEBLOWER_REWARD_QUOTIENT` | `uint64(2**9)` (= 512) |
|
|
| `PROPOSER_REWARD_QUOTIENT` | `uint64(2**3)` (= 8) |
|
|
| `INACTIVITY_PENALTY_QUOTIENT` | `uint64(2**26)` (= 67,108,864) |
|
|
| `MIN_SLASHING_PENALTY_QUOTIENT` | `uint64(2**7)` (= 128) |
|
|
| `PROPORTIONAL_SLASHING_MULTIPLIER` | `uint64(1)` |
|
|
|
|
- The `INACTIVITY_PENALTY_QUOTIENT` equals `INVERSE_SQRT_E_DROP_TIME**2` where `INVERSE_SQRT_E_DROP_TIME := 2**13` epochs (about 36 days) is the time it takes the inactivity penalty to reduce the balance of non-participating validators to about `1/sqrt(e) ~= 60.6%`. Indeed, the balance retained by offline validators after `n` epochs is about `(1 - 1/INACTIVITY_PENALTY_QUOTIENT)**(n**2/2)`; so after `INVERSE_SQRT_E_DROP_TIME` epochs, it is roughly `(1 - 1/INACTIVITY_PENALTY_QUOTIENT)**(INACTIVITY_PENALTY_QUOTIENT/2) ~= 1/sqrt(e)`. Note this value will be upgraded to `2**24` after Phase 0 mainnet stabilizes to provide a faster recovery in the event of an inactivity leak.
|
|
|
|
- The `PROPORTIONAL_SLASHING_MULTIPLIER` is set to `1` at initial mainnet launch, resulting in one-third of the minimum accountable safety margin in the event of a finality attack. After Phase 0 mainnet stabilizes, this value will be upgraded to `3` to provide the maximal minimum accountable safety margin.
|
|
|
|
### Max operations per block
|
|
|
|
| Name | Value |
|
|
| - | - |
|
|
| `MAX_PROPOSER_SLASHINGS` | `2**4` (= 16) |
|
|
| `MAX_ATTESTER_SLASHINGS` | `2**1` (= 2) |
|
|
| `MAX_ATTESTATIONS` | `2**7` (= 128) |
|
|
| `MAX_DEPOSITS` | `2**4` (= 16) |
|
|
| `MAX_VOLUNTARY_EXITS` | `2**4` (= 16) |
|
|
|
|
## Configuration
|
|
|
|
*Note*: The default mainnet configuration values are included here for illustrative purposes.
|
|
Defaults for this more dynamic type of configuration are available with the presets in the [`configs`](../../configs) directory.
|
|
Testnets and other types of chain instances may use a different configuration.
|
|
|
|
### Genesis settings
|
|
|
|
| Name | Value |
|
|
| - | - |
|
|
| `MIN_GENESIS_ACTIVE_VALIDATOR_COUNT` | `uint64(2**14)` (= 16,384) |
|
|
| `MIN_GENESIS_TIME` | `uint64(1606824000)` (Dec 1, 2020, 12pm UTC) |
|
|
| `GENESIS_FORK_VERSION` | `Version('0x00000000')` |
|
|
| `GENESIS_DELAY` | `uint64(604800)` (7 days) |
|
|
|
|
### Time parameters
|
|
|
|
| Name | Value | Unit | Duration |
|
|
| - | - | :-: | :-: |
|
|
| `SECONDS_PER_SLOT` | `uint64(12)` | seconds | 12 seconds |
|
|
| `SECONDS_PER_ETH1_BLOCK` | `uint64(14)` | seconds | 14 seconds |
|
|
| `MIN_VALIDATOR_WITHDRAWABILITY_DELAY` | `uint64(2**8)` (= 256) | epochs | ~27 hours |
|
|
| `SHARD_COMMITTEE_PERIOD` | `uint64(2**8)` (= 256) | epochs | ~27 hours |
|
|
| `ETH1_FOLLOW_DISTANCE` | `uint64(2**11)` (= 2,048) | Eth1 blocks | ~8 hours |
|
|
|
|
### Validator cycle
|
|
|
|
| Name | Value |
|
|
| - | - |
|
|
| `EJECTION_BALANCE` | `Gwei(2**4 * 10**9)` (= 16,000,000,000) |
|
|
| `MIN_PER_EPOCH_CHURN_LIMIT` | `uint64(2**2)` (= 4) |
|
|
| `CHURN_LIMIT_QUOTIENT` | `uint64(2**16)` (= 65,536) |
|
|
|
|
## Containers
|
|
|
|
The following types are [SimpleSerialize (SSZ)](../../ssz/simple-serialize.md) containers.
|
|
|
|
*Note*: The definitions are ordered topologically to facilitate execution of the spec.
|
|
|
|
*Note*: Fields missing in container instantiations default to their zero value.
|
|
|
|
### Misc dependencies
|
|
|
|
#### `Fork`
|
|
|
|
```python
|
|
class Fork(Container):
|
|
previous_version: Version
|
|
current_version: Version
|
|
epoch: Epoch # Epoch of latest fork
|
|
```
|
|
|
|
#### `ForkData`
|
|
|
|
```python
|
|
class ForkData(Container):
|
|
current_version: Version
|
|
genesis_validators_root: Root
|
|
```
|
|
|
|
#### `Checkpoint`
|
|
|
|
```python
|
|
class Checkpoint(Container):
|
|
epoch: Epoch
|
|
root: Root
|
|
```
|
|
|
|
#### `Validator`
|
|
|
|
```python
|
|
class Validator(Container):
|
|
pubkey: BLSPubkey
|
|
withdrawal_credentials: Bytes32 # Commitment to pubkey for withdrawals
|
|
effective_balance: Gwei # Balance at stake
|
|
slashed: boolean
|
|
# Status epochs
|
|
activation_eligibility_epoch: Epoch # When criteria for activation were met
|
|
activation_epoch: Epoch
|
|
exit_epoch: Epoch
|
|
withdrawable_epoch: Epoch # When validator can withdraw funds
|
|
```
|
|
|
|
#### `AttestationData`
|
|
|
|
```python
|
|
class AttestationData(Container):
|
|
slot: Slot
|
|
index: CommitteeIndex
|
|
# LMD GHOST vote
|
|
beacon_block_root: Root
|
|
# FFG vote
|
|
source: Checkpoint
|
|
target: Checkpoint
|
|
```
|
|
|
|
#### `IndexedAttestation`
|
|
|
|
```python
|
|
class IndexedAttestation(Container):
|
|
attesting_indices: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE]
|
|
data: AttestationData
|
|
signature: BLSSignature
|
|
```
|
|
|
|
#### `PendingAttestation`
|
|
|
|
```python
|
|
class PendingAttestation(Container):
|
|
aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE]
|
|
data: AttestationData
|
|
inclusion_delay: Slot
|
|
proposer_index: ValidatorIndex
|
|
```
|
|
|
|
#### `Eth1Data`
|
|
|
|
```python
|
|
class Eth1Data(Container):
|
|
deposit_root: Root
|
|
deposit_count: uint64
|
|
block_hash: Hash32
|
|
```
|
|
|
|
#### `HistoricalBatch`
|
|
|
|
```python
|
|
class HistoricalBatch(Container):
|
|
block_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT]
|
|
state_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT]
|
|
```
|
|
|
|
#### `DepositMessage`
|
|
|
|
```python
|
|
class DepositMessage(Container):
|
|
pubkey: BLSPubkey
|
|
withdrawal_credentials: Bytes32
|
|
amount: Gwei
|
|
```
|
|
|
|
#### `DepositData`
|
|
|
|
```python
|
|
class DepositData(Container):
|
|
pubkey: BLSPubkey
|
|
withdrawal_credentials: Bytes32
|
|
amount: Gwei
|
|
signature: BLSSignature # Signing over DepositMessage
|
|
```
|
|
|
|
#### `BeaconBlockHeader`
|
|
|
|
```python
|
|
class BeaconBlockHeader(Container):
|
|
slot: Slot
|
|
proposer_index: ValidatorIndex
|
|
parent_root: Root
|
|
state_root: Root
|
|
body_root: Root
|
|
```
|
|
|
|
#### `SigningData`
|
|
|
|
```python
|
|
class SigningData(Container):
|
|
object_root: Root
|
|
domain: Domain
|
|
```
|
|
|
|
### Beacon operations
|
|
|
|
#### `ProposerSlashing`
|
|
|
|
```python
|
|
class ProposerSlashing(Container):
|
|
signed_header_1: SignedBeaconBlockHeader
|
|
signed_header_2: SignedBeaconBlockHeader
|
|
```
|
|
|
|
#### `AttesterSlashing`
|
|
|
|
```python
|
|
class AttesterSlashing(Container):
|
|
attestation_1: IndexedAttestation
|
|
attestation_2: IndexedAttestation
|
|
```
|
|
|
|
#### `Attestation`
|
|
|
|
```python
|
|
class Attestation(Container):
|
|
aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE]
|
|
data: AttestationData
|
|
signature: BLSSignature
|
|
```
|
|
|
|
#### `Deposit`
|
|
|
|
```python
|
|
class Deposit(Container):
|
|
proof: Vector[Bytes32, DEPOSIT_CONTRACT_TREE_DEPTH + 1] # Merkle path to deposit root
|
|
data: DepositData
|
|
```
|
|
|
|
#### `VoluntaryExit`
|
|
|
|
```python
|
|
class VoluntaryExit(Container):
|
|
epoch: Epoch # Earliest epoch when voluntary exit can be processed
|
|
validator_index: ValidatorIndex
|
|
```
|
|
|
|
### Beacon blocks
|
|
|
|
#### `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]
|
|
```
|
|
|
|
#### `BeaconBlock`
|
|
|
|
```python
|
|
class BeaconBlock(Container):
|
|
slot: Slot
|
|
proposer_index: ValidatorIndex
|
|
parent_root: Root
|
|
state_root: Root
|
|
body: BeaconBlockBody
|
|
```
|
|
|
|
### Beacon state
|
|
|
|
#### `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
|
|
# Attestations
|
|
previous_epoch_attestations: List[PendingAttestation, MAX_ATTESTATIONS * SLOTS_PER_EPOCH]
|
|
current_epoch_attestations: List[PendingAttestation, MAX_ATTESTATIONS * SLOTS_PER_EPOCH]
|
|
# Finality
|
|
justification_bits: Bitvector[JUSTIFICATION_BITS_LENGTH] # Bit set for every recent justified epoch
|
|
previous_justified_checkpoint: Checkpoint # Previous epoch snapshot
|
|
current_justified_checkpoint: Checkpoint
|
|
finalized_checkpoint: Checkpoint
|
|
```
|
|
|
|
### Signed envelopes
|
|
|
|
#### `SignedVoluntaryExit`
|
|
|
|
```python
|
|
class SignedVoluntaryExit(Container):
|
|
message: VoluntaryExit
|
|
signature: BLSSignature
|
|
```
|
|
|
|
#### `SignedBeaconBlock`
|
|
|
|
```python
|
|
class SignedBeaconBlock(Container):
|
|
message: BeaconBlock
|
|
signature: BLSSignature
|
|
```
|
|
|
|
#### `SignedBeaconBlockHeader`
|
|
|
|
```python
|
|
class SignedBeaconBlockHeader(Container):
|
|
message: BeaconBlockHeader
|
|
signature: BLSSignature
|
|
```
|
|
|
|
## Helper functions
|
|
|
|
*Note*: The definitions below are for specification purposes and are not necessarily optimal implementations.
|
|
|
|
### Math
|
|
|
|
#### `integer_squareroot`
|
|
|
|
```python
|
|
def integer_squareroot(n: uint64) -> uint64:
|
|
"""
|
|
Return the largest integer ``x`` such that ``x**2 <= n``.
|
|
"""
|
|
x = n
|
|
y = (x + 1) // 2
|
|
while y < x:
|
|
x = y
|
|
y = (x + n // x) // 2
|
|
return x
|
|
```
|
|
|
|
#### `xor`
|
|
|
|
```python
|
|
def xor(bytes_1: Bytes32, bytes_2: Bytes32) -> Bytes32:
|
|
"""
|
|
Return the exclusive-or of two 32-byte strings.
|
|
"""
|
|
return Bytes32(a ^ b for a, b in zip(bytes_1, bytes_2))
|
|
```
|
|
|
|
#### `uint_to_bytes`
|
|
|
|
`def uint_to_bytes(n: uint) -> bytes` is a function for serializing the `uint` type object to bytes in ``ENDIANNESS``-endian. The expected length of the output is the byte-length of the `uint` type.
|
|
|
|
#### `bytes_to_uint64`
|
|
|
|
```python
|
|
def bytes_to_uint64(data: bytes) -> uint64:
|
|
"""
|
|
Return the integer deserialization of ``data`` interpreted as ``ENDIANNESS``-endian.
|
|
"""
|
|
return uint64(int.from_bytes(data, ENDIANNESS))
|
|
```
|
|
|
|
### Crypto
|
|
|
|
#### `hash`
|
|
|
|
`def hash(data: bytes) -> Bytes32` is SHA256.
|
|
|
|
#### `hash_tree_root`
|
|
|
|
`def hash_tree_root(object: SSZSerializable) -> Root` is a function for hashing objects into a single root by utilizing a hash tree structure, as defined in the [SSZ spec](../../ssz/simple-serialize.md#merkleization).
|
|
|
|
#### BLS signatures
|
|
|
|
The [IETF BLS signature draft standard v4](https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-04) with ciphersuite `BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_` defines the following functions:
|
|
|
|
- `def Sign(privkey: int, message: Bytes) -> BLSSignature`
|
|
- `def Verify(pubkey: BLSPubkey, message: Bytes, signature: BLSSignature) -> bool`
|
|
- `def Aggregate(signatures: Sequence[BLSSignature]) -> BLSSignature`
|
|
- `def FastAggregateVerify(pubkeys: Sequence[BLSPubkey], message: Bytes, signature: BLSSignature) -> bool`
|
|
- `def AggregateVerify(pubkeys: Sequence[BLSPubkey], messages: Sequence[Bytes], signature: BLSSignature) -> bool`
|
|
- `def KeyValidate(pubkey: BLSPubkey) -> bool`
|
|
|
|
The above functions are accessed through the `bls` module, e.g. `bls.Verify`.
|
|
|
|
### Predicates
|
|
|
|
#### `is_active_validator`
|
|
|
|
```python
|
|
def is_active_validator(validator: Validator, epoch: Epoch) -> bool:
|
|
"""
|
|
Check if ``validator`` is active.
|
|
"""
|
|
return validator.activation_epoch <= epoch < validator.exit_epoch
|
|
```
|
|
|
|
#### `is_eligible_for_activation_queue`
|
|
|
|
```python
|
|
def is_eligible_for_activation_queue(validator: Validator) -> bool:
|
|
"""
|
|
Check if ``validator`` is eligible to be placed into the activation queue.
|
|
"""
|
|
return (
|
|
validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH
|
|
and validator.effective_balance == MAX_EFFECTIVE_BALANCE
|
|
)
|
|
```
|
|
|
|
#### `is_eligible_for_activation`
|
|
|
|
```python
|
|
def is_eligible_for_activation(state: BeaconState, validator: Validator) -> bool:
|
|
"""
|
|
Check if ``validator`` is eligible for activation.
|
|
"""
|
|
return (
|
|
# Placement in queue is finalized
|
|
validator.activation_eligibility_epoch <= state.finalized_checkpoint.epoch
|
|
# Has not yet been activated
|
|
and validator.activation_epoch == FAR_FUTURE_EPOCH
|
|
)
|
|
```
|
|
|
|
#### `is_slashable_validator`
|
|
|
|
```python
|
|
def is_slashable_validator(validator: Validator, epoch: Epoch) -> bool:
|
|
"""
|
|
Check if ``validator`` is slashable.
|
|
"""
|
|
return (not validator.slashed) and (validator.activation_epoch <= epoch < validator.withdrawable_epoch)
|
|
```
|
|
|
|
#### `is_slashable_attestation_data`
|
|
|
|
```python
|
|
def is_slashable_attestation_data(data_1: AttestationData, data_2: AttestationData) -> bool:
|
|
"""
|
|
Check if ``data_1`` and ``data_2`` are slashable according to Casper FFG rules.
|
|
"""
|
|
return (
|
|
# Double vote
|
|
(data_1 != data_2 and data_1.target.epoch == data_2.target.epoch) or
|
|
# Surround vote
|
|
(data_1.source.epoch < data_2.source.epoch and data_2.target.epoch < data_1.target.epoch)
|
|
)
|
|
```
|
|
|
|
#### `is_valid_indexed_attestation`
|
|
|
|
```python
|
|
def is_valid_indexed_attestation(state: BeaconState, indexed_attestation: IndexedAttestation) -> bool:
|
|
"""
|
|
Check if ``indexed_attestation`` is not empty, has sorted and unique indices and has a valid aggregate signature.
|
|
"""
|
|
# Verify indices are sorted and unique
|
|
indices = indexed_attestation.attesting_indices
|
|
if len(indices) == 0 or not indices == sorted(set(indices)):
|
|
return False
|
|
# Verify aggregate signature
|
|
pubkeys = [state.validators[i].pubkey for i in indices]
|
|
domain = get_domain(state, DOMAIN_BEACON_ATTESTER, indexed_attestation.data.target.epoch)
|
|
signing_root = compute_signing_root(indexed_attestation.data, domain)
|
|
return bls.FastAggregateVerify(pubkeys, signing_root, indexed_attestation.signature)
|
|
```
|
|
|
|
#### `is_valid_merkle_branch`
|
|
|
|
```python
|
|
def is_valid_merkle_branch(leaf: Bytes32, branch: Sequence[Bytes32], depth: uint64, index: uint64, root: Root) -> bool:
|
|
"""
|
|
Check if ``leaf`` at ``index`` verifies against the Merkle ``root`` and ``branch``.
|
|
"""
|
|
value = leaf
|
|
for i in range(depth):
|
|
if index // (2**i) % 2:
|
|
value = hash(branch[i] + value)
|
|
else:
|
|
value = hash(value + branch[i])
|
|
return value == root
|
|
```
|
|
|
|
### Misc
|
|
|
|
#### `compute_shuffled_index`
|
|
|
|
```python
|
|
def compute_shuffled_index(index: uint64, index_count: uint64, seed: Bytes32) -> uint64:
|
|
"""
|
|
Return the shuffled index corresponding to ``seed`` (and ``index_count``).
|
|
"""
|
|
assert index < index_count
|
|
|
|
# Swap or not (https://link.springer.com/content/pdf/10.1007%2F978-3-642-32009-5_1.pdf)
|
|
# See the 'generalized domain' algorithm on page 3
|
|
for current_round in range(SHUFFLE_ROUND_COUNT):
|
|
pivot = bytes_to_uint64(hash(seed + uint_to_bytes(uint8(current_round)))[0:8]) % index_count
|
|
flip = (pivot + index_count - index) % index_count
|
|
position = max(index, flip)
|
|
source = hash(
|
|
seed
|
|
+ uint_to_bytes(uint8(current_round))
|
|
+ uint_to_bytes(uint32(position // 256))
|
|
)
|
|
byte = uint8(source[(position % 256) // 8])
|
|
bit = (byte >> (position % 8)) % 2
|
|
index = flip if bit else index
|
|
|
|
return index
|
|
```
|
|
|
|
#### `compute_proposer_index`
|
|
|
|
```python
|
|
def compute_proposer_index(state: BeaconState, indices: Sequence[ValidatorIndex], seed: Bytes32) -> ValidatorIndex:
|
|
"""
|
|
Return from ``indices`` a random index sampled by effective balance.
|
|
"""
|
|
assert len(indices) > 0
|
|
MAX_RANDOM_BYTE = 2**8 - 1
|
|
i = uint64(0)
|
|
total = uint64(len(indices))
|
|
while True:
|
|
candidate_index = indices[compute_shuffled_index(i % total, total, seed)]
|
|
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:
|
|
return candidate_index
|
|
i += 1
|
|
```
|
|
|
|
#### `compute_committee`
|
|
|
|
```python
|
|
def compute_committee(indices: Sequence[ValidatorIndex],
|
|
seed: Bytes32,
|
|
index: uint64,
|
|
count: uint64) -> Sequence[ValidatorIndex]:
|
|
"""
|
|
Return the committee corresponding to ``indices``, ``seed``, ``index``, and committee ``count``.
|
|
"""
|
|
start = (len(indices) * index) // count
|
|
end = (len(indices) * uint64(index + 1)) // count
|
|
return [indices[compute_shuffled_index(uint64(i), uint64(len(indices)), seed)] for i in range(start, end)]
|
|
```
|
|
|
|
#### `compute_epoch_at_slot`
|
|
|
|
```python
|
|
def compute_epoch_at_slot(slot: Slot) -> Epoch:
|
|
"""
|
|
Return the epoch number at ``slot``.
|
|
"""
|
|
return Epoch(slot // SLOTS_PER_EPOCH)
|
|
```
|
|
|
|
#### `compute_start_slot_at_epoch`
|
|
|
|
```python
|
|
def compute_start_slot_at_epoch(epoch: Epoch) -> Slot:
|
|
"""
|
|
Return the start slot of ``epoch``.
|
|
"""
|
|
return Slot(epoch * SLOTS_PER_EPOCH)
|
|
```
|
|
|
|
#### `compute_activation_exit_epoch`
|
|
|
|
```python
|
|
def compute_activation_exit_epoch(epoch: Epoch) -> Epoch:
|
|
"""
|
|
Return the epoch during which validator activations and exits initiated in ``epoch`` take effect.
|
|
"""
|
|
return Epoch(epoch + 1 + MAX_SEED_LOOKAHEAD)
|
|
```
|
|
|
|
#### `compute_fork_data_root`
|
|
|
|
```python
|
|
def compute_fork_data_root(current_version: Version, genesis_validators_root: Root) -> Root:
|
|
"""
|
|
Return the 32-byte fork data root for the ``current_version`` and ``genesis_validators_root``.
|
|
This is used primarily in signature domains to avoid collisions across forks/chains.
|
|
"""
|
|
return hash_tree_root(ForkData(
|
|
current_version=current_version,
|
|
genesis_validators_root=genesis_validators_root,
|
|
))
|
|
```
|
|
|
|
#### `compute_fork_digest`
|
|
|
|
```python
|
|
def compute_fork_digest(current_version: Version, genesis_validators_root: Root) -> ForkDigest:
|
|
"""
|
|
Return the 4-byte fork digest for the ``current_version`` and ``genesis_validators_root``.
|
|
This is a digest primarily used for domain separation on the p2p layer.
|
|
4-bytes suffices for practical separation of forks/chains.
|
|
"""
|
|
return ForkDigest(compute_fork_data_root(current_version, genesis_validators_root)[:4])
|
|
```
|
|
|
|
#### `compute_domain`
|
|
|
|
```python
|
|
def compute_domain(domain_type: DomainType, fork_version: Version=None, genesis_validators_root: Root=None) -> Domain:
|
|
"""
|
|
Return the domain for the ``domain_type`` and ``fork_version``.
|
|
"""
|
|
if fork_version is None:
|
|
fork_version = GENESIS_FORK_VERSION
|
|
if genesis_validators_root is None:
|
|
genesis_validators_root = Root() # all bytes zero by default
|
|
fork_data_root = compute_fork_data_root(fork_version, genesis_validators_root)
|
|
return Domain(domain_type + fork_data_root[:28])
|
|
```
|
|
|
|
#### `compute_signing_root`
|
|
|
|
```python
|
|
def compute_signing_root(ssz_object: SSZObject, domain: Domain) -> Root:
|
|
"""
|
|
Return the signing root for the corresponding signing data.
|
|
"""
|
|
return hash_tree_root(SigningData(
|
|
object_root=hash_tree_root(ssz_object),
|
|
domain=domain,
|
|
))
|
|
```
|
|
|
|
### Beacon state accessors
|
|
|
|
#### `get_current_epoch`
|
|
|
|
```python
|
|
def get_current_epoch(state: BeaconState) -> Epoch:
|
|
"""
|
|
Return the current epoch.
|
|
"""
|
|
return compute_epoch_at_slot(state.slot)
|
|
```
|
|
|
|
#### `get_previous_epoch`
|
|
|
|
```python
|
|
def get_previous_epoch(state: BeaconState) -> Epoch:
|
|
"""`
|
|
Return the previous epoch (unless the current epoch is ``GENESIS_EPOCH``).
|
|
"""
|
|
current_epoch = get_current_epoch(state)
|
|
return GENESIS_EPOCH if current_epoch == GENESIS_EPOCH else Epoch(current_epoch - 1)
|
|
```
|
|
|
|
#### `get_block_root`
|
|
|
|
```python
|
|
def get_block_root(state: BeaconState, epoch: Epoch) -> Root:
|
|
"""
|
|
Return the block root at the start of a recent ``epoch``.
|
|
"""
|
|
return get_block_root_at_slot(state, compute_start_slot_at_epoch(epoch))
|
|
```
|
|
|
|
#### `get_block_root_at_slot`
|
|
|
|
```python
|
|
def get_block_root_at_slot(state: BeaconState, slot: Slot) -> Root:
|
|
"""
|
|
Return the block root at a recent ``slot``.
|
|
"""
|
|
assert slot < state.slot <= slot + SLOTS_PER_HISTORICAL_ROOT
|
|
return state.block_roots[slot % SLOTS_PER_HISTORICAL_ROOT]
|
|
```
|
|
|
|
#### `get_randao_mix`
|
|
|
|
```python
|
|
def get_randao_mix(state: BeaconState, epoch: Epoch) -> Bytes32:
|
|
"""
|
|
Return the randao mix at a recent ``epoch``.
|
|
"""
|
|
return state.randao_mixes[epoch % EPOCHS_PER_HISTORICAL_VECTOR]
|
|
```
|
|
|
|
#### `get_active_validator_indices`
|
|
|
|
```python
|
|
def get_active_validator_indices(state: BeaconState, epoch: Epoch) -> Sequence[ValidatorIndex]:
|
|
"""
|
|
Return the sequence of active validator indices at ``epoch``.
|
|
"""
|
|
return [ValidatorIndex(i) for i, v in enumerate(state.validators) if is_active_validator(v, epoch)]
|
|
```
|
|
|
|
#### `get_validator_churn_limit`
|
|
|
|
```python
|
|
def get_validator_churn_limit(state: BeaconState) -> uint64:
|
|
"""
|
|
Return the validator churn limit for the current epoch.
|
|
"""
|
|
active_validator_indices = get_active_validator_indices(state, get_current_epoch(state))
|
|
return max(MIN_PER_EPOCH_CHURN_LIMIT, uint64(len(active_validator_indices)) // CHURN_LIMIT_QUOTIENT)
|
|
```
|
|
|
|
#### `get_seed`
|
|
|
|
```python
|
|
def get_seed(state: BeaconState, epoch: Epoch, domain_type: DomainType) -> Bytes32:
|
|
"""
|
|
Return the seed at ``epoch``.
|
|
"""
|
|
mix = get_randao_mix(state, Epoch(epoch + EPOCHS_PER_HISTORICAL_VECTOR - MIN_SEED_LOOKAHEAD - 1)) # Avoid underflow
|
|
return hash(domain_type + uint_to_bytes(epoch) + mix)
|
|
```
|
|
|
|
#### `get_committee_count_per_slot`
|
|
|
|
```python
|
|
def get_committee_count_per_slot(state: BeaconState, epoch: Epoch) -> uint64:
|
|
"""
|
|
Return the number of committees in each slot for the given ``epoch``.
|
|
"""
|
|
return max(uint64(1), min(
|
|
MAX_COMMITTEES_PER_SLOT,
|
|
uint64(len(get_active_validator_indices(state, epoch))) // SLOTS_PER_EPOCH // TARGET_COMMITTEE_SIZE,
|
|
))
|
|
```
|
|
|
|
#### `get_beacon_committee`
|
|
|
|
```python
|
|
def get_beacon_committee(state: BeaconState, slot: Slot, index: CommitteeIndex) -> Sequence[ValidatorIndex]:
|
|
"""
|
|
Return the beacon committee at ``slot`` for ``index``.
|
|
"""
|
|
epoch = compute_epoch_at_slot(slot)
|
|
committees_per_slot = get_committee_count_per_slot(state, epoch)
|
|
return compute_committee(
|
|
indices=get_active_validator_indices(state, epoch),
|
|
seed=get_seed(state, epoch, DOMAIN_BEACON_ATTESTER),
|
|
index=(slot % SLOTS_PER_EPOCH) * committees_per_slot + index,
|
|
count=committees_per_slot * SLOTS_PER_EPOCH,
|
|
)
|
|
```
|
|
|
|
#### `get_beacon_proposer_index`
|
|
|
|
```python
|
|
def get_beacon_proposer_index(state: BeaconState) -> ValidatorIndex:
|
|
"""
|
|
Return the beacon proposer index at the current slot.
|
|
"""
|
|
epoch = get_current_epoch(state)
|
|
seed = hash(get_seed(state, epoch, DOMAIN_BEACON_PROPOSER) + uint_to_bytes(state.slot))
|
|
indices = get_active_validator_indices(state, epoch)
|
|
return compute_proposer_index(state, indices, seed)
|
|
```
|
|
|
|
#### `get_total_balance`
|
|
|
|
```python
|
|
def get_total_balance(state: BeaconState, indices: Set[ValidatorIndex]) -> Gwei:
|
|
"""
|
|
Return the combined effective balance of the ``indices``.
|
|
``EFFECTIVE_BALANCE_INCREMENT`` Gwei minimum to avoid divisions by zero.
|
|
Math safe up to ~10B ETH, after which this overflows uint64.
|
|
"""
|
|
return Gwei(max(EFFECTIVE_BALANCE_INCREMENT, sum([state.validators[index].effective_balance for index in indices])))
|
|
```
|
|
|
|
#### `get_total_active_balance`
|
|
|
|
```python
|
|
def get_total_active_balance(state: BeaconState) -> Gwei:
|
|
"""
|
|
Return the combined effective balance of the active validators.
|
|
Note: ``get_total_balance`` returns ``EFFECTIVE_BALANCE_INCREMENT`` Gwei minimum to avoid divisions by zero.
|
|
"""
|
|
return get_total_balance(state, set(get_active_validator_indices(state, get_current_epoch(state))))
|
|
```
|
|
|
|
#### `get_domain`
|
|
|
|
```python
|
|
def get_domain(state: BeaconState, domain_type: DomainType, epoch: Epoch=None) -> Domain:
|
|
"""
|
|
Return the signature domain (fork version concatenated with domain type) of a message.
|
|
"""
|
|
epoch = get_current_epoch(state) if epoch is None else epoch
|
|
fork_version = state.fork.previous_version if epoch < state.fork.epoch else state.fork.current_version
|
|
return compute_domain(domain_type, fork_version, state.genesis_validators_root)
|
|
```
|
|
|
|
#### `get_indexed_attestation`
|
|
|
|
```python
|
|
def get_indexed_attestation(state: BeaconState, attestation: Attestation) -> IndexedAttestation:
|
|
"""
|
|
Return the indexed attestation corresponding to ``attestation``.
|
|
"""
|
|
attesting_indices = get_attesting_indices(state, attestation.data, attestation.aggregation_bits)
|
|
|
|
return IndexedAttestation(
|
|
attesting_indices=sorted(attesting_indices),
|
|
data=attestation.data,
|
|
signature=attestation.signature,
|
|
)
|
|
```
|
|
|
|
#### `get_attesting_indices`
|
|
|
|
```python
|
|
def get_attesting_indices(state: BeaconState,
|
|
data: AttestationData,
|
|
bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE]) -> Set[ValidatorIndex]:
|
|
"""
|
|
Return the set of attesting indices corresponding to ``data`` and ``bits``.
|
|
"""
|
|
committee = get_beacon_committee(state, data.slot, data.index)
|
|
return set(index for i, index in enumerate(committee) if bits[i])
|
|
```
|
|
|
|
### Beacon state mutators
|
|
|
|
#### `increase_balance`
|
|
|
|
```python
|
|
def increase_balance(state: BeaconState, index: ValidatorIndex, delta: Gwei) -> None:
|
|
"""
|
|
Increase the validator balance at index ``index`` by ``delta``.
|
|
"""
|
|
state.balances[index] += delta
|
|
```
|
|
|
|
#### `decrease_balance`
|
|
|
|
```python
|
|
def decrease_balance(state: BeaconState, index: ValidatorIndex, delta: Gwei) -> None:
|
|
"""
|
|
Decrease the validator balance at index ``index`` by ``delta``, with underflow protection.
|
|
"""
|
|
state.balances[index] = 0 if delta > state.balances[index] else state.balances[index] - delta
|
|
```
|
|
|
|
#### `initiate_validator_exit`
|
|
|
|
```python
|
|
def initiate_validator_exit(state: BeaconState, index: ValidatorIndex) -> None:
|
|
"""
|
|
Initiate the exit of the validator with index ``index``.
|
|
"""
|
|
# Return if validator already initiated exit
|
|
validator = state.validators[index]
|
|
if validator.exit_epoch != FAR_FUTURE_EPOCH:
|
|
return
|
|
|
|
# Compute exit queue epoch
|
|
exit_epochs = [v.exit_epoch for v in state.validators if v.exit_epoch != FAR_FUTURE_EPOCH]
|
|
exit_queue_epoch = max(exit_epochs + [compute_activation_exit_epoch(get_current_epoch(state))])
|
|
exit_queue_churn = len([v for v in state.validators if v.exit_epoch == exit_queue_epoch])
|
|
if exit_queue_churn >= get_validator_churn_limit(state):
|
|
exit_queue_epoch += Epoch(1)
|
|
|
|
# Set validator exit epoch and withdrawable epoch
|
|
validator.exit_epoch = exit_queue_epoch
|
|
validator.withdrawable_epoch = Epoch(validator.exit_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY)
|
|
```
|
|
|
|
#### `slash_validator`
|
|
|
|
```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)
|
|
|
|
# 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_REWARD_QUOTIENT)
|
|
increase_balance(state, proposer_index, proposer_reward)
|
|
increase_balance(state, whistleblower_index, Gwei(whistleblower_reward - proposer_reward))
|
|
```
|
|
|
|
## Genesis
|
|
|
|
Before the Ethereum beacon chain genesis has been triggered, and for every Ethereum proof-of-work block, let `candidate_state = initialize_beacon_state_from_eth1(eth1_block_hash, eth1_timestamp, deposits)` where:
|
|
|
|
- `eth1_block_hash` is the hash of the Ethereum proof-of-work block
|
|
- `eth1_timestamp` is the Unix timestamp corresponding to `eth1_block_hash`
|
|
- `deposits` is the sequence of all deposits, ordered chronologically, up to (and including) the block with hash `eth1_block_hash`
|
|
|
|
Proof-of-work blocks must only be considered once they are at least `SECONDS_PER_ETH1_BLOCK * ETH1_FOLLOW_DISTANCE` seconds old (i.e. `eth1_timestamp + SECONDS_PER_ETH1_BLOCK * ETH1_FOLLOW_DISTANCE <= current_unix_time`). Due to this constraint, if `GENESIS_DELAY < SECONDS_PER_ETH1_BLOCK * ETH1_FOLLOW_DISTANCE`, then the `genesis_time` can happen before the time/state is first known. Values should be configured to avoid this case.
|
|
|
|
```python
|
|
def initialize_beacon_state_from_eth1(eth1_block_hash: Hash32,
|
|
eth1_timestamp: uint64,
|
|
deposits: Sequence[Deposit]) -> BeaconState:
|
|
fork = Fork(
|
|
previous_version=GENESIS_FORK_VERSION,
|
|
current_version=GENESIS_FORK_VERSION,
|
|
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)
|
|
|
|
return state
|
|
```
|
|
|
|
*Note*: The ETH1 block with `eth1_timestamp` meeting the minimum genesis active validator count criteria can also occur before `MIN_GENESIS_TIME`.
|
|
|
|
### Genesis state
|
|
|
|
Let `genesis_state = candidate_state` whenever `is_valid_genesis_state(candidate_state) is True` for the first time.
|
|
|
|
```python
|
|
def is_valid_genesis_state(state: BeaconState) -> bool:
|
|
if state.genesis_time < MIN_GENESIS_TIME:
|
|
return False
|
|
if len(get_active_validator_indices(state, GENESIS_EPOCH)) < MIN_GENESIS_ACTIVE_VALIDATOR_COUNT:
|
|
return False
|
|
return True
|
|
```
|
|
|
|
### Genesis block
|
|
|
|
Let `genesis_block = BeaconBlock(state_root=hash_tree_root(genesis_state))`.
|
|
|
|
## Beacon chain state transition function
|
|
|
|
The post-state corresponding to a pre-state `state` and a signed block `signed_block` is defined as `state_transition(state, signed_block)`. State transitions that trigger an unhandled exception (e.g. a failed `assert` or an out-of-range list access) are considered invalid. State transitions that cause a `uint64` overflow or underflow are also considered invalid.
|
|
|
|
```python
|
|
def state_transition(state: BeaconState, signed_block: SignedBeaconBlock, validate_result: bool=True) -> None:
|
|
block = signed_block.message
|
|
# Process slots (including those with no blocks) since block
|
|
process_slots(state, block.slot)
|
|
# Verify signature
|
|
if validate_result:
|
|
assert verify_block_signature(state, signed_block)
|
|
# Process block
|
|
process_block(state, block)
|
|
# Verify state root
|
|
if validate_result:
|
|
assert block.state_root == hash_tree_root(state)
|
|
```
|
|
|
|
```python
|
|
def verify_block_signature(state: BeaconState, signed_block: SignedBeaconBlock) -> bool:
|
|
proposer = state.validators[signed_block.message.proposer_index]
|
|
signing_root = compute_signing_root(signed_block.message, get_domain(state, DOMAIN_BEACON_PROPOSER))
|
|
return bls.Verify(proposer.pubkey, signing_root, signed_block.signature)
|
|
```
|
|
|
|
```python
|
|
def process_slots(state: BeaconState, slot: Slot) -> None:
|
|
assert state.slot < slot
|
|
while state.slot < slot:
|
|
process_slot(state)
|
|
# Process epoch on the start slot of the next epoch
|
|
if (state.slot + 1) % SLOTS_PER_EPOCH == 0:
|
|
process_epoch(state)
|
|
state.slot = Slot(state.slot + 1)
|
|
```
|
|
|
|
```python
|
|
def process_slot(state: BeaconState) -> None:
|
|
# Cache state root
|
|
previous_state_root = hash_tree_root(state)
|
|
state.state_roots[state.slot % SLOTS_PER_HISTORICAL_ROOT] = previous_state_root
|
|
# Cache latest block header state root
|
|
if state.latest_block_header.state_root == Bytes32():
|
|
state.latest_block_header.state_root = previous_state_root
|
|
# Cache block root
|
|
previous_block_root = hash_tree_root(state.latest_block_header)
|
|
state.block_roots[state.slot % SLOTS_PER_HISTORICAL_ROOT] = previous_block_root
|
|
```
|
|
|
|
### Epoch processing
|
|
|
|
```python
|
|
def process_epoch(state: BeaconState) -> None:
|
|
process_justification_and_finalization(state)
|
|
process_rewards_and_penalties(state)
|
|
process_registry_updates(state)
|
|
process_slashings(state)
|
|
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_record_updates(state)
|
|
```
|
|
|
|
#### Helper functions
|
|
|
|
```python
|
|
def get_matching_source_attestations(state: BeaconState, epoch: Epoch) -> Sequence[PendingAttestation]:
|
|
assert epoch in (get_previous_epoch(state), get_current_epoch(state))
|
|
return state.current_epoch_attestations if epoch == get_current_epoch(state) else state.previous_epoch_attestations
|
|
```
|
|
|
|
```python
|
|
def get_matching_target_attestations(state: BeaconState, epoch: Epoch) -> Sequence[PendingAttestation]:
|
|
return [
|
|
a for a in get_matching_source_attestations(state, epoch)
|
|
if a.data.target.root == get_block_root(state, epoch)
|
|
]
|
|
```
|
|
|
|
```python
|
|
def get_matching_head_attestations(state: BeaconState, epoch: Epoch) -> Sequence[PendingAttestation]:
|
|
return [
|
|
a for a in get_matching_target_attestations(state, epoch)
|
|
if a.data.beacon_block_root == get_block_root_at_slot(state, a.data.slot)
|
|
]
|
|
```
|
|
|
|
```python
|
|
def get_unslashed_attesting_indices(state: BeaconState,
|
|
attestations: Sequence[PendingAttestation]) -> Set[ValidatorIndex]:
|
|
output = set() # type: Set[ValidatorIndex]
|
|
for a in attestations:
|
|
output = output.union(get_attesting_indices(state, a.data, a.aggregation_bits))
|
|
return set(filter(lambda index: not state.validators[index].slashed, output))
|
|
```
|
|
|
|
```python
|
|
def get_attesting_balance(state: BeaconState, attestations: Sequence[PendingAttestation]) -> Gwei:
|
|
"""
|
|
Return the combined effective balance of the set of unslashed validators participating in ``attestations``.
|
|
Note: ``get_total_balance`` returns ``EFFECTIVE_BALANCE_INCREMENT`` Gwei minimum to avoid divisions by zero.
|
|
"""
|
|
return get_total_balance(state, get_unslashed_attesting_indices(state, attestations))
|
|
```
|
|
|
|
#### Justification and finalization
|
|
|
|
```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_attestations = get_matching_target_attestations(state, get_previous_epoch(state))
|
|
current_attestations = get_matching_target_attestations(state, get_current_epoch(state))
|
|
total_active_balance = get_total_active_balance(state)
|
|
previous_target_balance = get_attesting_balance(state, previous_attestations)
|
|
current_target_balance = get_attesting_balance(state, current_attestations)
|
|
weigh_justification_and_finalization(state, total_active_balance, previous_target_balance, current_target_balance)
|
|
```
|
|
|
|
```python
|
|
def weigh_justification_and_finalization(state: BeaconState,
|
|
total_active_balance: Gwei,
|
|
previous_epoch_target_balance: Gwei,
|
|
current_epoch_target_balance: Gwei) -> None:
|
|
previous_epoch = get_previous_epoch(state)
|
|
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]
|
|
state.justification_bits[0] = 0b0
|
|
if previous_epoch_target_balance * 3 >= total_active_balance * 2:
|
|
state.current_justified_checkpoint = Checkpoint(epoch=previous_epoch,
|
|
root=get_block_root(state, previous_epoch))
|
|
state.justification_bits[1] = 0b1
|
|
if current_epoch_target_balance * 3 >= total_active_balance * 2:
|
|
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
|
|
if all(bits[1:4]) and old_previous_justified_checkpoint.epoch + 3 == current_epoch:
|
|
state.finalized_checkpoint = old_previous_justified_checkpoint
|
|
# The 2nd/3rd most recent epochs are justified, the 2nd using the 3rd as source
|
|
if all(bits[1:3]) and old_previous_justified_checkpoint.epoch + 2 == current_epoch:
|
|
state.finalized_checkpoint = old_previous_justified_checkpoint
|
|
# The 1st/2nd/3rd most recent epochs are justified, the 1st using the 3rd as source
|
|
if all(bits[0:3]) and old_current_justified_checkpoint.epoch + 2 == current_epoch:
|
|
state.finalized_checkpoint = old_current_justified_checkpoint
|
|
# The 1st/2nd most recent epochs are justified, the 1st using the 2nd as source
|
|
if all(bits[0:2]) and old_current_justified_checkpoint.epoch + 1 == current_epoch:
|
|
state.finalized_checkpoint = old_current_justified_checkpoint
|
|
```
|
|
|
|
#### Rewards and penalties
|
|
|
|
##### Helpers
|
|
|
|
```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) // BASE_REWARDS_PER_EPOCH)
|
|
```
|
|
|
|
|
|
```python
|
|
def get_proposer_reward(state: BeaconState, attesting_index: ValidatorIndex) -> Gwei:
|
|
return Gwei(get_base_reward(state, attesting_index) // PROPOSER_REWARD_QUOTIENT)
|
|
```
|
|
|
|
|
|
```python
|
|
def get_finality_delay(state: BeaconState) -> uint64:
|
|
return get_previous_epoch(state) - state.finalized_checkpoint.epoch
|
|
```
|
|
|
|
|
|
```python
|
|
def is_in_inactivity_leak(state: BeaconState) -> bool:
|
|
return get_finality_delay(state) > MIN_EPOCHS_TO_INACTIVITY_PENALTY
|
|
```
|
|
|
|
|
|
```python
|
|
def get_eligible_validator_indices(state: BeaconState) -> Sequence[ValidatorIndex]:
|
|
previous_epoch = get_previous_epoch(state)
|
|
return [
|
|
ValidatorIndex(index) for index, v in enumerate(state.validators)
|
|
if is_active_validator(v, previous_epoch) or (v.slashed and previous_epoch + 1 < v.withdrawable_epoch)
|
|
]
|
|
```
|
|
|
|
```python
|
|
def get_attestation_component_deltas(state: BeaconState,
|
|
attestations: Sequence[PendingAttestation]
|
|
) -> Tuple[Sequence[Gwei], Sequence[Gwei]]:
|
|
"""
|
|
Helper with shared logic for use by get source, target, and head deltas functions
|
|
"""
|
|
rewards = [Gwei(0)] * len(state.validators)
|
|
penalties = [Gwei(0)] * len(state.validators)
|
|
total_balance = get_total_active_balance(state)
|
|
unslashed_attesting_indices = get_unslashed_attesting_indices(state, attestations)
|
|
attesting_balance = get_total_balance(state, unslashed_attesting_indices)
|
|
for index in get_eligible_validator_indices(state):
|
|
if index in unslashed_attesting_indices:
|
|
increment = EFFECTIVE_BALANCE_INCREMENT # Factored out from balance totals to avoid uint64 overflow
|
|
if is_in_inactivity_leak(state):
|
|
# Since full base reward will be canceled out by inactivity penalty deltas,
|
|
# optimal participation receives full base reward compensation here.
|
|
rewards[index] += get_base_reward(state, index)
|
|
else:
|
|
reward_numerator = get_base_reward(state, index) * (attesting_balance // increment)
|
|
rewards[index] += reward_numerator // (total_balance // increment)
|
|
else:
|
|
penalties[index] += get_base_reward(state, index)
|
|
return rewards, penalties
|
|
```
|
|
|
|
##### Components of attestation deltas
|
|
|
|
```python
|
|
def get_source_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]:
|
|
"""
|
|
Return attester micro-rewards/penalties for source-vote for each validator.
|
|
"""
|
|
matching_source_attestations = get_matching_source_attestations(state, get_previous_epoch(state))
|
|
return get_attestation_component_deltas(state, matching_source_attestations)
|
|
```
|
|
|
|
```python
|
|
def get_target_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]:
|
|
"""
|
|
Return attester micro-rewards/penalties for target-vote for each validator.
|
|
"""
|
|
matching_target_attestations = get_matching_target_attestations(state, get_previous_epoch(state))
|
|
return get_attestation_component_deltas(state, matching_target_attestations)
|
|
```
|
|
|
|
```python
|
|
def get_head_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]:
|
|
"""
|
|
Return attester micro-rewards/penalties for head-vote for each validator.
|
|
"""
|
|
matching_head_attestations = get_matching_head_attestations(state, get_previous_epoch(state))
|
|
return get_attestation_component_deltas(state, matching_head_attestations)
|
|
```
|
|
|
|
```python
|
|
def get_inclusion_delay_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]:
|
|
"""
|
|
Return proposer and inclusion delay micro-rewards/penalties for each validator.
|
|
"""
|
|
rewards = [Gwei(0) for _ in range(len(state.validators))]
|
|
matching_source_attestations = get_matching_source_attestations(state, get_previous_epoch(state))
|
|
for index in get_unslashed_attesting_indices(state, matching_source_attestations):
|
|
attestation = min([
|
|
a for a in matching_source_attestations
|
|
if index in get_attesting_indices(state, a.data, a.aggregation_bits)
|
|
], key=lambda a: a.inclusion_delay)
|
|
rewards[attestation.proposer_index] += get_proposer_reward(state, index)
|
|
max_attester_reward = Gwei(get_base_reward(state, index) - get_proposer_reward(state, index))
|
|
rewards[index] += Gwei(max_attester_reward // attestation.inclusion_delay)
|
|
|
|
# No penalties associated with inclusion delay
|
|
penalties = [Gwei(0) for _ in range(len(state.validators))]
|
|
return rewards, penalties
|
|
```
|
|
|
|
```python
|
|
def get_inactivity_penalty_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]:
|
|
"""
|
|
Return inactivity reward/penalty deltas for each validator.
|
|
"""
|
|
penalties = [Gwei(0) for _ in range(len(state.validators))]
|
|
if is_in_inactivity_leak(state):
|
|
matching_target_attestations = get_matching_target_attestations(state, get_previous_epoch(state))
|
|
matching_target_attesting_indices = get_unslashed_attesting_indices(state, matching_target_attestations)
|
|
for index in get_eligible_validator_indices(state):
|
|
# If validator is performing optimally this cancels all rewards for a neutral balance
|
|
base_reward = get_base_reward(state, index)
|
|
penalties[index] += Gwei(BASE_REWARDS_PER_EPOCH * base_reward - get_proposer_reward(state, index))
|
|
if index not in matching_target_attesting_indices:
|
|
effective_balance = state.validators[index].effective_balance
|
|
penalties[index] += Gwei(effective_balance * get_finality_delay(state) // INACTIVITY_PENALTY_QUOTIENT)
|
|
|
|
# No rewards associated with inactivity penalties
|
|
rewards = [Gwei(0) for _ in range(len(state.validators))]
|
|
return rewards, penalties
|
|
```
|
|
|
|
##### `get_attestation_deltas`
|
|
|
|
```python
|
|
def get_attestation_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]:
|
|
"""
|
|
Return attestation reward/penalty deltas for each validator.
|
|
"""
|
|
source_rewards, source_penalties = get_source_deltas(state)
|
|
target_rewards, target_penalties = get_target_deltas(state)
|
|
head_rewards, head_penalties = get_head_deltas(state)
|
|
inclusion_delay_rewards, _ = get_inclusion_delay_deltas(state)
|
|
_, inactivity_penalties = get_inactivity_penalty_deltas(state)
|
|
|
|
rewards = [
|
|
source_rewards[i] + target_rewards[i] + head_rewards[i] + inclusion_delay_rewards[i]
|
|
for i in range(len(state.validators))
|
|
]
|
|
|
|
penalties = [
|
|
source_penalties[i] + target_penalties[i] + head_penalties[i] + inactivity_penalties[i]
|
|
for i in range(len(state.validators))
|
|
]
|
|
|
|
return rewards, penalties
|
|
```
|
|
|
|
##### `process_rewards_and_penalties`
|
|
|
|
```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
|
|
|
|
rewards, penalties = get_attestation_deltas(state)
|
|
for index in range(len(state.validators)):
|
|
increase_balance(state, ValidatorIndex(index), rewards[index])
|
|
decrease_balance(state, ValidatorIndex(index), penalties[index])
|
|
```
|
|
|
|
#### Registry updates
|
|
|
|
```python
|
|
def process_registry_updates(state: BeaconState) -> None:
|
|
# Process activation eligibility and ejections
|
|
for index, validator in enumerate(state.validators):
|
|
if is_eligible_for_activation_queue(validator):
|
|
validator.activation_eligibility_epoch = get_current_epoch(state) + 1
|
|
|
|
if (
|
|
is_active_validator(validator, get_current_epoch(state))
|
|
and validator.effective_balance <= EJECTION_BALANCE
|
|
):
|
|
initiate_validator_exit(state, ValidatorIndex(index))
|
|
|
|
# Queue validators eligible for activation and not yet dequeued for activation
|
|
activation_queue = sorted([
|
|
index for index, validator in enumerate(state.validators)
|
|
if is_eligible_for_activation(state, validator)
|
|
# Order by the sequence of activation_eligibility_epoch setting and then index
|
|
], key=lambda index: (state.validators[index].activation_eligibility_epoch, index))
|
|
# Dequeued validators for activation up to churn limit
|
|
for index in activation_queue[:get_validator_churn_limit(state)]:
|
|
validator = state.validators[index]
|
|
validator.activation_epoch = compute_activation_exit_epoch(get_current_epoch(state))
|
|
```
|
|
|
|
#### Slashings
|
|
|
|
```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, 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)
|
|
```
|
|
|
|
#### Eth1 data votes updates
|
|
```python
|
|
def process_eth1_data_reset(state: BeaconState) -> None:
|
|
next_epoch = Epoch(get_current_epoch(state) + 1)
|
|
# Reset eth1 data votes
|
|
if next_epoch % EPOCHS_PER_ETH1_VOTING_PERIOD == 0:
|
|
state.eth1_data_votes = []
|
|
```
|
|
|
|
#### Effective balances updates
|
|
|
|
```python
|
|
def process_effective_balance_updates(state: BeaconState) -> None:
|
|
# Update effective balances with hysteresis
|
|
for index, validator in enumerate(state.validators):
|
|
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 + DOWNWARD_THRESHOLD < validator.effective_balance
|
|
or validator.effective_balance + UPWARD_THRESHOLD < balance
|
|
):
|
|
validator.effective_balance = min(balance - balance % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE)
|
|
```
|
|
|
|
#### Slashings balances updates
|
|
|
|
```python
|
|
def process_slashings_reset(state: BeaconState) -> None:
|
|
next_epoch = Epoch(get_current_epoch(state) + 1)
|
|
# Reset slashings
|
|
state.slashings[next_epoch % EPOCHS_PER_SLASHINGS_VECTOR] = Gwei(0)
|
|
```
|
|
|
|
#### Randao mixes updates
|
|
|
|
```python
|
|
def process_randao_mixes_reset(state: BeaconState) -> None:
|
|
current_epoch = get_current_epoch(state)
|
|
next_epoch = Epoch(current_epoch + 1)
|
|
# Set randao mix
|
|
state.randao_mixes[next_epoch % EPOCHS_PER_HISTORICAL_VECTOR] = get_randao_mix(state, current_epoch)
|
|
```
|
|
|
|
#### Historical roots updates
|
|
```python
|
|
def process_historical_roots_update(state: BeaconState) -> None:
|
|
# Set historical root accumulator
|
|
next_epoch = Epoch(get_current_epoch(state) + 1)
|
|
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))
|
|
```
|
|
|
|
#### Participation records rotation
|
|
|
|
```python
|
|
def process_participation_record_updates(state: BeaconState) -> None:
|
|
# Rotate current/previous epoch attestations
|
|
state.previous_epoch_attestations = state.current_epoch_attestations
|
|
state.current_epoch_attestations = []
|
|
```
|
|
|
|
### 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)
|
|
```
|
|
|
|
#### Block header
|
|
|
|
```python
|
|
def process_block_header(state: BeaconState, block: BeaconBlock) -> None:
|
|
# Verify that the slots match
|
|
assert block.slot == state.slot
|
|
# Verify that the block is newer than latest block header
|
|
assert block.slot > state.latest_block_header.slot
|
|
# Verify that proposer index is the correct index
|
|
assert block.proposer_index == get_beacon_proposer_index(state)
|
|
# Verify that the parent matches
|
|
assert block.parent_root == hash_tree_root(state.latest_block_header)
|
|
# Cache current block as the new latest block
|
|
state.latest_block_header = BeaconBlockHeader(
|
|
slot=block.slot,
|
|
proposer_index=block.proposer_index,
|
|
parent_root=block.parent_root,
|
|
state_root=Bytes32(), # Overwritten in the next process_slot call
|
|
body_root=hash_tree_root(block.body),
|
|
)
|
|
|
|
# Verify proposer is not slashed
|
|
proposer = state.validators[block.proposer_index]
|
|
assert not proposer.slashed
|
|
```
|
|
|
|
#### RANDAO
|
|
|
|
```python
|
|
def process_randao(state: BeaconState, body: BeaconBlockBody) -> None:
|
|
epoch = get_current_epoch(state)
|
|
# Verify RANDAO reveal
|
|
proposer = state.validators[get_beacon_proposer_index(state)]
|
|
signing_root = compute_signing_root(epoch, get_domain(state, DOMAIN_RANDAO))
|
|
assert bls.Verify(proposer.pubkey, signing_root, body.randao_reveal)
|
|
# Mix in RANDAO reveal
|
|
mix = xor(get_randao_mix(state, epoch), hash(body.randao_reveal))
|
|
state.randao_mixes[epoch % EPOCHS_PER_HISTORICAL_VECTOR] = mix
|
|
```
|
|
|
|
#### Eth1 data
|
|
|
|
```python
|
|
def process_eth1_data(state: BeaconState, body: BeaconBlockBody) -> None:
|
|
state.eth1_data_votes.append(body.eth1_data)
|
|
if state.eth1_data_votes.count(body.eth1_data) * 2 > EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH:
|
|
state.eth1_data = body.eth1_data
|
|
```
|
|
|
|
#### Operations
|
|
|
|
```python
|
|
def process_operations(state: BeaconState, body: BeaconBlockBody) -> None:
|
|
# Verify that outstanding deposits are processed up to the maximum number of deposits
|
|
assert len(body.deposits) == min(MAX_DEPOSITS, state.eth1_data.deposit_count - state.eth1_deposit_index)
|
|
|
|
def for_ops(operations: Sequence[Any], fn: Callable[[BeaconState, Any], None]) -> None:
|
|
for operation in operations:
|
|
fn(state, operation)
|
|
|
|
for_ops(body.proposer_slashings, process_proposer_slashing)
|
|
for_ops(body.attester_slashings, process_attester_slashing)
|
|
for_ops(body.attestations, process_attestation)
|
|
for_ops(body.deposits, process_deposit)
|
|
for_ops(body.voluntary_exits, process_voluntary_exit)
|
|
```
|
|
|
|
##### Proposer slashings
|
|
|
|
```python
|
|
def process_proposer_slashing(state: BeaconState, proposer_slashing: ProposerSlashing) -> None:
|
|
header_1 = proposer_slashing.signed_header_1.message
|
|
header_2 = proposer_slashing.signed_header_2.message
|
|
|
|
# Verify header slots match
|
|
assert header_1.slot == header_2.slot
|
|
# Verify header proposer indices match
|
|
assert header_1.proposer_index == header_2.proposer_index
|
|
# Verify the headers are different
|
|
assert header_1 != header_2
|
|
# Verify the proposer is slashable
|
|
proposer = state.validators[header_1.proposer_index]
|
|
assert is_slashable_validator(proposer, get_current_epoch(state))
|
|
# Verify signatures
|
|
for signed_header in (proposer_slashing.signed_header_1, proposer_slashing.signed_header_2):
|
|
domain = get_domain(state, DOMAIN_BEACON_PROPOSER, compute_epoch_at_slot(signed_header.message.slot))
|
|
signing_root = compute_signing_root(signed_header.message, domain)
|
|
assert bls.Verify(proposer.pubkey, signing_root, signed_header.signature)
|
|
|
|
slash_validator(state, header_1.proposer_index)
|
|
```
|
|
|
|
##### Attester slashings
|
|
|
|
```python
|
|
def process_attester_slashing(state: BeaconState, attester_slashing: AttesterSlashing) -> None:
|
|
attestation_1 = attester_slashing.attestation_1
|
|
attestation_2 = attester_slashing.attestation_2
|
|
assert is_slashable_attestation_data(attestation_1.data, attestation_2.data)
|
|
assert is_valid_indexed_attestation(state, attestation_1)
|
|
assert is_valid_indexed_attestation(state, attestation_2)
|
|
|
|
slashed_any = False
|
|
indices = set(attestation_1.attesting_indices).intersection(attestation_2.attesting_indices)
|
|
for index in sorted(indices):
|
|
if is_slashable_validator(state.validators[index], get_current_epoch(state)):
|
|
slash_validator(state, index)
|
|
slashed_any = True
|
|
assert slashed_any
|
|
```
|
|
|
|
##### Attestations
|
|
|
|
```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)
|
|
|
|
pending_attestation = PendingAttestation(
|
|
data=data,
|
|
aggregation_bits=attestation.aggregation_bits,
|
|
inclusion_delay=state.slot - data.slot,
|
|
proposer_index=get_beacon_proposer_index(state),
|
|
)
|
|
|
|
if data.target.epoch == get_current_epoch(state):
|
|
assert data.source == state.current_justified_checkpoint
|
|
state.current_epoch_attestations.append(pending_attestation)
|
|
else:
|
|
assert data.source == state.previous_justified_checkpoint
|
|
state.previous_epoch_attestations.append(pending_attestation)
|
|
|
|
# Verify signature
|
|
assert is_valid_indexed_attestation(state, get_indexed_attestation(state, attestation))
|
|
```
|
|
|
|
##### Deposits
|
|
|
|
```python
|
|
def get_validator_from_deposit(pubkey: BLSPubkey, withdrawal_credentials: Bytes32, amount: uint64) -> Validator:
|
|
effective_balance = min(amount - amount % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE)
|
|
|
|
return Validator(
|
|
pubkey=pubkey,
|
|
withdrawal_credentials=withdrawal_credentials,
|
|
activation_eligibility_epoch=FAR_FUTURE_EPOCH,
|
|
activation_epoch=FAR_FUTURE_EPOCH,
|
|
exit_epoch=FAR_FUTURE_EPOCH,
|
|
withdrawable_epoch=FAR_FUTURE_EPOCH,
|
|
effective_balance=effective_balance,
|
|
)
|
|
```
|
|
|
|
```python
|
|
def add_validator_to_registry(state: BeaconState,
|
|
pubkey: BLSPubkey,
|
|
withdrawal_credentials: Bytes32,
|
|
amount: uint64) -> None:
|
|
state.validators.append(get_validator_from_deposit(pubkey, withdrawal_credentials, amount))
|
|
state.balances.append(amount)
|
|
```
|
|
|
|
```python
|
|
def apply_deposit(state: BeaconState,
|
|
pubkey: BLSPubkey,
|
|
withdrawal_credentials: Bytes32,
|
|
amount: uint64,
|
|
signature: BLSSignature) -> None:
|
|
validator_pubkeys = [v.pubkey for v 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=pubkey,
|
|
withdrawal_credentials=withdrawal_credentials,
|
|
amount=amount,
|
|
)
|
|
domain = compute_domain(DOMAIN_DEPOSIT) # Fork-agnostic domain since deposits are valid across forks
|
|
signing_root = compute_signing_root(deposit_message, domain)
|
|
if bls.Verify(pubkey, signing_root, signature):
|
|
add_validator_to_registry(state, pubkey, withdrawal_credentials, amount)
|
|
else:
|
|
# Increase balance by deposit amount
|
|
index = ValidatorIndex(validator_pubkeys.index(pubkey))
|
|
increase_balance(state, index, amount)
|
|
```
|
|
|
|
```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
|
|
|
|
apply_deposit(
|
|
state=state,
|
|
pubkey=deposit.data.pubkey,
|
|
withdrawal_credentials=deposit.data.withdrawal_credentials,
|
|
amount=deposit.data.amount,
|
|
signature=deposit.data.signature,
|
|
)
|
|
```
|
|
|
|
##### Voluntary exits
|
|
|
|
```python
|
|
def process_voluntary_exit(state: BeaconState, signed_voluntary_exit: SignedVoluntaryExit) -> None:
|
|
voluntary_exit = signed_voluntary_exit.message
|
|
validator = state.validators[voluntary_exit.validator_index]
|
|
# Verify the validator is active
|
|
assert is_active_validator(validator, get_current_epoch(state))
|
|
# Verify exit has not been initiated
|
|
assert validator.exit_epoch == FAR_FUTURE_EPOCH
|
|
# Exits must specify an epoch when they become valid; they are not valid before then
|
|
assert get_current_epoch(state) >= voluntary_exit.epoch
|
|
# Verify the validator has been active long enough
|
|
assert get_current_epoch(state) >= validator.activation_epoch + SHARD_COMMITTEE_PERIOD
|
|
# Verify signature
|
|
domain = get_domain(state, DOMAIN_VOLUNTARY_EXIT, voluntary_exit.epoch)
|
|
signing_root = compute_signing_root(voluntary_exit, domain)
|
|
assert bls.Verify(validator.pubkey, signing_root, signed_voluntary_exit.signature)
|
|
# Initiate exit
|
|
initiate_validator_exit(state, voluntary_exit.validator_index)
|
|
```
|