Status (2026-10-01) — decomposed, closing
The serve path for blinded payload-envelope storage shipped in #10089: blinded finalized-archive storage + EL-backed reconstruction for execution_payload_envelopes_by_range / _by_root, with transactions / withdrawals / blockAccessList restored and root-verified on every read (incl. unit handler tests for short-return, RESOURCE_UNAVAILABLE, and by-root omit-on-mismatch).
Remaining work is split into focused follow-ups:
Hot-DB / import-time blinding is intentionally deferred and kept as the TODO GLOAS in packages/beacon-node/src/chain/blocks/writePayloadEnvelopeInputToDb.ts — not tracked as a separate issue.
Acceptance criteria (original)
Original issue description
Problem
We already know Lodestar should avoid persisting duplicate full execution payload bodies on the CL side and instead keep a blinded representation + reconstruct the missing EL-side body data on demand.
There is already a broad tracking issue for the storage dedup idea:
But we should keep a more specific pre-testnet reminder for the serve path:
if Lodestar stores the execution payload envelope / block in blinded form, it must still be able to serve peers correctly during sync (especially beacon_blocks_by_range / beacon_blocks_by_root style serving), reconstructing the missing execution payload body data from EL and restoring any Gloas-specific fields such as BAL before serving.
Terence warned that Prysm hit a late bug in this area: they were not unblinding / re-adding BAL correctly when serving to peers.
Why this needs a dedicated reminder
This can easily be missed if we only test:
- local import / processing
- proposer-side flows
- REST full-block reconstruction
The tricky part is the sync serving path:
- range sync serving works
- block/root serving works
- the served payload is reconstructed from EL correctly
- BAL / other body-only fields are restored correctly before serving
- peers can consume the served block/payload without blinded/full mismatch bugs
Prysm reference points
I could not cleanly pull the exact ethpandaops/prysm:glamsterdam-devnet-0 branch into the local checkout, but the current local Prysm repo already shows the relevant shape:
1. Store the envelope in blinded form
beacon-chain/db/kv/execution_payload_envelope.go
SaveExecutionPayloadEnvelope() replaces the full payload with its blockHash
- keeps metadata like
executionRequests, builderIndex, beaconBlockRoot, slot, stateRoot
- comment explicitly says the full payload can later be retrieved from EL via
engine_getPayloadBodiesByHash
2. Reconstruct full payload bodies from EL on demand
beacon-chain/execution/payload_body.go
reconstructBlindedBlockBatch()
requestBodiesByHash() using engine_getPayloadBodiesByHashV1
- fallback
requestBodiesByRange() using engine_getPayloadBodiesByRangeV1
fullPayloadFromPayloadBody() rebuilds the full execution payload from the blinded header + EL body data
3. Use reconstructed full blocks for serving/API paths
beacon-chain/rpc/eth/beacon/handlers.go
GetBlockV2() reconstructs the full block when the stored block is blinded:
ExecutionReconstructor.ReconstructFullBlock(ctx, blk)
Lodestar context
Lodestar already has the relevant TODO in:
packages/beacon-node/src/chain/blocks/writePayloadEnvelopeInputToDb.ts
// TODO GLOAS: Persist envelope metadata (stateRoot, executionRequests, builderIndex, etc.) without the full
// execution payload body — only keep the blockHash reference. The EL already stores the payload.
// See https://github.com/ChainSafe/lodestar/issues/5671
Lodestar also already has EL payload body fetch methods:
packages/beacon-node/src/execution/engine/http.ts
getPayloadBodiesByHash()
getPayloadBodiesByRange()
Requested work / acceptance criteria
Before testnets, we should make sure Lodestar either implements or explicitly tests the full serve path for blinded envelope storage:
Related
Status (2026-10-01) — decomposed, closing
The serve path for blinded payload-envelope storage shipped in #10089: blinded finalized-archive storage + EL-backed reconstruction for
execution_payload_envelopes_by_range/_by_root, withtransactions/withdrawals/blockAccessListrestored and root-verified on every read (incl. unit handler tests for short-return,RESOURCE_UNAVAILABLE, and by-root omit-on-mismatch).Remaining work is split into focused follow-ups:
engine_getPayloadBodiesByHashV2+ BAL retention, Update EIP-7928: align BAL retention with the history expiry window ethereum/EIPs#12347)Hot-DB / import-time blinding is intentionally deferred and kept as the
TODO GLOASinpackages/beacon-node/src/chain/blocks/writePayloadEnvelopeInputToDb.ts— not tracked as a separate issue.Acceptance criteria (original)
execution_payload_envelopes_by_rangeserving works — feat: de-duplicate execution payload from archived Gloas envelopes #10089execution_payload_envelopes_by_rootserving works — feat: de-duplicate execution payload from archived Gloas envelopes #10089Original issue description
Problem
We already know Lodestar should avoid persisting duplicate full execution payload bodies on the CL side and instead keep a blinded representation + reconstruct the missing EL-side body data on demand.
There is already a broad tracking issue for the storage dedup idea:
De-duplicate payload from persisted beacon blocksBut we should keep a more specific pre-testnet reminder for the serve path:
Terence warned that Prysm hit a late bug in this area: they were not unblinding / re-adding BAL correctly when serving to peers.
Why this needs a dedicated reminder
This can easily be missed if we only test:
The tricky part is the sync serving path:
Prysm reference points
I could not cleanly pull the exact
ethpandaops/prysm:glamsterdam-devnet-0branch into the local checkout, but the current local Prysm repo already shows the relevant shape:1. Store the envelope in blinded form
beacon-chain/db/kv/execution_payload_envelope.goSaveExecutionPayloadEnvelope()replaces the full payload with itsblockHashexecutionRequests,builderIndex,beaconBlockRoot,slot,stateRootengine_getPayloadBodiesByHash2. Reconstruct full payload bodies from EL on demand
beacon-chain/execution/payload_body.goreconstructBlindedBlockBatch()requestBodiesByHash()usingengine_getPayloadBodiesByHashV1requestBodiesByRange()usingengine_getPayloadBodiesByRangeV1fullPayloadFromPayloadBody()rebuilds the full execution payload from the blinded header + EL body data3. Use reconstructed full blocks for serving/API paths
beacon-chain/rpc/eth/beacon/handlers.goGetBlockV2()reconstructs the full block when the stored block is blinded:ExecutionReconstructor.ReconstructFullBlock(ctx, blk)Lodestar context
Lodestar already has the relevant TODO in:
packages/beacon-node/src/chain/blocks/writePayloadEnvelopeInputToDb.tsLodestar also already has EL payload body fetch methods:
packages/beacon-node/src/execution/engine/http.tsgetPayloadBodiesByHash()getPayloadBodiesByRange()Requested work / acceptance criteria
Before testnets, we should make sure Lodestar either implements or explicitly tests the full serve path for blinded envelope storage:
beacon_blocks_by_rangeserving worksbeacon_blocks_by_rootserving worksSignedExecutionPayloadEnvelopeentries; see the #10089 review thread and theexecutionPayloadEnvelopeArchive.tsline-35 comment)Related
De-duplicate payload from persisted beacon blocks