Skip to content

Instantly share code, notes, and snippets.

@lodekeeper
Created April 15, 2026 15:20
Show Gist options
  • Select an option

  • Save lodekeeper/4589ba76a356622046b77d087bca0fc4 to your computer and use it in GitHub Desktop.

Select an option

Save lodekeeper/4589ba76a356622046b77d087bca0fc4 to your computer and use it in GitHub Desktop.
Execution requests slot attribution analysis: alpha.4 vs PR #5094

Execution Requests Slot Attribution: Alpha.4 vs PR #5094

Analysis of whether execution requests being processed at the child's slot (N+1) instead of the parent's slot (N) introduces meaningful differences.

The concern

In PR #5094, apply_parent_execution_payload contains:

# Process execution requests from parent's payload. The execution
# requests are processed at state.slot (child's slot), not the parent's slot.

In alpha.4, process_execution_payload runs at slot N (parent's slot). In #5094, process_parent_execution_payload runs at slot N+1 (child's slot).

Can we process at the parent's slot instead?

No — it's not safe at epoch boundaries. By the time apply_parent_execution_payload runs during process_block at slot N+1, process_slots has already advanced the state and (if at an epoch boundary) process_epoch has already mutated the state. Setting state.slot = N would give you epoch E's slot context against epoch E+1's state data (post-epoch balances, validator activations, exit queue). This mismatch is worse than the 1-slot offset.

Processing before process_slots is impossible — you need the block to know the requests, but state_transition runs process_slots before process_block.

What concretely changes

PendingDeposit.slot

process_deposit_request creates PendingDeposit(slot=state.slot).

process_pending_deposits gates processing on:

if deposit.slot > finalized_slot:
    break
  • Alpha.4: PendingDeposit.slot = N → processable when epoch of N is finalized
  • #5094: PendingDeposit.slot = N+1 → processable when epoch of N+1 is finalized

At an epoch boundary (N = last slot of epoch E, N+1 = first slot of epoch E+1): the deposit waits an extra ~6.4 minutes for epoch E+1 to be finalized. In all other cases, N and N+1 are in the same epoch — no difference.

Withdrawal/consolidation requests

process_withdrawal_request and process_consolidation_request use get_current_epoch(state) for eligibility checks (is_active_validator, SHARD_COMMITTEE_PERIOD, exit queue computation).

At epoch boundaries, a 1-epoch difference could theoretically affect eligibility, but requires a validator to cross an activation/exit/period threshold at that exact epoch — an extremely rare edge case.

CL+EL snapshot consistency

For consumers like Lido's oracle that need consistent CL+EL snapshots:

State Withdrawals (CL→EL) Deposits (EL→CL)
block_states[X] (alpha.4) Applied ✓ NOT applied ✗
block_states[X] (#5094) Applied ✓ NOT applied ✗
block_states[X+1] (both) Applied ✓ Applied ✓

The canonical state is identical in both versions. Deposits from slot X only appear at slot X+1 in the canonical state. In alpha.4, payload_states[X] has deposits at slot X, but this is a fork choice internal — not exposed via any beacon API, not part of checkpoint states.

How consumers can resolve this

Option 1: Snapshot at slot X+1 paired with EL block from slot X. The CL state at X+1 includes all execution effects from slot X. Works identically in alpha.4 and #5094.

Option 2: Account for in-flight requests. At slot X, read block.body.parent_execution_requests from block X+1 to know what deposits are pending. The requests are committed via execution_requests_root in the bid, so they're deterministic.

Option 3: Pair CL state with the previous slot's EL block. CL state at slot X includes effects from slot X-1's payload (applied during block X's processing). Pair with EL block from slot X-1 for a consistent snapshot with no gap.

Conclusion

The 1-slot offset in execution request processing is an inherent property of deferred payload processing — it cannot be eliminated without reintroducing dual states (payload_states). The canonical state behavior is identical between alpha.4 and #5094.

The only concrete new effect is PendingDeposit.slot = N+1 instead of N, which at epoch boundaries delays deposit processability by ~6.4 minutes. Given deposits are queue-based with multi-epoch finalization delays, this is acceptable.

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