Skip to content

Comments

fix: repair Gloas block replay crash with IncorrectStateVariant#8876

Closed
bit2swaz wants to merge 1 commit intosigp:stablefrom
bit2swaz:fix/8869-gloas-payload-replay
Closed

fix: repair Gloas block replay crash with IncorrectStateVariant#8876
bit2swaz wants to merge 1 commit intosigp:stablefrom
bit2swaz:fix/8869-gloas-payload-replay

Conversation

@bit2swaz
Copy link

Issue Addressed

Fixes #8869

Proposed Changes

Gloas (EIP-7732) decouples the execution payload from the beacon block, the block body carries only a signed_execution_payload_bid, while the actual payload arrives separately as a SignedExecutionPayloadEnvelope. Two code paths in per_block_processing were calling pre-Gloas accessors that don't exist on Gloas block bodies, causing block replay to crash with IncorrectStateVariant and the HTTP API to return 500 UNHANDLED_ERROR for any finalized
Gloas state.

Bug 1: execution payload path (per_block_processing.rs)
is_execution_enabled() returns true for Gloas (Capella is always enabled, making is_merge_transition_complete return true). This caused body.execution_payload()? to be called on a Gloas body, which returns Err(IncorrectStateVariant).
Fix: guard the process_withdrawals / process_execution_payload block with && !gloas_enabled().

Bug 2: Electra operations path (process_operations.rs)
electra_enabled() is also true for Gloas, causing block_body.execution_requests()? to be called. That field only exists on Electra/Fulu bodies, so Gloas returned Err(IncorrectStateVariant).
Fix: guard the execution-requests block with && !gloas_enabled().

Envelope processing architecture
Following the EIP-7732 spec design, envelopes are applied between blocks using a look-ahead:
after applying block N, if block N+1's bid.parent_block_hash matches state.latest_execution_payload_bid.block_hash, the envelope for block N is applied before continuing. This is wired through:

  • process_execution_payload_envelope(): new function that validates the hash chain and updates state.latest_block_hash.
  • BlockReplayer: new payload_envelopes map and look-ahead loop in apply_blocks.
  • HotColdDB: new DBColumn::ExecPayloadEnvelope, put/get_execution_payload_envelope methods, and replay_blocks wired to load and pass envelopes for any Gloas blocks in the replay range.

Regression test
gloas_block_replay_no_incorrect_state_variant: builds a minimal Gloas genesis, constructs a
valid blinded Gloas block at slot 1, and asserts BlockReplayer::apply_blocks succeeds.

