Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
77 changes: 71 additions & 6 deletions specs/gloas/fork-choice.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
- [Modified `is_head_late`](#modified-is_head_late)
- [Modified `is_head_weak`](#modified-is_head_weak)
- [Modified `is_parent_strong`](#modified-is_parent_strong)
- [Modified `get_proposer_head`](#modified-get_proposer_head)
- [`on_attestation` helpers](#on_attestation-helpers)
- [Modified `validate_on_attestation`](#modified-validate_on_attestation)
- [Modified `update_latest_messages`](#modified-update_latest_messages)
Expand Down Expand Up @@ -416,9 +417,8 @@ def is_previous_slot_payload_decision(store: Store, node: ForkChoiceNode) -> boo

*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.
follows the node's payload status. For a *full* node from the previous slot, it
considers the PTC view on both payload timeliness and data availability.

```python
def should_build_on_full(store: Store, head: ForkChoiceNode) -> bool:
Expand Down Expand Up @@ -762,17 +762,82 @@ def is_head_weak(store: Store, head_root: Root) -> bool:

#### Modified `is_parent_strong`

*Note*: This function is modified to measure support for the parent beacon block
root regardless of its payload status.

```python
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_root = store.blocks[root].parent_root
# [Modified in Gloas:EIP7732]
parent_node = ForkChoiceNode(root=parent_root, payload_status=PAYLOAD_STATUS_PENDING)
parent_weight = get_attestation_score(store, parent_node, justified_state)
return parent_weight > parent_threshold
```

#### Modified `get_proposer_head`

*Note*: This function is modified to preserve the payload status of the parent
node when a proposer re-org is selected.

```python
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]
# [Modified in Gloas:EIP7732]
parent_payload_status = get_parent_payload_status(store, head_block)
parent_node = ForkChoiceNode(root=parent_root, payload_status=parent_payload_status)

# Only re-org the head block if it arrived later than the attestation deadline.
head_late = is_head_late(store, head_node.root)

# Do not re-org on an epoch boundary where the proposer shuffling could change.
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,
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
```

### `on_attestation` helpers

#### Modified `validate_on_attestation`
Expand Down
6 changes: 4 additions & 2 deletions specs/gloas/validator.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,10 @@ def get_proposer_preferences_signature(

#### Constructing the `BeaconBlockBody`

Let `head = get_head(store)` be the parent block the proposer is building on,
from which `state` was derived.
Let `head = get_head(store)`. A proposer may set
`head = get_proposer_head(store, head, slot)` if proposer re-orgs are
implemented and enabled. Let `head` be the parent node the proposer builds on,
from which `state` is derived.

##### Signed execution payload bid

Expand Down
28 changes: 15 additions & 13 deletions specs/phase0/fork-choice.md
Original file line number Diff line number Diff line change
Expand Up @@ -638,7 +638,8 @@ 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)
parent_root = store.blocks[root].parent_root
parent_weight = get_weight(store, ForkChoiceNode(root=parent_root))
parent_node = ForkChoiceNode(root=parent_root)
parent_weight = get_weight(store, parent_node)
return parent_weight > parent_threshold
```

Expand All @@ -661,19 +662,20 @@ def is_proposer_equivocation(store: Store, root: Root) -> bool:
##### `get_proposer_head`

```python
def get_proposer_head(store: Store, head_root: Root, slot: Slot) -> Root:
head_block = store.blocks[head_root]
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_root)
head_late = is_head_late(store, head_node.root)

# Do not re-org on an epoch boundary where the proposer shuffling could change.
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_root, parent_root)
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)
Expand All @@ -687,14 +689,14 @@ def get_proposer_head(store: Store, head_root: Root, slot: Slot) -> Root:
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_root # ensure boost has worn off
head_weak = is_head_weak(store, head_root)
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_root)
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_root)
proposer_equivocation = is_proposer_equivocation(store, head_node.root)

if all([
head_late,
Expand All @@ -706,12 +708,12 @@ def get_proposer_head(store: Store, head_root: Root, slot: Slot) -> Root:
head_weak,
parent_strong,
]):
# We can re-org the current head by building upon its parent block.
return parent_root
# 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_root
return parent_node
else:
return head_root
return head_node
```

*Note*: The ordering of conditions is a suggestion only. Implementations are
Expand Down
8 changes: 4 additions & 4 deletions specs/phase0/validator.md
Original file line number Diff line number Diff line change
Expand Up @@ -375,13 +375,13 @@ To propose, the validator selects a `BeaconBlock`, `parent` using this process:

1. Compute fork choice's view of the head at the start of `slot`, after running
`on_tick` and applying any queued attestations from `slot - 1`. Set
`head_root = get_head(store).root`.
`head_node = get_head(store)`.
2. Compute the _proposer head_, which is the head upon which the proposer SHOULD
build in order to incentivise timely block propagation by other validators.
Set `parent_root = get_proposer_head(store, head_root, slot)`. A proposer may
set `parent_root == head_root` if proposer re-orgs are not implemented or
Set `parent_node = get_proposer_head(store, head_node, slot)`. A proposer may
set `parent_node == head_node` if proposer re-orgs are not implemented or
have been disabled.
3. Let `parent` be the block with `parent_root`.
3. Let `parent` be the block with `parent_node.root`.

The validator creates, signs, and broadcasts a `block` that is a child of
`parent` and satisfies a valid
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,14 @@ def test_basic_is_head_root(spec, state):

current_time = slot * spec.config.SLOT_DURATION_MS // 1000 + store.genesis_time
on_tick_and_append_step(spec, store, current_time, test_steps)
proposer_head = spec.get_proposer_head(store, head.root, slot)
assert proposer_head == head.root
proposer_head = spec.get_proposer_head(store, head, slot)
assert proposer_head.root == head.root

output_store_checks(spec, store, test_steps)
test_steps.append(
{
"checks": {
"get_proposer_head": encode_hex(proposer_head),
"get_proposer_head": encode_hex(proposer_head.root),
}
}
)
Expand Down Expand Up @@ -168,14 +168,14 @@ def test_basic_is_parent_root(spec, state):
assert spec.is_head_weak(store, head.root)
assert spec.is_parent_strong(store, head.root)

proposer_head = spec.get_proposer_head(store, head.root, state.slot)
assert proposer_head == parent_root
proposer_head = spec.get_proposer_head(store, head, state.slot)
assert proposer_head.root == parent_root

output_store_checks(spec, store, test_steps)
test_steps.append(
{
"checks": {
"get_proposer_head": encode_hex(proposer_head),
"get_proposer_head": encode_hex(proposer_head.root),
}
}
)
Expand Down