Skip to content

Commit

Permalink
init p2p
Browse files Browse the repository at this point in the history
  • Loading branch information
potuz committed Apr 3, 2024
1 parent 58c2fad commit 46432e7
Show file tree
Hide file tree
Showing 3 changed files with 548 additions and 38 deletions.
55 changes: 17 additions & 38 deletions specs/_features/epbs/beacon-chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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]
```
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -444,45 +445,14 @@ 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)
process_operations(state, block.body) # [Modified in ePBS]
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
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
259 changes: 259 additions & 0 deletions specs/_features/epbs/p2p-interface.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
# ePBS -- Networking

This document contains the consensus-layer networking specification for ePBS.

<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**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)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

## 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 `<context-bytes>` 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.
Loading

0 comments on commit 46432e7

Please sign in to comment.