-
Notifications
You must be signed in to change notification settings - Fork 3
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
[WIP] Minimal ePBS without Max EB and 7002 #2
base: dev
Are you sure you want to change the base?
Changes from all commits
5ef39b8
1a49a51
0e2267d
844381d
ee37d4c
13d75bc
f4b566b
08b881d
d339dfc
ff1eff7
ef7bd40
9dbd2c5
c7331a1
8d6ce8b
759cdd6
90b42e6
a604462
d6ca928
70a6e6d
2c4fc84
43a8dd6
52067d5
a3c2468
800a910
0228ea0
1ae9cd9
2ed9678
4389af4
23c46d7
4fe85f8
4eaddd9
d9e0278
4060a9b
f3006ca
5004908
dc7b24c
169e02b
a7e3ba1
837897e
a29a34c
faacffe
aa88419
66f8ee1
b06e017
c2eacf9
1c6304b
c43400a
0c645ad
bcc1d5f
fad1f49
458bb8f
296a4e8
9c5e60b
545d657
bffcc04
99b2109
5db1f29
f8db1ea
37e892b
945d8bb
ba51dcc
6e03e58
d89e527
7c5b458
7198b11
7adb9ea
4160deb
77cd6aa
29a1c4d
2bde439
5759e34
de20380
c4aa0cd
99132a4
c9e1834
f785453
075837e
0659a91
4d799b2
7b6fa0f
3411f45
b33c222
680bcdb
25dde5e
9fa6e14
960e92d
018fa39
ac93d02
727a043
5f97041
84280a7
b68d884
8674517
b2c6482
17b9e90
c63b81e
5ff8d54
f35cc68
18c31e4
ba0283c
9edc282
f3a035a
6c60686
502e98d
05fbb9a
1e9406d
8883b29
ae35416
eedf7fb
5ebda89
2decd43
d0060c0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,116 @@ | ||||||
# ePBS -- Honest Builder | ||||||
|
||||||
This is an accompanying document which describes the expected actions of a "builder" participating in the Ethereum proof-of-stake protocol. | ||||||
|
||||||
<!-- START doctoc generated TOC please keep comment here to allow auto update --> | ||||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> | ||||||
|
||||||
- [Introduction](#introduction) | ||||||
- [Builders attributions](#builders-attributions) | ||||||
- [Constructing the payload bid](#constructing-the-payload-bid) | ||||||
- [Constructing the `BlobSidecar`s](#constructing-the-blobsidecars) | ||||||
- [Constructing the execution payload envelope](#constructing-the-execution-payload-envelope) | ||||||
- [Honest payload withheld messages](#honest-payload-withheld-messages) | ||||||
|
||||||
<!-- END doctoc generated TOC please keep comment here to allow auto update --> | ||||||
|
||||||
## Introduction | ||||||
|
||||||
With the ePBS Fork, the protocol includes new staked participants of the protocol called *Builders*. While Builders are a subset of the validator set, they have extra attributions that are optional. Validators may opt to not be builders and as such we collect the set of guidelines for those validators that want to act as builders in this document. | ||||||
|
||||||
## Builders attributions | ||||||
|
||||||
Builders can submit bids to produce execution payloads. They can broadcast these bids in the form of `SignedExecutionPayloadHeader` objects, these objects encode a commitment to reveal an execution payload in exchange for a payment. When their bids are chosen by the corresponding proposer, builders are expected to broadcast an accompanying `SignedExecutionPayloadEnvelope` object honoring the commitment. | ||||||
|
||||||
Thus, builders tasks are divided in two, submitting bids, and submitting payloads. | ||||||
|
||||||
### Constructing the payload bid | ||||||
|
||||||
Builders can broadcast a payload bid for the current or the next slot's proposer to include. They produce a `SignedExecutionPayloadHeader` as follows. | ||||||
|
||||||
Prior to constructing a payload, the builder **MUST** have a full `InclusionList` object from the proposer matching `state.previous_inclusion_list_proposer`. The signed summary for this inclusion list will be needed when revealing the full execution payload (see below). | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. may be totally remove inclusion list for now? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it will be removed in the EIP, I'll open a new PR in this repo based on Electra soon |
||||||
1. Set `header.parent_block_hash` to the current head of the execution chain (this can be obtained from the beacon state as `state.last_block_hash`). | ||||||
2. Set `header.parent_block_root` to be the head of the consensus chain (this can be obtained from the beacon state as `hash_tree_root(state.latest_block_header)`. | ||||||
3. Construct an execution payload. This can be performed with an external execution engine with a call to `engine_getPayloadV4`. | ||||||
4. Set `header.block_hash` to be the block hash of the constructed payload, that is `payload.block_hash` | ||||||
5. Set `header.builder_index` to be the validator index of the builder performing these actions. | ||||||
6. Set `header.slot` to be the slot for which this bid is aimed. This slot **MUST** be either the current slot or the next slot. | ||||||
7. Set `header.value` to be the value that the builder will pay the proposer if the bid is accepted. The builder **MUST** have balance enough to fulfill this bid. | ||||||
8. Set `header.kzg_commitments_root` to be the `hash_tree_root` of the `blobsbundle.commitments` field returned by `engine_getPayloadV4`. | ||||||
|
||||||
After building the `header`, the builder obtains a `signature` of the header by using | ||||||
|
||||||
```python | ||||||
def get_execution_payload_header_signature(state: BeaconState, header: ExecutionPayloadHeader, privkey: int) -> BLSSignature: | ||||||
domain = get_domain(state, DOMAIN_BEACON_BUILDER, compute_epoch_at_slot(header.slot)) | ||||||
signing_root = compute_signing_root(header, domain) | ||||||
return bls.Sign(privkey, signing_root) | ||||||
``` | ||||||
|
||||||
The builder assembles then `signed_exceution_payload_header = SignedExecutionPayloadHeader(message=header, signature=signature)` and broadcasts it on the `execution_payload_header` global gossip topic. | ||||||
|
||||||
### Constructing the `BlobSidecar`s | ||||||
|
||||||
[Modified in ePBS] | ||||||
|
||||||
The `BlobSidecar` container is modified indirectly because the constant `KZG_COMMITMENT_INCLUSION_PROOF_DEPTH` is modified. Each sidecar is obtained from the modified | ||||||
|
||||||
```python | ||||||
def get_blob_sidecars(signed_block: SignedBeaconBlock, | ||||||
blobs: Sequence[Blob], | ||||||
blob_kzg_proofs: Sequence[KZGProof]) -> Sequence[BlobSidecar]: | ||||||
block = signed_block.message | ||||||
block_header = BeaconBlockHeader( | ||||||
slot=block.slot, | ||||||
proposer_index=block.proposer_index, | ||||||
parent_root=block.parent_root, | ||||||
state_root=block.state_root, | ||||||
body_root=hash_tree_root(block.body), | ||||||
) | ||||||
signed_block_header = SignedBeaconBlockHeader(message=block_header, signature=signed_block.signature) | ||||||
return [ | ||||||
BlobSidecar( | ||||||
index=index, | ||||||
blob=blob, | ||||||
kzg_commitment=block.body.blob_kzg_commitments[index], | ||||||
kzg_proof=blob_kzg_proofs[index], | ||||||
signed_block_header=signed_block_header, | ||||||
kzg_commitment_inclusion_proof=compute_merkle_proof( | ||||||
block.body, | ||||||
GeneralizedIndex(KZG_GENERALIZED_INDEX_PREFIX + index), | ||||||
), | ||||||
) | ||||||
for index, blob in enumerate(blobs) | ||||||
] | ||||||
``` | ||||||
|
||||||
### Constructing the execution payload envelope | ||||||
|
||||||
When the proposer publishes a valid `SignedBeaconBlock` containing a signed commitment by the builder, the builder is later expected to broadcast the corresponding `SignedExecutionPayloadEnvelope` that fulfills this commitment. See below for a special case of an *honestly withheld payload*. | ||||||
|
||||||
To construct the `execution_payload_envelope` the builder must perform the following steps, we alias `header` to be the committed `ExecutionPayloadHeader` in the beacon block. | ||||||
|
||||||
1. Set the `payload` field to be the `ExecutionPayload` constructed when creating the corresponding bid. This payload **MUST** have the same block hash as `header.block_hash`. | ||||||
2. Set the `builder_index` field to be the validator index of the builder performing these steps. This field **MUST** be `header.builder_index`. | ||||||
3. Set `beacon_block_root` to be the `hash_tree_root` of the corresponding beacon block. | ||||||
4. Set `blob_kzg_commitments` to be the `commitments` field of the blobs bundle constructed when constructing the bid. This field **MUST** have a `hash_tree_root` equal to `header.blob_kzg_commitments_root`. | ||||||
5. Set `inclusion_list_proposer_index` to be the `inclusion_list_summary.proposer_index` from the inclusion list used when creating the bid. | ||||||
6. Set `inclusion_list_slot` to be the `inclusion_list_summary.slot` from the inclusion list used when creating the bid. | ||||||
7. Set the `inclusion_list_signature` to be `signed_inclusion_list_summary.signature` from the inclusion list used when creating the bid. | ||||||
8. Set `payload_witheld` to `False`. | ||||||
|
||||||
After setting these parameters, the builder should run `process_execution_payload(state, signed_envelope, verify=False)` and this function should not trigger an exception. | ||||||
|
||||||
9. Set `state_root` to `hash_tree_root(state)`. | ||||||
After preparing the `envelope` the builder should sign the envelope using: | ||||||
```python | ||||||
def get_execution_payload_envelope_signature(state: BeaconState, envelope: ExecutionPayloadEnvelope, privkey: int) -> BLSSignature: | ||||||
domain = get_domain(state, DOMAIN_BEACON_BUILDER, compute_epoch_at_slot(state.slot)) | ||||||
signing_root = compute_signing_root(envelope, domain) | ||||||
return bls.Sign(privkey, signing_root) | ||||||
``` | ||||||
The builder assembles then `signed_exceution_payload_envelope = SignedExecutionPayloadEnvelope(message=envelope, signature=signature)` and broadcasts it on the `execution_payload` global gossip topic. | ||||||
|
||||||
### Honest payload withheld messages | ||||||
|
||||||
An honest builder that has seen a `SignedBeaconBlock` referencing his signed bid, but that block was not timely and thus it is not the head of the builder's chain, may choose to withhold their execution payload. For this the builder should simply act as if it were building an empty payload, without any transactions, withdrawals, etc. Omit step 9 above and set `payload_withheld` to `True`. If the PTC sees this message and votes for it, validators will attribute a *withholding boost* to the builder, which would increase the forkchoice weight of the parent block, favouring it and preventing the builder from being charged for the bid by not revealing. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,220 @@ | ||
# Engine API -- ePBS | ||
|
||
Engine API changes introduced in the ePBS fork. | ||
|
||
## Table of contents | ||
|
||
<!-- START doctoc generated TOC please keep comment here to allow auto update --> | ||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> | ||
|
||
- [Structures](#structures) | ||
- [ExecutionPayloadV4](#executionpayloadv4) | ||
- [PayloadAttributesV3](#payloadattributesv3) | ||
- [InclusionListV1](#inclusionlistv1) | ||
- [InclusionListStatusV1](#inclusionliststatusv1) | ||
- [Methods](#methods) | ||
- [engine_newInclusionListV1](#engine_newinclusionlistv1) | ||
- [Request](#request) | ||
- [Response](#response) | ||
- [Specification](#specification) | ||
- [engine_newPayloadV4](#engine_newpayloadv4) | ||
- [Request](#request-1) | ||
- [Response](#response-1) | ||
- [Specification](#specification-1) | ||
- [engine_forkchoiceUpdatedV3](#engine_forkchoiceupdatedv3) | ||
- [Request](#request-2) | ||
- [Response](#response-2) | ||
- [Specification](#specification-2) | ||
- [engine_getPayloadV3](#engine_getpayloadv3) | ||
- [Request](#request-3) | ||
- [Response](#response-3) | ||
- [Specification](#specification-3) | ||
- [`engine_getInclusionListV1`](#engine_getinclusionlistv1) | ||
- [Request](#request-4) | ||
- [Response](#response-4) | ||
|
||
<!-- END doctoc generated TOC please keep comment here to allow auto update --> | ||
|
||
## Structures | ||
|
||
### ExecutionPayloadV4 | ||
|
||
This structure has the syntax of [`ExecutionPayloadV3`](./cancun.md#executionpayloadv3) and appends the new field `inclusionListSummary` | ||
|
||
- `parentHash`: `DATA`, 32 Bytes | ||
- `feeRecipient`: `DATA`, 20 Bytes | ||
- `stateRoot`: `DATA`, 32 Bytes | ||
- `receiptsRoot`: `DATA`, 32 Bytes | ||
- `logsBloom`: `DATA`, 256 Bytes | ||
- `prevRandao`: `DATA`, 32 Bytes | ||
- `blockNumber`: `QUANTITY`, 64 Bits | ||
- `gasLimit`: `QUANTITY`, 64 Bits | ||
- `gasUsed`: `QUANTITY`, 64 Bits | ||
- `timestamp`: `QUANTITY`, 64 Bits | ||
- `extraData`: `DATA`, 0 to 32 Bytes | ||
- `baseFeePerGas`: `QUANTITY`, 256 Bits | ||
- `blockHash`: `DATA`, 32 Bytes | ||
- `transactions`: `Array of DATA` - Array of transaction objects, each object is a byte list (`DATA`) representing `TransactionType || TransactionPayload` or `LegacyTransaction` as defined in [EIP-2718](https://eips.ethereum.org/EIPS/eip-2718) | ||
- `withdrawals`: `Array of WithdrawalV1` - Array of withdrawals, each object is an `OBJECT` containing the fields of a `WithdrawalV1` structure. | ||
- `blobGasUsed`: `QUANTITY`, 64 Bits | ||
- `excessBlobGas`: `QUANTITY`, 64 Bits | ||
- `inclusionListSummary`: `Array of (DATA, 20 Bytes)` - The summary of an inclusion list signed by a previous proposer. | ||
|
||
### PayloadAttributesV3 | ||
|
||
This structure has the syntax of [`PayloadAttributesV2`](./shanghai.md#payloadattributesv2) and appends the fields `inclusionListParentHash` and `inclusionListProposerIndex`. | ||
|
||
- `timestamp`: `QUANTITY`, 64 Bits - value for the `timestamp` field of the new payload | ||
- `prevRandao`: `DATA`, 32 Bytes - value for the `prevRandao` field of the new payload | ||
- `suggestedFeeRecipient`: `DATA`, 20 Bytes - suggested value for the `feeRecipient` field of the new payload | ||
- `withdrawals`: `Array of WithdrawalV1` - Array of withdrawals, each object is an `OBJECT` containing the fields of a `WithdrawalV1` structure. | ||
- `parentBeaconBlockRoot`: `DATA`, 32 Bytes - Root of the parent beacon block. | ||
- `inclusionListParentHash`: `DATA`, 32 Bytes - Hash of the parent block of the required inclusion list. | ||
- `inclusionListProposer`: 64 Bits - Validator index of the proposer of the inclusion list. | ||
|
||
### InclusionListV1 | ||
|
||
[New in ePBS] | ||
|
||
This structure contains the full list of transactions and the summary broadcast by a proposer of an inclusion list. | ||
|
||
- `transactions`: `Array of DATA` - Array of transaction objects, each object is a byte list (`DATA`) representing `TransactionType || TransactionPayload` or `LegacyTransaction` as defined in [EIP-2718](https://eips.ethereum.org/EIPS/eip-2718) | ||
- `summary`: `Array of DATA` - Array of addresses, each object is a byte list (`DATA`, 20 Bytes) representing the "from" address from the transactions in the `transactions` list. | ||
- `parentHash`: `DATA`, 32 Bytes. | ||
- `proposerIndex` : `QUANTITY`, 64 Bits. | ||
|
||
### InclusionListStatusV1 | ||
|
||
[New in ePBS] | ||
|
||
This structure contains the result of processing an inclusion list. The fields are encoded as follows: | ||
|
||
- `status`: `enum`- `"VALID" | "INVALID" | "SYNCING" | "ACCEPTED"` | ||
- `validationError`: `String|null` - a message providing additional details on the validation error if the inclusion list is classified as `INVALID`. | ||
|
||
## Methods | ||
|
||
### engine_newInclusionListV1 | ||
|
||
[New in ePBS] | ||
|
||
#### Request | ||
|
||
* method: `engine_newInclusionListV1` | ||
* params: | ||
1. `inclusion_list`: [`InclusionListV1`](#InclusionListV1). | ||
|
||
#### Response | ||
|
||
- result: [`InclusionListStatusV1`](#InclusionListStatusV1). | ||
- error: code and message set in case an exception happens while processing the payload. | ||
|
||
#### Specification | ||
|
||
### engine_newPayloadV4 | ||
|
||
#### Request | ||
|
||
* method: `engine_newPayloadV3` | ||
* params: | ||
1. `executionPayload`: [`ExecutionPayloadV3`](#ExecutionPayloadV3). | ||
2. `expectedBlobVersionedHashes`: `Array of DATA`, 32 Bytes - Array of expected blob versioned hashes to validate. | ||
3. `parentBeaconBlockRoot`: `DATA`, 32 Bytes - Root of the parent beacon block. | ||
|
||
#### Response | ||
|
||
Refer to the response for [`engine_newPayloadV2`](./shanghai.md#engine_newpayloadv2). | ||
|
||
#### Specification | ||
|
||
This method follows the same specification as [`engine_newPayloadV2`](./shanghai.md#engine_newpayloadv2) with the addition of the following: | ||
|
||
1. Client software **MUST** check that provided set of parameters and their fields strictly matches the expected one and return `-32602: Invalid params` error if this check fails. Any field having `null` value **MUST** be considered as not provided. | ||
|
||
2. Client software **MUST** return `-38005: Unsupported fork` error if the `timestamp` of the payload does not fall within the time frame of the Cancun fork. | ||
|
||
3. Given the expected array of blob versioned hashes client software **MUST** run its validation by taking the following steps: | ||
1. Obtain the actual array by concatenating blob versioned hashes lists (`tx.blob_versioned_hashes`) of each [blob transaction](https://eips.ethereum.org/EIPS/eip-4844#new-transaction-type) included in the payload, respecting the order of inclusion. If the payload has no blob transactions the expected array **MUST** be `[]`. | ||
2. Return `{status: INVALID, latestValidHash: null, validationError: errorMessage | null}` if the expected and the actual arrays don't match. | ||
|
||
This validation **MUST** be instantly run in all cases even during active sync process. | ||
|
||
### engine_forkchoiceUpdatedV3 | ||
|
||
#### Request | ||
|
||
* method: `engine_forkchoiceUpdatedV3` | ||
* params: | ||
1. `forkchoiceState`: [`ForkchoiceStateV1`](./paris.md#ForkchoiceStateV1). | ||
2. `payloadAttributes`: `Object|null` - Instance of [`PayloadAttributesV3`](#payloadattributesv3) or `null`. | ||
* timeout: 8s | ||
|
||
#### Response | ||
|
||
Refer to the response for [`engine_forkchoiceUpdatedV2`](./shanghai.md#engine_forkchoiceupdatedv2). | ||
|
||
#### Specification | ||
|
||
This method follows the same specification as [`engine_forkchoiceUpdatedV2`](./shanghai.md#engine_forkchoiceupdatedv2) with the following changes to the processing flow: | ||
|
||
1. Client software **MUST** verify that `forkchoiceState` matches the [`ForkchoiceStateV1`](./paris.md#ForkchoiceStateV1) structure and return `-32602: Invalid params` on failure. | ||
|
||
2. Extend point (7) of the `engine_forkchoiceUpdatedV1` [specification](./paris.md#specification-1) by defining the following sequence of checks that **MUST** be run over `payloadAttributes`: | ||
|
||
1. `payloadAttributes` matches the [`PayloadAttributesV3`](#payloadattributesv3) structure, return `-38003: Invalid payload attributes` on failure. | ||
|
||
2. `payloadAttributes.timestamp` falls within the time frame of the Cancun fork, return `-38005: Unsupported fork` on failure. | ||
|
||
3. `payloadAttributes.timestamp` is greater than `timestamp` of a block referenced by `forkchoiceState.headBlockHash`, return `-38003: Invalid payload attributes` on failure. | ||
|
||
4. If any of the above checks fails, the `forkchoiceState` update **MUST NOT** be rolled back. | ||
|
||
### engine_getPayloadV3 | ||
|
||
The response of this method is extended with [`BlobsBundleV1`](#blobsbundlev1) containing the blobs, their respective KZG commitments | ||
and proofs corresponding to the `versioned_hashes` included in the blob transactions of the execution payload. | ||
|
||
#### Request | ||
|
||
* method: `engine_getPayloadV3` | ||
* params: | ||
1. `payloadId`: `DATA`, 8 Bytes - Identifier of the payload build process | ||
* timeout: 1s | ||
|
||
#### Response | ||
|
||
* result: `object` | ||
- `executionPayload`: [`ExecutionPayloadV3`](#ExecutionPayloadV3) | ||
- `blockValue` : `QUANTITY`, 256 Bits - The expected value to be received by the `feeRecipient` in wei | ||
- `blobsBundle`: [`BlobsBundleV1`](#BlobsBundleV1) - Bundle with data corresponding to blob transactions included into `executionPayload` | ||
- `shouldOverrideBuilder` : `BOOLEAN` - Suggestion from the execution layer to use this `executionPayload` instead of an externally provided one | ||
* error: code and message set in case an exception happens while getting the payload. | ||
|
||
#### Specification | ||
|
||
Refer to the specification for [`engine_getPayloadV2`](./shanghai.md#engine_getpayloadv2) with addition of the following: | ||
|
||
1. Client software **MUST** return `-38005: Unsupported fork` error if the `timestamp` of the built payload does not fall within the time frame of the Cancun fork. | ||
|
||
2. The call **MUST** return `blobsBundle` with empty `blobs`, `commitments` and `proofs` if the payload doesn't contain any blob transactions. | ||
|
||
3. The call **MUST** return `commitments` matching the versioned hashes of the transactions list of the execution payload, in the same order, | ||
i.e. `assert verify_kzg_commitments_against_transactions(payload.transactions, blobsBundle.commitments)` (see EIP-4844 consensus-specs). | ||
|
||
4. The call **MUST** return `blobs` and `proofs` that match the `commitments` list, i.e. `assert len(blobsBundle.commitments) == len(blobsBundle.blobs) == len(blobsBundle.proofs)` and `assert verify_blob_kzg_proof_batch(blobsBundle.blobs, blobsBundle.commitments, blobsBundle.proofs)`. | ||
|
||
5. Client software **MAY** use any heuristics to decide whether to set `shouldOverrideBuilder` flag or not. If client software does not implement any heuristic this flag **SHOULD** be set to `false`. | ||
|
||
### `engine_getInclusionListV1` | ||
|
||
#### Request | ||
|
||
* method: `engine_getInclusionListV1` | ||
* params: | ||
1. `parentHash`: `DATA`, 32 Bytes - hash of the block which the returning inclusion list bases on | ||
* timeout: 1s | ||
|
||
#### Response | ||
|
||
* result: [`InclusionListV1`](#inclusionlistv1) | ||
* error: code and message set in case an exception happens while getting the inclusion list. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe mention that a validator that is not a builder is called a proposer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this statement is true though.