diff --git a/specs/_features/epbs/beacon-chain.md b/specs/_features/epbs/beacon-chain.md index 80d63222a4..3dcb2d9544 100644 --- a/specs/_features/epbs/beacon-chain.md +++ b/specs/_features/epbs/beacon-chain.md @@ -44,7 +44,6 @@ - [`get_indexed_payload_attestation`](#get_indexed_payload_attestation) - [Beacon chain state transition function](#beacon-chain-state-transition-function) - [Block processing](#block-processing) - - [Modified `process_withdrawals`](#modified-process_withdrawals) - [New `verify_execution_payload_header_signature`](#new-verify_execution_payload_header_signature) - [New `process_execution_payload_header`](#new-process_execution_payload_header) - [Modified `process_operations`](#modified-process_operations) @@ -203,7 +202,7 @@ class SignedInclusionListSummary(Container): ```python class InclusionList(Container) - summary: SignedInclusionListSummary + signed_summary: SignedInclusionListSummary slot: Slot transactions: List[Transaction, MAX_TRANSACTIONS_PER_INCLUSION_LIST] ``` @@ -265,17 +264,20 @@ class ExecutionPayload(Container): #### `ExecutionPayloadHeader` -**Note:** The `ExecutionPayloadHeader` is modified to only contain the block hash of the committed `ExecutionPayload` in addition to the builder's payment information. +**Note:** The `ExecutionPayloadHeader` is modified to only contain the block hash of the committed `ExecutionPayload` in addition to the builder's payment information and KZG commitments root to verify the inclusion proofs. ```python class ExecutionPayloadHeader(Container): + parent_block_root: Root block_hash: Hash32 builder_index: ValidatorIndex + slot: Slot value: Gwei + blob_kzg_commitments_root: Root ``` #### `BeaconState` -*Note*: the beacon state is modified to store a signed latest execution payload header and to track the last withdrawals honored in the CL. It is also modified to no longer store the full last execution payload header but rather only the last block hash and the last slot that was full, that is in which there were both consensus and execution blocks included. +*Note*: the beacon state is modified to store a signed latest execution payload header. It is also modified to no longer store the full last execution payload header but rather only the last block hash and the last slot that was full, that is in which there were both consensus and execution blocks included. ```python class BeaconState(Container): @@ -326,7 +328,6 @@ class BeaconState(Container): latest_block_hash: Hash32 # [New in ePBS] latest_full_slot: Slot # [New in ePBS] signed_execution_payload_header: SignedExecutionPayloadHeader # [New in ePBS] - last_withdrawals_root: Root # [New in ePBS] ``` ## Helper functions @@ -444,7 +445,7 @@ The post-state corresponding to a pre-state `state` and a signed execution paylo ```python def process_block(state: BeaconState, block: BeaconBlock) -> None: process_block_header(state, block) # - process_withdrawals(state) # [Modified in ePBS] + # removed process_withdrawals(state, block.body.execution_payload) [Removed in ePBS] process_execution_payload_header(state, block) # [Modified in ePBS, removed process_execution_payload] process_randao(state, block.body) process_eth1_data(state, block.body) @@ -452,37 +453,6 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None: process_sync_aggregate(state, block.body.sync_aggregate) ``` -#### Modified `process_withdrawals` -**Note:** This is modified to take only the `state` parameter. The payload is required to honor these withdrawals. - -```python -def process_withdrawals(state: BeaconState) -> None: - ## return early if the parent block was empty - if !is_parent_block_full(state): - return - - withdrawals = get_expected_withdrawals(state) - state.last_withdrawals_root = hash_tree_root(withdrawals) - for withdrawal in withdrawals: - decrease_balance(state, withdrawal.validator_index, withdrawal.amount) - - # Update the next withdrawal index if this block contained withdrawals - if len(withdrawals) != 0: - latest_withdrawal = withdrawals[-1] - state.next_withdrawal_index = WithdrawalIndex(latest_withdrawal.index + 1) - - # Update the next validator index to start the next withdrawal sweep - if len(withdrawals) == MAX_WITHDRAWALS_PER_PAYLOAD: - # Next sweep starts after the latest withdrawal's validator index - next_validator_index = ValidatorIndex((withdrawals[-1].validator_index + 1) % len(state.validators)) - state.next_withdrawal_validator_index = next_validator_index - else: - # Advance sweep by the max length of the sweep if there was not a full set of withdrawals - next_index = state.next_withdrawal_validator_index + MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP - next_validator_index = ValidatorIndex(next_index % len(state.validators)) - state.next_withdrawal_validator_index = next_validator_index -``` - #### New `verify_execution_payload_header_signature` ```python @@ -501,11 +471,18 @@ def process_execution_payload_header(state: BeaconState, block: BeaconBlock) -> # Verify the header signature signed_header = block.body.signed_execution_payload_header assert verify_execution_payload_header_signature(state, signed_header) + # Check that the builder has funds to cover the bid header = signed_header.message builder_index = header.builder_index amount = header.value assert state.balances[builder_index] >= amount + + # Verify that the bid is for the current slot + assert header.slot = block.slot + # Verify that the bid is for the right parent block + assert header.parent_block_root = block.parent_root + # Transfer the funds from the builder to the proposer decrease_balance(state, builder_index, amount) increase_balance(state, block.proposer_index, amount) @@ -650,6 +627,8 @@ def process_execution_payload(state: BeaconState, signed_envelope: SignedExecuti assert verify_execution_envelope_signature(state, signed_envelope) envelope = signed_envelope.message payload = envelope.payload + # Process withdrawals + process_withdrawals(state, payload) # Verify inclusion list proposer proposer_index = envelope.inclusion_list_proposer_index assert proposer_index == state.previous_inclusion_list_proposer @@ -665,7 +644,7 @@ def process_execution_payload(state: BeaconState, signed_envelope: SignedExecuti # Verify consistency with the committed header committed_header = state.signed_execution_payload_header.message assert committed_header.block_hash == payload.block_hash - # Verify consistency with the envelope + assert committed_header.blob_kzg_commitments_root == hash_tree_root(envelope.blob_kzg_commitments) assert envelope.builder_index == committed_header.builder_index # Verify consistency of the parent hash with respect to the previous execution payload assert payload.parent_hash == state.latest_block_hash diff --git a/specs/_features/epbs/p2p-interface.md b/specs/_features/epbs/p2p-interface.md new file mode 100644 index 0000000000..6fd830a164 --- /dev/null +++ b/specs/_features/epbs/p2p-interface.md @@ -0,0 +1,259 @@ +# ePBS -- Networking + +This document contains the consensus-layer networking specification for ePBS. + + + +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [Modification in ePBS](#modification-in-epbs) + - [Preset](#preset) + - [Containers](#containers) + - [`BlobSidecar`](#blobsidecar) + - [Helpers](#helpers) + - [Modified `verify_blob_sidecar_inclusion_proof`](#modified-verify_blob_sidecar_inclusion_proof) + - [The gossip domain: gossipsub](#the-gossip-domain-gossipsub) + - [Topics and messages](#topics-and-messages) + - [Global topics](#global-topics) + - [`beacon_block`](#beacon_block) + - [`execution_payload`](#execution_payload) + - [`payload_attestation_message`](#payload_attestation_message) + - [`execution_payload_header`](#execution_payload_header) + - [`inclusion_list`](#inclusion_list) + - [Transitioning the gossip](#transitioning-the-gossip) + - [The Req/Resp domain](#the-reqresp-domain) + - [Messages](#messages) + - [BeaconBlocksByRange v3](#beaconblocksbyrange-v3) + - [BeaconBlocksByRoot v3](#beaconblocksbyroot-v3) + - [BlobSidecarsByRoot v2](#blobsidecarsbyroot-v2) + - [ExecutionPayloadEnvelopeByRoot v1](#executionpayloadenvelopebyroot-v1) + + + +## Modification in ePBS + +### Preset + +*[Modified in ePBS]* + +| Name | Value | Description | +|------------------------------------------|-----------------------------------|---------------------------------------------------------------------| +| `KZG_COMMITMENT_INCLUSION_PROOF_DEPTH` | TODO: Compute it when the spec stabilizes | Merkle proof depth for the `blob_kzg_commitments` list item | +| `KZG_GENERALIZED_INDEX_PREFIX` | TODO: Compute it when the spec stabilizes | Generalized index for the first item in the `blob_kzg_commitments` list | + +### Containers + +#### `BlobSidecar` + +The `BlobSidecar` container is modified indirectly because the constant `KZG_COMMITMENT_INCLUSION_PROOF_DEPTH` is modified. + +```python +class BlobSidecar(Container): + index: BlobIndex # Index of blob in block + blob: Blob + kzg_commitment: KZGCommitment + kzg_proof: KZGProof # Allows for quick verification of kzg_commitment + signed_block_header: SignedBeaconBlockHeader + kzg_commitment_inclusion_proof: Vector[Bytes32, KZG_COMMITMENT_INCLUSION_PROOF_DEPTH] +``` + +#### Helpers + +##### Modified `verify_blob_sidecar_inclusion_proof` + +`verify_blob_sidecar_inclusion_proof` is modified in ePBS to account for the fact that the kzg commitments are included in the `ExecutionPayloadEnvelope` and no longer in the beacon block body. + +```python +def verify_blob_sidecar_inclusion_proof(blob_sidecar: BlobSidecar) -> bool: + # hardcoded here because the block does not include the commitments but only their root. + gindex = GeneralizedIndex(KZG_GENERALIZED_INDEX_PREFIX + blob_sidecar.index) + + return is_valid_merkle_branch( + leaf=blob_sidecar.kzg_commitment.hash_tree_root(), + branch=blob_sidecar.kzg_commitment_inclusion_proof, + depth=KZG_COMMITMENT_INCLUSION_PROOF_DEPTH, + index=gindex, + root=blob_sidecar.signed_block_header.message.body_root, + ) +``` + +### The gossip domain: gossipsub + +Some gossip meshes are upgraded in the fork of ePBS to support upgraded types. + +#### Topics and messages + +Topics follow the same specification as in prior upgrades. + +The `beacon_block` topic is updated to support the modified type +| Name | Message Type | +| --- | --- | +| `beacon_block` | `SignedBeaconBlock` (modified) | + +The new topics along with the type of the `data` field of a gossipsub message are given in this table: + +| Name | Message Type | +|-------------------------------|------------------------------------------------------| +| `execution_payload_header` | `SignedExecutionPayloadHeader` [New in ePBS] | +| `execution_payload` | `SignedExecutionPayloadEnvelope` [New in ePBS] | +| `payload_attestation_message` | `PayloadAttestationMessage` [New in ePBS] | +| `inclusion_list` | `InclusionList` [New in ePBS] | + +##### Global topics + +ePBS introduces new global topics for execution header, execution payload, payload attestation and inclusion list. + +###### `beacon_block` + +The *type* of the payload of this topic changes to the (modified) `SignedBeaconBlock` found in ePBS spec. + +There are no new validations for this topic. + +###### `execution_payload` + +This topic is used to propagate execution payload messages as `SignedExecutionPayloadEnvelope`. + +The following validations MUST pass before forwarding the `signed_execution_payload_envelope` on the network, assuming the alias `envelope = signed_execution_payload_envelope.message`, `payload = payload_envelope.payload`: + +- _[IGNORE]_ The envelope's block root `envelope.block_root` has been seen (via both gossip and non-gossip sources) (a client MAY queue payload for processing once the block is retrieved). + +Let `block` be the block with `envelope.beacon_block_root`. +Let `header` alias `block.body.signed_execution_payload_header.message` (notice that this can be obtained from the `state.signed_execution_payload_header`) +- _[REJECT]_ `block` passes validation. +- _[REJECT]_ `envelope.builder_index == header.builder_index` +- _[REJECT]_ `payload.block_hash == header.block_hash` +- _[REJECT]_ `hash_tree_root(payload.blob_kzg_commitments) == header.blob_kzg_commitments_root` +- _[REJECT]_ The builder signature, `signed_execution_payload_envelope.signature`, is valid with respect to the builder's public key. + +###### `payload_attestation_message` + +This topic is used to propagate signed payload attestation message. + +The following validations MUST pass before forwarding the `payload_attestation_message` on the network, assuming the alias `data = payload_attestation_message.data`: + +- _[IGNORE]_ `data.slot` is the current slot. +- _[REJECT]_ `data.payload_status < PAYLOAD_INVALID_STATUS` +- _[IGNORE]_ The attestation's `data.beacon_block_root` has been seen (via both gossip and non-gossip sources) (a client MAY queue attestation for processing once the block is retrieved. Note a client might want to request payload after). +- _[REJECT]_ The validator index is within the payload committee in `get_ptc(state, data.slot)`. For the current's slot head state. +- _[REJECT]_ The signature of `payload_attestation_message.signature` is valid with respect to the validator index. + +###### `execution_payload_header` + +This topic is used to propagate signed bids as `SignedExecutionPayloadHeader`. + +The following validations MUST pass before forwarding the `signed_execution_payload_header` on the network, assuming the alias `header = signed_execution_payload_header.message`: + +- _[IGNORE]_ this is the first signed bid seen with a valid signature from the given builder for this slot. +- _[REJECT]_ The signed builder bid, `header.builder_index` is a valid and non-slashed builder index in state. +- _[IGNORE]_ The signed builder bid value, `header.value`, is less or equal than the builder's balance in state. i.e. `MIN_BUILDER_BALANCE + header.value < state.builder_balances[header.builder_index]`. +- _[IGNORE]_ `header.parent_block_root` is a known block root in fork choice. +- _[IGNORE]_ `header.slot` is the current slot or the next slot. +- _[REJECT]_ The builder signature, `signed_execution_payload_header_envelope.signature`, is valid with respect to the `header_envelope.builder_index`. + +###### `inclusion_list` + +This topic is used to propagate inclusion lists as `InclusionList` objects. + +The following validations MUST pass before forwarding the `inclusion_list` on the network, assuming the alias `signed_summary = inclusion_list.signed_summary`, `summary = signed_summary.message`: + +- _[IGNORE]_ The inclusion list is for the current slot or the next slot (a client MAY queue future inclusion lists for processing at the appropriate slot). +- _[IGNORE]_ The inclusion list is the first inclusion list with valid signature received for the proposer for the slot, `inclusion_list.slot`. +- _[REJECT]_ The inclusion list transactions `inclusion_list.transactions` length is less or equal than `MAX_TRANSACTIONS_PER_INCLUSION_LIST`. +- _[REJECT]_ The inclusion list summary has the same length of transactions `len(summary.summary) == len(inclusion_list.transactions)`. +- _[REJECT]_ The summary signature, `signed_summary.signature`, is valid with respect to the `proposer_index` pubkey. +- _[REJECT]_ The summary is proposed by the expected proposer_index for the summary's slot in the context of the current shuffling (defined by parent_root/slot). If the proposer_index cannot immediately be verified against the expected shuffling, the inclusion list MAY be queued for later processing while proposers for the summary's branch are calculated -- in such a case do not REJECT, instead IGNORE this message. + +#### Transitioning the gossip + +See gossip transition details found in the [Altair document](../altair/p2p-interface.md#transitioning-the-gossip) for +details on how to handle transitioning gossip topics for this upgrade. + +### The Req/Resp domain + +#### Messages + +##### BeaconBlocksByRange v3 + +**Protocol ID:** `/eth2/beacon_chain/req/beacon_blocks_by_range/3/` + +[0]: # (eth2spec: skip) + +| `fork_version` | Chunk SSZ type | +|--------------------------|-------------------------------| +| `GENESIS_FORK_VERSION` | `phase0.SignedBeaconBlock` | +| `ALTAIR_FORK_VERSION` | `altair.SignedBeaconBlock` | +| `BELLATRIX_FORK_VERSION` | `bellatrix.SignedBeaconBlock` | +| `CAPELLA_FORK_VERSION` | `capella.SignedBeaconBlock` | +| `DENEB_FORK_VERSION` | `deneb.SignedBeaconBlock` | +| `EPBS_FORK_VERSION` | `epbs.SignedBeaconBlock` | + +##### BeaconBlocksByRoot v3 + +**Protocol ID:** `/eth2/beacon_chain/req/beacon_blocks_by_root/3/` + +Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: + +[1]: # (eth2spec: skip) + +| `fork_version` | Chunk SSZ type | +|--------------------------|-------------------------------| +| `GENESIS_FORK_VERSION` | `phase0.SignedBeaconBlock` | +| `ALTAIR_FORK_VERSION` | `altair.SignedBeaconBlock` | +| `BELLATRIX_FORK_VERSION` | `bellatrix.SignedBeaconBlock` | +| `CAPELLA_FORK_VERSION` | `capella.SignedBeaconBlock` | +| `DENEB_FORK_VERSION` | `deneb.SignedBeaconBlock` | +| `EPBS_FORK_VERSION` | `epbs.SignedBeaconBlock` | + + +##### BlobSidecarsByRoot v2 + +**Protocol ID:** `/eth2/beacon_chain/req/blob_sidecars_by_root/2/` + +[1]: # (eth2spec: skip) + +| `fork_version` | Chunk SSZ type | +|--------------------------|-------------------------------| +| `DENEB_FORK_VERSION` | `deneb.BlobSidecar` | +| `EPBS_FORK_VERSION` | `epbs.BlobSidecar` | + + +##### ExecutionPayloadEnvelopeByRoot v1 + +**Protocol ID:** `/eth2/beacon_chain/req/execution_payload_envelope_by_root/1/` + +The `` field is calculated as `context = compute_fork_digest(fork_version, genesis_validators_root)`: + +[1]: # (eth2spec: skip) + +| `fork_version` | Chunk SSZ type | +|---------------------|---------------------------------------| +| `EPBS_FORK_VERSION` | `epbs.SignedExecutionPayloadEnvelope` | + +Request Content: + +``` +( + List[Root, MAX_REQUEST_PAYLOAD] +) +``` + +Response Content: + +``` +( + List[SignedExecutionPayloadEnvelope, MAX_REQUEST_PAYLOAD] +) +``` +Requests execution payload envelope by `signed_execution_payload_envelope.message.block_root`. The response is a list of SignedExecutionPayloadEnvelope whose length is less than or equal to the number of requested execution payload envelopes. It may be less in the case that the responding peer is missing payload envelopes. + +No more than MAX_REQUEST_PAYLOAD may be requested at a time. + +ExecutionPayloadEnvelopeByRoot is primarily used to recover recent execution payload envelope (e.g. when receiving a payload attestation with revealed status as true but never received a payload). + +The request MUST be encoded as an SSZ-field. + +The response MUST consist of zero or more response_chunk. Each successful response_chunk MUST contain a single SignedExecutionPayloadEnvelope payload. + +Clients MUST support requesting payload envelopes since the latest finalized epoch. + +Clients MUST respond with at least one payload envelope, if they have it. Clients MAY limit the number of payload envelopes in the response. diff --git a/specs/_features/epbs/validator.md b/specs/_features/epbs/validator.md new file mode 100644 index 0000000000..b345b808fc --- /dev/null +++ b/specs/_features/epbs/validator.md @@ -0,0 +1,272 @@ + + +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [ePBS -- Honest Validator](#epbs----honest-validator) + - [Introduction](#introduction) + - [Prerequisites](#prerequisites) + - [Protocols](#protocols) + - [`ExecutionEngine`](#executionengine) + - [`get_execution_inclusion_list`](#get_execution_inclusion_list) + - [Beacon chain responsibilities](#beacon-chain-responsibilities) + - [Validator assignment](#validator-assignment) + - [Lookahead](#lookahead) + - [Inclusion list proposal](#inclusion-list-proposal) + - [Constructing the inclusion list](#constructing-the-inclusion-list) + - [Broadcast inclusion list](#broadcast-inclusion-list) + - [Block proposal](#block-proposal) + - [Constructing the new `signed_execution_payload_header_envelope` field in `BeaconBlockBody`](#constructing-the-new-signed_execution_payload_header_envelope-field-in--beaconblockbody) + - [Constructing the new `payload_attestations` field in `BeaconBlockBody`](#constructing-the-new-payload_attestations-field-in--beaconblockbody) + - [Aggregation selection](#aggregation-selection) + - [Payload timeliness attestation](#payload-timeliness-attestation) + - [Constructing payload attestation](#constructing-payload-attestation) + - [Prepare payload attestation message](#prepare-payload-attestation-message) + - [Broadcast payload attestation](#broadcast-payload-attestation) + - [Attesting](#attesting) + - [Attestation aggregation](#attestation-aggregation) + - [Design Rationale](#design-rationale) + - [What is the honest behavior to build on top of a skip slot for inclusion list?](#what-is-the-honest-behavior-to-build-on-top-of-a-skip-slot-for-inclusion-list) + - [Why skip the attestation if you are assigned to PTC?](#why-skip-the-attestation-if-you-are-assigned-to-ptc) + + + +# ePBS -- Honest Validator + +**Notice**: This document is a work-in-progress for researchers and implementers. + +## Introduction + +This document represents the changes to be made in the code of an "honest validator" to implement ePBS. + +## Prerequisites + +This document is an extension of the Deneb -- Honest Validator guide. +All behaviors and definitions defined in this document, and documents it extends, carry over unless explicitly noted or overridden. + +All terminology, constants, functions, and protocol mechanics defined in the updated Beacon Chain doc of ePBS. are requisite for this document and used throughout. +Please see related Beacon Chain doc before continuing and use them as a reference throughout. + +## Protocols + +### `ExecutionEngine` + +*Note*: `get_execution_inclusion_list` function is added to the `ExecutionEngine` protocol for use as a validator. + +The body of this function is implementation dependent. +The Engine API may be used to implement it with an external execution engine. + +#### `get_execution_inclusion_list` + +Given the `parent_block_hash`, `get_execution_inclusion_list` returns `GetInclusionListResponse` with the most recent version of the inclusion list based on the parent block hash. + +```python +class GetInclusionListResponse(container) + inclusion_list_summary: InclusionListSummary + transactions: List[Transaction, MAX_TRANSACTIONS_PER_INCLUSION_LIST] +``` + +```python +def get_execution_inclusion_list(self: ExecutionEngine, parent_block_hash: Root) -> GetInclusionListResponse: + """ + Return ``GetInclusionListResponse`` object. + """ + ... +``` + +## Beacon chain responsibilities + +All validator responsibilities remain unchanged other than those noted below. Namely, proposer normal block production switched to a new `BeaconBlockBody`. +Proposer with additional duty to construct and broadcast `InclusionList` alongside `SignedBeaconBlock`. +Attester with an additional duty to be part of PTC committee and broadcast `PayloadAttestationMessage`. + +## Validator assignment + +A validator can get PTC committee assignments for a given slot using the following helper via `get_ptc_assignment(state, epoch, validator_index)` where `epoch <= next_epoch`. + +A validator can use the following function to see if they are supposed to submit payload attestation message during a slot across an epoch. +PTC committee selection is only stable within the context of the current and next epoch. + +```python +def get_ptc_assignment(state: BeaconState, + epoch: Epoch, + validator_index: ValidatorIndex + ) -> Optional[Tuple[Sequence[ValidatorIndex], Slot]]: + """ + Return the ptc committee assignment in the ``slot`` for ``validator_index``. + ``assignment`` returned is a tuple of the following form: + * ``assignment[0]`` is the list of validators in the ptc + * ``assignment[1]`` is the slot at which the ptc is assigned + Return None if no assignment. + """ + next_epoch = Epoch(get_current_epoch(state) + 1) + assert epoch <= next_epoch + + start_slot = compute_start_slot_at_epoch(epoch) + committee_count_per_slot = get_committee_count_per_slot(state, epoch) + for slot in range(start_slot, start_slot + SLOTS_PER_EPOCH): + for index in range(committee_count_per_slot): + committee = get_ptc(state, Slot(slot)) + if validator_index in committee: + return committee, Slot(slot) + return None +``` + +### Lookahead + +The beacon chain shufflings are designed to provide a minimum of 1 epoch lookahead +on the validator's upcoming ptc assignments for attesting dictated by the shuffling and slot. + +[New in ePBS] + +`get_ptc_assignment` should be called at the start of each epoch to get the assignment for the next epoch (`current_epoch + 1`). +A validator should plan for future assignments by noting their assigned ptc committee +slot and planning to participate in the ptc committee subnet. + +[Modified in MaxEB] + +a validator should: +* Find peers of the pubsub topic `beacon_attestation_{subnet_id}`. + * If the validator is assigned to be an aggregator for the slot (see `is_aggregator()`)[Modified in MaxEB], then subscribe to the topic. + +### Inclusion list proposal + +ePBS introduces forward inclusion list. The detail design is described in this [post](https://ethresear.ch/t/no-free-lunch-a-new-inclusion-list-design/16389) +Proposer must construct and broadcast `InclusionList` alongside `SignedBeaconBlock`. +- Proposer for slot `N` submits `SignedBeaconBlock` and in parallel submits `InclusionList` to be included at the beginning of slot `N+1` builder. +- Within `InclusionList`, `Transactions` are list of transactions that the proposer wants to include at the beginning of slot `N+1` builder. +- Within `inclusionList`, `Summaries` are lists consisting on addresses sending those transactions and their gas limits. The summaries are signed by the proposer `N`. +- Proposer may send many of these pairs that aren't committed to its beacon block so no double proposing slashing is involved. + +#### Constructing the inclusion list + +To obtain an inclusion list, a block proposer building a block on top of a `state` must take the following actions: + +1. Check if the previous slot is skipped. If `state.latest_execution_payload_header.time_stamp` is from previous slot. + * If it's skipped, the proposer should not propose an inclusion list. It can ignore rest of the steps. + +2. Retrieve inclusion list from execution layer by calling `get_execution_inclusion_list`. + +3. Call `build_inclusion_list` to build `InclusionList`. + +```python +def build_inclusion_list(state: BeaconState, inclusion_list_response: GetInclusionListResponse, block_slot: Slot, privkey: int) -> InclusionList: + inclusion_list_summary = inclusion_list_response.inclusion_list_summary + signature = get_inclusion_list_summary_signature(state, inclusion_list_summary, block_slot, privkey) + signed_inclusion_list_summary = SignedInclusionListSummary(summary=inclusion_list_summary, signature=signature) + return InclusionList(summaries=signed_inclusion_list_summary, transactions=inclusion_list_response.transactions) +``` + +In order to get inclusion list summary signature, the proposer will call `get_inclusion_list_summary_signature`. + +```python +def get_inclusion_list_summary_signature(state: BeaconState, inclusion_list_summary: InclusionListSummary, block_slot: Slot, privkey: int) -> BLSSignature: + domain = get_domain(state, DOMAIN_BEACON_PROPOSER, compute_epoch_at_slot(block_slot)) + signing_root = compute_signing_root(inclusion_list_summary, domain) + return bls.Sign(privkey, signing_root) +``` + +#### Broadcast inclusion list + +Finally, the proposer broadcasts `inclusion_list` to the inclusion list subnet, the `inclusion_list` pubsub topic. + +### Block proposal + +#### Constructing the new `signed_execution_payload_header_envelope` field in `BeaconBlockBody` + +To obtain `signed_execution_payload_header_envelope`, a block proposer building a block on top of a `state` must take the following actions: +* Listen to the `execution_payload_header_envelope` gossip subnet and save accepted `signed_execution_payload_header_envelope` from the builders. +* Filter out the header envelops where `signed_execution_payload_header_envelope.message.header.parent_hash` matches `state.latest_execution_payload_header.block_hash` +* The `signed_execution_payload_header_envelope` must satisfy the verification conditions found in `process_execution_payload_header` using `state` advance to the latest slot. +* Select the one bid and set `body.signed_execution_payload_header_envelope = signed_execution_payload_header_envelope` + +#### Constructing the new `payload_attestations` field in `BeaconBlockBody` + +Up to `MAX_PAYLOAD_ATTESTATIONS`, aggregate payload attestations can be included in the block. +The payload attestations added must satisfy the verification conditions found in payload attestation processing. It must pass `process_payload_attestation`. +`payload_attestations` can only be included in the next slot, so there's only a maximum of two possible aggregates that are valid. + +Some validators are selected to locally aggregate attestations with a similar `attestation_data` to their constructed `attestation` for the assigned `slot`. + +#### Aggregation selection + +A validator is selected to aggregate based upon the return value of `is_aggregator()`. [Modified in ePBS]. Taken from [PR](https://github.com/michaelneuder/consensus-specs/pull/3) + +```python +def is_aggregator(state: BeaconState, slot: Slot, index: CommitteeIndex, validator_index: ValidatorIndex, slot_signature: BLSSignature) -> bool: + validator = state.validators[validator_index] + committee = get_beacon_committee(state, slot, index) + min_balance_increments = validator.effective_balance // MIN_ACTIVATION_BALANCE + committee_balance_increments = get_total_balance(state, set(committee)) // MIN_ACTIVATION_BALANCE + denominator = committee_balance_increments ** min_balance_increments + numerator = denominator - (committee_balance_increments - TARGET_AGGREGATORS_PER_COMMITTEE) ** min_balance_increments + modulo = denominator // numerator + return bytes_to_uint64(hash(slot_signature)[0:8]) % modulo == 0 +``` + +Rest of the aggregation process remains unchanged. + +### Payload timeliness attestation + +Some validators are selected to submit payload timeliness attestation. The assigned `slot` for which the validator performs this role during an epoch are defined by `get_ptc(state, slot)`. + +A validator should create and broadcast the `payload_attestation_message` to the global execution attestation subnet at `SECONDS_PER_SLOT * 3 / INTERVALS_PER_SLOT` seconds after the start of `slot` + +#### Constructing payload attestation + +##### Prepare payload attestation message +If a validator is in the payload attestation committee (i.e. `is_assigned_to_payload_committee()` below returns True), +then the validator should prepare a `PayloadAttestationMessage` for the current slot, +according to the logic in `get_payload_attestation_message` at `SECONDS_PER_SLOT * 3 / INTERVALS_PER_SLOT` interval, and broadcast it to the global `payload_attestation_message` pubsub topic. + +```python +def is_assigned_to_payload_committee(state: BeaconState, + slot: Slot, + validator_index: ValidatorIndex) -> bool: + committe = get_ptc(state, slot) + return validator_index in committee +``` + +Next, the validator creates `payload_attestation_message` as follows: +* Set `payload_attestation_data.slot = slot` where `slot` is the assigned slot. +* Set `payload_attestation_data.beacon_block_root = block_root` where `block_root` is the head of the chain. +* Set `payload_attestation_data.payload_revealed = True` if the `SignedExecutionPayloadEnvelope` is seen from the block builder reveal at `SECONDS_PER_SLOT * 2 / INTERVALS_PER_SLOT`, and if `ExecutionPayloadEnvelope.beacon_block_root` matches `block_root` + * Otherwise, set `payload_attestation_data.payload_revealed = False`. +* Set `payload_attestation_message.validator_index = validator_index` where `validator_index` is the validator chosen to submit. The private key mapping to `state.validators[validator_index].pubkey` is used to sign the payload timeliness attestation. +* Set `payload_attestation_message = PayloadAttestationMessage(data=payload_attestation_data, signature=payload_attestation_signature)`, where `payload_attestation_signature` is obtained from: + +```python +def get_payload_attestation_message_signature(state: BeaconState, attestation: PayloadAttestationMessage, privkey: int) -> BLSSignature: + domain = get_domain(state, DOMAIN_PTC_ATTESTER, compute_epoch_at_slot(attestation.slot)) + signing_root = compute_signing_root(attestation, domain) + return bls.Sign(privkey, signing_root) +``` + +#### Broadcast payload attestation + +Finally, the validator broadcasts `payload_attestation_message` to the global `payload_attestation_message` pubsub topic. + +### Attesting + +If you are assigned to PTC `is_assigned_to_payload_committee(state, slot, validtor_index)==true`, then you can skip attesting at `slot`. +The attestation will not be gaining any rewards and will be dropped on the gossip network. + +### Attestation aggregation + +Even if you skip attesting because of PTC, you should still aggregate attestations for the assigned slot. if `is_aggregator==true`. This is the honest behavior. + +## Design Rationale + +### What is the honest behavior to build on top of a skip slot for inclusion list? +The proposer shouldn't propose an inclusion list on top of a skip slot. +If the payload for block N isn't revealed, the summaries and transactions for slot N-1 remain valid. +The slot N+1 proposer can't submit a new IL, and any attempt will be ignored. +The builder for N+1 must adhere to the N-1 summary. +If k consecutive slots lack payloads, the next full slot must still follow the N-1 inclusion list. + +### Why skip the attestation if you are assigned to PTC? + +PTC validators are selected as the first index from each beacon committee, excluding builders. +These validators receive a full beacon attestation reward when they correctly identify the payload reveal status. +Specifically, if they vote for "full" and the payload is included, or vote for "empty" and the payload is excluded. +Attestations directed at the CL block from these validators are disregarded, eliminating the need for broadcasting. +This does not apply if you are an aggregator. \ No newline at end of file