diff --git a/specs/gloas/fork-choice.md b/specs/gloas/fork-choice.md index aeb091f9277..f526a5551ea 100644 --- a/specs/gloas/fork-choice.md +++ b/specs/gloas/fork-choice.md @@ -19,13 +19,14 @@ - [Modified `get_forkchoice_store`](#modified-get_forkchoice_store) - [New `notify_ptc_messages`](#new-notify_ptc_messages) - [New `is_payload_verified`](#new-is_payload_verified) - - [New `is_payload_timely`](#new-is_payload_timely) - - [New `is_payload_data_available`](#new-is_payload_data_available) + - [New `payload_timeliness`](#new-payload_timeliness) + - [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_ancestor`](#modified-get_ancestor) - [Modified `get_checkpoint_block`](#modified-get_checkpoint_block) - [New `is_supporting_vote`](#new-is_supporting_vote) + - [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) @@ -262,13 +263,14 @@ def is_payload_verified(store: Store, root: Root) -> bool: return root in store.payloads ``` -### New `is_payload_timely` +### New `payload_timeliness` ```python -def is_payload_timely(store: Store, root: Root) -> bool: +def payload_timeliness(store: Store, root: Root, timely: bool) -> bool: """ Return whether the execution payload for the beacon block with root ``root`` - was voted as present by the PTC, and was locally determined to be available. + 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 @@ -276,19 +278,20 @@ def is_payload_timely(store: Store, root: Root) -> bool: # 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 False + return not timely votes = store.payload_timeliness_vote[root] - return sum(vote is True for vote in votes) > PAYLOAD_TIMELY_THRESHOLD + return sum(vote is timely for vote in votes) > PAYLOAD_TIMELY_THRESHOLD ``` -### New `is_payload_data_available` +### New `payload_data_availability` ```python -def is_payload_data_available(store: Store, root: Root) -> bool: +def payload_data_availability(store: Store, root: Root, available: bool) -> bool: """ - Return whether the blob data for the beacon block with root ``root`` - was voted as present by the PTC, and was locally determined to be available. + 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 @@ -296,10 +299,10 @@ def is_payload_data_available(store: Store, root: Root) -> bool: # 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 False + return not available votes = store.payload_data_availability_vote[root] - return sum(vote is True for vote in votes) > DATA_AVAILABILITY_TIMELY_THRESHOLD + return sum(vote is available for vote in votes) > DATA_AVAILABILITY_TIMELY_THRESHOLD ``` ### New `get_parent_payload_status` @@ -386,6 +389,21 @@ def is_supporting_vote(store: Store, node: ForkChoiceNode, message: LatestMessag ) ``` +### New `should_build_on_full` + +*Note*: `should_build_on_full` is called by the proposer before deciding whether +to build on top of the empty or full parent pending node. This function is +similar to `should_extend_payload` but takes into consideration the PTC view on +data availability. + +```python +def should_build_on_full(store: Store, head: ForkChoiceNode) -> bool: + assert head.payload_status != PAYLOAD_STATUS_PENDING + if head.payload_status == PAYLOAD_STATUS_EMPTY: + return False + return not payload_data_availability(store, head.root, available=False) +``` + ### New `should_extend_payload` *Note*: `should_extend_payload` decides whether to extend an available payload @@ -400,8 +418,10 @@ def should_extend_payload(store: Store, root: Root) -> bool: 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 ( - (is_payload_timely(store, root) and is_payload_data_available(store, root)) + (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]) diff --git a/specs/gloas/validator.md b/specs/gloas/validator.md index 49b690c2800..c5f7b574304 100644 --- a/specs/gloas/validator.md +++ b/specs/gloas/validator.md @@ -231,29 +231,34 @@ construct the `payload_attestations` field in `BeaconBlockBody`: ##### Parent execution requests The `parent_execution_requests` field contains the execution requests from the -parent's execution payload. The proposer constructs this field as follows: +parent's execution payload. Let `head = get_head(store)`. The proposer +constructs this field as follows: - If the parent block is pre-Gloas (first Gloas block), set `parent_execution_requests` to an empty `ExecutionRequests()`. -- If `should_extend_payload(store, block.parent_root)` is true (the proposer is +- If `should_build_on_full(store, head)` returns `True` (the proposer is building on the parent's full payload), set `parent_execution_requests` to - `store.payloads[block.parent_root].execution_requests`. + `store.payloads[head.root].execution_requests`. - Otherwise (the proposer is building on the parent's empty variant), set `parent_execution_requests` to an empty `ExecutionRequests()`. ##### ExecutionPayload -*Note*: `prepare_execution_payload` is modified in Gloas to take `store` as an -additional parameter. It consults `should_extend_payload` to decide whether to -build on the parent's full payload or its empty variant, selecting both the -withdrawals source and the execution head for the new payload. When building on -a full parent, `apply_parent_execution_payload` is called so that withdrawals -are computed against the post-processing state. +*Note*: `prepare_execution_payload` is modified in Gloas to take `store` and +`head` as additional parameters. `head` is the return value of `get_head(store)` +and must correspond to the parent that `state` was derived from. It consults +`should_build_on_full(store, head)` to decide whether to build on the parent's +full payload or its empty variant, selecting both the withdrawals source and the +execution head for the new payload. When building on a full parent, +`apply_parent_execution_payload` is called so that withdrawals are computed +against the post-processing state. ```python def prepare_execution_payload( # [New in Gloas:EIP7732] store: Store, + # [New in Gloas:EIP7732] + head: ForkChoiceNode, state: BeaconState, safe_block_hash: Hash32, finalized_block_hash: Hash32, @@ -262,9 +267,8 @@ def prepare_execution_payload( ) -> Optional[PayloadId]: # [New in Gloas:EIP7732] parent_bid = state.latest_execution_payload_bid - parent_root = hash_tree_root(state.latest_block_header) - if should_extend_payload(store, parent_root): - envelope = store.payloads[parent_root] + if should_build_on_full(store, head): + envelope = store.payloads[head.root] # Make a copy of the state to avoid mutability issues state = copy(state) # Apply parent payload before computing withdrawals diff --git a/specs/heze/fork-choice.md b/specs/heze/fork-choice.md index 91fe42c11a6..1277f17703b 100644 --- a/specs/heze/fork-choice.md +++ b/specs/heze/fork-choice.md @@ -225,8 +225,10 @@ def should_extend_payload(store: Store, root: Root) -> bool: if not is_payload_inclusion_list_satisfied(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 ( - (is_payload_timely(store, root) and is_payload_data_available(store, root)) + (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]) diff --git a/specs/heze/validator.md b/specs/heze/validator.md index 49f62cb2768..f7999b0e928 100644 --- a/specs/heze/validator.md +++ b/specs/heze/validator.md @@ -137,6 +137,7 @@ inclusion lists they have observed. ```python def prepare_execution_payload( store: Store, + head: ForkChoiceNode, state: BeaconState, safe_block_hash: Hash32, finalized_block_hash: Hash32, @@ -144,9 +145,8 @@ def prepare_execution_payload( execution_engine: ExecutionEngine, ) -> Optional[PayloadId]: parent_bid = state.latest_execution_payload_bid - parent_root = hash_tree_root(state.latest_block_header) - if should_extend_payload(store, parent_root): - envelope = store.payloads[parent_root] + if should_build_on_full(store, head): + envelope = store.payloads[head.root] # Make a copy of the state to avoid mutability issues state = copy(state) # Apply parent payload before computing withdrawals diff --git a/tests/core/pyspec/eth_consensus_specs/test/gloas/unittests/validator/test_prepare_execution_payload.py b/tests/core/pyspec/eth_consensus_specs/test/gloas/unittests/validator/test_prepare_execution_payload.py index b40dc2285c9..3331df6e93f 100644 --- a/tests/core/pyspec/eth_consensus_specs/test/gloas/unittests/validator/test_prepare_execution_payload.py +++ b/tests/core/pyspec/eth_consensus_specs/test/gloas/unittests/validator/test_prepare_execution_payload.py @@ -144,6 +144,7 @@ def test_prepare_execution_payload__extend_payload(spec, state): parent_bid = proposal_state.latest_execution_payload_bid payload_id = spec.prepare_execution_payload( store=store, + head=spec.get_head(store), state=proposal_state, safe_block_hash=spec.Hash32(), finalized_block_hash=spec.Hash32(), @@ -181,6 +182,7 @@ def test_prepare_execution_payload__no_payload_verified(spec, state): parent_bid = proposal_state.latest_execution_payload_bid payload_id = spec.prepare_execution_payload( store=store, + head=spec.get_head(store), state=proposal_state, safe_block_hash=spec.Hash32(), finalized_block_hash=spec.Hash32(), @@ -204,6 +206,7 @@ def test_prepare_execution_payload__extend_payload_does_not_mutate_state(spec, s engine = CaptureEngine() spec.prepare_execution_payload( store=store, + head=spec.get_head(store), state=proposal_state, safe_block_hash=spec.Hash32(), finalized_block_hash=spec.Hash32(), @@ -223,6 +226,7 @@ def test_prepare_execution_payload__payload_attributes(spec, state): engine = CaptureEngine() spec.prepare_execution_payload( store=store, + head=spec.get_head(store), state=proposal_state, safe_block_hash=spec.Hash32(), finalized_block_hash=spec.Hash32(), @@ -253,6 +257,7 @@ def test_prepare_execution_payload__block_passes_state_transition(spec, state): engine = CaptureEngine() spec.prepare_execution_payload( store=store, + head=spec.get_head(store), state=proposal_state, safe_block_hash=spec.Hash32(), finalized_block_hash=spec.Hash32(),