EIP-8379 Top-up sync: consensus-layer-led execution-layer sync (Top-up Sync)

Declined for Inclusion · Networking & nodes · EIP status Draft · created 2026-08-06

Adds Engine API methods that let the consensus client query the execution client's block head and execution head, and adds response values that distinguish the no-state case from the no-block-history case, so that the consensus client can directly lead the execution client's block sync.

Why it is needed

Today the consensus client and the execution client each receive blocks on their own. The consensus client uses gossip and req/resp, and the execution client uses devp2p, so the same networking function is duplicated across two layers. For the consensus client to hand blocks directly to the execution client, it needs to know which block the execution client has up to, and up to which block it has executed and holds state, but the Engine API has no way to ask this. The two values differ when the execution client has received blocks but not executed them. This very difference makes an efficient sync possible, in which blocks are received without execution and the state is caught up quickly by another method. Also, when engine_newPayload returns SYNCING, the consensus client cannot tell whether an ancestor block is missing (in which case it must send the ancestors) or whether there is no pre-state because of a snap sync (in which case sending is pointless and it must wait). Distinguishing these two is what makes the exchange between engine_forkchoiceUpdated and engine_newPayload run deterministically.

What changes

Today

After EIP-8379

Who is affected

In depth

engine_getSyncStatus method

Adds an engine_getSyncStatus method to the Engine API. Clients must support this method from the fork activation that includes this EIP and must advertise support through engine_exchangeCapabilities. It takes no parameters and has a 500ms timeout. The response is a SyncStatus object with four fields. headBlockHash (DATA, 32 bytes) and headBlockNumber (QUANTITY, 64 bits) point to the latest block on the client's canonical chain. executedBlockHash (DATA, 32 bytes or null) and executedBlockNumber (QUANTITY, 64 bits or null) point to the latest canonical block for which the client holds the post-state. The block that latestValidHash in a VALID response points to is the same block as this executed head. The non-normative Interface migration section describes the mapping when moving to the REST/SSZ Engine API: engine_getSyncStatus becomes a non-scoped endpoint GET /syncstatus, similar to GET /identity, and MISSING_STATE is added to the payload status SSZ enumeration of the relevant fork-scoped endpoints.

Rules for reporting the block head and the executed head

The executed head is the latest block among the canonical ancestors of the block head, or the block head itself, that is fully validated and has a post-state. If no such block exists, for example before initial state sync finishes, executedBlockHash and executedBlockNumber are set to null. The block head is the latest block among the held blocks that connect without a gap, starting from the executed head (or from genesis if there is no executed head), and that follow the canonical chain chosen by the most recent engine_forkchoiceUpdated. So a freshly started client reports the genesis block until initial state sync finishes. An engine_newPayload call for a payload whose parent is the reported block head must be answered with one of VALID, INVALID, or MISSING_STATE, and must not be answered with SYNCING. If no engine_newPayload or engine_forkchoiceUpdated has been processed between two calls, the client must return the same SyncStatus. A block received through engine_newPayload that has not yet been attached to the canonical chain must not be reported.

MISSING_STATE status value: execution client rules

Adds MISSING_STATE to the payload status enumeration of the engine_newPayload and engine_forkchoiceUpdated versions introduced with this EIP. If the client knows all ancestor blocks of the payload but could not execute it because the required pre-state is missing, it returns {status: MISSING_STATE, latestValidHash: null, validationError: null}. A payload answered this way must be kept for later processing and extends the block chain as if it had been executed. If that payload later becomes canonical through engine_forkchoiceUpdated, the block head advances but the executed head stays where it is. engine_forkchoiceUpdated also returns the same MISSING_STATE response if the client knows the block that headBlockHash points to and all its ancestors but could not execute it because the pre-state is missing. SYNCING is returned only when ancestor block data needed for processing is missing. The gap between the executed head and the block head may be filled by any method. The client may execute the stored payloads in order, or it may fetch the state directly through snap sync or an EIP-7928 block access list fast-forward and skip executing the blocks below that point.

Consensus client response rules and backwards compatibility

