Phase 0 -- The Beacon Chain
Table of contents
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 |
UINT64_MAX |
uint64(2**64 - 1) |
UINT64_MAX_SQRT |
uint64(4294967295) |
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
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; 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
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) 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
| class Fork(Container):
previous_version: Version
current_version: Version
epoch: Epoch # Epoch of latest fork
|
ForkData
| class ForkData(Container):
current_version: Version
genesis_validators_root: Root
|
Checkpoint
| class Checkpoint(Container):
epoch: Epoch
root: Root
|
Validator
| 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
| class AttestationData(Container):
slot: Slot
index: CommitteeIndex
# LMD GHOST vote
beacon_block_root: Root
# FFG vote
source: Checkpoint
target: Checkpoint
|
IndexedAttestation
| class IndexedAttestation(Container):
attesting_indices: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE]
data: AttestationData
signature: BLSSignature
|
PendingAttestation
| class PendingAttestation(Container):
aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE]
data: AttestationData
inclusion_delay: Slot
proposer_index: ValidatorIndex
|
Eth1Data
| class Eth1Data(Container):
deposit_root: Root
deposit_count: uint64
block_hash: Hash32
|
HistoricalBatch
| class HistoricalBatch(Container):
block_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT]
state_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT]
|
DepositMessage
| class DepositMessage(Container):
pubkey: BLSPubkey
withdrawal_credentials: Bytes32
amount: Gwei
|
DepositData
| class DepositData(Container):
pubkey: BLSPubkey
withdrawal_credentials: Bytes32
amount: Gwei
signature: BLSSignature # Signing over DepositMessage
|
| class BeaconBlockHeader(Container):
slot: Slot
proposer_index: ValidatorIndex
parent_root: Root
state_root: Root
body_root: Root
|
SigningData
| class SigningData(Container):
object_root: Root
domain: Domain
|
Beacon operations
ProposerSlashing
| class ProposerSlashing(Container):
signed_header_1: SignedBeaconBlockHeader
signed_header_2: SignedBeaconBlockHeader
|
AttesterSlashing
| class AttesterSlashing(Container):
attestation_1: IndexedAttestation
attestation_2: IndexedAttestation
|
Attestation
| class Attestation(Container):
aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE]
data: AttestationData
signature: BLSSignature
|
Deposit
| class Deposit(Container):
proof: Vector[Bytes32, DEPOSIT_CONTRACT_TREE_DEPTH + 1] # Merkle path to deposit root
data: DepositData
|
VoluntaryExit
| class VoluntaryExit(Container):
epoch: Epoch # Earliest epoch when voluntary exit can be processed
validator_index: ValidatorIndex
|
Beacon blocks
BeaconBlockBody
| 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
| class BeaconBlock(Container):
slot: Slot
proposer_index: ValidatorIndex
parent_root: Root
state_root: Root
body: BeaconBlockBody
|
Beacon state
BeaconState
| 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
| class SignedVoluntaryExit(Container):
message: VoluntaryExit
signature: BLSSignature
|
SignedBeaconBlock
| class SignedBeaconBlock(Container):
message: BeaconBlock
signature: BLSSignature
|
| 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
| def integer_squareroot(n: uint64) -> uint64:
"""
Return the largest integer ``x`` such that ``x**2 <= n``.
"""
if n == UINT64_MAX:
return UINT64_MAX_SQRT
x = n
y = (x + 1) // 2
while y < x:
x = y
y = (x + n // x) // 2
return x
|
xor
| 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
| def bytes_to_uint64(data: bytes) -> uint64:
"""
Return the integer deserialization of ``data`` interpreted as ``ENDIANNESS``-endian.
"""
return uint64(int.from_bytes(data, ENDIANNESS))
|
saturating_sub
| def saturating_sub(a: int, b: int) -> int:
"""
Computes a - b, saturating at numeric bounds.
"""
return a - b if a > b else 0
|
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.
BLS signatures
The IETF BLS signature draft standard v4 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
| 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
| 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
| 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
| 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
| 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
| 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
| 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
| 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
| 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
| 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
| 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
| 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
| 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
| 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
| 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
| 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
| 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
| def get_current_epoch(state: BeaconState) -> Epoch:
"""
Return the current epoch.
"""
return compute_epoch_at_slot(state.slot)
|
get_previous_epoch
| 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
| 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
| 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
| 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
| 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
| 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
| 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
| 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
| 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
| 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
| 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
| 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
| 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
| def get_indexed_attestation(state: BeaconState, attestation: Attestation) -> IndexedAttestation:
"""
Return the indexed attestation corresponding to ``attestation``.
"""
attesting_indices = get_attesting_indices(state, attestation)
return IndexedAttestation(
attesting_indices=sorted(attesting_indices),
data=attestation.data,
signature=attestation.signature,
)
|
get_attesting_indices
| def get_attesting_indices(state: BeaconState, attestation: Attestation) -> Set[ValidatorIndex]:
"""
Return the set of attesting indices corresponding to ``data`` and ``bits``.
"""
committee = get_beacon_committee(state, attestation.data.slot, attestation.data.index)
return set(index for i, index in enumerate(committee) if attestation.aggregation_bits[i])
|
Beacon state mutators
increase_balance
| 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
| 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
| 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
| 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.
| 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.
| 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.
| 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)
|
| 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)
|
| 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)
|
| 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
| 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
| 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
|
| 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)
]
|
| 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)
]
|
| 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))
return set(filter(lambda index: not state.validators[index].slashed, output))
|
| 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
| 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)
|
| 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
| 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)
|
| def get_proposer_reward(state: BeaconState, attesting_index: ValidatorIndex) -> Gwei:
return Gwei(get_base_reward(state, attesting_index) // PROPOSER_REWARD_QUOTIENT)
|
| def get_finality_delay(state: BeaconState) -> uint64:
return get_previous_epoch(state) - state.finalized_checkpoint.epoch
|
| def is_in_inactivity_leak(state: BeaconState) -> bool:
return get_finality_delay(state) > MIN_EPOCHS_TO_INACTIVITY_PENALTY
|
| 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)
]
|
| 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
| 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)
|
| 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)
|
| 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)
|
| 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)
], 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
|
| 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
| 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
| 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
| 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
| 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
| 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
| 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
| 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
| 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
| 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
| 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
| 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)
|
| 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
| 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
| 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
| 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
| 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
| 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
| 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
| 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,
)
|
| 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)
|
| 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)
|
| 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
| 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)
|