Skip to content

Ensure payload envelope streamer always serves canonical envelopes after the split slot#9085

Merged
mergify[bot] merged 19 commits intosigp:unstablefrom
eserilev:gloas-envelope-streamer-serves-canonical
Apr 23, 2026
Merged

Ensure payload envelope streamer always serves canonical envelopes after the split slot#9085
mergify[bot] merged 19 commits intosigp:unstablefrom
eserilev:gloas-envelope-streamer-serves-canonical

Conversation

@eserilev
Copy link
Copy Markdown
Member

@eserilev eserilev commented Apr 3, 2026

If an envelope is after the split slot check fork choice to for envelope payload weight to see if the envelope is canonical or not

Note that we still need envelope pruning to ensure we serve canonical envelopes before the split slot

@eserilev eserilev added gloas ready-for-review The code is ready for review labels Apr 3, 2026
@eserilev eserilev changed the title Ensure payload envelope streamer always serves canonical envelope after the split slot Ensure payload envelope streamer always serves canonical envelopes after the split slot Apr 3, 2026
@eserilev eserilev added blocked and removed ready-for-review The code is ready for review labels Apr 4, 2026
@eserilev eserilev mentioned this pull request Apr 4, 2026
40 tasks
@eserilev eserilev added ready-for-review The code is ready for review and removed blocked labels Apr 7, 2026
@eserilev eserilev force-pushed the gloas-envelope-streamer-serves-canonical branch from 63cce7a to 50f374c Compare April 7, 2026 06:03
@mergify
Copy link
Copy Markdown

mergify Bot commented Apr 7, 2026

Some required checks have failed. Could you please take a look @eserilev? 🙏

@mergify mergify Bot added waiting-on-author The reviewer has suggested changes and awaits thier implementation. and removed ready-for-review The code is ready for review labels Apr 7, 2026
Comment on lines +1049 to +1051
if node.full_payload_weight >= node.empty_payload_weight {
return Some(PayloadStatus::Full);
}
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.

Sorry I lied saying it was just a weight comparison 😅

I think this also needs to call get_payload_status_tiebreaker to choose between the full and empty nodes when their weights are equal (which is likely for the head block before it receives attestations). See:

https://github.com/ethereum/consensus-specs/blob/master/specs/gloas/fork-choice.md#modified-get_head

I think we have all the info needed here to make that call (the IndexedForkChoiceNodes can be synthesised, and the block roots &

We don't need to compare roots because both empty and full children have the same block root.

We also don't need to do anything if payload_received is false (then the status is always just Empty).

Copy link
Copy Markdown
Member Author

@eserilev eserilev Apr 7, 2026

Choose a reason for hiding this comment

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

I was hoping to get away with the head block case further upstream here (by just looking at the cached head). Because if we're not checking the head root then full >= empty is the only check we need i believe

but maybe this is a bit of a footgun, wdyt?

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 it's a bit sketchy because there could definitely be non-head blocks with equal full and empty weights. I've seen a bunch of places where it would probably be good to use this function, so a general purpose + correct impl is worth it IMO

justified_state_balances: vec![1],
expected_head: get_root(3),
current_slot: Slot::new(0),
expected_payload_status: None,
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.

Not sure why this is None, but haven't looked super closely. I would expect all Gloas blocks have a status?

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.

maybe this doesn't need to be Option since find_head always return a payload status for pre-gloas (empty)?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

it doesn't need to be None but I think it'd be better to handle this in a separate PR. theres like 80+ call sites that need to be updated if we make this change

let index = *self.proto_array.indices.get(block_root)?;
let node = self.proto_array.nodes.get(index)?;

let v29 = node.as_v29().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.

I think accessing the fields with the functions be more forward compatible?

node.payload_received()?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

ok refactored things a bit

Comment on lines +1065 to +1087

let tiebreaker_for_status = |status| {
let node_ref = IndexedForkChoiceNode {
root: node.root(),
proto_node_index: index,
payload_status: status,
};
self.proto_array.get_payload_status_tiebreaker::<E>(
&node_ref,
node,
current_slot,
proposer_boost_root,
)
};

let full_tiebreaker = tiebreaker_for_status(PayloadStatus::Full).ok()?;
let empty_tiebreaker = tiebreaker_for_status(PayloadStatus::Empty).ok()?;

if full_tiebreaker >= empty_tiebreaker {
Some(PayloadStatus::Full)
} else {
Some(PayloadStatus::Empty)
}
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'm not sure if the tie breaker logic is covered in spec tests, can we add some unit tests if they are not?

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.

oh yeah just read the tests in gloas_payload.rs, i think we can test this scenario there?

Copy link
Copy Markdown
Member Author

@eserilev eserilev Apr 13, 2026

Choose a reason for hiding this comment

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

added a test
cbe8b7a

let full_tiebreaker = tiebreaker_for_status(PayloadStatus::Full).ok()?;
let empty_tiebreaker = tiebreaker_for_status(PayloadStatus::Empty).ok()?;

if full_tiebreaker >= empty_tiebreaker {
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.

this can never be equal right?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

yeah it can never be equal

Comment thread beacon_node/beacon_chain/src/payload_envelope_streamer/tests.rs Outdated
justified_state_balances: vec![1],
expected_head: get_root(3),
current_slot: Slot::new(0),
expected_payload_status: None,
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.

maybe this doesn't need to be Option since find_head always return a payload status for pre-gloas (empty)?

Comment thread consensus/fork_choice/src/fork_choice.rs Outdated
Comment thread consensus/proto_array/src/proto_array_fork_choice.rs Outdated
Comment thread consensus/proto_array/src/proto_array.rs Outdated
@eserilev eserilev added ready-for-review The code is ready for review and removed waiting-on-author The reviewer has suggested changes and awaits thier implementation. labels Apr 13, 2026
@eserilev eserilev requested a review from jimmygchen April 16, 2026 12:13
Comment thread beacon_node/beacon_chain/src/payload_envelope_streamer/mod.rs Outdated
Comment thread consensus/proto_array/src/proto_array_fork_choice.rs Outdated
@eserilev
Copy link
Copy Markdown
Member Author

eserilev commented Apr 17, 2026

the fork choice function to verify payload envelope canonicity was incorrect. I've updated it and added tests that verify specific edge cases that the previous impl wasn't handling correctly

@eserilev eserilev requested a review from jimmygchen April 17, 2026 09:30
Copy link
Copy Markdown
Member

@michaelsproul michaelsproul left a comment

Choose a reason for hiding this comment

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

Looks good. Didn't review the fork choice test changes in depth, but can after the merge conflicts are resolved.

Copy link
Copy Markdown
Member

@michaelsproul michaelsproul left a comment

Choose a reason for hiding this comment

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

LGTM

@michaelsproul michaelsproul added ready-for-merge This PR is ready to merge. and removed ready-for-review The code is ready for review labels Apr 23, 2026
@michaelsproul
Copy link
Copy Markdown
Member

@mergify queue

@mergify
Copy link
Copy Markdown

mergify Bot commented Apr 23, 2026

Merge Queue Status

This pull request spent 27 minutes 12 seconds in the queue, including 25 minutes 43 seconds running CI.

Required conditions to merge

@mergify mergify Bot added the queued label Apr 23, 2026
mergify Bot added a commit that referenced this pull request Apr 23, 2026
@mergify mergify Bot merged commit 82dc8b4 into sigp:unstable Apr 23, 2026
39 checks passed
@mergify mergify Bot removed the queued label Apr 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gloas ready-for-merge This PR is ready to merge.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants