Skip to content

feat: implement head_v2 event#9486

Draft
markolazic01 wants to merge 22 commits into
ChainSafe:unstablefrom
markolazic01:feat/head-v2
Draft

feat: implement head_v2 event#9486
markolazic01 wants to merge 22 commits into
ChainSafe:unstablefrom
markolazic01:feat/head-v2

Conversation

@markolazic01

@markolazic01 markolazic01 commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Description

Implements server-side emission of the head_v2 event as specified in ethereum/beacon-APIs#590, alongside the existing head event which is kept for backwards compatibility.

Closes #9472

Changes

  • Add head_v2 to EventType enum and eventTypes map in packages/api
  • Define HeadV2 SSZ container type and EventData shape
  • Emit head_v2 from importBlock alongside head
  • Add toApiPayloadStatus mapper from fork choice PayloadStatus enum to API string values
  • Add test data for head_v2

TODO

  • VC-side consumption of head_v2 with fallback to head
  • Update duty revalidation logic for attester, proposer-v2, and PTC (updated through changes in chainHeadTracker.ts)

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces support for the head_v2 Server-Sent Events (SSE) event type, defining its schema, serialization logic, and emitting it upon block import. The review feedback highlights three key improvements: handling undefined payload status for pre-Bellatrix blocks in toApiPayloadStatus to prevent serialization errors, using newHead.slot instead of blockSlot to accurately determine the fork name for the event version, and simplifying the event type mapping by utilizing the existing WithVersion helper.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread packages/beacon-node/src/chain/blocks/importBlock.ts Outdated
Comment thread packages/beacon-node/src/chain/blocks/importBlock.ts Outdated
Comment thread packages/api/src/beacon/routes/events.ts Outdated
@markolazic01 markolazic01 marked this pull request as ready for review June 10, 2026 19:12
@markolazic01 markolazic01 requested a review from a team as a code owner June 10, 2026 19:12
@markolazic01 markolazic01 marked this pull request as draft June 10, 2026 22:31
@markolazic01 markolazic01 marked this pull request as ready for review June 10, 2026 22:32

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 142d9c5b03

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread packages/validator/src/services/chainHeaderTracker.ts Outdated
Comment thread packages/beacon-node/src/chain/blocks/importBlock.ts Outdated
@markolazic01

Copy link
Copy Markdown
Contributor Author

Only tests left to add

@markolazic01

Copy link
Copy Markdown
Contributor Author

Now ready for review

@markolazic01 markolazic01 changed the title feat: implement head_v2 event feat: implement head_v2 event Jun 19, 2026
@markolazic01

Copy link
Copy Markdown
Contributor Author

type check was failing, it should pass now (together with other checks)

@nflaig nflaig left a comment

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.

few comments from a quick pass

* Cache of head block payload status at time of head_v2 emission, used to detect
* empty -> full transitions and emit a second head_v2 event accordingly.
*/
readonly headV2PayloadStatusCache = new Map<RootHex, {status: PayloadStatus; slot: Slot}>();

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 entirely clear to me why we need a cache for this, the second head event can be emitted if your head in fork choice moves from empty to full

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

well, I added the cache to prevent duplicate emissions between importBlock and importExecutionPayload, but after investigating further I see the importExecutionPayload emission doesn't work correctly since the head is never updated there.
Should I add a head update call after onExecutionPayload, or should both emission types come from importBlock?
Either way the cache can be removed. Let me know what works best.

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 I raised something similar in the PR itself, to me emitting head twice per slot still feels strange but that's what we are supposed to do, I would need to re-read what the decision was on the PR, if it's up to me I would probably get rid of payload_status field or report the payload status of the parent, need to take more time to think about this and re-read what we discussed on the PR

import {isOptimisticBlock} from "../../util/forkChoice.js";
import {BeaconChain} from "../chain.js";

export function emitHeadV2(this: BeaconChain, head: ProtoBlock, headChanged: boolean) {

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 reusing the same function in importBlock and importExecutionPayload is a bad abstraction

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

i will keep this in mind and address it once I handle the emissions properly

Comment on lines +116 to +117
previousDutyDependentRoot: currentEpochDependentRoot,
currentDutyDependentRoot: nextEpochDependentRoot,

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 we refactor our head event data and add the shim to v1 event, the new semantics of head v2 are much nicer

@markolazic01 markolazic01 Jul 5, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

i'm glad to make that change, but maybe it is best to do it in another PR to keep this one readable.
I did some greps and it looks like change touches a lot of places.

let me know if you agree and I will make an issue for this

@markolazic01 markolazic01 marked this pull request as draft July 5, 2026 13:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement head_v2 event

2 participants