Skip to content

engineapi: switch Engine API SSZ to execution-apis#793#21729

Draft
yperbasis wants to merge 13 commits into
mainfrom
yperbasis/engine-ssz-793
Draft

engineapi: switch Engine API SSZ to execution-apis#793#21729
yperbasis wants to merge 13 commits into
mainfrom
yperbasis/engine-ssz-793

Conversation

@yperbasis

@yperbasis yperbasis commented Jun 10, 2026

Copy link
Copy Markdown
Member

Closes #21600.

Implements the REST + SSZ Engine API transport from execution-apis#793.

Endpoints

Endpoint Replaces
POST /engine/v2/{fork}/payloads engine_newPayloadV{1..5} — SSZ ExecutionPayloadEnvelope; expectedBlobVersionedHashes recomputed from payload.transactions
GET /engine/v2/{fork}/payloads/{id} engine_getPayloadV{1..6} — SSZ BuiltPayload, Cache-Control: no-store
POST /engine/v2/{fork}/forkchoice engine_forkchoiceUpdatedV{1..4} — optional payload_attributes and (Amsterdam) custody_columns
POST /engine/v2/{fork}/bodies/hash, GET /engine/v2/{fork}/bodies?from=N&count=M engine_getPayloadBodiesBy* — fork-era-scoped
POST /engine/v2/blobs/v{1..3} engine_getBlobsV{1..3}
GET /engine/v2/capabilities, GET /engine/v2/identity capability / client-version discovery (JSON)

Fork segment paris…amsterdamBellatrix…Gloas; unscheduled or unknown fork → 400 unsupported-fork. Errors are RFC 7807 application/problem+json; wrong Content-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:

  • ExecutionPayloadEnvelopeparent_beacon_block_root from Cancun, execution_requests from Prague.
  • BuiltPayload{payload, block_value} from Paris; + blobs_bundle, should_override_builder from Cancun; + execution_requests (before should_override_builder) from Prague; blobs_bundle becomes V2 from Osaka.
  • execution_requestsList[ByteList[2³⁰], 256]; an over-length list is rejected on decode.
  • PayloadStatus.validation_errorOptional[String] (List[List[byte, 1024], 1]), so absent is distinguishable from empty; the spec's 41-byte VALID / 27-byte INVALID examples are pinned in tests.
  • LimitsMAX_REQUEST_BODY_SIZE = 2²⁶ (advertised as limits.payload.max_bytes); BlobsBundleV2 proofs bound MAX_BLOB_COMMITMENTS_PER_BLOCK * CELLS_PER_EXT_BLOB.

Bodies: by-hash returns available=false for 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; capabilities advertises blobs: [v1, v2, v3]. Erigon has no engine_getBlobsV4 backend yet (JSON-RPC also stops at V3); to be added alongside it.
  • custody_columns is 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, PayloadStatus byte examples, execution_requests bound, geth golden vectors); make lint clean; make erigon integration builds; live smoke test on a dev node over JWT-authenticated h2c.

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 RFC7807 application/problem+json error 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.

Comment thread execution/engineapi/sszrest_wire.go
Comment thread execution/engineapi/sszrest_wire.go
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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Comment thread execution/engineapi/sszrest_wire.go
Comment thread execution/engineapi/sszrest_wire.go
Comment thread execution/engineapi/engine_types/ssz.go
…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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Comment thread execution/engineapi/sszrest_handler.go Outdated
…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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Comment thread execution/engineapi/sszrest_wire.go
Comment thread execution/engineapi/sszrest_wire.go
Comment thread execution/engineapi/sszrest_wire.go Outdated
noop and others added 3 commits June 18, 2026 17:08
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Switch Engine API SSZ to execution-apis#793

2 participants