Skip to content

feat: add Gloas payload status to node notifier#18

Closed
lodekeeper wants to merge 4 commits into
unstablefrom
feat/gloas-notifier
Closed

feat: add Gloas payload status to node notifier#18
lodekeeper wants to merge 4 commits into
unstablefrom
feat/gloas-notifier

Conversation

@lodekeeper
Copy link
Copy Markdown
Owner

Summary

Updates the node notifier to display Gloas-specific payload status information.

Before (pre-Gloas)

Synced - slot: 100 - head: 0xabc... - exec-block: valid(12345 0xdef...) - finalized: ...

After (Gloas)

Synced - slot: 100 - head: 0xabc... - payload: pending - finalized: ...
Synced - slot: 100 - head: 0xabc... - payload: full(12345 0xdef...) - finalized: ...

What changed

For Gloas blocks, the notifier now shows:

  • payload: pending — beacon block received, waiting for execution payload
  • payload: empty — no payload was delivered for this block
  • payload: full(number hash) — execution payload received and imported

This replaces the pre-Gloas exec-block: valid/optimistic/syncing(...) display, which doesn't apply to Gloas's two-phase block lifecycle.

Changes

  • packages/beacon-node/src/node/notifier.ts — New getGloasExecutionInfo() function, Gloas fork guard in getHeadExecutionInfo()

For Gloas (EIP-7732), the notifier now displays payload status
(pending/empty/full) instead of the pre-Gloas execution status.
When the head block has a FULL payload, the block number and hash
are shown inline.

Examples:
  Synced - slot: 100 - head: 0xabc... - payload: pending - ...
  Synced - slot: 100 - head: 0xabc... - payload: full(12345 0xdef...) - ...
@lodekeeper lodekeeper closed this Apr 4, 2026
@lodekeeper lodekeeper reopened this Apr 4, 2026
@nflaig
Copy link
Copy Markdown

nflaig commented Apr 19, 2026

@lodekeeper is this branch up to date with latest unstable on lodestar repo?

@lodekeeper
Copy link
Copy Markdown
Owner Author

@nflaig It wasn't — just merged latest unstable (26 commits behind). Merge commit c2ec5c81c1, no conflicts (no upstream changes to notifier.ts since the merge base). Build, lint, and check-types all pass locally.

@lodekeeper
Copy link
Copy Markdown
Owner Author

Yes — branch is up to date with unstable.

  • origin/unstable HEAD: 433e692fd9
  • PR head: c2ec5c81c1
  • git merge-base --is-ancestor origin/unstable feat/gloas-notifier → true (0 behind, 2 ahead)
  • GitHub reports MERGEABLE

@lodekeeper lodekeeper closed this Apr 19, 2026
@lodekeeper lodekeeper reopened this Apr 19, 2026
? "full"
: headInfo.payloadStatus === PayloadStatus.EMPTY
? "empty"
: "pending";
Copy link
Copy Markdown

@nflaig nflaig Apr 19, 2026

Choose a reason for hiding this comment

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

I feel like this shouldn't be pending ever, we might need to re-think how out notifier works in gloas, my intuition right now is that instead of printing the log out half way through the slot we should rather do it at 10 seconds (or so) in the slot, and then either print out full or empty. what do you think @lodekeeper ?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Agreed, good catch. Pushed fdd80f44d0: in Gloas we now log at 5/6 of the slot (10s on mainnet 12s slots, 5s on future 6s slots) instead of halfway. Pre-Gloas keeps the half-slot cadence to avoid shifting existing behavior.

Rationale for 5/6 rather than a fixed 10s: PTC votes land around 2/3 of the slot, so 5/6 gives ~2s margin for proto-array to update the head from PENDING → FULL/EMPTY. I kept the PENDING branch in getGloasExecutionInfo as a safety net (e.g. if PTC quorum never lands), but it should be rare at 10s.

In Gloas, the head block's payload status is PENDING until PTC votes land
(~2/3 of the slot) and the fork choice promotes the PENDING variant to
FULL or EMPTY. Logging at half-slot (6s) surfaces "pending" even for
healthy slots.

