diff --git a/specs/capella/fork-choice.md b/specs/capella/fork-choice.md index 0d82ab7aa0..2e6ebe76d7 100644 --- a/specs/capella/fork-choice.md +++ b/specs/capella/fork-choice.md @@ -9,7 +9,7 @@ - [Helpers](#helpers) - [Modified `PayloadAttributes`](#modified-payloadattributes) - [Handlers](#handlers) - - [`on_block`](#on_block) + - [Modified `on_block`](#modified-on_block) @@ -61,7 +61,7 @@ class PayloadAttributes: ## Handlers -### `on_block` +### Modified `on_block` *Note*: The only modification is the deletion of the verification of merge transition block conditions. diff --git a/specs/deneb/fork-choice.md b/specs/deneb/fork-choice.md index ccc9490757..4e824c24aa 100644 --- a/specs/deneb/fork-choice.md +++ b/specs/deneb/fork-choice.md @@ -5,9 +5,9 @@ - [Introduction](#introduction) - [Helpers](#helpers) - [Modified `PayloadAttributes`](#modified-payloadattributes) - - [`is_data_available`](#is_data_available) + - [New `is_data_available`](#new-is_data_available) - [Handlers](#handlers) - - [`on_block`](#on_block) + - [Modified `on_block`](#modified-on_block) @@ -32,7 +32,7 @@ class PayloadAttributes: parent_beacon_block_root: Root ``` -### `is_data_available` +### New `is_data_available` *[New in Deneb:EIP4844]* @@ -65,7 +65,7 @@ def is_data_available( ## Handlers -### `on_block` +### Modified `on_block` *Note*: The only modification is the addition of the blob data availability check. diff --git a/specs/gloas/fork-choice.md b/specs/gloas/fork-choice.md index 1e2b0d5aac..d8e79d7f16 100644 --- a/specs/gloas/fork-choice.md +++ b/specs/gloas/fork-choice.md @@ -14,10 +14,10 @@ - [Modified `ForkChoiceNode`](#modified-forkchoicenode) - [Modified `PayloadAttributes`](#modified-payloadattributes) - [Modified `LatestMessage`](#modified-latestmessage) - - [Modified `update_latest_messages`](#modified-update_latest_messages) - [Modified `Store`](#modified-store) - [Modified `get_forkchoice_store`](#modified-get_forkchoice_store) - [New `notify_ptc_messages`](#new-notify_ptc_messages) + - [Modified `is_data_available`](#modified-is_data_available) - [New `is_payload_verified`](#new-is_payload_verified) - [New `payload_timeliness`](#new-payload_timeliness) - [New `payload_data_availability`](#new-payload_data_availability) @@ -35,25 +35,28 @@ - [Modified `get_weight`](#modified-get_weight) - [Modified `get_node_children`](#modified-get_node_children) - [Modified `get_head`](#modified-get_head) - - [Modified `record_block_timeliness`](#modified-record_block_timeliness) - - [Modified `get_dependent_root`](#modified-get_dependent_root) - - [Modified `update_proposer_boost_root`](#modified-update_proposer_boost_root) - - [Modified `validate_on_attestation`](#modified-validate_on_attestation) - - [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_latest_message_epoch`](#modified-get_latest_message_epoch) + - [New `verify_execution_payload_envelope_signature`](#new-verify_execution_payload_envelope_signature) + - [New `verify_execution_payload_envelope`](#new-verify_execution_payload_envelope) - [Modified `get_attestation_due_ms`](#modified-get_attestation_due_ms) - [Modified `get_aggregate_due_ms`](#modified-get_aggregate_due_ms) - [Modified `get_sync_message_due_ms`](#modified-get_sync_message_due_ms) - [Modified `get_contribution_due_ms`](#modified-get_contribution_due_ms) - [New `get_payload_due_ms`](#new-get_payload_due_ms) - [New `get_payload_attestation_due_ms`](#new-get_payload_attestation_due_ms) - - [New `verify_execution_payload_envelope_signature`](#new-verify_execution_payload_envelope_signature) - - [New `verify_execution_payload_envelope`](#new-verify_execution_payload_envelope) + - [Proposer head and reorg helpers](#proposer-head-and-reorg-helpers) + - [Modified `is_head_late`](#modified-is_head_late) + - [Modified `is_head_weak`](#modified-is_head_weak) + - [Modified `is_parent_strong`](#modified-is_parent_strong) + - [`on_attestation` helpers](#on_attestation-helpers) + - [Modified `validate_on_attestation`](#modified-validate_on_attestation) + - [Modified `update_latest_messages`](#modified-update_latest_messages) + - [`on_block` helpers](#on_block-helpers) + - [Modified `record_block_timeliness`](#modified-record_block_timeliness) + - [Modified `get_dependent_root`](#modified-get_dependent_root) + - [Modified `update_proposer_boost_root`](#modified-update_proposer_boost_root) - [Handlers](#handlers) - [Modified `on_block`](#modified-on_block) - - [Modified `is_data_available`](#modified-is_data_available) - [New `on_execution_payload_envelope`](#new-on_execution_payload_envelope) - [New `on_payload_attestation_message`](#new-on_payload_attestation_message) @@ -143,34 +146,6 @@ class LatestMessage: payload_present: boolean ``` -### 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`. - -```python -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, - ) -``` - ### Modified `Store` ```python @@ -261,6 +236,26 @@ def notify_ptc_messages( ) ``` +### Modified `is_data_available` + +```python +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` ```python @@ -600,103 +595,127 @@ def get_head(store: Store) -> ForkChoiceNode: ) ``` -### Modified `record_block_timeliness` +### Modified `get_latest_message_epoch` ```python -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] - ] +def get_latest_message_epoch(latest_message: LatestMessage) -> Epoch: + return compute_epoch_at_slot(latest_message.slot) ``` -### Modified `get_dependent_root` +### New `verify_execution_payload_envelope_signature` ```python -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() +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 - # [Modified in Gloas:EIP7732] - node = ForkChoiceNode( - root=root, - payload_status=PAYLOAD_STATUS_PENDING, + signing_root = compute_signing_root( + signed_envelope.message, get_domain(state, DOMAIN_BEACON_BUILDER) ) - dependent_slot = Slot(compute_start_slot_at_epoch(epoch - MIN_SEED_LOOKAHEAD) - 1) - return get_ancestor(store, node, dependent_slot).root + return bls.Verify(pubkey, signing_root, signed_envelope.signature) ``` -### Modified `update_proposer_boost_root` +### New `verify_execution_payload_envelope` ```python -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) +def verify_execution_payload_envelope( + state: BeaconState, + signed_envelope: SignedExecutionPayloadEnvelope, + execution_engine: ExecutionEngine, +) -> None: + envelope = signed_envelope.message + payload = envelope.payload - # 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 + # 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 `validate_on_attestation` +### Modified `get_attestation_due_ms` ```python -def validate_on_attestation(store: Store, attestation: Attestation, is_from_block: bool) -> None: - target = attestation.data.target +def get_attestation_due_ms() -> uint64: + # [Modified in Gloas] + return get_slot_component_duration_ms(ATTESTATION_DUE_BPS_GLOAS) +``` - # 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) +### Modified `get_aggregate_due_ms` - # Check that the epoch number and slot number are matching. - assert target.epoch == compute_epoch_at_slot(attestation.data.slot) +```python +def get_aggregate_due_ms() -> uint64: + # [Modified in Gloas] + return get_slot_component_duration_ms(AGGREGATE_DUE_BPS_GLOAS) +``` - # 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 +### Modified `get_sync_message_due_ms` - # 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 +```python +def get_sync_message_due_ms() -> uint64: + # [Modified in Gloas] + return get_slot_component_duration_ms(SYNC_MESSAGE_DUE_BPS_GLOAS) +``` - # [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) +### Modified `get_contribution_due_ms` - # LMD vote must be consistent with FFG vote target - assert target.root == get_checkpoint_block( - store, attestation.data.beacon_block_root, target.epoch - ) +```python +def get_contribution_due_ms() -> uint64: + # [Modified in Gloas] + return get_slot_component_duration_ms(CONTRIBUTION_DUE_BPS_GLOAS) +``` - # 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 +### New `get_payload_due_ms` + +```python +def get_payload_due_ms() -> uint64: + return get_slot_component_duration_ms(PAYLOAD_DUE_BPS) ``` -### Modified `is_head_late` +### New `get_payload_attestation_due_ms` + +```python +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 @@ -708,7 +727,7 @@ def is_head_late(store: Store, head_root: Root) -> bool: return not store.block_timeliness[head_root][ATTESTATION_TIMELINESS_INDEX] ``` -### Modified `is_head_weak` +#### 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 @@ -741,7 +760,7 @@ def is_head_weak(store: Store, head_root: Root) -> bool: return head_weight < reorg_threshold ``` -### Modified `is_parent_strong` +#### Modified `is_parent_strong` ```python def is_parent_strong(store: Store, root: Root) -> bool: @@ -754,122 +773,132 @@ def is_parent_strong(store: Store, root: Root) -> bool: return parent_weight > parent_threshold ``` -### Modified `get_latest_message_epoch` +### `on_attestation` helpers -```python -def get_latest_message_epoch(latest_message: LatestMessage) -> Epoch: - return compute_epoch_at_slot(latest_message.slot) -``` - -### Modified `get_attestation_due_ms` +#### Modified `validate_on_attestation` ```python -def get_attestation_due_ms() -> uint64: - # [Modified in Gloas] - return get_slot_component_duration_ms(ATTESTATION_DUE_BPS_GLOAS) -``` +def validate_on_attestation(store: Store, attestation: Attestation, is_from_block: bool) -> None: + target = attestation.data.target -### Modified `get_aggregate_due_ms` + # 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) -```python -def get_aggregate_due_ms() -> uint64: - # [Modified in Gloas] - return get_slot_component_duration_ms(AGGREGATE_DUE_BPS_GLOAS) -``` + # Check that the epoch number and slot number are matching. + assert target.epoch == compute_epoch_at_slot(attestation.data.slot) -### Modified `get_sync_message_due_ms` + # 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 -```python -def get_sync_message_due_ms() -> uint64: - # [Modified in Gloas] - return get_slot_component_duration_ms(SYNC_MESSAGE_DUE_BPS_GLOAS) -``` + # 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 -### Modified `get_contribution_due_ms` + # [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) -```python -def get_contribution_due_ms() -> uint64: - # [Modified in Gloas] - return get_slot_component_duration_ms(CONTRIBUTION_DUE_BPS_GLOAS) + # 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 ``` -### New `get_payload_due_ms` +#### 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`. ```python -def get_payload_due_ms() -> uint64: - return get_slot_component_duration_ms(PAYLOAD_DUE_BPS) +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, + ) ``` -### New `get_payload_attestation_due_ms` +### `on_block` helpers + +#### Modified `record_block_timeliness` ```python -def get_payload_attestation_due_ms() -> uint64: - return get_slot_component_duration_ms(PAYLOAD_ATTESTATION_DUE_BPS) +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] + ] ``` -### New `verify_execution_payload_envelope_signature` +#### Modified `get_dependent_root` ```python -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 +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() - signing_root = compute_signing_root( - signed_envelope.message, get_domain(state, DOMAIN_BEACON_BUILDER) + # [Modified in Gloas:EIP7732] + node = ForkChoiceNode( + root=root, + payload_status=PAYLOAD_STATUS_PENDING, ) - return bls.Verify(pubkey, signing_root, signed_envelope.signature) + dependent_slot = Slot(compute_start_slot_at_epoch(epoch - MIN_SEED_LOOKAHEAD) - 1) + return get_ancestor(store, node, dependent_slot).root ``` -### New `verify_execution_payload_envelope` +#### Modified `update_proposer_boost_root` ```python -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 +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) - # 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, - ) - ) + # 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 @@ -940,26 +969,6 @@ def on_block(store: Store, signed_block: SignedBeaconBlock) -> None: compute_pulled_up_tip(store, block_root) ``` -### Modified `is_data_available` - -```python -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 `on_execution_payload_envelope` The handler `on_execution_payload_envelope` is called when the node receives a