engineapi: switch Engine API SSZ to execution-apis#793#21729
Draft
yperbasis wants to merge 13 commits into
Draft
engineapi: switch Engine API SSZ to execution-apis#793#21729yperbasis wants to merge 13 commits into
yperbasis wants to merge 13 commits into
Conversation
Replace the execution-apis#764 surface (version-scoped /engine/vN/...
endpoints) with the fork-scoped REST surface of execution-apis#793:
POST/GET /engine/v2/{fork}/payloads, POST /engine/v2/{fork}/forkchoice,
fork-era-scoped bodies endpoints, POST /engine/v2/blobs/v{1..3}, JSON
GET /engine/v2/capabilities + /engine/v2/identity, and RFC 7807
problem+json errors. PayloadStatus.validation_error becomes
Optional[String]; expectedBlobVersionedHashes is recomputed from the
payload transactions; PayloadAttributes SSZ for Electra/Fulu now uses
the Cancun shape instead of falling into the Gloas branch.
Closes #21600
Contributor
There was a problem hiding this comment.
Pull request overview
Updates Erigon’s authenticated SSZ-REST Engine API transport to match the newer draft surface from execution-apis#793, replacing the previously implemented #764-based endpoints. This affects both HTTP routing (/engine/v2/…) and several SSZ wire types/codecs used by newPayload/getPayload/forkchoice/blobs/bodies.
Changes:
- Replaces legacy
/engine/v{N}/…SSZ-REST endpoints with fork-scoped/engine/v2/{fork}/…endpoints plus JSON discovery endpoints (GET /engine/v2/capabilities,GET /engine/v2/identity) and RFC7807application/problem+jsonerror responses. - Updates SSZ codecs for the new envelopes/responses (newPayload envelope, BuiltPayload field order, forkchoice update + optional custody columns, blob/body entry wrappers).
- Rewrites SSZ-REST tests to target the new surface, error mapping, and wire-format details (including the PayloadStatus spec example).
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| execution/engineapi/sszrest_wire.go | Implements new fork-scoped SSZ schemas/codecs for payload envelopes, forkchoice update, BuiltPayload, bodies, and blobs responses. |
| execution/engineapi/sszrest_handler.go | Replaces routing with /engine/v2/... fork-scoped handlers, adds JSON discovery endpoints, and standardizes errors to RFC7807 problem+json. |
| execution/engineapi/engine_types/ssz.go | Updates SSZ encoding/decoding for PayloadStatus.validation_error optionality and fixes fork-branching for PayloadAttributes encoding. |
| execution/engineapi/sszrest_test.go | Rewrites tests to validate new endpoints, routing/error behavior, and updated SSZ wire formats. |
| execution/engineapi/engine_api_methods.go | Removes legacy SSZ-REST endpoint strings from engine_exchangeCapabilities advertised capabilities. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
This was referenced Jun 10, 2026
Track the June 14 "resolve feedback from implementers" update to
execution-apis#793:
- Wrap the top-level bodies and blobs request/response bodies in
single-field SSZ containers (BodiesByHashRequest, BodiesResponse,
BlobsV{1,2,3}Request/Response) instead of bare lists, matching the
now-normative refactor.md.
- Range bodies truncate at the latest known block (no trailing
available=false padding); in-range out-of-era blocks stay
available=false.
- BuiltPayload for Shanghai now carries should_override_builder, per the
spec's per-fork catalogue.
- MAX_REQUEST_BODY_SIZE is 2**26 (64 MiB), advertised as
limits.payload.max_bytes.
- Tighten the BlobsBundleV2 proofs list bound to
MAX_BLOBS_PER_PAYLOAD * CELLS_PER_EXT_BLOB.
Wire format verified byte-compatible with go-ethereum#35171 for every
container both implement.
…ionals Address review feedback on the SSZ-REST transport: - decodeHashListRequest now rejects lists longer than its limit instead of relying on a caller-side size guard (hashList.DecodeSSZ accepts any multiple-of-32 length without capping). - PayloadStatus.DecodeSSZ clears latest_valid_hash and validation_error before decoding so a reused instance can't leak stale optionals.
Track execution-apis#793 commit e509f20f9e ("advertise all forks"), which corrected the per-fork BuiltPayload catalogue: should_override_builder is introduced at Cancun (getPayloadV3), alongside blobs_bundle, not at Shanghai. BuiltPayloadShanghai is now the Paris shape {payload, block_value}; should_override_builder first appears from Cancun on.
Resolved one conflict in execution/engineapi/sszrest_test.go: - Add/add of two unrelated tests at the same location: kept this branch's TestSSZRESTUnscheduledFork; dropped main's TestSSZRESTNewPayloadV5UsesGloasPayloadSchema, which called the pre-#793 encodeNewPayloadRequest/sszNewPayloadVersion/decodeNewPayloadRequest API removed by this branch. Its Gloas-schema round-trip coverage (SlotNumber + BlockAccessList) is already provided by TestSSZRESTNewPayloadEnvelopeRoundTrip. - Adopted main's ExecutionPayload.BlockAccessList *hexutil.Bytes pointer type in the round-trip tests (assignments and assertions).
Borrow the payload/attributes/body golden vectors from go-ethereum's REST-SSZ engine API (ethereum/go-ethereum#35171, beacon/engine/ssz/bytecompat_test.go @ fcefb32655) and assert Erigon reproduces the exact bytes on each fork (paris/shanghai/cancun/amsterdam). Locks cross-client wire parity into CI: base_fee LE, withdrawals, blob-gas, and the Amsterdam block_access_list/slot_number/target_gas_limit fields.
…N_REQUESTS_PER_PAYLOAD execution_requests was built via the shared TransactionsSSZ helper with unset limits, so decode fell back to the transaction default (2**20) rather than the spec bound MAX_EXECUTION_REQUESTS_PER_PAYLOAD (2**8 = 256). Add a dedicated 256 / 2**30-bounded list for the requests list in newPayload and getPayload, matching execution-apis#793 and go-ethereum; payload/body transactions keep the transaction-list bound.
…n header Rename the geth* identifiers in sszrest_bytecompat_test.go to golden*, and move the go-ethereum attribution for the borrowed vectors into a dual-copyright file header (go-ethereum Authors for the golden vectors, Erigon Authors for the test harness), matching execution/abi/abi.go's style.
…ode-only TransactionsSSZ.EncodeSSZ ignores the list limit, so bounding only the decode path suffices: the two decode sites now use the existing NewTransactionsSSZWithLimits, and the dedicated newExecutionRequestsSSZ helper plus the unused NewTransactionsSSZFromTransactionsWithLimits constructor are removed. Behavior unchanged (over-256 requests rejected on decode); reverts the no-op encode-site changes and drops the cl/cltypes/solid addition.
Address PR review feedback: readSSZBody mapped every io.ReadAll error to 413 request-too-large, mislabeling non-size read errors (truncated body, client disconnect). Now only *http.MaxBytesError yields 413; other read errors yield 400 invalid-request. Also pass the ResponseWriter to http.MaxBytesReader so the server can signal Connection: close on overflow.
The BlobsBundle list bound (4096) is MAX_BLOB_COMMITMENTS_PER_BLOCK per execution-apis#793, not MAX_BLOBS_PER_PAYLOAD (the dropped comment) and not a blob-hash count — sszMaxGetBlobHashes (128 = MAX_BLOBS_REQUEST) is the request bound. Rename sszMaxBlobHashes accordingly and drop the now-redundant comment; value and wire format unchanged.
EncodeDynamicList builds its per-element offset table from EncodingSizeSSZ, so each entry's reported size must match EncodeSSZ exactly (the multi-entry round-trip tests verify this): - sszBodyEntry.EncodingSizeSSZ built a body that can error and returned a bogus 0 on failure; derive the size from the entry fields instead. - sszBlobV2Entry.EncodingSizeSSZ re-encoded the whole entry (a 128 KiB blob) just to measure its length; compute it directly. - sszBlobV1Entry.EncodeSSZ allocated a fresh 128 KiB+48 B zero blob/proof for every unavailable entry; reuse shared read-only zero buffers. Addresses Copilot review feedback; no wire-format change.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #21600.
Implements the REST + SSZ Engine API transport from execution-apis#793.
Endpoints
POST /engine/v2/{fork}/payloadsengine_newPayloadV{1..5}— SSZExecutionPayloadEnvelope;expectedBlobVersionedHashesrecomputed frompayload.transactionsGET /engine/v2/{fork}/payloads/{id}engine_getPayloadV{1..6}— SSZBuiltPayload,Cache-Control: no-storePOST /engine/v2/{fork}/forkchoiceengine_forkchoiceUpdatedV{1..4}— optionalpayload_attributesand (Amsterdam)custody_columnsPOST /engine/v2/{fork}/bodies/hash,GET /engine/v2/{fork}/bodies?from=N&count=Mengine_getPayloadBodiesBy*— fork-era-scopedPOST /engine/v2/blobs/v{1..3}engine_getBlobsV{1..3}GET /engine/v2/capabilities,GET /engine/v2/identityFork segment
paris…amsterdam→Bellatrix…Gloas; unscheduled or unknown fork →400 unsupported-fork. Errors are RFC 7807application/problem+json; wrongContent-Type→ 415; unknown route or trailing slash → 404. Transport is h2c (HTTP/2 with HTTP/1.1 fallback).Wire format
Top-level bodies are single-field SSZ containers; per-fork shapes follow the #793 catalogue:
ExecutionPayloadEnvelope—parent_beacon_block_rootfrom Cancun,execution_requestsfrom Prague.BuiltPayload—{payload, block_value}from Paris;+ blobs_bundle, should_override_builderfrom Cancun;+ execution_requests(beforeshould_override_builder) from Prague;blobs_bundlebecomes V2 from Osaka.execution_requests—List[ByteList[2³⁰], 256]; an over-length list is rejected on decode.PayloadStatus.validation_error—Optional[String](List[List[byte, 1024], 1]), so absent is distinguishable from empty; the spec's 41-byteVALID/ 27-byteINVALIDexamples are pinned in tests.MAX_REQUEST_BODY_SIZE = 2²⁶(advertised aslimits.payload.max_bytes);BlobsBundleV2proofs boundMAX_BLOB_COMMITMENTS_PER_BLOCK * CELLS_PER_EXT_BLOB.Bodies: by-hash returns
available=falsefor out-of-era/missing blocks; by-range truncates at head with no trailing padding.Known limitations
/blobs/v4(Amsterdam cell-range selection) is not implemented;capabilitiesadvertisesblobs: [v1, v2, v3]. Erigon has noengine_getBlobsV4backend yet (JSON-RPC also stops at V3); to be added alongside it.custody_columnsis decoded and validated but not acted on — Erigon manages data-column custody in Caplin, not via the Engine API.Verification
go test ./execution/engineapi/...(wire round-trips,PayloadStatusbyte examples,execution_requestsbound, geth golden vectors);make lintclean;make erigon integrationbuilds; live smoke test on a dev node over JWT-authenticated h2c.