Skip to content

Re-enable optimistic sync for Gloas payload envelopes - #9934

Open
dapplion wants to merge 8 commits into
sigp:unstablefrom
dapplion:gloas-optimistic-payload-status
Open

Re-enable optimistic sync for Gloas payload envelopes#9934
dapplion wants to merge 8 commits into
sigp:unstablefrom
dapplion:gloas-optimistic-payload-status

Conversation

@dapplion

Copy link
Copy Markdown
Collaborator

Breaking change

Can we force existing Gloas nodes to resync when updating to this new version? Then we can simplify the migrations as the property execution_status has continuity in mainnet from Fulu to Gloas. Otherwise there's an awkward partial disappearance between v29 and v30.

Issue Addressed

Re-enable optimistic sync for Gloas payload envelopes

Proposed Changes

Track the status of the payload (FULL) node in ProtoNode::execution_status

@dapplion
dapplion force-pushed the gloas-optimistic-payload-status branch from 105919b to 142e3f1 Compare August 29, 2026 12:44
@eserilev

Copy link
Copy Markdown
Member

Yes we can force a resync for all Gloas nodes

@dapplion
dapplion force-pushed the gloas-optimistic-payload-status branch from 142e3f1 to 9bc89e2 Compare August 29, 2026 14:16
@pawanjay176

Copy link
Copy Markdown
Member

I think we can easily force resync devnet nodes. Not an issue. Halfway through the review. Will complete tomorrow

@pawanjay176 pawanjay176 added the under-review A reviewer has only partially completed a review. label Sep 2, 2026
@michaelsproul michaelsproul added the backwards-incompat Backwards-incompatible API change label Sep 2, 2026
Comment thread consensus/proto_array/src/proto_array.rs
Comment on lines +644 to +645
// The payload is revealed later, in an envelope.
execution_status: ExecutionStatus::Irrelevant(false),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Almost need another variant (like NotYetRevealed or Pending) for the Gloas case?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Added NotYetRevealed

Comment on lines 670 to 678
// Execution status tracking only exists on V17 (pre-Gloas) nodes.
if let Ok(v17) = parent.as_v17()
&& v17.execution_status.is_invalid()
{
return Err(Error::ParentExecutionStatusIsInvalid {
block_root: block.root,
parent_root: parent.root(),
});
}

@michaelsproul michaelsproul Sep 2, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Out of date. Probably need to handle parent payload status here to know if the parent's invalidity affects this new block (if we build on the empty variant of a parent whose payload is invalid, that's ok).

-> Good test case.

Comment on lines 456 to 459
// If the node has an invalid execution payload, reduce its weight to zero.
0_i64
.checked_sub(node.weight() as i64)
.ok_or(Error::InvalidExecutionDeltaOverflow(node_index))?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think we need to not do this for Gloas. I think we should just do it for the full weight?

Comment on lines 476 to 477
// Invalid nodes always have a weight of 0.
*node.weight_mut() = 0;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think we need to tweak this for Gloas too.

&& matches!(block.execution_status, ExecutionStatus::Valid(_))
{
self.propagate_execution_payload_validation_by_index(parent_index)?;
let parent_status = node.parent_payload_status().unwrap_or(PayloadStatus::Full);

@michaelsproul michaelsproul Sep 2, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Conflicting convention with get_parent_payload_status:

https://github.com/dapplion/lighthouse/blob/8d1aa2a09853d6df7efbcd6b63186d2833bacd61/consensus/proto_array/src/proto_array.rs#L185-L189

Maybe we should be consistent and treat pre-Gloas blocks as Empty? Or add a new enum with a pre-Gloas status to this codepath.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Added ParentPayloadStatus and removed all defaults

PayloadStatus::Empty | PayloadStatus::Pending => self
.proto_array
.empty_node_execution_status(*block_root)
.ok(),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit: probably shouldn't swallow this error (we don't expect this to error, I imagine?)

}
Ok(ExecutionStatus::Irrelevant(_)) => break,
Err(_) => break,
ExecutionStatus::Irrelevant(_) => break,

@michaelsproul michaelsproul Sep 2, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Once we add the new status, can probably return an error for that variant (we should not be able to invalidate a head block with an unrevealed payload, and the block should not have any Full ancestors with unrevealed payloads)

}

if let Some(parent_index) = node.parent() {
status = node.parent_payload_status().unwrap_or(PayloadStatus::Full);

@michaelsproul michaelsproul Sep 2, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

To exercise this path, need a test case for the first payload after the Gloas fork being invalid

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could simplify this loop using ProtoArray::children field

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done

Comment on lines +1104 to +1110
// A Gloas descendant becomes invalid only when it built on the payload of the
// ancestor. A descendant that took the `EMPTY` edge stays viable.
if let ProtoNode::V29(gloas_node) = node
&& gloas_node.parent_payload_status != PayloadStatus::Full
{
continue;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It's incorrect to continue here. A block (node) with parent_payload_status Empty could descend from some other Full block which is invalid because it descends from the latest valid hash.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed

Comment on lines +1123 to +1130
// In Gloas this means only that the payload is not revealed yet. The block did
// commit to the invalid ancestry. Pre-Gloas this state is a contradiction.
match node {
ProtoNode::V29(gloas_node) => {
gloas_node.execution_status = ExecutionStatus::Invalid(
gloas_node.execution_payload_block_hash,
);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks like a good use for the separate variant

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Added NotYetRevealed

proto_block.execution_status.is_optimistic_or_invalid();
let new_head_is_optimistic = fork_choice
.get_node_execution_status(&block_root, head_payload_status)
.is_some_and(|status| status.is_optimistic_or_invalid());

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Needs to walk backwards to find the first full ancestor which is the head's payload status

Comment on lines +644 to +645
// The payload is revealed later, in an envelope.
execution_status: ExecutionStatus::Irrelevant(false),

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Added NotYetRevealed

&& matches!(block.execution_status, ExecutionStatus::Valid(_))
{
self.propagate_execution_payload_validation_by_index(parent_index)?;
let parent_status = node.parent_payload_status().unwrap_or(PayloadStatus::Full);

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Added ParentPayloadStatus and removed all defaults

Comment on lines +1123 to +1130
// In Gloas this means only that the payload is not revealed yet. The block did
// commit to the invalid ancestry. Pre-Gloas this state is a contradiction.
match node {
ProtoNode::V29(gloas_node) => {
gloas_node.execution_status = ExecutionStatus::Invalid(
gloas_node.execution_payload_block_hash,
);
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Added NotYetRevealed

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done

Comment on lines +1104 to +1110
// A Gloas descendant becomes invalid only when it built on the payload of the
// ancestor. A descendant that took the `EMPTY` edge stays viable.
if let ProtoNode::V29(gloas_node) = node
&& gloas_node.parent_payload_status != PayloadStatus::Full
{
continue;
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed

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

Labels

backwards-incompat Backwards-incompatible API change under-review A reviewer has only partially completed a review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants