Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
48fdac7
Defer payload processing to next block
nflaig Apr 10, 2026
baa90e2
don't seed anchor_root in payloads
nflaig Apr 10, 2026
d723e0b
move execution requests match the bid commitment check
nflaig Apr 10, 2026
4efcb35
remove unncessary state copy & mutation
nflaig Apr 10, 2026
6a02647
cleanup
nflaig Apr 10, 2026
5d259e9
A little more cleanup
jtraglia Apr 10, 2026
2a762fe
wording
nflaig Apr 11, 2026
b5998c2
recompute state root to verify beacon block root
nflaig Apr 11, 2026
1041bf4
comments
nflaig Apr 11, 2026
b1bb1a7
update heze
nflaig Apr 11, 2026
5a47768
nits
nflaig Apr 11, 2026
9c7d24d
move payload availability update after verification
nflaig Apr 11, 2026
9de223e
add missing payloads=set()
nflaig Apr 11, 2026
3883ee8
Merge branch 'master' into deferred-payload-processing
nflaig Apr 11, 2026
2ea00dd
wording
nflaig Apr 12, 2026
2267bcc
remove is_parent_block_full
nflaig Apr 13, 2026
edfc9f2
drop redundant parent block checks from gossip and on_block
nflaig Apr 13, 2026
b8dce83
use should_extend_payload
nflaig Apr 13, 2026
7321908
update builder instructions
nflaig Apr 13, 2026
8014017
rename to verify_execution_payload
nflaig Apr 13, 2026
ec3fd1b
rename to verify_execution_payload_envelope
nflaig Apr 13, 2026
a7f02ca
wording
nflaig Apr 14, 2026
e307112
cleanup
nflaig Apr 14, 2026
1000bf8
Update tests
jtraglia Apr 14, 2026
2e53b0f
Move verification functions to fork-choice.md
jtraglia Apr 14, 2026
2154fb6
Do not run capella process_execution_payload test in Gloas
jtraglia Apr 14, 2026
50c6210
update prepare_execution_payload
nflaig Apr 14, 2026
3fdde79
don't seed anchor_root in payloads
nflaig Apr 14, 2026
1f219f0
refactor
nflaig Apr 14, 2026
21dccc2
move verification functions back to beacon-chain.md
nflaig Apr 14, 2026
2fa1768
fix sections
nflaig Apr 14, 2026
d967e8f
update note
nflaig Apr 14, 2026
9c00e07
Merge branch 'master' into deferred-payload-processing
nflaig Apr 14, 2026
b1557f6
update references to on_execution_payload_envelope
nflaig Apr 14, 2026
cdf8dbf
restructure beacon-chain.md
nflaig Apr 14, 2026
d1b8306
remove helper change
nflaig Apr 14, 2026
97073fb
move execution payload section
nflaig Apr 14, 2026
a8c4391
typo
nflaig Apr 14, 2026
8e222c2
update tests
nflaig Apr 14, 2026
11e2a30
Fix some issues with tests
jtraglia Apr 14, 2026
6e7da52
Update test comments
jtraglia Apr 14, 2026
f870edc
set execution_requests_root on genesis/anchor block bodies
nflaig Apr 14, 2026
31b788e
use should_extend_payload again
nflaig Apr 14, 2026
ab3d73e
lint
nflaig Apr 14, 2026
3284032
wording
nflaig Apr 14, 2026
6b37268
add is_payload_verified
nflaig Apr 15, 2026
26ed32e
Refactor verify_execution_payload_envelope
jtraglia Apr 15, 2026
0a4bb37
Merge branch 'master' into deferred-payload-processing
nflaig Apr 15, 2026
f3f53bd
Merge branch 'master' into deferred-payload-processing
jtraglia Apr 15, 2026
dfce0c0
Fix process_parent_execution_payload tests
jtraglia Apr 15, 2026
ada2db4
Deprecate process_execution_payload
jtraglia Apr 15, 2026
6be2385
Move verification function to fork-choice specs
jtraglia Apr 15, 2026
5597ae2
Remove confusing note
jtraglia Apr 16, 2026
0923d8a
Fix builder payment issue after missed epochs
jtraglia Apr 16, 2026
ffcf9f8
Rename helper to settle_builder_payment
jtraglia Apr 16, 2026
8d6b635
nits
nflaig Apr 16, 2026
f2ff66f
Merge branch 'master' into deferred-payload-processing
jtraglia Apr 16, 2026
d6293cf
remove verify_execution_payload_envelope note
nflaig Apr 16, 2026
f48d980
add guard to should_extend_payload for missing payloads
nflaig Apr 16, 2026
cfcd9b6
Merge branch 'master' into deferred-payload-processing
jtraglia Apr 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pysetup/spec_builders/gloas.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ def deprecate_functions(cls) -> set[str]:
return set(
[
"compute_proposer_index",
"process_execution_payload",
"retrieve_column_sidecars",
]
)
Expand Down
256 changes: 118 additions & 138 deletions specs/gloas/beacon-chain.md

