EIP-8379 - Top-up Sync

Created 2026-08-06
Status Draft
Category Core
Type Standards Track
Authors

Abstract

This EIP introduces an engine API method for querying the execution client head, distinguishing the head of the block chain from the head for which state is held, and refines the payload status semantics so that a missing pre-state is distinguishable from missing block history. Together, these changes allow the consensus client to drive execution block sync deterministically ("top-up sync"), using engine_newPayload and engine_forkchoiceUpdated as the sole block delivery mechanism.

Motivation

Today, execution and consensus clients each obtain blocks independently: consensus clients via gossip and req/resp, execution clients via devp2p. This duplicates network functionality across the two layers. With top-up sync, the consensus client syncs as usual and, in parallel, feeds the execution client each block that immediately follows the execution client head, until the execution client reaches the consensus client head. Enabling this through the engine API provides:

  1. Consensus-driven sync: The consensus client can provide a canonical block source for the execution client, opening a path towards deprecating the eth protocol block-distribution messages (GetBlockHeaders/BlockHeaders, GetBlockBodies/BlockBodies), with snap sync and the mempool remaining on devp2p.

  2. A head query: The top-up loop requires the consensus client to learn the head of the execution client's block chain, so it knows which payload to deliver next, and separately the head of the execution client's executed chain — the latest block for which the execution client holds post-state — so it knows how far the execution client has validated. The two differ whenever the execution client holds blocks it has not executed, which is what enables efficient syncing: the execution client can accept payloads without executing them and fast-forward its state by other means. Neither head is currently queryable through the engine API; both queries therefore need to be defined within it.

  3. Deterministic choreography: When engine_newPayload returns SYNCING, the consensus client cannot tell whether the execution client is missing the payload's ancestors — in which case the consensus client should supply them — or missing the pre-state while snap-syncing — in which case supplying ancestors is useless and the consensus client should wait. Disambiguating the two makes the engine_forkchoiceUpdated/engine_newPayload choreography deterministic.

Specification

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.

engine_getSyncStatus

This method is added to the engine API. Client software MUST support it from the activation of the fork in which this EIP is included onwards, and MUST advertise it via engine_exchangeCapabilities.

Request

Response

SyncStatus is an object with the following fields:

Name Type Description
headBlockHash DATA, 32 Bytes Hash of the latest block in the client's canonical chain
headBlockNumber QUANTITY, 64 Bits Number of the latest block in the client's canonical chain
executedBlockHash DATA, 32 Bytes or null Hash of the latest canonical block whose post-state the client holds
executedBlockNumber QUANTITY, 64 Bits or null Number of the block referenced by executedBlockHash

The pair (executedBlockHash, executedBlockNumber) describes the head of the client's executed chain: the latest canonical block that the client has validated and holds the post-state of, i.e. the block whose child the client is able to execute. In a VALID PayloadStatus response, latestValidHash references this same block; see the Rationale for why a distinct field name is used. The pair (headBlockHash, headBlockNumber) describes the head of the client's block chain: the latest block in the contiguous chain of held canonical blocks that is rooted in the executed head — or in the genesis block, if there is no executed head.

Client software:

Payload status disambiguation

The payload status enum returned by the versions of engine_newPayload and engine_forkchoiceUpdated introduced alongside this EIP is extended with the value MISSING_STATE.

Client software:

Consensus layer client software:

Top-up sync procedure

This section describes how a consensus client uses the above primitives to drive execution block sync. The forkchoice state communicated via engine_forkchoiceUpdated is unchanged from the existing engine API specification.

While its own (optimistic) sync proceeds, the consensus client runs a top-up loop against the execution client:

  1. Query engine_getSyncStatus.
  2. If the block head matches the consensus client's view of the canonical execution head, delivery is complete — the chain is fully validated once the executed head catches up to the block head.
  3. If the consensus client's canonical chain contains a block at height headBlockNumber + 1 with parent hash headBlockHash, deliver it via engine_newPayload and repeat. By the guarantees above, the response is VALID, INVALID or MISSING_STATE, so the loop always makes progress.
  4. If the block at height headBlockNumber + 1 on the consensus client's canonical chain has a different parent hash, the execution client's head is on a fork the consensus client has abandoned; the consensus client locates the fork point against its canonical chain and re-delivers from there.
  5. If the block at height headBlockNumber + 1 is older than the history the consensus client retains (at most the weak subjectivity period), top-up is not possible and the execution client has to acquire recent state by other means, e.g., snap sync. The consensus client continues issuing engine_forkchoiceUpdated for its head — from which the execution client selects its state sync target — and resumes the loop once the executed head, and with it the block head, has moved within range.

Since a single top-up may span fork boundaries, execution client software MUST accept payloads belonging to earlier forks via the engine_newPayload version introduced alongside this EIP, applying the validation rules of the payload's fork. Consensus client software SHOULD deliver historical payloads through this version rather than the version matching the payload's fork, making MISSING_STATE available across fork boundaries, and MUST supply the associated data required by the payload's fork (e.g., blob versioned hashes and execution requests).

