Notice: This document is a work-in-progress for researchers and implementers.
This is the modification of the fork choice according to the executable beacon chain proposal.
Note: It introduces the process of transition from the last PoW block to the first PoS block.
Note: The notify_forkchoice_updated
function is added to the ExecutionEngine
protocol to signal the fork choice updates.
The body of this function is implementation dependent. The Engine API may be used to implement it with an external execution engine.
This function performs two actions atomically:
- Re-organizes the execution payload chain and corresponding state to make
head_block_hash
the head. - Applies finality to the execution state: it irreversibly persists the chain of all execution payloads
and corresponding state, up to and including
finalized_block_hash
.
def notify_forkchoice_updated(self: ExecutionEngine, head_block_hash: Hash32, finalized_block_hash: Hash32) -> None:
...
Note: The call of the notify_forkchoice_updated
function maps on the POS_FORKCHOICE_UPDATED
event defined in the EIP-3675.
class PowBlock(Container):
block_hash: Hash32
parent_hash: Hash32
total_difficulty: uint256
difficulty: uint256
Let get_pow_block(block_hash: Hash32) -> Optional[PowBlock]
be the function that given the hash of the PoW block returns its data.
It may result in None
if the requested block is not found if execution engine is still syncing.
Note: The eth_getBlockByHash
JSON-RPC method may be used to pull this information from an execution client.
Used by fork-choice handler, on_block
.
def is_valid_terminal_pow_block(block: PowBlock, parent: PowBlock) -> bool:
if block.block_hash == TERMINAL_BLOCK_HASH:
return True
is_total_difficulty_reached = block.total_difficulty >= TERMINAL_TOTAL_DIFFICULTY
is_parent_total_difficulty_valid = parent.total_difficulty < TERMINAL_TOTAL_DIFFICULTY
return is_total_difficulty_reached and is_parent_total_difficulty_valid
Note: The only modification is the addition of the verification of transition block conditions.
def on_block(store: Store, signed_block: SignedBeaconBlock) -> None:
"""
Run ``on_block`` upon receiving a new block.
An block that is asserted as invalid due to unavailable PoW block may be valid at a later time,
consider scheduling it for later processing in such case.
"""
block = signed_block.message
# Parent block must be known
assert block.parent_root in store.block_states
# Make a copy of the state to avoid mutability issues
pre_state = copy(store.block_states[block.parent_root])
# Blocks cannot be in the future. If they are, their consideration must be delayed until the are in the past.
assert get_current_slot(store) >= block.slot
# Check that block is later than the finalized epoch slot (optimization to reduce calls to get_ancestor)
finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)
assert block.slot > finalized_slot
# Check block is a descendant of the finalized block at the checkpoint finalized slot
assert get_ancestor(store, block.parent_root, finalized_slot) == store.finalized_checkpoint.root
# Check the block is valid and compute the post-state
state = pre_state.copy()
state_transition(state, signed_block, True)
# [New in Merge]
if is_merge_block(pre_state, block.body):
# Note: the unavailable PoW block(s) may be available later
pow_block = get_pow_block(block.body.execution_payload.parent_hash)
assert pow_block is not None
pow_parent = get_pow_block(pow_block.parent_hash)
assert pow_parent is not None
assert is_valid_terminal_pow_block(pow_block, pow_parent)
# Add new block to the store
store.blocks[hash_tree_root(block)] = block
# Add new state for this block to the store
store.block_states[hash_tree_root(block)] = state
# Update justified checkpoint
if state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch:
if state.current_justified_checkpoint.epoch > store.best_justified_checkpoint.epoch:
store.best_justified_checkpoint = state.current_justified_checkpoint
if should_update_justified_checkpoint(store, state.current_justified_checkpoint):
store.justified_checkpoint = state.current_justified_checkpoint
# Update finalized checkpoint
if state.finalized_checkpoint.epoch > store.finalized_checkpoint.epoch:
store.finalized_checkpoint = state.finalized_checkpoint
# Potentially update justified if different from store
if store.justified_checkpoint != state.current_justified_checkpoint:
# Update justified if new justified is later than store justified
if state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch:
store.justified_checkpoint = state.current_justified_checkpoint
return
# Update justified if store justified is not in chain with finalized checkpoint
finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)
ancestor_at_finalized_slot = get_ancestor(store, store.justified_checkpoint.root, finalized_slot)
if ancestor_at_finalized_slot != store.finalized_checkpoint.root:
store.justified_checkpoint = state.current_justified_checkpoint