Large diffs are not rendered by default.

26 changes: 12 additions & 14 deletions specs/gloas/builder.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,14 +102,17 @@ proposer what it promised whether it submits the payload or not.
Builders can broadcast a payload bid for the current or the next slot's proposer
to include. They produce a `SignedExecutionPayloadBid` as follows.

01. Set `bid.parent_block_hash` to the current head of the execution chain (this
can be obtained from the beacon state as `state.latest_block_hash`).
02. Set `bid.parent_block_root` to be the head of the consensus chain; this can
01. Set `bid.parent_block_hash` to the current head of the execution chain. Let
`parent_root = hash_tree_root(state.latest_block_header)`. This is
`state.latest_execution_payload_bid.block_hash` if
`should_extend_payload(store, parent_root)` is true, otherwise
`state.latest_execution_payload_bid.parent_block_hash`.
02. Set `bid.parent_block_root` to be the head of the consensus chain. This can
be obtained from the beacon state as
`hash_tree_root(state.latest_block_header)`. The `parent_block_root` and
`parent_block_hash` must be compatible, in the sense that they both should
come from the same `state` by the method described in this and the previous
point.
come from the same `state` and `store` by the method described in this and
the previous point.
03. Construct an execution payload. This can be performed with an external
execution engine via a call to `engine_getPayloadV5`.
04. Set `bid.block_hash` to be the block hash of the constructed payload, that
Expand All @@ -134,6 +137,9 @@ to include. They produce a `SignedExecutionPayloadBid` as follows.
be broadcast to the `execution_payload_bid` gossip topic.
12. Set `bid.blob_kzg_commitments` to be the `blobsbundle.commitments` field
returned by `engine_getPayloadV5`.
13. Set `bid.execution_requests_root` to `hash_tree_root(execution_requests)`,
where `execution_requests` is the `ExecutionRequests` field returned by
`engine_getPayloadV5`.
Comment thread
jtraglia marked this conversation as resolved.

After building the `bid`, the builder obtains a `signature` of the bid by using:

Expand Down Expand Up @@ -240,15 +246,7 @@ alias `bid` to be the committed `ExecutionPayloadBid` in
4. Set `envelope.beacon_block_root` to be `hash_tree_root(block)`.
5. Set `envelope.slot` to be `block.slot`.

After setting these parameters, the builder assembles
`signed_execution_payload_envelope = SignedExecutionPayloadEnvelope(message=envelope, signature=BLSSignature())`,
then verify that the envelope is valid with
`process_execution_payload(state, signed_execution_payload_envelope, execution_engine, verify=False)`.
This function should not trigger an exception.

7. Set `envelope.state_root` to `hash_tree_root(state)`.

After preparing the `envelope` the builder should sign the envelope using:
After preparing the `envelope` the builder signs it using:

