5.9 KiB
Ethereum 2.0 Phase 0 -- Beacon Chain Fork Choice
NOTICE: This document is a work in progress for researchers and implementers.
Table of contents
Introduction
This document represents is the specification for the beacon chain fork choice rule, part of Ethereum 2.0 phase 0.
Prerequisites
All terminology, constants, functions, and protocol mechanics defined in the Phase 0 -- The Beacon Chain doc are requisite for this document and used throughout. Please see the Phase 0 doc before continuing and use as a reference throughout.
Constants
Time parameters
Name | Value | Unit | Duration |
---|---|---|---|
SECONDS_PER_SLOT |
6 |
seconds | 6 seconds |
Beacon chain processing
Processing the beacon chain is similar to processing the Ethereum 1.0 chain. Clients download and process blocks and maintain a view of what is the current "canonical chain", terminating at the current "head". For a beacon block, block
, to be processed by a node, the following conditions must be met:
- The parent block with root
block.previous_block_root
has been processed and accepted. - An Ethereum 1.0 block pointed to by the
state.latest_eth1_data.block_hash
has been processed and accepted. - The node's Unix time is greater than or equal to
state.genesis_time + block.slot * SECONDS_PER_SLOT
.
Note: Leap seconds mean that slots will occasionally last SECONDS_PER_SLOT + 1
or SECONDS_PER_SLOT - 1
seconds, possibly several times a year.
Note: Nodes needs to have a clock that is roughly (i.e. within SECONDS_PER_SLOT
seconds) synchronized with the other nodes.
Beacon chain fork choice rule
The beacon chain fork choice rule is a hybrid that combines justification and finality with Latest Message Driven (LMD) Greediest Heaviest Observed SubTree (GHOST). At any point in time a validator v
subjectively calculates the beacon chain head as follows.
- Abstractly define
Store
as the type of storage object for the chain data andstore
be the set of attestations and blocks that the validatorv
has observed and verified (in particular, block ancestors must be recursively verified). Attestations not yet included in any chain are still included instore
. - Let
finalized_head
be the finalized block with the highest epoch. (A blockB
is finalized if there is a descendant ofB
instore
the processing of which setsB
as finalized.) - Let
justified_head
be the descendant offinalized_head
with the highest epoch that has been justified for at least 1 epoch. (A blockB
is justified if there is a descendant ofB
instore
the processing of which setsB
as justified.) If no such descendant exists setjustified_head
tofinalized_head
. - Let
get_ancestor(store: Store, block: BeaconBlock, slot: Slot) -> BeaconBlock
be the ancestor ofblock
with slot numberslot
. Theget_ancestor
function can be defined recursively as:
def get_ancestor(store: Store, block: BeaconBlock, slot: Slot) -> BeaconBlock:
"""
Get the ancestor of ``block`` with slot number ``slot``; return ``None`` if not found.
"""
if block.slot == slot:
return block
elif block.slot < slot:
return None
else:
return get_ancestor(store, store.get_parent(block), slot)
- Let
get_latest_attestation(store: Store, index: ValidatorIndex) -> Attestation
be the attestation with the highest slot number instore
from the validator with the givenindex
. If several such attestations exist, use the one the validatorv
observed first. - Let
get_latest_attestation_target(store: Store, index: ValidatorIndex) -> BeaconBlock
be the target block in the attestationget_latest_attestation(store, index)
. - Let
get_children(store: Store, block: BeaconBlock) -> List[BeaconBlock]
returns the child blocks of the givenblock
. - Let
justified_head_state
be the resultingBeaconState
object from processing the chain up to thejustified_head
. - The
head
islmd_ghost(store, justified_head_state, justified_head)
where the functionlmd_ghost
is defined below. Note that the implementation below is suboptimal; there are implementations that compute the head in time logarithmic in slot count.
def lmd_ghost(store: Store, start_state: BeaconState, start_block: BeaconBlock) -> BeaconBlock:
"""
Execute the LMD-GHOST algorithm to find the head ``BeaconBlock``.
"""
validators = start_state.validator_registry
active_validator_indices = get_active_validator_indices(validators, slot_to_epoch(start_state.slot))
attestation_targets = [(i, get_latest_attestation_target(store, i)) for i in active_validator_indices]
# Use the rounded-balance-with-hysteresis supplied by the protocol for fork
# choice voting. This reduces the number of recomputations that need to be
# made for optimized implementations that precompute and save data
def get_vote_count(block: BeaconBlock) -> int:
return sum(
start_state.validator_registry[validator_index].effective_balance
for validator_index, target in attestation_targets
if get_ancestor(store, target, block.slot) == block
)
head = start_block
while 1:
children = get_children(store, head)
if len(children) == 0:
return head
# Ties broken by favoring block with lexicographically higher root
head = max(children, key=lambda x: (get_vote_count(x), hash_tree_root(x)))