Skip to content

Add initial FOCIL spec #609

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

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions src/engine/experimental/eip7805.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Engine API -- EIP-7805

Engine API changes introduced in EIP-7805.

This specification is based on and extends [Engine API - Osaka](./osaka.md) specification.

## 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 -->

- [Constants](#constants)
- [Methods](#methods)
- [engine_newPayloadV5](#engine_newpayloadv5)
- [Request](#request)
- [Response](#response)
- [Specification](#specification)
- [engine_getInclusionListV1](#engine_getinclusionlistv1)
- [Request](#request-1)
- [Response](#response-1)
- [Specification](#specification-1)
- [engine_updatePayloadWithInclusionListV1](#engine_updatepayloadwithinclusionlistv1)
- [Request](#request-2)
- [Response](#response-2)
- [Specification](#specification-2)
- [Update the methods of previous forks](#update-the-methods-of-previous-forks)

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

## Constants

| Name | Value |
| - | - |
| `MAX_BYTES_PER_INCLUSION_LIST` | `uint64(8192) = 2**13` |

## Methods

### engine_newPayloadV5

Method parameter list is extended with `inclusionList`.

#### Request

* method: `engine_newPayloadV5`
* params:
1. `executionPayload`: [`ExecutionPayloadV3`](./cancun.md#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.
4. `executionRequests`: `Array of DATA` - List of execution layer triggered requests. Each list element is a `requests` byte array as defined by [EIP-7685](https://eips.ethereum.org/EIPS/eip-7685). The first byte of each element is the `request_type` and the remaining bytes are the `request_data`. Elements of the list **MUST** be ordered by `request_type` in ascending order. Elements with empty `request_data` **MUST** be excluded from the list.
5. `inclusionList`: `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).

#### Response

Refer to the response for [`engine_newPayloadV4`](./prague.md#engine_newpayloadv4).

#### Specification

This method follows the same specification as [`engine_newPayloadV4`](./prague.md#engine_newpayloadv4) with the following changes:

1. Client software **MUST** return `{status: INVALID_INCLUSION_LIST, latestValidHash: null, validationError: null}` if there are any transactions of `inclusionList` that are not part of the `executionPayload`, even if they can be appended at the end of the `executionPayload`.

### engine_getInclusionListV1

#### Request

* method: `engine_getInclusionListV1`
* params:
1. `parentHash`: `DATA`, 32 Bytes - parent hash which returned inclusion list should be built upon.
* timeout: 1s

#### Response

* result: `inclusionList`: `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).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can introduce a response object like it is done in the forkchoiceUpdated case. Should we extend inclusion list with some meta information in the future, it will be smoother with an object in response

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We had object for that purpose but removed it as it makes the caller's job easier. I want to ask @terencechain's opinion on this. Relevant discussion here.

* error: code and message set in case an exception happens while getting the inclusion list.

#### Specification

1. Client software **MUST** provide a list of transactions for the inclusion list based on local view of the mempool and according to the config specifications.

2. Client software **MUST** provide a list of transactions within upperbound `MAX_BYTES_PER_INCLUSION_LIST`.

3. Client software **MUST NOT** include any [blob transaction](https://eips.ethereum.org/EIPS/eip-4844#blob-transaction) within the provided list.

### engine_updatePayloadWithInclusionListV1

#### Request

* method: `engine_updatePayloadWithInclusionListV1`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is supposed to update the payload build process. The problem with doing it via a separate call can be illustrated as the following:
Suppose IL update and fork choice update are triggered at the same time, two potential outcomes:

  1. IL update is handled first and it gets lost because fork choice update may restart the payload build process, in this case an invalid payload wrt IL can be built
  2. Fork choice update is handled first, then payloadId is no longer valid and IL update fails

For the second case there can be error in the response, so CL will re-send IL information with the new payloadId. For the first case it is up to EL to move the IL information from the old build process to the new one.

To avoid such failure modes IL can be made a part of a payloadAttributes. In this case all the information required for the payload build process will be updated atomically, in the way it is done today. If such approach was under consideration then I am curious why it wasn’t chosen?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is very spot on. I'm gathering feedback on the approach recently that is forkchoiceUpdated vs updatePayloadWithInclusionList.

One thing is that updatePayloadWithInclusionList is called with payloadId and it's supposed to be called after IL view freeze deadline. It makes a pair of calls, which is forkchoiceUpdated followed by updatePayloadWithInclusionList.

For the first case, I think it's implementation dependent. As long as it has the same payloadId, the EL can use ILs that it has been provided. Currently, Geth is implemented in this way.

For the second case, I'm not sure if I understood the scenario. The returned payloadId of forkchoiceUpdated should be fed to updatePayloadWithInclusionList. If the CL calls forkchoiceUpdated with different build params, it will have updatePayloadWithInclusionList with different payloadId. It's just different context.

This may arise a question regarding scope. Some may argue that placing ILs into payloadAttribute could give us a better scope. But another feedback I got is it's not right to overload payloadAttributes as ILs don't participate in choosing a head. I don't have a strong opinion tbh and I do love to have more feedback on this.

Another point I want to share is that the number of ILs is not monotonically increasing. It will decrease when there is any equivocation. So the CL would want to pass the ILs after the view freeze deadline. Of course it won't hurt even if the EL builds payload with equivocated ILs as long as it satisfies non-equivocated ILs.

Copy link
Contributor

@mkalinin mkalinin Jun 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One of the specifics of the fcU call is that it can be called multiple time, each time with different head restarting the build process, then ILs should be moved from one process to another.

payloadAttributes isn't related to the head at all, it keeps information required for the build process only; from this perspective IL should be a part of payloadAttributes too. The thing is that the canonical head state itself is a very important part of the payload build process, this is why fcU has double semantics (fc update that sets the EL into the canonical state + build process trigger).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the feedback! When other parameters are not changed, fcU approach consumes a bit more bandwidth than updatePayloadWithInclusionList. Would you say the clarity gained from clearer semantics outweighs this?

* params:
1. `payloadId`: `DATA`, 8 Bytes - Identifier of the payload build process.
2. `inclusionList`: `inclusionList`: `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).
* timeout: 1s

#### Response

* result: `payloadId`: `DATA|null`, 8 Bytes - identifier of the payload build process or `null`
* error: code and message set in case an exception happens while getting the inclusion list.

#### Specification

1. Given the `payloadId` client software **MUST** update payload build process building with`inclusionList`. The transactions must be part of the execution payload unless it fails to be included at the end of it.

2. Client software **SHOULD** ignore any [blob transactions](https://eips.ethereum.org/EIPS/eip-4844#blob-transaction) present in the `inclusionList` when updating the execution payload.

### Update the methods of previous forks

This document defines how FOCIL payload should be handled by the [`Prague API`](./prague.md).

For the following methods:

- [`engine_newPayloadV4`](./prague.md#engine_newpayloadV4)

a validation **MUST** be added:

1. Client software **MUST** return `-38005: Unsupported fork` error if the `timestamp` of payload or payloadAttributes greater or equal to the FOCIL activation timestamp.