Skip to content

Commit 8f603fa

Browse files
Merge branch 'main' into history-expiry
2 parents d2e26e7 + abb87c4 commit 8f603fa

5 files changed

Lines changed: 321 additions & 24 deletions

File tree

docs/_sidebar.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
- Execution Layer
1313
- [EL Specs](/wiki/EL/el-specs.md)
1414
- [Client architecture](/wiki/EL/el-architecture.md)
15+
- [Engine API](/wiki/EL/engine-api.md)
1516
- [EL Clients](/wiki/EL/el-clients.md)
1617
- [Besu](/wiki/EL/clients/besu.md)
1718
- [Reth](/wiki/EL/clients/reth.md)

docs/wiki/EL/engine-api.md

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
# Engine API
2+
3+
> [!WARNING]
4+
> The Glamsterdam section of this page covers active areas of research (EIP-7732 ePBS and EIP-7928 BALs). Those sections may be outdated at time of reading and are subject to future updates as the design space evolves.
5+
6+
**Prerequisite reading:** [EL Architecture](/wiki/EL/el-architecture.md)
7+
8+
The Engine API is the authenticated communication interface between the Consensus Layer (CL) and the Execution Layer (EL), introduced at The Merge. The CL drives the EL's block building, validation, and fork choice through this interface. It tells the EL which chain is canonical, asks it to build new blocks, and sends received blocks for validation.
9+
10+
## Architecture
11+
12+
### Network Isolation
13+
14+
The Engine API is served on a **dedicated port (default: 8551)**, strictly separate from the public-facing JSON-RPC API (default: 8545). This isolation is a security requirement. Shared ports would allow public traffic or deliberate DoS floods to starve the consensus dialogue, causing missed proposals and attestations.
15+
16+
### Communication Protocols
17+
18+
| Protocol | Authentication | Notes |
19+
|----------|---------------|-------|
20+
| HTTP | JWT on every request | Standard; stateless |
21+
| WebSocket | JWT on initial handshake only | Persistent connection; no per-frame auth |
22+
| IPC | None | Same-machine only; filesystem permissions provide isolation |
23+
24+
### Ancillary eth_ Methods
25+
26+
The spec requires the EL to expose all nine of the following `eth_` methods on the authenticated Engine API port, enabling the CL to query chain state without a separate connection:
27+
28+
`eth_blockNumber`, `eth_call`, `eth_chainId`, `eth_getCode`, `eth_getBlockByHash`, `eth_getBlockByNumber`, `eth_getLogs`, `eth_sendRawTransaction`, `eth_syncing`
29+
30+
`eth_getLogs` is critical for CL monitoring of the deposit contract (pre-Electra). `eth_call` enables the CL to verify EIP-7002 withdrawal credentials without broadcasting transactions.
31+
32+
## Authentication
33+
34+
The CL and EL share a 256-bit (32-byte) hex-encoded JWT secret, configured via a `--jwt-secret` CLI flag pointing to a `jwt.hex` file. If omitted, the EL auto-generates a random secret for that session and writes it to `jwt.hex` in its data directory.
35+
36+
**Algorithm**: The EL must enforce **HS256** (HMAC-SHA256). Any JWT specifying `alg: none` must be immediately rejected to prevent authentication bypass.
37+
38+
**Replay protection**: Every JWT must include an `iat` (issued-at) claim. The EL must reject any request where the token's `iat` deviates by more than **±60 seconds** from the EL's local clock. This prevents captured tokens from being replayed to induce chain reorganizations.
39+
40+
Optional claims (`id`, `clv`) may be included for telemetry but are not validated for access control.
41+
42+
## Capability Negotiation
43+
44+
### engine_exchangeCapabilities
45+
46+
`engine_exchangeCapabilities` has no version suffix. It is the only Engine API method without one. It lets clients discover each other's supported method versions.
47+
48+
- The EL **must** support this method; the CL may call it optionally
49+
- Each party sends an array of their supported method names with version suffixes (e.g. `["engine_newPayloadV3", "engine_newPayloadV4"]`)
50+
- The EL responds with its own list. `engine_exchangeCapabilities` itself **must not** appear in the response.
51+
- The EL must not log errors if this method is never called (backward compatibility)
52+
53+
### engine_getClientVersionV1
54+
55+
An optional method allowing the CL and EL to identify each other's software. Each side returns a `ClientVersionV1` structure with a two-letter client code, human-readable name, version string, and 4-byte commit hash. The compact identifiers are designed to fit in the 32-byte beacon block graffiti field for network diversity tracking.
56+
57+
| Code | EL Client | Code | CL Client |
58+
|------|-----------|------|-----------|
59+
| `BU` | Besu | `GR` | Grandine |
60+
| `EG` | Erigon | `LH` | Lighthouse |
61+
| `EJ` | EthereumJS | `LS` | Lodestar |
62+
| `EX` | Ethrex | `NB` | Nimbus |
63+
| `GE` | Geth | `PM` | Prysm |
64+
| `NM` | Nethermind | `TK` | Teku |
65+
| `RH` | Reth | | |
66+
| `TE` | Trin-Execution | | |
67+
68+
The EL must not log errors if this method is never called.
69+
70+
## Core Methods
71+
72+
### engine_forkchoiceUpdatedV3
73+
74+
Updates the EL's canonical chain view and optionally initiates block building.
75+
76+
**Parameters:**
77+
- `forkchoiceState`: `{headBlockHash, safeBlockHash, finalizedBlockHash}`
78+
- `payloadAttributes` (optional): `{timestamp, prevRandao, suggestedFeeRecipient, withdrawals, parentBeaconBlockRoot}`. If provided, the EL starts building a payload and returns a `payloadId`.
79+
80+
**Returns:** `{payloadStatus, payloadId}`
81+
82+
`engine_forkchoiceUpdatedV3` returns only `VALID`, `INVALID`, or `SYNCING`. It never returns `ACCEPTED`. A fork choice update is an authoritative command to reorganize or extend the canonical chain, so the EL must fully resolve the head block's state before executing the update. `ACCEPTED` is exclusive to `engine_newPayload`.
83+
84+
### Payload Status Values
85+
86+
| Status | Returned by | Meaning |
87+
|--------|-------------|---------|
88+
| `VALID` | both | Block and all ancestors fully downloaded and EVM-verified |
89+
| `INVALID` | both | Violates consensus rules; `latestValidHash` identifies the highest valid ancestor for fork recovery |
90+
| `SYNCING` | both | Required ancestor data is missing locally; EL has begun fetching from p2p |
91+
| `ACCEPTED` | newPayload only | Block hash valid, all transactions non-zero length, payload does **not** extend the canonical chain (it is on a side branch), ancestors are locally available. EVM execution is deliberately deferred until fork choice may pivot to this branch. |
92+
93+
**ACCEPTED vs SYNCING**: `SYNCING` means the EL cannot accept the block because its chain history is missing. `ACCEPTED` means the ancestry is intact. The EL chose not to run full EVM validation because this is currently a non-canonical side branch. If LMD-GHOST later pivots to this branch, the EL executes the deferred state transitions.
94+
95+
### engine_newPayloadV4
96+
97+
Delivers an execution payload from a received beacon block to the EL for validation.
98+
99+
**Parameters:**
100+
- `executionPayload`: the full block
101+
- `expectedBlobVersionedHashes`: ordered list of blob versioned hashes
102+
- `parentBeaconBlockRoot`: parent beacon block root (EIP-4788)
103+
- `executionRequests`: EL-generated requests for the CL (Electra+)
104+
105+
**Validation:**
106+
- Executes all transactions and verifies the resulting state root
107+
- **Blob hash validation**: extracts `blob_versioned_hashes` from every blob-carrying transaction in the payload, preserving inclusion order, concatenates them, and compares against `expectedBlobVersionedHashes`. Any mismatch or ordering difference returns `INVALID`.
108+
109+
**Returns:** `VALID`, `INVALID`, `SYNCING`, or `ACCEPTED`
110+
111+
### engine_getPayloadV4
112+
113+
Retrieves a payload the EL built after a `forkchoiceUpdated` call with `payloadAttributes`.
114+
115+
**Parameters:** `payloadId` is the 8-byte ID returned by `engine_forkchoiceUpdatedV3`.
116+
117+
**Returns:**
118+
- `executionPayload`: the assembled block
119+
- `blockValue`: total wei in priority fees accruing to `feeRecipient` (256-bit quantity)
120+
- `blobsBundle`: `{commitments, proofs, blobs}`. Contains 48-byte KZG commitments, 48-byte KZG proofs, and 131,072-byte blob data for the CL to build blob sidecars.
121+
- `shouldOverrideBuilder`: a boolean **suggestion** from the EL that the locally built payload should be used instead of an external MEV-Boost bid. The CL may or may not act on this. It is one input into the CL's decision, not a command. The EL sets it using implementation-defined heuristics (e.g. a high-value transaction has been persistently excluded from builder bids). If the EL implements no heuristic, it must default to `false`.
122+
- `executionRequests`: EL-generated requests (Electra+)
123+
124+
## Execution Requests (EIP-7685)
125+
126+
Introduced in V4 (Prague/Electra), execution requests allow EVM smart contracts to trigger consensus-layer state changes. Each request is `request_type ++ request_data` where `request_type` is a 1-byte prefix.
127+
128+
**Supported types in Electra:**
129+
130+
| Type | EIP | Description |
131+
|------|-----|-------------|
132+
| `0x00` | EIP-6110 | **Deposit requests**: EL pushes deposit events to the CL directly, replacing CL log polling. Reduces validator activation time from ~12 hours to ~13 minutes. Max per payload: `MAX_DEPOSIT_REQUESTS_PER_PAYLOAD = 8,192`. |
133+
| `0x01` | EIP-7002 | **Withdrawal requests**: Smart contracts holding `0x01` withdrawal credentials can trigger partial or full validator exits from the EVM. Processed via predeploy at `0x00000961Ef480Eb55e80D19ad83579A64c007002`. Target: 2/block; max: 16/block. Fee: `fake_exponential(1_wei, excess, 17)`, near 1 wei under normal load and exponentially expensive under sustained demand. System call gas: 30,000,000 (excluded from block gas accounting). |
134+
| `0x02` | EIP-7251 | **Consolidation requests**: Merge multiple 32 ETH validators into a single compounding validator (up to 2048 ETH effective balance). Target: 1/block; max: 2/block. Pending queue hard limit: `PENDING_CONSOLIDATIONS_LIMIT = 262,144` (2^18). |
135+
136+
**Ordering and validation rules**: The `executionRequests` array must be:
137+
- Sorted in **strictly ascending order** by `request_type`
138+
- Each `request_type` byte appears **at most once** (no duplicates)
139+
- No element with empty `request_data` (elements of length <= 1 byte must be excluded)
140+
141+
Any violation returns `-32602: Invalid params`. The EL also computes a `requests_hash` (SHA-256 over the sorted list) and validates it against the block header; a mismatch returns `INVALID`. For a block with no requests, the hash defaults to `sha256("") = 0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855`.
142+
143+
## Version History
144+
145+
| Version | EL Fork | CL Fork | Key Changes |
146+
|---------|---------|---------|-------------|
147+
| V1 | Paris | Bellatrix | Initial post-Merge: `forkchoiceUpdated`, `newPayload`, `getPayload` |
148+
| V2 | Shanghai | Capella | Added `withdrawals` to payload attributes and execution payload |
149+
| V3 | Cancun | Deneb | Added `blobVersionedHashes`, `parentBeaconBlockRoot`; `executionPayload` gains `blobGasUsed`, `excessBlobGas`; `getPayload` returns `BlobsBundleV1` |
150+
| V4 | Prague | Electra | Added `executionRequests` (EIP-7685) |
151+
152+
## Slot Lifecycle
153+
154+
The Engine API operates within a strict 12-second slot heartbeat. For a proposer node:
155+
156+
| Time | Action |
157+
|------|--------|
158+
| **t = 0s** | Slot begins. CL calls `engine_forkchoiceUpdatedV3` with `payloadAttributes`. EL returns `payloadId`. |
159+
| **t = 1-3s** | EL builds the payload: selects mempool transactions, executes them, computes state root. CL optionally queries external MEV relays. |
160+
| **t = 3s** | CL calls `engine_getPayloadV4`, wraps payload into a `BeaconBlock`, signs it, broadcasts. |
161+
| **t = 4s** | **Attestation deadline.** Other validators must have received the block, called `engine_newPayloadV4`, and received `VALID` by this point. Late blocks are not attested to and earn no inclusion revenue. |
162+
| **t = 4-12s** | CL calls `engine_forkchoiceUpdatedV3` (without `payloadAttributes`) to set the new block as head. |
163+
164+
Non-proposer nodes skip the first three steps and only perform `engine_newPayloadV4` followed by `engine_forkchoiceUpdatedV3`.
165+
166+
### Optimistic Sync
167+
168+
During heavy load, the CL may import a beacon block optimistically without waiting for full EVM execution. If the EL later returns `INVALID`, the CL reorgs back to `latestValidHash`. The maximum optimistic import depth is bounded by `SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY` (default: **128 slots**, ~25.6 minutes), configurable via `--safe-slots-to-import-optimistically`. This prevents "fork choice poisoning" attacks where a malicious peer feeds structurally valid but computationally invalid blocks at the chain tip.
169+
170+
## Error Handling
171+
172+
### JSON-RPC Error Codes
173+
174+
| Code | Name | Trigger |
175+
|------|------|---------|
176+
| `-32700` to `-32603` | Standard JSON-RPC | Parse errors, invalid requests, method not found, invalid params, internal errors |
177+
| `-32000` | Server error | Generic EL failure; response **must** include a `data.err` string with diagnostic context (e.g. `"LevelDB read failure"`) |
178+
| `-38001` | Unknown payload | `engine_getPayload` called with a `payloadId` that has no active build process or has timed out |
179+
| `-38002` | Invalid forkchoice state | `forkchoiceState` hashes are logically inconsistent (e.g. `safeBlockHash` is not an ancestor of `headBlockHash`) |
180+
| `-38003` | Invalid payload attributes | `payloadAttributes` fields are structurally invalid or missing fork-required fields |
181+
| `-38004` | Too large request | An array parameter exceeds hardcoded memory constraints |
182+
| `-38005` | Unsupported fork | Payload timestamp does not align with the EL's active fork (e.g. a Deneb-format payload submitted before Deneb activation) |
183+
184+
### Failure Modes
185+
186+
**`SYNCING`**: CL retries `engine_forkchoiceUpdatedV3` until the EL catches up. Do not attest to or build on that block.
187+
188+
**`INVALID`**: CL marks that block and all descendants as invalid, reverts fork choice to `latestValidHash`.
189+
190+
**EL unreachable** (port down, JWT mismatch, crash): CL cannot propose or validate. Validators miss all duties until the connection is restored.
191+
192+
## Future Upgrades
193+
194+
### Fusaka (Fulu/Osaka, December 3, 2025, epoch 411392)
195+
196+
The Fusaka upgrade spans 13 EIPs covering DA scaling, execution performance, and protocol cleanup. Key Engine API impacts:
197+
198+
**Data availability:**
199+
- **EIP-7594 (PeerDAS)**: Nodes sample small column subsets instead of downloading full blobs, enabling safe blob throughput increases. `engine_getPayloadBodiesByHashV2` and `engine_getPayloadBodiesByRangeV2` are updated to support PeerDAS cell proof structures.
200+
- **EIP-7918**: Blob base fee floor tied proportionally to the execution base fee, preventing blob fees from collapsing to 1 wei during low demand.
201+
- **EIP-7892 (BPO forks)**: Blob Parameter Only forks allow blob counts to be scaled post-Fusaka via lightweight network adjustments without triggering full hard forks. BPO1 and BPO2 activated shortly after Fusaka, raising blob targets to 10/15 and 14/21 respectively.
202+
203+
**Execution performance:**
204+
- **EIP-7935**: Default block gas limit raised to **60M** (coordinated among clients, not a consensus rule).
205+
- **EIP-7825**: Transaction gas limit capped at **2^24 = 16,777,216 gas**. No single transaction can occupy an entire block.
206+
- **EIP-7934**: RLP execution block size capped at **8 MiB** (`MAX_RLP_BLOCK_SIZE = 10 MiB - 2 MiB safety margin`). The CL gossip protocol already drops blocks over 10 MiB; the 2 MiB margin reserves headroom for the beacon block, making the effective EL payload limit 8 MiB.
207+
- **EIP-7883**: MODEXP precompile repriced. Minimum gas raised from 200 to **500**, general cost formula **tripled**, and large-exponent multiplier raised from 8 to **16**.
208+
- **EIP-7823**: MODEXP inputs bounded at **8192 bits (1024 bytes)** per input (base, exponent, modulus). All historical real-world usage was under this limit.
209+
- **EIP-7917**: Deterministic proposer lookahead. The proposer schedule is known one full epoch in advance, improving MEV and PBS coordination.
210+
211+
### Glamsterdam (post-Fusaka)
212+
213+
**EIP-7732 (ePBS)** is the proposed Glamsterdam CL headliner. It moves PBS from the out-of-protocol MEV-Boost sidecar into the consensus layer, eliminating reliance on trusted relays. The Engine API slot lifecycle changes materially: the proposer receives only a signed **builder commitment** at t=3s and broadcasts it without the execution payload; the builder then reveals the full `ExecutionPayloadEnvelope` to the network after the t=4s attestation deadline. This prevents the proposer from stealing the builder's MEV while retaining the ability to attest. New Engine API methods handle commitment validation separately from full EVM execution.
214+
215+
**EIP-7928 (Block-Level Access Lists)** is the proposed Glamsterdam EL headliner. Every block would include an explicit map of all addresses and storage slots accessed during execution, enabling:
216+
- Parallel pre-fetching of state before EVM execution
217+
- Parallel execution of non-conflicting transactions across CPU cores
218+
- Execution-less state verification for stateless and ZK clients
219+
220+
The Engine API would validate this by having the EL execute the payload, compute the access list internally, and verify it against the `blockAccessList` in the payload header. A mismatch returns `INVALID`. Both EIPs are draft proposals.
221+
222+
## Resources
223+
224+
- [Engine API specification (execution-apis)](https://github.com/ethereum/execution-apis/tree/main/src/engine)
225+
- [EIP-3675: Upgrade to Proof-of-Stake](https://eips.ethereum.org/EIPS/eip-3675)
226+
- [EIP-7685: General purpose EL requests](https://eips.ethereum.org/EIPS/eip-7685)
227+
- [EIP-7002: Execution layer triggerable withdrawals](https://eips.ethereum.org/EIPS/eip-7002)
228+
- [EIP-7607: Hardfork Meta - Fusaka](https://eips.ethereum.org/EIPS/eip-7607)
229+
- [EIP-7732: Enshrined Proposer-Builder Separation](https://eips.ethereum.org/EIPS/eip-7732)
230+
- [EIP-7928: Block-Level Access Lists](https://eips.ethereum.org/EIPS/eip-7928)
231+
- [Engine API visual guide](https://hackmd.io/@danielrachi/engine_api)

0 commit comments

Comments
 (0)