Shift the notifier log point to 5/6 of the slot (10s on mainnet) for
Gloas, so the head has transitioned to FULL/EMPTY by the time we print.
Pre-Gloas keeps the half-slot cadence.

🤖 Generated with AI assistance
Copy link
Copy Markdown

@nflaig nflaig left a comment

Choose a reason for hiding this comment

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

@lodekeeper can you show some example logs how this looks now, pre-gloas vs. post-gloas

@lodekeeper
Copy link
Copy Markdown
Owner Author

Here's what the notifier output looks like. Format is consistent across forks — only the exec-block: / payload: column swaps, and the emit timing shifts from T+6s (half-slot) pre-Gloas to T+10s (5/6 slot) in Gloas.

Pre-Gloas, synced (logs at T+6s on 12s slot):

Synced - slot: 12345678 - head: 0x1234…abcd - exec-block: valid(29876543 0xabcd…ef01) - finalized: 0x5678…9abc:385802 - peers: 42

Post-Gloas, synced, payload FULL (logs at T+10s on 12s slot, after PTC vote cutoff at ~2/3 slot):

Synced - slot: 12345678 - head: 0x1234…abcd - payload: full(29876543 0xabcd…ef01) - finalized: 0x5678…9abc:385802 - peers: 42

Post-Gloas, synced, payload EMPTY (builder missed publish, PTC voted empty):

Synced - slot: 12345678 - head: 0x1234…abcd - payload: empty - finalized: 0x5678…9abc:385802 - peers: 42