When a consensus client receives SYNCING, it SHOULD fetch the missing ancestor payloads from its own block store or the req/resp protocol and deliver them in ascending order through engine_newPayload. When it receives MISSING_STATE, it SHOULD keep sending the next payloads and engine_forkchoiceUpdated calls as its own head advances, and it MUST NOT interpret MISSING_STATE as a request for ancestor payloads. It also must not treat a payload answered with MISSING_STATE as validated. That payload remains unverified until the execution client's executed head passes that block. On backwards compatibility, engine_getSyncStatus is a new method, so existing consumers are unaffected, and MISSING_STATE is returned only from new method versions. Earlier versions keep their existing semantics, and SYNCING covers both cases. A consensus client that does not implement top-up sync can treat MISSING_STATE exactly like today's SYNCING without losing correctness.

Top-up across fork boundaries and the engine_newPayload version

Because a single top-up can cross a fork boundary, the execution client must accept payloads from earlier forks through the engine_newPayload version introduced with this EIP, and must apply the validation rules of the fork the payload belongs to. The consensus client SHOULD deliver historical payloads through this new version rather than the version matching the payload's fork, and MUST send along the auxiliary data that fork requires (for example blob versioned hashes and execution requests). This makes MISSING_STATE usable across fork boundaries. Each existing Engine API version accepted payloads from exactly one fork, so this is a deliberate departure from the established versioning approach. The source describes it as a change that is necessary for top-up across fork boundaries. The forkchoice state passed through engine_forkchoiceUpdated is the same as in the existing specification.

Design rationale: new method, single head, new status value, separate field name

The new method exists because top-up sync reverses the direction of control. The execution client no longer fetches blocks itself, so even in a passive state it needs a meaningful query, and none of the existing Engine API methods reports either head directly. Queries outside the Engine API are not consistently offered on the authenticated port and are not precise enough to build a deterministic sync loop on. An alternative that returns every tip of the subtree rooted at the finalized block was set aside because the execution client would have to track all tips. Even with a single head, there is a guarantee that children of the reported head are never answered with SYNCING and that the report changes only in response to engine_newPayload or engine_forkchoiceUpdated, so the consensus client can find the divergence point and redeliver. A state query for an arbitrary block hash can be added later as an extension. MISSING_STATE follows the precedent of ACCEPTED, which was added without changing the response structure. A proposal to attach a syncingReason field to SYNCING was also considered, but with a separate status value the consensus client's branching stays a simple switch and cannot be silently ignored. The separate name executedBlockHash is used because latestValidHash in an INVALID response is relative to the invalid branch, whereas the executed head is a property of the canonical chain and can move backwards if a reorg happens beneath it.

Security considerations and open questions

Blocks delivered through engine_newPayload during top-up are validated by the same rules as blocks received in normal operation. Because the consensus client sends only payloads from its own canonical chain, the execution client is less exposed to malformed or malicious block data than with devp2p block downloads. The Engine API is served on the authenticated port, so engine_getSyncStatus and MISSING_STATE are exposed only to the trusted consensus client and do not leak sync state to third parties. On the other hand, the requirement to retain payloads answered with MISSING_STATE means storage could grow without bound if the executed head never catches up. The source says it needs discussion whether the execution client may drop such payloads and rely on the consensus client to redeliver them, and how to set the buffer limit. The design assumes a single consensus client drives the Engine API. If several consensus clients attach to one execution client, their calls interleave and the stability guarantees from each client's perspective break, and multiplexer software is expected to serialize top-up traffic. Whether to place normative requirements on the multiplexer remains a TODO. Batched payload delivery in units such as 1024 payloads and relaxed validation of finalized blocks are intentionally deferred to a future EIP.

Current status and fork discussion

This EIP is in Draft status, so the method name, fields, and the meaning of the status value may still change. In fork discussions, EIP-8379 was accepted as a Proposed for Inclusion (PFI) candidate for Hegotá at the 08.20 ACDC #185. This meant an Engine API extension that makes the consensus client the single source of truth for sync had become a candidate. Then at the 10.01 ACDC #188, Top Up Sync was declined for inclusion (DFI) in Hegotá. Teams that want it can continue experimenting with the Engine API outside the fork schedule.

Original EIP · ← EIPs