Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
181 changes: 91 additions & 90 deletions specs/gloas/fork-choice.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
- [`ExecutionEngine`](#executionengine)
- [`notify_forkchoice_updated`](#notify_forkchoice_updated)
- [Helpers](#helpers)
- [New `ForkChoiceNode`](#new-forkchoicenode)
- [Modified `ForkChoiceNode`](#modified-forkchoicenode)
- [Modified `PayloadAttributes`](#modified-payloadattributes)
- [Modified `LatestMessage`](#modified-latestmessage)
- [Modified `update_latest_messages`](#modified-update_latest_messages)
Expand All @@ -23,16 +23,17 @@
- [New `payload_data_availability`](#new-payload_data_availability)
- [New `get_parent_payload_status`](#new-get_parent_payload_status)
- [New `is_parent_node_full`](#new-is_parent_node_full)
- [Modified `get_block_root_node`](#modified-get_block_root_node)
- [Modified `get_ancestor`](#modified-get_ancestor)
- [Modified `is_ancestor`](#modified-is_ancestor)
- [Modified `get_checkpoint_block`](#modified-get_checkpoint_block)
- [New `is_supporting_vote`](#new-is_supporting_vote)
- [Modified `get_supported_node`](#modified-get_supported_node)
- [New `should_build_on_full`](#new-should_build_on_full)
- [New `should_extend_payload`](#new-should_extend_payload)
- [New `get_payload_status_tiebreaker`](#new-get_payload_status_tiebreaker)
- [New `should_apply_proposer_boost`](#new-should_apply_proposer_boost)
- [Modified `get_attestation_score`](#modified-get_attestation_score)
- [Modified `get_weight`](#modified-get_weight)
- [New `get_node_children`](#new-get_node_children)
- [Modified `get_node_children`](#modified-get_node_children)
- [Modified `get_head`](#modified-get_head)
- [Modified `record_block_timeliness`](#modified-record_block_timeliness)
- [Modified `update_proposer_boost_root`](#modified-update_proposer_boost_root)
Expand Down Expand Up @@ -100,11 +101,13 @@ Where:

## Helpers

### New `ForkChoiceNode`
### Modified `ForkChoiceNode`

```python
class ForkChoiceNode(Container):
@dataclass(eq=True, frozen=True)
class ForkChoiceNode:
root: Root
# [New in Gloas:EIP7732]
payload_status: PayloadStatus # One of PAYLOAD_STATUS_* values
Comment thread
jtraglia marked this conversation as resolved.
```

Expand Down Expand Up @@ -325,71 +328,96 @@ def is_parent_node_full(store: Store, block: BeaconBlock) -> bool:
return get_parent_payload_status(store, block) == PAYLOAD_STATUS_FULL
```

### Modified `get_block_root_node`
Comment thread
brech1 marked this conversation as resolved.
Outdated

*Note:* This function is modified to return an extended `ForkChoiceNode`
structure with `PAYLOAD_STATUS_PENDING` payload status as a common ancestor of
all nodes referring to a given `block_root`.

```python
def get_block_root_node(block_root: Root) -> ForkChoiceNode:
# [Modified in Gloas:EIP7732]
return ForkChoiceNode(root=block_root, payload_status=PAYLOAD_STATUS_PENDING)
Comment thread
jtraglia marked this conversation as resolved.
Outdated
```

### Modified `get_ancestor`

*Note*: `get_ancestor` is modified to return whether the chain is based on an
*empty* or *full* block.
*empty* or *full* block. If a `node.root` is from the same `slot` then the
`node` is returned as is. For past slots this function returns nodes with either
`PAYLOAD_STATUS_FULL` or `PAYLOAD_STATUS_EMPTY` payload status.

```python
def get_ancestor(store: Store, root: Root, slot: Slot) -> ForkChoiceNode:
"""
Returns the beacon block root and the payload status of the ancestor of the beacon block
with ``root`` at ``slot``. If the beacon block with ``root`` is already at ``slot`` or we are
requesting an ancestor "in the future", it returns ``PAYLOAD_STATUS_PENDING``.
"""
block = store.blocks[root]
if block.slot <= slot:
return ForkChoiceNode(root=root, payload_status=PAYLOAD_STATUS_PENDING)
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),
)
Comment thread
jtraglia marked this conversation as resolved.
return get_ancestor(store, parent, slot)
return node
```

parent = store.blocks[block.parent_root]
while parent.slot > slot:
block = parent
parent = store.blocks[block.parent_root]
### Modified `is_ancestor`

*Note*: This function is modified to use an extended `ForkChoiceNode` structure.
It handles relation between the same slot nodes based on the payload status of
`maybe_ancestor` and `ancestor`.

return ForkChoiceNode(
root=block.parent_root,
payload_status=get_parent_payload_status(store, block),
```python
def is_ancestor(store: Store, node: ForkChoiceNode, maybe_ancestor: ForkChoiceNode) -> bool:
ancestor = get_ancestor(store, node, store.blocks[maybe_ancestor.root].slot)
if ancestor.root != maybe_ancestor.root:
return False

# [New in Gloas:EIP7732]
return (
ancestor.payload_status == maybe_ancestor.payload_status
or maybe_ancestor.payload_status == PAYLOAD_STATUS_PENDING
)
Comment thread
brech1 marked this conversation as resolved.
Outdated
```

### Modified `get_checkpoint_block`

*Note*: `get_checkpoint_block` is modified to use the new `get_ancestor`
*Note*: `get_checkpoint_block` is modified to use an extended `ForkChoiceNode`
structure.

```python
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)
return get_ancestor(store, root, epoch_first_slot).root
# [Modified in Gloas:EIP7732]
node = ForkChoiceNode(root=root, payload_status=PAYLOAD_STATUS_PENDING)
return get_ancestor(store, node, epoch_first_slot).root
```

### New `is_supporting_vote`
### Modified `get_supported_node`

*Note*: `get_supported_node` is modified to use an extended `ForkChoiceNode` and
Comment thread
brech1 marked this conversation as resolved.
Outdated
`LatestMessage` structures. It sets the `payload_status` according to message
`block.slot` and `message.payload_present`.

```python
def is_supporting_vote(store: Store, node: ForkChoiceNode, message: LatestMessage) -> bool:
def get_supported_node(store: Store, message: LatestMessage) -> ForkChoiceNode:
"""
Returns whether the vote ``message`` supports the chain containing the
forkchoice node ``node``.
Return a node supported by the ``message``.
"""
block = store.blocks[node.root]
if node.root == message.root:
if node.payload_status == PAYLOAD_STATUS_PENDING:
return True
assert message.slot >= block.slot
if message.slot == block.slot:
return False
# [New in Gloas:EIP7732]
block = store.blocks[message.root]
if block.slot < message.slot:
if message.payload_present:
return node.payload_status == PAYLOAD_STATUS_FULL
payload_status = PAYLOAD_STATUS_FULL
else:
return node.payload_status == PAYLOAD_STATUS_EMPTY
payload_status = PAYLOAD_STATUS_EMPTY
else:
ancestor = get_ancestor(store, message.root, block.slot)
return node.root == ancestor.root and (
node.payload_status == PAYLOAD_STATUS_PENDING
or node.payload_status == ancestor.payload_status
)
payload_status = PAYLOAD_STATUS_PENDING

# [Modified in Gloas:7732]
Comment thread
mkalinin marked this conversation as resolved.
Outdated
return ForkChoiceNode(root=message.root, payload_status=payload_status)
Comment thread
jtraglia marked this conversation as resolved.
```

### New `should_build_on_full`
Expand Down Expand Up @@ -482,74 +510,43 @@ def should_apply_proposer_boost(store: Store) -> bool:
return len(equivocations) == 0
```

### Modified `get_attestation_score`

```python
def get_attestation_score(
store: Store,
# [Modified in Gloas:EIP7732]
# Removed `root`
# [New in Gloas:EIP7732]
node: ForkChoiceNode,
state: BeaconState,
) -> Gwei:
unslashed_and_active_indices = [
i
for i in get_active_validator_indices(state, get_current_epoch(state))
if not state.validators[i].slashed
]
return Gwei(
sum(
state.validators[i].effective_balance
for i in unslashed_and_active_indices
if (
i in store.latest_messages
and i not in store.equivocating_indices
# [Modified in Gloas:EIP7732]
and is_supporting_vote(store, node, store.latest_messages[i])
)
)
)
```

### Modified `get_weight`

*Note*: This function is modified to use new `should_apply_proposer_boost`
function and extended `ForkChoiceNode` structure.

```python
def get_weight(
store: Store,
# [Modified in Gloas:EIP7732]
node: ForkChoiceNode,
) -> Gwei:
def get_weight(store: Store, node: ForkChoiceNode) -> Gwei:
if node.payload_status == PAYLOAD_STATUS_PENDING or store.blocks[
node.root
].slot + 1 != get_current_slot(store):
Comment thread
jtraglia marked this conversation as resolved.
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
Comment thread
mkalinin marked this conversation as resolved.
Outdated
return attestation_score

# Calculate proposer score if `proposer_boost_root` is set
Comment thread
mkalinin marked this conversation as resolved.
Outdated
proposer_score = Gwei(0)

# `proposer_boost_root` is treated as a vote for the
# proposer's block in the current slot. Proposer boost
# is applied accordingly to all ancestors
message = LatestMessage(
slot=get_current_slot(store),
root=store.proposer_boost_root,
payload_present=False,
# [Modified in Gloas:EIP7732]
proposer_boost_node = ForkChoiceNode(
root=store.proposer_boost_root, payload_status=PAYLOAD_STATUS_PENDING
)
if is_supporting_vote(store, node, message):
# 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
else:
return Gwei(0)
```

### New `get_node_children`
### Modified `get_node_children`

*Note*: This function is modified to introduce new type of children nodes
representing *full* and *empty* blocks.

```python
def get_node_children(
Expand All @@ -573,8 +570,10 @@ def get_node_children(

### Modified `get_head`

*Note*: `get_head` is modified to use the new `get_weight` function. It returns
the `ForkChoiceNode` object corresponding to the head block.
*Note*: `get_head` is modified to use modified `get_weight`, `get_node_children`
functions and new `get_payload_status_tiebreaker` function. The latter is used
to break the ties between *full* and *empty* nodes that have an equal block
root.

```python
def get_head(store: Store) -> ForkChoiceNode:
Expand All @@ -583,6 +582,7 @@ def get_head(store: Store) -> ForkChoiceNode:
# Execute the LMD-GHOST fork-choice
head = ForkChoiceNode(
root=store.justified_checkpoint.root,
# [New in Gloas:EIP7732]
payload_status=PAYLOAD_STATUS_PENDING,
Comment thread
jtraglia marked this conversation as resolved.
)

Expand All @@ -596,6 +596,7 @@ def get_head(store: Store) -> ForkChoiceNode:
key=lambda child: (
get_weight(store, child),
child.root,
# [New in Gloas:EIP7732]
get_payload_status_tiebreaker(store, child),
),
)
Expand Down
Loading