```python
def get_execution_payload_envelope_signature(
Expand Down
136 changes: 104 additions & 32 deletions specs/gloas/fork-choice.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
- [Modified `Store`](#modified-store)
- [Modified `get_forkchoice_store`](#modified-get_forkchoice_store)
- [New `notify_ptc_messages`](#new-notify_ptc_messages)
- [New `is_payload_verified`](#new-is_payload_verified)
- [New `is_payload_timely`](#new-is_payload_timely)
- [New `is_payload_data_available`](#new-is_payload_data_available)
- [New `get_parent_payload_status`](#new-get_parent_payload_status)
Expand All @@ -39,6 +40,8 @@
- [Modified `get_sync_message_due_ms`](#modified-get_sync_message_due_ms)
- [Modified `get_contribution_due_ms`](#modified-get_contribution_due_ms)
- [New `get_payload_attestation_due_ms`](#new-get_payload_attestation_due_ms)
- [New `verify_execution_payload_envelope_signature`](#new-verify_execution_payload_envelope_signature)
- [New `verify_execution_payload_envelope`](#new-verify_execution_payload_envelope)
- [Handlers](#handlers)
- [Modified `on_block`](#modified-on_block)
- [Modified `is_data_available`](#modified-is_data_available)
Expand Down Expand Up @@ -122,10 +125,6 @@ def update_latest_messages(

### Modified `Store`

*Note*: `Store` is modified to track the intermediate states of "empty"
consensus blocks, that is, those consensus blocks for which the corresponding
execution payload has not been revealed or has not been included on chain.

```python
@dataclass
class Store(object):
Expand All @@ -146,7 +145,7 @@ class Store(object):
latest_messages: Dict[ValidatorIndex, LatestMessage] = field(default_factory=dict)
unrealized_justifications: Dict[Root, Checkpoint] = field(default_factory=dict)
# [New in Gloas:EIP7732]
payload_states: Dict[Root, BeaconState] = field(default_factory=dict)
payloads: Dict[Root, ExecutionPayloadEnvelope] = field(default_factory=dict)
# [New in Gloas:EIP7732]
payload_timeliness_vote: Dict[Root, Vector[boolean, PTC_SIZE]] = field(default_factory=dict)
# [New in Gloas:EIP7732]
Expand Down Expand Up @@ -181,7 +180,7 @@ def get_forkchoice_store(anchor_state: BeaconState, anchor_block: BeaconBlock) -
checkpoint_states={justified_checkpoint: copy(anchor_state)},
unrealized_justifications={anchor_root: justified_checkpoint},
# [New in Gloas:EIP7732]
payload_states={anchor_root: copy(anchor_state)},
payloads={},
# [New in Gloas:EIP7732]
payload_timeliness_vote={
anchor_root: Vector[boolean, PTC_SIZE](True for _ in range(PTC_SIZE))
Expand Down Expand Up @@ -219,6 +218,18 @@ def notify_ptc_messages(
)
```

### New `is_payload_verified`

```python
def is_payload_verified(store: Store, root: Root) -> bool:
"""
Return whether the execution payload envelope for the beacon block with
root ``root`` has been locally delivered and verified via
``on_execution_payload_envelope``.
"""
return root in store.payloads
```

### New `is_payload_timely`

```python
Expand All @@ -232,7 +243,7 @@ def is_payload_timely(store: Store, root: Root) -> bool:

# If the payload is not locally available, the payload
# is not considered available regardless of the PTC vote
if root not in store.payload_states:
if not is_payload_verified(store, root):
return False

return sum(store.payload_timeliness_vote[root]) > PAYLOAD_TIMELY_THRESHOLD
Expand All @@ -251,7 +262,7 @@ def is_payload_data_available(store: Store, root: Root) -> bool:

# If the payload is not locally available, the blob data
# is not considered available regardless of the PTC vote
if root not in store.payload_states:
if not is_payload_verified(store, root):
return False

return sum(store.payload_data_availability_vote[root]) > DATA_AVAILABILITY_TIMELY_THRESHOLD
Expand Down Expand Up @@ -351,6 +362,8 @@ extending the payload.

```python
def should_extend_payload(store: Store, root: Root) -> bool:
if not is_payload_verified(store, root):
Comment thread
nflaig marked this conversation as resolved.
return False
proposer_root = store.proposer_boost_root
return (
(is_payload_timely(store, root) and is_payload_data_available(store, root))
Expand Down Expand Up @@ -488,7 +501,7 @@ def get_node_children(
) -> Sequence[ForkChoiceNode]:
if node.payload_status == PAYLOAD_STATUS_PENDING:
children = [ForkChoiceNode(root=node.root, payload_status=PAYLOAD_STATUS_EMPTY)]
if node.root in store.payload_states:
if is_payload_verified(store, node.root):
children.append(ForkChoiceNode(root=node.root, payload_status=PAYLOAD_STATUS_FULL))
return children
else:
Expand All @@ -504,8 +517,8 @@ def get_node_children(

### Modified `get_head`

*Note*: `get_head` is a modified to use the new `get_weight` function. It
returns the `ForkChoiceNode` object corresponding to the head block.
*Note*: `get_head` is modified to use the new `get_weight` function. It returns
the `ForkChoiceNode` object corresponding to the head block.

```python
def get_head(store: Store) -> ForkChoiceNode:
Expand Down Expand Up @@ -604,7 +617,7 @@ def validate_on_attestation(store: Store, attestation: Attestation, is_from_bloc
# [New in Gloas:EIP7732]
# If attesting for a full node, the payload must be known
if attestation.data.index == 1:
assert attestation.data.beacon_block_root in store.payload_states
assert is_payload_verified(store, attestation.data.beacon_block_root)

# LMD vote must be consistent with FFG vote target
assert target.root == get_checkpoint_block(
Expand Down Expand Up @@ -713,14 +726,78 @@ def get_payload_attestation_due_ms() -> uint64:
return get_slot_component_duration_ms(PAYLOAD_ATTESTATION_DUE_BPS)
```

### New `verify_execution_payload_envelope_signature`

```python
def verify_execution_payload_envelope_signature(
Comment thread
nflaig marked this conversation as resolved.
state: BeaconState, signed_envelope: SignedExecutionPayloadEnvelope
) -> bool:
builder_index = signed_envelope.message.builder_index
if builder_index == BUILDER_INDEX_SELF_BUILD:
validator_index = state.latest_block_header.proposer_index
pubkey = state.validators[validator_index].pubkey
else:
pubkey = state.builders[builder_index].pubkey

signing_root = compute_signing_root(
signed_envelope.message, get_domain(state, DOMAIN_BEACON_BUILDER)
)
return bls.Verify(pubkey, signing_root, signed_envelope.signature)
```

### New `verify_execution_payload_envelope`

```python
def verify_execution_payload_envelope(
state: BeaconState,
signed_envelope: SignedExecutionPayloadEnvelope,
execution_engine: ExecutionEngine,
) -> None:
envelope = signed_envelope.message
payload = envelope.payload

# Verify signature
assert verify_execution_payload_envelope_signature(state, signed_envelope)

# Verify consistency with the beacon block
header = copy(state.latest_block_header)
header.state_root = hash_tree_root(state)
assert envelope.beacon_block_root == hash_tree_root(header)
assert envelope.slot == state.slot

# Verify consistency with the committed bid
bid = state.latest_execution_payload_bid
assert envelope.builder_index == bid.builder_index
assert payload.prev_randao == bid.prev_randao
assert payload.gas_limit == bid.gas_limit
assert payload.block_hash == bid.block_hash
assert hash_tree_root(envelope.execution_requests) == bid.execution_requests_root

# Verify the execution payload is valid
assert payload.parent_hash == state.latest_block_hash
assert payload.timestamp == compute_time_at_slot(state, state.slot)
assert hash_tree_root(payload.withdrawals) == hash_tree_root(state.payload_expected_withdrawals)
assert execution_engine.verify_and_notify_new_payload(
NewPayloadRequest(
execution_payload=payload,
versioned_hashes=[
kzg_commitment_to_versioned_hash(commitment)
for commitment in bid.blob_kzg_commitments
],
parent_beacon_block_root=state.latest_block_header.parent_root,
execution_requests=envelope.execution_requests,
)
)
```

## Handlers

### Modified `on_block`

*Note*: The handler `on_block` is modified to consider the pre `state` of the
given consensus beacon block depending not only on the parent block root, but
also on the parent blockhash. In addition we delay the checking of blob data
availability until the processing of the execution payload.
*Note*: The handler `on_block` is modified to assert that the parent payload has
been verified (`is_payload_verified`) when the block builds on a full parent. In
addition we delay the checking of blob data availability until the processing of
the execution payload.

```python
def on_block(store: Store, signed_block: SignedBeaconBlock) -> None:
Expand All @@ -731,17 +808,10 @@ def on_block(store: Store, signed_block: SignedBeaconBlock) -> None:
# Parent block must be known
assert block.parent_root in store.block_states

# Check if this blocks builds on empty or full parent block
parent_block = store.blocks[block.parent_root]
bid = block.body.signed_execution_payload_bid.message
parent_bid = parent_block.body.signed_execution_payload_bid.message
# Make a copy of the state to avoid mutability issues
# If this block builds on the parent's full payload, that payload must
# have been verified by on_execution_payload_envelope
if is_parent_node_full(store, block):
assert block.parent_root in store.payload_states
state = copy(store.payload_states[block.parent_root])
else:
assert bid.parent_block_hash == parent_bid.parent_block_hash
Comment thread
jtraglia marked this conversation as resolved.
state = copy(store.block_states[block.parent_root])
assert is_payload_verified(store, block.parent_root)

# Blocks cannot be in the future. If they are, their consideration must be delayed until they are in the past.
current_slot = get_current_slot(store)
Expand All @@ -758,6 +828,9 @@ def on_block(store: Store, signed_block: SignedBeaconBlock) -> None:
)
assert store.finalized_checkpoint.root == finalized_checkpoint_block

# Make a copy of the state to avoid mutability issues
state = copy(store.block_states[block.parent_root])

# Check the block is valid and compute the post-state
block_root = hash_tree_root(block)
state_transition(state, signed_block, True)
Expand Down Expand Up @@ -823,14 +896,13 @@ def on_execution_payload_envelope(
# If not, this payload MAY be queued and subsequently considered when blob data becomes available
assert is_data_available(envelope.beacon_block_root)

# Make a copy of the state to avoid mutability issues
state = copy(store.block_states[envelope.beacon_block_root])
state = store.block_states[envelope.beacon_block_root]
Comment thread
nflaig marked this conversation as resolved.

# Process the execution payload
process_execution_payload(state, signed_envelope, EXECUTION_ENGINE)
# Verify the execution payload envelope
verify_execution_payload_envelope(state, signed_envelope, EXECUTION_ENGINE)

# Add new state for this payload to the store
store.payload_states[envelope.beacon_block_root] = state
# Add execution payload envelope to the store
store.payloads[envelope.beacon_block_root] = envelope
```

### New `on_payload_attestation_message`
Expand Down
1 change: 1 addition & 0 deletions specs/gloas/fork.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ def upgrade_to_gloas(pre: fulu.BeaconState) -> BeaconState:
# [New in Gloas:EIP7732]
latest_execution_payload_bid=ExecutionPayloadBid(
block_hash=pre.latest_execution_payload_header.block_hash,
execution_requests_root=hash_tree_root(ExecutionRequests()),
),
# [New in Gloas:EIP7732]
payload_expected_withdrawals=[],
Expand Down
2 changes: 2 additions & 0 deletions specs/gloas/p2p-interface.md
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,8 @@ obtained from the `state.latest_execution_payload_bid`)
- _[REJECT]_ `block.slot` equals `envelope.slot`.
- _[REJECT]_ `envelope.builder_index == bid.builder_index`
- _[REJECT]_ `payload.block_hash == bid.block_hash`
- _[REJECT]_
`hash_tree_root(envelope.execution_requests) == bid.execution_requests_root`
Comment thread
nflaig marked this conversation as resolved.
- _[REJECT]_ `signed_execution_payload_envelope.signature` is valid as verified
by `verify_execution_payload_envelope_signature`.

Expand Down
Loading