-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Finishing touches for the gloas fork-choice #4807
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 13 commits
71ff980
eeafb9a
0eff45a
984c930
1678379
fcfcc92
5e8c4a8
e343520
29b4f1f
e1296e6
fc2fb64
c9bda1c
db95ef2
41ef674
a1cfdc5
dad156d
48c2dfb
084de4a
e58a2bd
07fe527
abb8d1b
0ad2bba
a556309
2921593
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,6 +22,8 @@ | |
| - [New `is_supporting_vote`](#new-is_supporting_vote) | ||
| - [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) | ||
| - [New `get_attestation_weight`](#new-get_attestation_weight) | ||
| - [Modified `get_weight`](#modified-get_weight) | ||
| - [New `get_node_children`](#new-get_node_children) | ||
| - [Modified `get_head`](#modified-get_head) | ||
|
|
@@ -33,11 +35,15 @@ | |
| - [New fork-choice helpers](#new-fork-choice-helpers) | ||
| - [New `get_payload_attestation_due_ms`](#new-get_payload_attestation_due_ms) | ||
| - [Updated fork-choice handlers](#updated-fork-choice-handlers) | ||
| - [Modified `update_proposer_boost_root`](#modified-update_proposer_boost_root) | ||
| - [New `record_block_timeliness`](#new-record_block_timeliness) | ||
| - [Modified `on_block`](#modified-on_block) | ||
| - [New fork-choice handlers](#new-fork-choice-handlers) | ||
| - [New `on_execution_payload`](#new-on_execution_payload) | ||
| - [New `on_payload_attestation_message`](#new-on_payload_attestation_message) | ||
| - [Modified `validate_on_attestation`](#modified-validate_on_attestation) | ||
| - [Modified `is_head_late`](#modified-is_head_late) | ||
| - [Modified `is_head_weak`](#modified-is_head_weak) | ||
|
|
||
| <!-- mdformat-toc end --> | ||
|
|
||
|
|
@@ -53,12 +59,15 @@ This is the modification of the fork-choice accompanying the Gloas upgrade. | |
|
|
||
| ## Constants | ||
|
|
||
| | Name | Value | | ||
| | -------------------------- | ----------------------- | | ||
| | `PAYLOAD_TIMELY_THRESHOLD` | `PTC_SIZE // 2` (= 256) | | ||
| | `PAYLOAD_STATUS_PENDING` | `PayloadStatus(0)` | | ||
| | `PAYLOAD_STATUS_EMPTY` | `PayloadStatus(1)` | | ||
| | `PAYLOAD_STATUS_FULL` | `PayloadStatus(2)` | | ||
| | Name | Value | | ||
| | -------------------------------- | ----------------------- | | ||
| | `PAYLOAD_TIMELY_THRESHOLD` | `PTC_SIZE // 2` (= 256) | | ||
| | `PAYLOAD_STATUS_PENDING` | `PayloadStatus(0)` | | ||
| | `PAYLOAD_STATUS_EMPTY` | `PayloadStatus(1)` | | ||
| | `PAYLOAD_STATUS_FULL` | `PayloadStatus(2)` | | ||
| | `NUM_BLOCK_TIMELINESS_DEADLINES` | `2` | | ||
| | `ATTESTATION_TIMELINESS_INDEX` | `0` | | ||
| | `PTC_TIMELINESS_INDEX` | `1` | | ||
|
|
||
| ## Helpers | ||
|
|
||
|
|
@@ -126,7 +135,9 @@ class Store(object): | |
| equivocating_indices: Set[ValidatorIndex] | ||
| blocks: Dict[Root, BeaconBlock] = field(default_factory=dict) | ||
| block_states: Dict[Root, BeaconState] = field(default_factory=dict) | ||
| block_timeliness: Dict[Root, boolean] = field(default_factory=dict) | ||
| block_timeliness: Dict[Root, Vector[boolean, NUM_BLOCK_TIMELINESS_DEADLINES]] = 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) | ||
|
|
@@ -162,6 +173,7 @@ def get_forkchoice_store(anchor_state: BeaconState, anchor_block: BeaconBlock) - | |
| # [New in Gloas:EIP7732] | ||
| execution_payload_states={anchor_root: copy(anchor_state)}, | ||
| ptc_vote={anchor_root: Vector[boolean, PTC_SIZE]()}, | ||
| block_timeliness={anchor_root: [True, True]}, | ||
| ) | ||
| ``` | ||
|
|
||
|
|
@@ -329,33 +341,78 @@ def get_payload_status_tiebreaker(store: Store, node: ForkChoiceNode) -> uint8: | |
| return 2 if should_extend_payload(store, node.root) else 0 | ||
| ``` | ||
|
|
||
| ### New `should_apply_proposer_boost` | ||
|
|
||
| ```python | ||
| 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 | ||
| ``` | ||
|
|
||
| ### New `get_attestation_weight` | ||
|
|
||
| ```python | ||
| def get_attestation_weight(store: Store, node: ForkChoiceNode) -> Gwei: | ||
| state = store.checkpoint_states[store.justified_checkpoint] | ||
| unslashed_and_active_indices = [ | ||
| i | ||
| for i in get_active_validator_indices(state, get_current_epoch(state)) | ||
| if not state.validators[i].slashed | ||
| ] | ||
| attestation_score = 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 | ||
| and is_supporting_vote(store, node, store.latest_messages[i]) | ||
| ) | ||
| ) | ||
| ) | ||
| return attestation_score | ||
| ``` | ||
|
|
||
| ### Modified `get_weight` | ||
|
|
||
| ```python | ||
| 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): | ||
| state = store.checkpoint_states[store.justified_checkpoint] | ||
| unslashed_and_active_indices = [ | ||
| i | ||
| for i in get_active_validator_indices(state, get_current_epoch(state)) | ||
| if not state.validators[i].slashed | ||
| ] | ||
| attestation_score = 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 | ||
| and is_supporting_vote(store, node, store.latest_messages[i]) | ||
| ) | ||
| ) | ||
| ) | ||
| attestation_score = get_attestation_weight(store, node) | ||
|
|
||
| if store.proposer_boost_root == Root(): | ||
| # Return only attestation score if `proposer_boost_root` is not set | ||
| 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_root` is set | ||
|
|
@@ -482,6 +539,45 @@ def get_payload_attestation_due_ms(epoch: Epoch) -> uint64: | |
|
|
||
| ## Updated fork-choice handlers | ||
|
|
||
| ### Modified `update_proposer_boost_root` | ||
|
|
||
| ```python | ||
| def update_proposer_boost_root(store: Store, root: Root) -> None: | ||
| is_first_block = store.proposer_boost_root == Root() | ||
| # [Modified in Gloas:EIP7732] | ||
| is_timely = store.block_timeliness[root][ATTESTATION_TIMELINESS_INDEX] | ||
|
|
||
| # Add proposer score boost if the block is the first timely block | ||
| # for this slot, with the same the proposer as the canonical chain. | ||
fradamt marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if is_timely and is_first_block: | ||
| head_state = copy(store.block_states[get_head(store).root]) | ||
| slot = get_current_slot(store) | ||
| if head_state.slot < slot: | ||
| process_slots(head_state, slot) | ||
| block = store.blocks[root] | ||
| # Only update if the proposer is the same as on the canonical chain | ||
| if block.proposer_index == get_beacon_proposer_index(head_state): | ||
| store.proposer_boost_root = root | ||
| ``` | ||
|
|
||
| ### New `record_block_timeliness` | ||
|
|
||
| ```python | ||
| def record_block_timeliness(store: Store, root: Root) -> None: | ||
|
||
| # Record timeliness | ||
| 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 | ||
| is_current_slot = get_current_slot(store) == block.slot | ||
| epoch = get_current_store_epoch(store) | ||
| attestation_threshold_ms = get_attestation_due_ms(epoch) | ||
| ptc_threshold_ms = get_payload_attestation_due_ms(epoch) | ||
| store.block_timeliness[root] = [ | ||
| is_current_slot and time_into_slot_ms < threshold | ||
| for threshold in [attestation_threshold_ms, ptc_threshold_ms] | ||
| ] | ||
jtraglia marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ``` | ||
|
|
||
| ### Modified `on_block` | ||
|
|
||
| *Note*: The handler `on_block` is modified to consider the pre `state` of the | ||
|
|
@@ -538,19 +634,9 @@ def on_block(store: Store, signed_block: SignedBeaconBlock) -> None: | |
|
|
||
| # Notify the store about the payload_attestations in the block | ||
| notify_ptc_messages(store, state, block.body.payload_attestations) | ||
| # Add proposer score boost if the block is timely | ||
| seconds_since_genesis = store.time - store.genesis_time | ||
| time_into_slot_ms = seconds_to_milliseconds(seconds_since_genesis) % SLOT_DURATION_MS | ||
| epoch = get_current_store_epoch(store) | ||
| attestation_threshold_ms = get_attestation_due_ms(epoch) | ||
| is_before_attesting_interval = time_into_slot_ms < attestation_threshold_ms | ||
| is_timely = get_current_slot(store) == block.slot and is_before_attesting_interval | ||
| store.block_timeliness[hash_tree_root(block)] = is_timely | ||
|
|
||
| # Add proposer score boost if the block is timely and not conflicting with an existing block | ||
| is_first_block = store.proposer_boost_root == Root() | ||
| if is_timely and is_first_block: | ||
| store.proposer_boost_root = hash_tree_root(block) | ||
| record_block_timeliness(store, block_root) | ||
| update_proposer_boost_root(store, block_root) | ||
|
|
||
| # Update checkpoints in store if necessary | ||
| update_checkpoints(store, state.current_justified_checkpoint, state.finalized_checkpoint) | ||
|
|
@@ -668,3 +754,48 @@ def validate_on_attestation(store: Store, attestation: Attestation, is_from_bloc | |
| # Delay consideration in the fork-choice until their slot is in the past. | ||
| assert get_current_slot(store) >= attestation.data.slot + 1 | ||
| ``` | ||
|
|
||
| ### Modified `is_head_late` | ||
|
|
||
| *Note*: the only change is that `store.block_timeliness[root]` now records | ||
fradamt marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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`. | ||
|
|
||
| ```python | ||
| 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 | ||
fradamt marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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. | ||
|
|
||
| ```python | ||
| 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_weight(store, head_node) | ||
| 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 | ||
| ``` | ||
Uh oh!
There was an error while loading. Please reload this page.