Post-Gloas, PENDING (should be rare at 5/6 slot — only when PTC votes haven't arrived yet; before fdd80f44d0 this was the common case at half-slot):

Synced - slot: 12345678 - head: 0x1234…abcd - payload: pending - finalized: 0x5678…9abc:385802 - peers: 42

Syncing, post-Gloas:

Syncing - 2m 10s left - 0.153 slots/s - slot: 12345678 - head: (slot -20) 0x1234…abcd - payload: full(29876543 0xabcd…ef01) - finalized: 0x5678…9abc:385802 - peers: 42

Happy to produce real captured logs from a gloas-devnet node if you want; the above is reconstructed from the code path (getGloasExecutionInfo in notifier.ts).

@lodekeeper
Copy link
Copy Markdown
Owner Author

@nflaig Here are representative log lines (15 char truncation for roots is prettyBytes = 0x1234…cdef; block-hash inline uses prettyBytesShort = 0x1234…):

Pre-Gloas (bellatrix+), logged at 6s into slot — unchanged:

Apr-19 19:30:06.000  info: Synced - slot: 1234567 - head: 0xabcdef…89ab - exec-block: valid(19876543 0x123456…) - finalized: 0xfedcba…4321:38579 - peers: 52

Post-Gloas, logged at 10s into slot (5/6 of 12s slot):

FULL (happy path — payload arrived, PTC quorum voted present):

Apr-19 19:30:10.000  info: Synced - slot: 2345678 - head: 0xabcdef…89ab - payload: full(19876543 0x123456…) - finalized: 0xfedcba…4321:73308 - peers: 52

EMPTY (payload didn't arrive, or PTC voted not present — head promotes to the EMPTY variant):

Apr-19 19:30:22.000  info: Synced - slot: 2345679 - head: 0xabcdef…89ab - payload: empty - finalized: 0xfedcba…4321:73308 - peers: 52

PENDING (rare — kept as safety net; expected only if PTC quorum never lands, e.g. severe committee outage):

Apr-19 19:30:34.000  info: Synced - slot: 2345680 - head: 0xabcdef…89ab - payload: pending - finalized: 0xfedcba…4321:73308 - peers: 52

Key shape change: pre-Gloas exec-block: <status>(number hash) → post-Gloas payload: <status>(number hash) where status is full/empty/pending (the fork-choice payload variant), and number hash is shown only for full.

// In Gloas, PTC votes decide payload timeliness around 2/3 of the slot; log at 5/6 so the
// head has transitioned from PENDING to FULL/EMPTY. Pre-Gloas keeps the half-slot cadence.
const currentSlot = chain.clock.currentSlot;
const msSlotOffset = isForkPostGloas(config.getForkName(currentSlot)) ? (msPerSlot * 5) / 6 : msPerSlot / 2;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

it would be better to define these a BPS values

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Done in e399634f7a. Introduced NOTIFIER_LOG_DUE_BPS (5000 = half-slot, pre-Gloas) and NOTIFIER_LOG_DUE_BPS_GLOAS (8333 ≈ 5/6 slot = 10s on 12s slots), and switched the offset computation to config.getSlotComponentDurationMs(logDueBps) to match the existing ATTESTATION_DUE_BPS / AGGREGATE_DUE_BPS pattern. Kept the constants module-local since this is purely notifier UX, not a spec timing — happy to lift them into chainConfig if you'd prefer.

Align the notifier log point with the other slot-component timings
(ATTESTATION_DUE_BPS, AGGREGATE_DUE_BPS, ...). Replace hardcoded
msPerSlot/2 and (msPerSlot * 5) / 6 fractions with NOTIFIER_LOG_DUE_BPS
(5000) and NOTIFIER_LOG_DUE_BPS_GLOAS (8333), and compute the offset
via config.getSlotComponentDurationMs().

🤖 Generated with AI assistance
@nflaig
Copy link
Copy Markdown

nflaig commented Apr 21, 2026

@lodekeeper can you open a second PR similar to that to your lodekeeper/lodestar repo which always prints out the execution payload status of previous slot (based on what the next block build on by looking at the parent hash)

@lodekeeper
Copy link
Copy Markdown
Owner Author

@nflaig opened as #19 — adds a prev-payload: row that resolves headInfo.parentRoot via fork-choice and shows the parent block's payload status (what the next block will reference via its parent hash).

  • Pre-Gloas: always prev-payload: full(N 0xhash) (parent has single FULL variant).
  • Gloas: parent resolves to FULL (payload revealed) or EMPTY (PTC voted not-present); shows the block-number + hash only on FULL.

Independent of #18 — branched off latest unstable so it doesn't depend on the Gloas-specific display here.

@lodekeeper
Copy link
Copy Markdown
Owner Author

Parking advisor feedback here for later — not blockers, but things to address if this PR is what we decide to land (vs. the minimal #19 shape):

  • Move the BPS constant out of notifier.ts. NOTIFIER_LOG_DUE_BPS / NOTIFIER_LOG_DUE_BPS_GLOAS = 8333 should live in @lodestar/params next to ATTESTATION_DUE_BPS_GLOAS / AGGREGATE_DUE_BPS_GLOAS / PAYLOAD_ATTESTATION_DUE_BPS. Keeps the BPS idiom consistent and discoverable.
  • Don't replace exec-block: with payload: — augment it. The new row drops the execution anchor (N hash) for Gloas heads, which is still useful context (operators read number+hash to spot stalls). Prefer keeping exec-block: and adding a payload: full | empty | pending field alongside it.
  • Gate the payload: row on sync.state === "Synced". During catch-up the head's payloadStatus can legitimately be PENDING for many slots in a row; we'd be spamming payload: pending for a non-issue.
  • Add fork-boundary tests. The log-point-shift (5000 → 8333 BPS) flips at the Gloas activation slot; want explicit coverage for the slot-before / slot-of / slot-after transition to catch any off-by-one in timeToNextLogPoint.

Captured from a 3-advisor review (Defender persona, Devil's Advocate, GPT-5.4 xhigh). 🤖 AI-assisted.

@nflaig
Copy link
Copy Markdown

nflaig commented Apr 25, 2026

@lodekeeper please close this and consider it done

@lodekeeper
Copy link
Copy Markdown
Owner Author

Closing per @nflaig request — superseded by direction in #18 (comment).

@lodekeeper lodekeeper closed this Apr 25, 2026
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.

2 participants