Additional Info

  • The envelope processing in process_execution_payload_envelope is intentionally minimal for now (updates latest_block_hash`, validates parent hash chain). Withdrawals, execution requests, and other envelope contents are left as TODOs, consistent with the broader in-progress EIP-7732 implementation in this codebase.
  • put_execution_payload_envelope is added but not yet called at block import time; that wiring belongs in the beacon chain's block processing pipeline and can be done as a follow-up once the full Gloas block import path is built out.
  • All 39 existing state_processing unit tests pass. cargo clippy -D warnings and cargo fmt are clean on the changed packages.

…igp#8869)

Gloas (EIP-7732) decouples the execution payload from the beacon block:
the block body carries only a signed_execution_payload_bid, while the
actual payload arrives as a separate SignedExecutionPayloadEnvelope.

Two code paths in per_block_processing incorrectly called pre-Gloas
accessors that are not defined on Gloas block bodies:

1. process_execution_payload / process_withdrawals:
   is_execution_enabled() returns true for Gloas (Capella is always
   enabled), so body.execution_payload()? was called and returned
   Err(IncorrectStateVariant) for Gloas block bodies.

2. process_operations (Electra path):
   electra_enabled() is also true for Gloas, causing
   block_body.execution_requests()? to be called. That field only
   exists on Electra/Fulu bodies, so Gloas returned
   Err(IncorrectStateVariant) again.

Fixes applied:
  so they are skipped for Gloas blocks.
- Add process_execution_payload_envelope() which applies a
  SignedExecutionPayloadEnvelope to a Gloas state (updates
  latest_block_hash; TODO: withdrawals, execution_requests, etc.).
- Add a look-ahead loop in BlockReplayer: after each Gloas block N,
  compare block N+1's bid.parent_block_hash against
  state.latest_execution_payload_bid.block_hash; if they match, apply
  the envelope for block N before continuing.
- Add DBColumn::ExecPayloadEnvelope and
  put/get_execution_payload_envelope methods in hot_cold_store so
  envelopes can be stored and retrieved by ExecutionBlockHash.
- Wire replay_blocks to load envelopes for all Gloas blocks in the
  replay range and pass them to BlockReplayer.

A regression test gloas_block_replay_no_incorrect_state_variant
confirms the fix: it builds a minimal Gloas genesis and replays one
Gloas block, asserting per_block_processing succeeds.
@cla-assistant
Copy link

cla-assistant bot commented Feb 20, 2026

CLA assistant check
All committers have signed the CLA.

Copy link
Member

@eserilev eserilev left a comment

Choose a reason for hiding this comment

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

Looks like this PR duplicates some DB functionality that already exists from this PR
https://github.com/sigp/lighthouse/pull/8717/changes#top

We should just use the existing db operations. Also note that the payload envelopes are keyed by beacon block root, not execution block hash

/// - Key: 32-byte execution `block_hash` from the block's `signed_execution_payload_bid`.
/// - Value: SSZ-encoded `SignedExecutionPayloadEnvelope`.
#[strum(serialize = "ppe")]
ExecPayloadEnvelope,
Copy link
Member

Choose a reason for hiding this comment

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

We already have a PayloadEnvelope column defined

/// The key is the execution `block_hash` committed to by the builder in the corresponding
/// `signed_execution_payload_bid`. This allows efficient lookup during block replay using
/// only the information present in each block's bid header.
pub fn put_execution_payload_envelope(
Copy link
Member

Choose a reason for hiding this comment

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

we already have a function payload_envelope_as_kv_store_ops that does this

/// Returns `None` when no envelope has been stored for the given hash (e.g. the builder did
/// not deliver the payload for that slot, or envelope storage is not yet implemented for the
/// current database schema).
pub fn get_execution_payload_envelope(
Copy link
Member

Choose a reason for hiding this comment

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

we already have a function get_payload_envelope that does this. Note that we fetch execution payload envelopes by block root, not by execution block hash

@eserilev eserilev added waiting-on-author The reviewer has suggested changes and awaits thier implementation. gloas labels Feb 20, 2026
@eserilev
Copy link
Member

eserilev commented Feb 20, 2026

I'm sorry, I'm going to go ahead and close this PR. This reads as fully LLM-generated which isn't something were interested in accepting. I'm not anti-LLM but we could produce this same output ourselves in a matter of minutes. Contributing should be a learning experience and I'm happy to support external contributors who want to dig in and genuinely engage with the code.

@eserilev eserilev closed this Feb 20, 2026
@bit2swaz bit2swaz deleted the fix/8869-gloas-payload-replay branch February 20, 2026 20:32
@bit2swaz bit2swaz restored the fix/8869-gloas-payload-replay branch February 20, 2026 22:34
@bit2swaz
Copy link
Author

I'm sorry, I'm going to go ahead and close this PR. This reads as fully LLM-generated which isn't something were interested in accepting. I'm not anti-LLM but we could produce this same output ourselves in a matter of minutes. Contributing should be a learning experience and I'm happy to support external contributors who want to dig in and genuinely engage with the code.

ah, fair point. i leaned way too heavily on an llm to try and blast through the boilerplate this weekend and completely missed the broader context of #8717 and the correct db keys. lesson learned.

i genuinely want to dig into the codebase and learn the EIP-7732 architecture properly. if youre still open to it, id love to scrap this branch, review the db operations you linked in #8717, and take a manual, ground up pass at wiring the look ahead loop the right way. if not, i completely understand keeping it closed.

sorry for any inconveniences i may have caused

@eserilev
Copy link
Member

@bit2swaz hey no worries, yes please feel free to continue working on this. Were going to update our contribution guidelines regarding AI usage, I think the guidelines here might give a general idea of what we think good contributions could look like #8879

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gloas waiting-on-author The reviewer has suggested changes and awaits thier implementation.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Update block replayer for Gloas

2 participants