Gloas -- Fork Choice
Note: This document is a work-in-progress for researchers and implementers.
Introduction
This is the modification of the fork-choice accompanying the Gloas upgrade.
Types
| Name |
SSZ equivalent |
Description |
PayloadStatus |
uint8 |
Possible status of a payload in the fork-choice |
Constants
| Name |
Value |
PAYLOAD_TIMELY_THRESHOLD |
PTC_SIZE // 2 (= 256) |
DATA_AVAILABILITY_TIMELY_THRESHOLD |
PTC_SIZE // 2 (= 256) |
PAYLOAD_STATUS_EMPTY |
PayloadStatus(0) |
PAYLOAD_STATUS_FULL |
PayloadStatus(1) |
PAYLOAD_STATUS_PENDING |
PayloadStatus(2) |
ATTESTATION_TIMELINESS_INDEX |
0 |
PTC_TIMELINESS_INDEX |
1 |
NUM_BLOCK_TIMELINESS_DEADLINES |
2 |
Protocols
ExecutionEngine
notify_forkchoice_updated
In Gloas, finalized_block_hash and safe_block_hash values MUST be
computed as the following. All other semantics of notify_forkchoice_updated
and its invocation are inherited from prior forks.
finalized_block_hash = finalized_block_bid.parent_block_hash,
safe_block_hash = get_safe_execution_block_hash(fcr_store).
Where:
finalized_block = store.blocks[store.finalized_checkpoint.root],
finalized_block_bid = finalized_block.body.signed_execution_payload_bid.message.
Note: get_safe_execution_block_hash is modified in Gloas, see
Fast Confirmation.
Helpers
Modified ForkChoiceNode
| @dataclass(eq=True, frozen=True)
class ForkChoiceNode:
root: Root
# [New in Gloas:EIP7732]
payload_status: PayloadStatus # One of PAYLOAD_STATUS_* values
|
Modified PayloadAttributes
| @dataclass
class PayloadAttributes:
timestamp: uint64
prev_randao: Bytes32
suggested_fee_recipient: ExecutionAddress
withdrawals: Sequence[Withdrawal]
parent_beacon_block_root: Root
# [New in Gloas:EIP7843]
slot_number: uint64
# [New in Gloas]
target_gas_limit: uint64
|
Modified LatestMessage
Note: The class is modified to keep track of the slot instead of the epoch.
| @dataclass(eq=True, frozen=True)
class LatestMessage:
slot: Slot
root: Root
payload_present: boolean
|
Modified Store
| @dataclass
class Store:
time: uint64
genesis_time: uint64
justified_checkpoint: Checkpoint
finalized_checkpoint: Checkpoint
unrealized_justified_checkpoint: Checkpoint
unrealized_finalized_checkpoint: Checkpoint
proposer_boost_root: Root
equivocating_indices: Set[ValidatorIndex]
blocks: Dict[Root, BeaconBlock] = field(default_factory=dict)
block_states: Dict[Root, BeaconState] = field(default_factory=dict)
# [Modified in Gloas:EIP7732]
block_timeliness: Dict[Root, list[boolean]] = field(default_factory=dict)
checkpoint_states: Dict[Checkpoint, BeaconState] = field(default_factory=dict)
latest_messages: Dict[ValidatorIndex, LatestMessage] = field(default_factory=dict)
unrealized_justifications: Dict[Root, Checkpoint] = field(default_factory=dict)
# [New in Gloas:EIP7732]
payloads: Dict[Root, ExecutionPayloadEnvelope] = field(default_factory=dict)
# [New in Gloas:EIP7732]
payload_timeliness_vote: Dict[Root, list[Optional[boolean]]] = field(default_factory=dict)
# [New in Gloas:EIP7732]
payload_data_availability_vote: Dict[Root, list[Optional[boolean]]] = field(
default_factory=dict
)
|
Modified get_forkchoice_store
| def get_forkchoice_store(anchor_state: BeaconState, anchor_block: BeaconBlock) -> Store:
assert anchor_block.state_root == hash_tree_root(anchor_state)
anchor_root = hash_tree_root(anchor_block)
anchor_epoch = get_current_epoch(anchor_state)
justified_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root)
finalized_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root)
proposer_boost_root = Root()
return Store(
time=uint64(anchor_state.genesis_time + SLOT_DURATION_MS * anchor_state.slot // 1000),
genesis_time=anchor_state.genesis_time,
justified_checkpoint=justified_checkpoint,
finalized_checkpoint=finalized_checkpoint,
unrealized_justified_checkpoint=justified_checkpoint,
unrealized_finalized_checkpoint=finalized_checkpoint,
proposer_boost_root=proposer_boost_root,
equivocating_indices=set(),
blocks={anchor_root: copy(anchor_block)},
block_states={anchor_root: copy(anchor_state)},
# [New in Gloas:EIP7732]
block_timeliness={anchor_root: [True, True]},
checkpoint_states={justified_checkpoint: copy(anchor_state)},
unrealized_justifications={anchor_root: justified_checkpoint},
# [New in Gloas:EIP7732]
payloads={},
# [New in Gloas:EIP7732]
payload_timeliness_vote={},
# [New in Gloas:EIP7732]
payload_data_availability_vote={},
)
|
New notify_ptc_messages
| def notify_ptc_messages(
store: Store, state: BeaconState, payload_attestations: Sequence[PayloadAttestation]
) -> None:
"""
Extracts a list of ``PayloadAttestationMessage`` from ``payload_attestations`` and updates the store with them
These Payload attestations are assumed to be in the beacon block hence signature verification is not needed
"""
if state.slot == 0:
return
for payload_attestation in payload_attestations:
indexed_payload_attestation = get_indexed_payload_attestation(state, payload_attestation)
for idx in indexed_payload_attestation.attesting_indices:
on_payload_attestation_message(
store,
PayloadAttestationMessage(
validator_index=idx,
data=payload_attestation.data,
signature=BLSSignature(),
),
is_from_block=True,
)
|
Modified is_data_available
| def is_data_available(beacon_block_root: Root) -> bool:
# `retrieve_column_sidecars_and_kzg_commitments` is implementation and
# context dependent, replacing `retrieve_column_sidecars`. For the given
# block root, it returns all column sidecars to sample, or raises an
# exception if they are not available, in addition it returns all the
# corresponding kzg commitments. The p2p network does not guarantee sidecar
# retrieval outside of `MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS` epochs.
column_sidecars, kzg_commitments = retrieve_column_sidecars_and_kzg_commitments(
beacon_block_root
)
return all(
verify_data_column_sidecar(column_sidecar, kzg_commitments)
and verify_data_column_sidecar_kzg_proofs(column_sidecar, kzg_commitments)
for column_sidecar in column_sidecars
)
|
New is_payload_verified
| def is_payload_verified(store: Store, root: Root) -> bool:
"""
Return whether the execution payload envelope for the beacon block with
root ``root`` has been locally delivered and verified via
``on_execution_payload_envelope``.
"""
return root in store.payloads
|
New payload_timeliness
| def payload_timeliness(store: Store, root: Root, timely: bool) -> bool:
"""
Return whether the execution payload for the beacon block with root ``root``
is considered ``timely`` (or not, when ``timely`` is ``False``), taking into
consideration local availability and PTC votes.
"""
# The beacon block root must be known
assert root in store.payload_timeliness_vote
# If the payload is not locally available, the payload
# is not considered available regardless of the PTC vote
if not is_payload_verified(store, root):
return not timely
votes = store.payload_timeliness_vote[root]
return sum(vote == timely for vote in votes) > PAYLOAD_TIMELY_THRESHOLD
|
New payload_data_availability
| def payload_data_availability(store: Store, root: Root, available: bool) -> bool:
"""
Return whether the blob data for the beacon block with root ``root`` is
considered ``available`` (or not, when ``available`` is ``False``), taking into
consideration local availability and PTC votes.
"""
# The beacon block root must be known
assert root in store.payload_data_availability_vote
# If the payload is not locally available, the blob data
# is not considered available regardless of the PTC vote
if not is_payload_verified(store, root):
return not available
votes = store.payload_data_availability_vote[root]
return sum(vote == available for vote in votes) > DATA_AVAILABILITY_TIMELY_THRESHOLD
|
New get_parent_payload_status
| def get_parent_payload_status(store: Store, block: BeaconBlock) -> PayloadStatus:
parent = store.blocks[block.parent_root]
parent_block_hash = block.body.signed_execution_payload_bid.message.parent_block_hash
message_block_hash = parent.body.signed_execution_payload_bid.message.block_hash
return PAYLOAD_STATUS_FULL if parent_block_hash == message_block_hash else PAYLOAD_STATUS_EMPTY
|
New is_parent_node_full
| def is_parent_node_full(store: Store, block: BeaconBlock) -> bool:
return get_parent_payload_status(store, block) == PAYLOAD_STATUS_FULL
|
Modified get_ancestor
Note: get_ancestor is modified to return whether the chain is based on an
empty or full block.
| def get_ancestor(store: Store, node: ForkChoiceNode, slot: Slot) -> ForkChoiceNode:
block = store.blocks[node.root]
if block.slot > slot:
# [Modified in Gloas:EIP7732]
parent = ForkChoiceNode(
root=block.parent_root,
payload_status=get_parent_payload_status(store, block),
)
return get_ancestor(store, parent, slot)
return node
|
Modified is_ancestor
Note: This function is modified to handle relation between pending, empty
and full fork choice nodes.
| def is_ancestor(store: Store, node: ForkChoiceNode, ancestor: ForkChoiceNode) -> bool:
node_ancestor = get_ancestor(store, node, store.blocks[ancestor.root].slot)
if node_ancestor.root != ancestor.root:
return False
# [New in Gloas:EIP7732]
return (
node_ancestor.payload_status == ancestor.payload_status
or ancestor.payload_status == PAYLOAD_STATUS_PENDING
)
|
Modified get_checkpoint_block
| def get_checkpoint_block(store: Store, root: Root, epoch: Epoch) -> Root:
"""
Compute the checkpoint block for epoch ``epoch`` in the chain of block ``root``
"""
epoch_first_slot = compute_start_slot_at_epoch(epoch)
# [Modified in Gloas:EIP7732]
node = ForkChoiceNode(root=root, payload_status=PAYLOAD_STATUS_PENDING)
return get_ancestor(store, node, epoch_first_slot).root
|
Modified get_supported_node
Note: get_supported_node is modified to set the payload_status.
| def get_supported_node(store: Store, message: LatestMessage) -> ForkChoiceNode:
"""
Return a node supported by the ``message``.
"""
# [New in Gloas:EIP7732]
block = store.blocks[message.root]
if block.slot < message.slot:
if message.payload_present:
payload_status = PAYLOAD_STATUS_FULL
else:
payload_status = PAYLOAD_STATUS_EMPTY
else:
payload_status = PAYLOAD_STATUS_PENDING
# [Modified in Gloas:EIP7732]
return ForkChoiceNode(root=message.root, payload_status=payload_status)
|
New is_previous_slot_payload_decision
Note: This predicate identifies the case where a block from the previous slot
has not yet received the attestations that resolve its payload as empty or
full, so the choice cannot be made by weight.
| def is_previous_slot_payload_decision(store: Store, node: ForkChoiceNode) -> bool:
is_previous_slot = store.blocks[node.root].slot + 1 == get_current_slot(store)
is_payload_decision = node.payload_status in [PAYLOAD_STATUS_EMPTY, PAYLOAD_STATUS_FULL]
return is_previous_slot and is_payload_decision
|
New should_build_on_full
Note: This function is called by the proposer to decide whether to build on
top of the empty or full parent node. For a node from an earlier slot, it
follows the payload status resolved by get_head. For a full node from the
previous slot, it considers the PTC view on both payload timeliness and data
availability.
| def should_build_on_full(store: Store, head: ForkChoiceNode) -> bool:
assert head.payload_status != PAYLOAD_STATUS_PENDING
if store.blocks[head.root].slot + 1 != get_current_slot(store):
return head.payload_status == PAYLOAD_STATUS_FULL
if head.payload_status == PAYLOAD_STATUS_EMPTY:
return False
if payload_timeliness(store, head.root, timely=False):
return False
if payload_data_availability(store, head.root, available=False):
return False
return True
|
New should_extend_payload
Note: This function decides whether to extend an available payload
corresponding to the beacon block root from the previous slot. It extends the
payload when the PTC view supports both payload timeliness and data
availability. Otherwise, it still extends unless the proposer boost block builds
on the beacon block root and chooses empty.
| def should_extend_payload(store: Store, root: Root) -> bool:
assert store.blocks[root].slot + 1 == get_current_slot(store)
if not is_payload_verified(store, root):
return False
proposer_root = store.proposer_boost_root
payload_is_timely = payload_timeliness(store, root, timely=True)
payload_data_is_available = payload_data_availability(store, root, available=True)
return (
(payload_is_timely and payload_data_is_available)
or proposer_root == Root()
or store.blocks[proposer_root].parent_root != root
or is_parent_node_full(store, store.blocks[proposer_root])
)
|
New get_payload_status_tiebreaker
| def get_payload_status_tiebreaker(store: Store, node: ForkChoiceNode) -> uint8:
if is_previous_slot_payload_decision(store, node):
# To decide on a payload from the previous slot, choose
# between FULL and EMPTY based on `should_extend_payload`
if node.payload_status == PAYLOAD_STATUS_EMPTY:
return 1
if should_extend_payload(store, node.root):
return 2
return 0
else:
return node.payload_status
|
New should_apply_proposer_boost
| def should_apply_proposer_boost(store: Store) -> bool:
if store.proposer_boost_root == Root():
return False
block = store.blocks[store.proposer_boost_root]
parent_root = block.parent_root
parent = store.blocks[parent_root]
slot = block.slot
# Apply proposer boost if `parent` is not from the previous slot
if parent.slot + 1 < slot:
return True
# Apply proposer boost if `parent` is not weak
if not is_head_weak(store, parent_root):
return True
# If `parent` is weak and from the previous slot, apply
# proposer boost if there are no early equivocations
equivocations = [
root
for root, block in store.blocks.items()
if (
store.block_timeliness[root][PTC_TIMELINESS_INDEX]
and block.proposer_index == parent.proposer_index
and block.slot + 1 == slot
and root != parent_root
)
]
return len(equivocations) == 0
|
Modified get_weight
| def get_weight(store: Store, node: ForkChoiceNode) -> Gwei:
# [New in Gloas:EIP7732]
if is_previous_slot_payload_decision(store, node):
return Gwei(0)
state = store.checkpoint_states[store.justified_checkpoint]
attestation_score = get_attestation_score(store, node, state)
# [Modified in Gloas:EIP7732]
if not should_apply_proposer_boost(store):
# Return only attestation score if proposer boost should not apply
return attestation_score
# Calculate proposer score if proposer boost should apply
proposer_score = Gwei(0)
# [Modified in Gloas:EIP7732]
proposer_boost_node = ForkChoiceNode(
root=store.proposer_boost_root, payload_status=PAYLOAD_STATUS_PENDING
)
# Boost is applied if ``node`` is an ancestor of ``proposer_boost_node``
if is_ancestor(store, proposer_boost_node, node):
proposer_score = get_proposer_score(store)
return attestation_score + proposer_score
|
Modified get_node_children
Note: This function is modified to introduce new type of children nodes
representing full and empty blocks.
| def get_node_children(
store: Store, blocks: Dict[Root, BeaconBlock], node: ForkChoiceNode
) -> Sequence[ForkChoiceNode]:
if node.payload_status == PAYLOAD_STATUS_PENDING:
children = [ForkChoiceNode(root=node.root, payload_status=PAYLOAD_STATUS_EMPTY)]
if is_payload_verified(store, node.root):
children.append(ForkChoiceNode(root=node.root, payload_status=PAYLOAD_STATUS_FULL))
return children
else:
return [
ForkChoiceNode(root=root, payload_status=PAYLOAD_STATUS_PENDING)
for root in blocks
if (
blocks[root].parent_root == node.root
and node.payload_status == get_parent_payload_status(store, blocks[root])
)
]
|
Modified get_head
Note: Modified to use get_payload_status_tiebreaker to break the ties
between full and empty nodes.
| def get_head(store: Store) -> ForkChoiceNode:
# Get filtered block tree that only includes viable branches
blocks = get_filtered_block_tree(store)
# Execute the LMD-GHOST fork-choice
head = ForkChoiceNode(
root=store.justified_checkpoint.root,
# [New in Gloas:EIP7732]
payload_status=PAYLOAD_STATUS_PENDING,
)
while True:
children = get_node_children(store, blocks, head)
if len(children) == 0:
return head
# Sort by latest attesting balance with ties broken lexicographically
head = max(
children,
key=lambda child: (
get_weight(store, child),
child.root,
# [New in Gloas:EIP7732]
get_payload_status_tiebreaker(store, child),
),
)
|
Modified get_latest_message_epoch
| def get_latest_message_epoch(latest_message: LatestMessage) -> Epoch:
return compute_epoch_at_slot(latest_message.slot)
|
New verify_execution_payload_envelope_signature
| def verify_execution_payload_envelope_signature(
state: BeaconState, signed_envelope: SignedExecutionPayloadEnvelope
) -> bool:
builder_index = signed_envelope.message.builder_index
if builder_index == BUILDER_INDEX_SELF_BUILD:
validator_index = state.latest_block_header.proposer_index
pubkey = state.validators[validator_index].pubkey
else:
pubkey = state.builders[builder_index].pubkey
signing_root = compute_signing_root(
signed_envelope.message, get_domain(state, DOMAIN_BEACON_BUILDER)
)
return bls.Verify(pubkey, signing_root, signed_envelope.signature)
|
New verify_execution_payload_envelope
| def verify_execution_payload_envelope(
state: BeaconState,
signed_envelope: SignedExecutionPayloadEnvelope,
execution_engine: ExecutionEngine,
) -> None:
envelope = signed_envelope.message
payload = envelope.payload
# Verify signature
assert verify_execution_payload_envelope_signature(state, signed_envelope)
# Verify consistency with the beacon block
header = copy(state.latest_block_header)
header.state_root = hash_tree_root(state)
assert envelope.beacon_block_root == hash_tree_root(header)
assert envelope.parent_beacon_block_root == state.latest_block_header.parent_root
# Verify consistency with the committed bid
bid = state.latest_execution_payload_bid
assert envelope.builder_index == bid.builder_index
assert payload.prev_randao == bid.prev_randao
assert payload.gas_limit == bid.gas_limit
assert payload.block_hash == bid.block_hash
assert hash_tree_root(envelope.execution_requests) == bid.execution_requests_root
# Verify the execution payload is valid
assert payload.slot_number == state.slot
assert payload.parent_hash == state.latest_block_hash
assert payload.timestamp == compute_time_at_slot(state, state.slot)
assert hash_tree_root(payload.withdrawals) == hash_tree_root(state.payload_expected_withdrawals)
assert execution_engine.verify_and_notify_new_payload(
NewPayloadRequest(
execution_payload=payload,
versioned_hashes=[
kzg_commitment_to_versioned_hash(commitment)
for commitment in bid.blob_kzg_commitments
],
parent_beacon_block_root=envelope.parent_beacon_block_root,
execution_requests=envelope.execution_requests,
)
)
|
Modified get_attestation_due_ms
| def get_attestation_due_ms() -> uint64:
# [Modified in Gloas]
return get_slot_component_duration_ms(ATTESTATION_DUE_BPS_GLOAS)
|
Modified get_aggregate_due_ms
| def get_aggregate_due_ms() -> uint64:
# [Modified in Gloas]
return get_slot_component_duration_ms(AGGREGATE_DUE_BPS_GLOAS)
|
Modified get_sync_message_due_ms
| def get_sync_message_due_ms() -> uint64:
# [Modified in Gloas]
return get_slot_component_duration_ms(SYNC_MESSAGE_DUE_BPS_GLOAS)
|
Modified get_contribution_due_ms
| def get_contribution_due_ms() -> uint64:
# [Modified in Gloas]
return get_slot_component_duration_ms(CONTRIBUTION_DUE_BPS_GLOAS)
|
New get_payload_due_ms
| def get_payload_due_ms() -> uint64:
return get_slot_component_duration_ms(PAYLOAD_DUE_BPS)
|
New get_payload_attestation_due_ms
| def get_payload_attestation_due_ms() -> uint64:
return get_slot_component_duration_ms(PAYLOAD_ATTESTATION_DUE_BPS)
|
Proposer head and reorg helpers
Modified is_head_late
Note: The only change is that store.block_timeliness[root] now records
timeliness with respect to two different deadlines. is_head_late takes into
account timeliness with respect to the attestation deadline, which is retrieved
at ATTESTATION_TIMELINESS_INDEX.
| def is_head_late(store: Store, head_root: Root) -> bool:
return not store.block_timeliness[head_root][ATTESTATION_TIMELINESS_INDEX]
|
Modified is_head_weak
Note: The function is_head_weak now also counts weight from equivocating
validators from the committees of the head slot. This ensures that the counted
weight and the output of is_head_weak are monotonic: more attestations can
only increase the weight and change the output from True to False, not
vice-versa.
| def is_head_weak(store: Store, head_root: Root) -> bool:
# Calculate weight threshold for weak head
justified_state = store.checkpoint_states[store.justified_checkpoint]
reorg_threshold = calculate_committee_fraction(justified_state, REORG_HEAD_WEIGHT_THRESHOLD)
# Compute head weight including equivocations
head_state = store.block_states[head_root]
head_block = store.blocks[head_root]
epoch = compute_epoch_at_slot(head_block.slot)
head_node = ForkChoiceNode(root=head_root, payload_status=PAYLOAD_STATUS_PENDING)
head_weight = get_attestation_score(store, head_node, justified_state)
for index in range(get_committee_count_per_slot(head_state, epoch)):
committee = get_beacon_committee(head_state, head_block.slot, CommitteeIndex(index))
head_weight += Gwei(
sum(
justified_state.validators[i].effective_balance
for i in committee
if i in store.equivocating_indices
)
)
return head_weight < reorg_threshold
|
Modified is_parent_strong
| def is_parent_strong(store: Store, root: Root) -> bool:
justified_state = store.checkpoint_states[store.justified_checkpoint]
parent_threshold = calculate_committee_fraction(justified_state, REORG_PARENT_WEIGHT_THRESHOLD)
block = store.blocks[root]
parent_payload_status = get_parent_payload_status(store, block)
parent_node = ForkChoiceNode(root=block.parent_root, payload_status=parent_payload_status)
parent_weight = get_attestation_score(store, parent_node, justified_state)
return parent_weight > parent_threshold
|
on_attestation helpers
Modified validate_on_attestation
| def validate_on_attestation(store: Store, attestation: Attestation, is_from_block: bool) -> None:
target = attestation.data.target
# If the given attestation is not from a beacon block message,
# we have to check the target epoch scope.
if not is_from_block:
validate_target_epoch_against_current_time(store, attestation)
# Check that the epoch number and slot number are matching.
assert target.epoch == compute_epoch_at_slot(attestation.data.slot)
# Attestation target must be for a known block. If target block
# is unknown, delay consideration until block is found.
assert target.root in store.blocks
# Attestations must be for a known block. If block
# is unknown, delay consideration until the block is found.
assert attestation.data.beacon_block_root in store.blocks
# Attestations must not be for blocks in the future.
# If not, the attestation should not be considered.
block_slot = store.blocks[attestation.data.beacon_block_root].slot
assert block_slot <= attestation.data.slot
# [New in Gloas:EIP7732]
assert attestation.data.index in [0, 1]
if block_slot == attestation.data.slot:
assert attestation.data.index == 0
# [New in Gloas:EIP7732]
# If attesting for a full node, the payload must be known
if attestation.data.index == 1:
assert is_payload_verified(store, attestation.data.beacon_block_root)
# LMD vote must be consistent with FFG vote target
assert target.root == get_checkpoint_block(
store, attestation.data.beacon_block_root, target.epoch
)
# Attestations can only affect the fork-choice of subsequent slots.
# Delay consideration in the fork-choice until their slot is in the past.
assert get_current_slot(store) >= attestation.data.slot + 1
|
Modified update_latest_messages
Note: The function update_latest_messages is updated to use the attestation
slot instead of target. Notice that this function is only called on validated
attestations and validators cannot attest twice in the same epoch without
equivocating. Notice also that target epoch number and slot number are validated
on validate_on_attestation.
| def update_latest_messages(
store: Store, attesting_indices: Sequence[ValidatorIndex], attestation: Attestation
) -> None:
slot = attestation.data.slot
beacon_block_root = attestation.data.beacon_block_root
payload_present = attestation.data.index == 1
non_equivocating_attesting_indices = [
i for i in attesting_indices if i not in store.equivocating_indices
]
for i in non_equivocating_attesting_indices:
if i not in store.latest_messages or slot > store.latest_messages[i].slot:
# [Modified in Gloas:EIP7732]
store.latest_messages[i] = LatestMessage(
slot=slot,
root=beacon_block_root,
payload_present=payload_present,
)
|
on_block helpers
Modified record_block_timeliness
| def record_block_timeliness(store: Store, root: Root) -> None:
block = store.blocks[root]
seconds_since_genesis = store.time - store.genesis_time
time_into_slot_ms = seconds_to_milliseconds(seconds_since_genesis) % SLOT_DURATION_MS
attestation_threshold_ms = get_attestation_due_ms()
# [New in Gloas:EIP7732]
is_current_slot = get_current_slot(store) == block.slot
ptc_threshold_ms = get_payload_attestation_due_ms()
# [Modified in Gloas:EIP7732]
store.block_timeliness[root] = [
is_current_slot and time_into_slot_ms < threshold
for threshold in [attestation_threshold_ms, ptc_threshold_ms]
]
|
Modified get_dependent_root
| def get_dependent_root(store: Store, root: Root) -> Root:
epoch = get_current_store_epoch(store)
if epoch <= MIN_SEED_LOOKAHEAD:
# Genesis block parent
return Root()
# [Modified in Gloas:EIP7732]
node = ForkChoiceNode(
root=root,
payload_status=PAYLOAD_STATUS_PENDING,
)
dependent_slot = Slot(compute_start_slot_at_epoch(epoch - MIN_SEED_LOOKAHEAD) - 1)
return get_ancestor(store, node, dependent_slot).root
|
Modified update_proposer_boost_root
| def update_proposer_boost_root(store: Store, head: Root, root: Root) -> None:
is_first_block = store.proposer_boost_root == Root()
# [Modified in Gloas:EIP7732]
is_timely = store.block_timeliness[root][ATTESTATION_TIMELINESS_INDEX]
is_same_dependent_root = get_dependent_root(store, root) == get_dependent_root(store, head)
# Add proposer score boost if the block is timely, not conflicting with an
# existing block, with the same dependent root as the canonical chain head.
if is_timely and is_first_block and is_same_dependent_root:
store.proposer_boost_root = root
|
Handlers
Modified on_block
Note: The handler on_block is modified to assert that the parent payload has
been verified (is_payload_verified) when the block builds on a full parent. In
addition we delay the checking of blob data availability until the processing of
the execution payload.
| def on_block(store: Store, signed_block: SignedBeaconBlock) -> None:
"""
Run ``on_block`` upon receiving a new block.
"""
block = signed_block.message
# Parent block must be known
assert block.parent_root in store.block_states
# If this block builds on the parent's full payload, that payload must
# have been verified by on_execution_payload_envelope
if is_parent_node_full(store, block):
assert is_payload_verified(store, block.parent_root)
# Blocks cannot be in the future. If they are, their consideration must be delayed until they are in the past.
current_slot = get_current_slot(store)
assert current_slot >= 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
# Make a copy of the state to avoid mutability issues
state = copy(store.block_states[block.parent_root])
# Check the block is valid and compute the post-state
block_root = hash_tree_root(block)
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
# Add a new PTC voting for this block to the store
store.payload_timeliness_vote[block_root] = [None] * PTC_SIZE
store.payload_data_availability_vote[block_root] = [None] * PTC_SIZE
# Notify the store about the payload_attestations in the block
notify_ptc_messages(store, state, block.body.payload_attestations)
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)
|
New on_execution_payload_envelope
The handler on_execution_payload_envelope is called when the node receives a
SignedExecutionPayloadEnvelope to sync.
| def on_execution_payload_envelope(
store: Store, signed_envelope: SignedExecutionPayloadEnvelope
) -> None:
"""
Run ``on_execution_payload_envelope`` upon receiving a new execution payload envelope.
"""
envelope = signed_envelope.message
# The corresponding beacon block root needs to be known
assert envelope.beacon_block_root in store.block_states
# 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(envelope.beacon_block_root)
state = store.block_states[envelope.beacon_block_root]
# Verify the execution payload envelope
verify_execution_payload_envelope(state, signed_envelope, EXECUTION_ENGINE)
# Add execution payload envelope to the store
store.payloads[envelope.beacon_block_root] = envelope
|
New on_payload_attestation_message
| def on_payload_attestation_message(
store: Store, ptc_message: PayloadAttestationMessage, is_from_block: bool = False
) -> None:
"""
Run ``on_payload_attestation_message`` upon receiving a new ``ptc_message`` from
either within a block or directly on the wire.
"""
data = ptc_message.data
# PTC attestation must be for a known block. If block is unknown, delay consideration until the block is found
assert data.beacon_block_root in store.block_states
state = store.block_states[data.beacon_block_root]
# PTC votes can only change the vote for their assigned beacon block, return early otherwise
if data.slot != state.slot:
return
# Get all positions of the attester in the PTC
ptc_indices = []
ptc = get_ptc(state, data.slot)
for ptc_index, validator_index in enumerate(ptc):
if validator_index == ptc_message.validator_index:
ptc_indices.append(ptc_index)
# Check that the attester is from the PTC
assert len(ptc_indices) > 0
# Verify the signature and check that its for the current slot if it is coming from the wire
if not is_from_block:
# Check that the attestation is for the current slot
assert data.slot == get_current_slot(store)
# Verify the signature
assert is_valid_indexed_payload_attestation(
state,
IndexedPayloadAttestation(
attesting_indices=[ptc_message.validator_index],
data=data,
signature=ptc_message.signature,
),
)
# Update the votes for the block
payload_timeliness_vote = store.payload_timeliness_vote[data.beacon_block_root]
payload_data_availability_vote = store.payload_data_availability_vote[data.beacon_block_root]
for ptc_index in ptc_indices:
payload_timeliness_vote[ptc_index] = data.payload_present
payload_data_availability_vote[ptc_index] = data.blob_data_available
|