Fulu -- Fork Choice
Introduction
This is the modification of the fork choice accompanying Fulu.
Helpers
Modified get_proposer_head
Note: The function is modified to allow proposer re-orgs at epoch boundaries.
The proposer lookahead introduced in Fulu fixes proposer assignments before the
epoch boundary, so a re-org of a late block at the end of the previous epoch
cannot change the current slot's proposer.
| def get_proposer_head(store: Store, head_node: ForkChoiceNode, slot: Slot) -> ForkChoiceNode:
head_block = store.blocks[head_node.root]
parent_root = head_block.parent_root
parent_block = store.blocks[parent_root]
parent_node = ForkChoiceNode(root=parent_root)
# Only re-org the head block if it arrived later than the attestation deadline.
head_late = is_head_late(store, head_node.root)
# [Modified in Fulu:EIP7917]
# Removed `shuffling_stable = is_shuffling_stable(slot)`
# Ensure that the FFG information of the new head will be competitive with the current head.
ffg_competitive = is_ffg_competitive(store, head_node.root, parent_root)
# Do not re-org if the chain is not finalizing with acceptable frequency.
finalization_ok = is_finalization_ok(store, slot)
# Only re-org if we are proposing on-time.
proposing_on_time = is_proposing_on_time(store)
# Only re-org a single slot at most.
parent_slot_ok = parent_block.slot + 1 == head_block.slot
current_time_ok = head_block.slot + 1 == slot
single_slot_reorg = parent_slot_ok and current_time_ok
# Check that the head has few enough votes to be overpowered by our proposer boost.
assert store.proposer_boost_root != head_node.root # ensure boost has worn off
head_weak = is_head_weak(store, head_node.root)
# Check that the missing votes are assigned to the parent and not being hoarded.
parent_strong = is_parent_strong(store, head_node.root)
# Re-org more aggressively if there is a proposer equivocation in the previous slot.
proposer_equivocation = is_proposer_equivocation(store, head_node.root)
if all([
head_late,
# [Modified in Fulu:EIP7917]
# Removed `shuffling_stable`
ffg_competitive,
finalization_ok,
proposing_on_time,
single_slot_reorg,
head_weak,
parent_strong,
]):
# We can re-org the current head by building upon its parent node.
return parent_node
elif all([head_weak, current_time_ok, proposer_equivocation]):
return parent_node
else:
return head_node
|
Modified is_data_available
| def is_data_available(beacon_block_root: Root) -> bool:
# `retrieve_column_sidecars` is implementation and context dependent, replacing
# `retrieve_blobs_and_proofs`. For the given block root, it returns all column
# sidecars to sample, or raises an exception if they are not available.
# The p2p network does not guarantee sidecar retrieval outside of
# `MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS` epochs.
column_sidecars = retrieve_column_sidecars(beacon_block_root)
return all(
verify_data_column_sidecar(column_sidecar)
and verify_data_column_sidecar_kzg_proofs(column_sidecar)
for column_sidecar in column_sidecars
)
|
Handlers
Modified on_block
Note: The only modification is that is_data_available does not take
blob_kzg_commitments as input.
| def on_block(store: Store, signed_block: SignedBeaconBlock) -> None:
"""
Run ``on_block`` upon receiving a new block.
"""
block = signed_block.message
block_root = hash_tree_root(block)
# Return early if the block is already known
if block_root in store.blocks:
return
# Parent block must be known
assert block.parent_root in store.block_states
# Make a copy of the state to avoid mutability issues
state = store.block_states[block.parent_root].copy()
# Blocks cannot be in the future. If they are, their consideration must be delayed until they 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
finalized_checkpoint_block = get_checkpoint_block(
store,
block.parent_root,
store.finalized_checkpoint.epoch,
)
assert store.finalized_checkpoint.root == finalized_checkpoint_block
# [Modified in Fulu:EIP7594]
# Check if blob data is available
# If not, this payload MAY be queued and subsequently considered when blob data becomes available
assert is_data_available(block_root)
# Check the block is valid and compute the post-state
state_transition(state, signed_block, validate_result=True)
# Compute head before applying the block
head = get_head(store)
# Add new block to the store
store.blocks[block_root] = block
# Add new state for this block to the store
store.block_states[block_root] = state
record_block_timeliness(store, block_root)
update_proposer_boost_root(store, head.root, block_root)
# Update checkpoints in store if necessary
update_checkpoints(store, state.current_justified_checkpoint, state.finalized_checkpoint)
# Eagerly compute unrealized justification and finality.
compute_pulled_up_tip(store, block_root)
|