Interface migration

This EIP is specified against the JSON-RPC transport of the engine API. This section is non-normative and records the intended mapping onto the REST/Simple Serialize (SSZ) engine API transport in the REST/Simple Serialize (SSZ) engine API. | Method | Endpoint | | - | - | | engine_getSyncStatus | GET /syncstatus, unscoped, analogous to GET /identity | | Payload status enum | MISSING_STATE added to the payload status SSZ enum for endpoints scoped to the fork in which this EIP is included |

Rationale

Why a new method?

Top-up sync inverts the direction of control: the execution client no longer syncs blocks by itself, so the consensus client needs a query meaningful when the execution client is passive. No existing engine API method reports either head on demand, and queries outside the engine API are not consistently served on the authenticated port nor specified precisely enough for a deterministic sync loop to be built on them.

A single head rather than a set of heads

An alternative design returns the set of chain tips the client is able to extend — every head of the block subtree rooted in the finalized block — giving the consensus client a definitive delivery point for any fork it may switch to. This obliges the execution client to track all such tips and was set aside in favor of a single head carrying the same per-head guarantees (a child of the reported head is never answered with SYNCING, and the report only changes in response to engine_newPayload/engine_forkchoiceUpdated): when the reported head is not on the consensus client's canonical chain, the consensus client can locate the fork point against its own chain and re-deliver from there, exactly as when responding to SYNCING. A status query for an arbitrary block hash, allowing a common ancestor to be found in lock step, can later be added as an extension without changing this method's semantics.

New status value rather than a new field

MISSING_STATE follows the precedent of ACCEPTED, which was added to the payload status enum without altering the response structure. A separate syncingReason field on the SYNCING status was considered, but a distinct status value keeps dispatch on the consensus client side a simple switch over the enum and cannot be silently ignored by consensus client implementations.

Two heads

A client has two distinct notions of "head": the head of its block chain and the head of its executed chain, and syncing benefits from decoupling them. The block head drives payload delivery, so the client does not have to execute every block to accept it — it can defer execution and instead fast-forward its state, e.g., via snap sync or EIP-7928 block access lists, executing only near the network head. The executed head drives validation status: it is the block that latestValidHash references in a VALID PayloadStatus response, and it determines how far the consensus client may consider the chain validated. A distinct field name — executedBlockHash — is used regardless, because latestValidHash in an INVALID response is branch-relative (the last valid ancestor of the invalid branch), while the executed head is a property of the canonical chain and may even move backwards between calls when the chain reorgs below it. A richer sync-progress report (e.g., pivot block, downloaded trie ranges) is left to future extensions of this method: the two heads are sufficient for driving sync.

Batched payload delivery

Delivering historical payloads in batches (e.g., 1024 payloads per call) and reduced verification for finalized blocks would further improve top-up throughput. Both are deliberately left to future EIPs; this EIP is the minimal change that makes the choreography correct.

Backwards Compatibility

engine_getSyncStatus is a new method and does not affect existing consumers. MISSING_STATE is only returned by the method versions of engine_newPayload and engine_forkchoiceUpdated introduced alongside this EIP; prior method versions retain their existing semantics, where SYNCING covers both missing-data cases. Consensus clients that do not implement top-up sync can treat MISSING_STATE exactly as they treat SYNCING today without loss of correctness.

Unlike prior method versions, which each accept payloads of exactly one fork, the engine_newPayload version introduced alongside this EIP accepts payloads of all earlier forks — a deliberate departure from established engine API versioning, required for top-up across fork boundaries.

Security Considerations

Blocks delivered via engine_newPayload during top-up are validated by the same rules as blocks received during regular operation. Since the consensus client only feeds payloads on its canonical chain, the execution client's exposure to invalid or adversarial block data is reduced relative to devp2p block download.

The requirement to persist payloads answered with MISSING_STATE implies unbounded storage growth if the executed head never catches up. Whether execution clients may prune such stored payloads (relying on the consensus client to re-deliver them) and what bounds apply to this buffering need discussion.

The engine API is served on an authenticated port, so both engine_getSyncStatus and the MISSING_STATE status are only exposed to the trusted consensus client and do not leak sync state to third parties.

The choreography assumes a single consensus client driving the engine API. Multiple consensus clients attached to the same execution client interleave engine_newPayload and engine_forkchoiceUpdated calls, voiding the stability guarantee of engine_getSyncStatus from each individual client's perspective; this matches the existing engine API, where a multiplexed execution client follows the most recent forkchoice update, and multiplexer software is expected to serialize top-up traffic.

TODO: whether this EIP should place normative requirements on multiplexer software (e.g., serializing top-up traffic per consensus client, or presenting each consensus client with a consistent engine_getSyncStatus view) remains to be decided.

Copyright

Copyright and related rights waived via CC0.