Skip to content

ePBS (EIP-7732) Support - #94

Open
shane-moore wants to merge 85 commits into
ssvlabs:mainfrom
shane-moore:feat/epbs
Open

shane-moore wants to merge 85 commits into
ssvlabs:mainfrom
shane-moore:feat/epbs

Conversation

@shane-moore

Copy link
Copy Markdown
Collaborator

This SIP describes the ssv spec changes needed to keep operators performing validator duties correctly after ePBS (EIP-7732) lands in the consensus layer Gloas fork. Covers earlier slot deadlines, AttestationData.Index propagation through BeaconVote, the new PTC committee duty, the produceBlockV4 proposer flow (self-build vs external-builder variants), and the new SignedProposerPreferences broadcast.

@iurii-ssv iurii-ssv 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.

Appreciate this effort! Doing first pass, just started looking into Gloas, forgive AI-heavy commentary (seems relevant though).

Nit: would be nice to list/organize the duties affected/added by the actual slot timeline (eg. "Proposer Preferences Duty", should come first, then "Modified Proposer Duty", then "Modified Attestation Duty", etc.)

Comment thread sips/epbs_support.md Outdated
Comment thread sips/epbs_support.md
Comment thread sips/epbs_support.md Outdated
Comment thread sips/epbs_support.md Outdated
Comment thread sips/epbs_support.md Outdated
Comment thread sips/epbs_support.md Outdated
Comment thread sips/epbs_support.md Outdated
Comment thread sips/epbs_support.md Outdated
Comment thread sips/epbs_support.md Outdated
@shane-moore

Copy link
Copy Markdown
Collaborator Author

Appreciate this effort! Doing first pass, just started looking into Gloas, forgive AI-heavy commentary (seems relevant though).

Nit: would be nice to list/organize the duties affected/added by the actual slot timeline (eg. "Proposer Preferences Duty", should come first, then "Modified Proposer Duty", then "Modified Attestation Duty", etc.)

thanks for taking a look! will resolve all the comments this week 😃

Comment thread sips/epbs_support.md Outdated
Comment thread sips/epbs_support.md Outdated
Comment thread sips/epbs_support.md Outdated
Comment thread sips/epbs_support.md Outdated
Comment thread sips/epbs_support.md Outdated
@diegomrsantos

Copy link
Copy Markdown
Collaborator

I opened ethereum/EIPs#11684 to update the EIP-7732 Gloas summary against the current consensus-specs Gloas files:

ethereum/EIPs#11684

Since this SIP depends on those EIP-7732 details, I would appreciate review there as well.

@shane-moore

Copy link
Copy Markdown
Collaborator Author

Nit: would be nice to list/organize the duties affected/added by the actual slot timeline (eg. "Proposer Preferences Duty", should come first, then "Modified Proposer Duty", then "Modified Attestation Duty", etc.)

the current top-level order mirrors upstream's grouping in gloas/validator.md: Attestation → Sync → Block proposal (with Broadcasting SignedProposerPreferences nested) → PTC. §1 Slot Timing already gives the temporal table. That said, I don't feel strongly here; happy to reorder timeline-first if SSV prefers.

…erences

Pin updated from f1371480c4 to upstream master HEAD following PR review
feedback from iurii-ssv and diegomrsantos. Net changes in the Proposer
Preferences section: ProposerPreferences now carries dependent_root,
bid handshake matches on (proposal_slot, dependent_root), gossip rule is
first-valid-per-tuple, new Security Considerations entry on too-early
publication. PTC paragraph also tightened to distinguish
PAYLOAD_ATTESTATION_DUE_BPS from PAYLOAD_DUE_BPS. Slot Timing,
Attestation Duty, and Proposer Duty sections unchanged at target pin.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A fresh-eyes review found §6 and §7 disagreeing on what first-valid means:
§6 said operators store the first dissemination passing the decision-binding
checks, while §7 records the first structurally valid message and classifies
the binding checks as runner concerns, making §6's reading unreachable. §6
now matches §7: the first §7-valid dissemination is stored, and the runner
abstains if the stored envelope fails its own decision bindings.

Also makes the §7 decode rule explicit about the inner blinded container
(undecodable bytes must not consume the slot's dissemination budget), bounds
it by the SSV message data cap rather than an undefined progressive-container
maximum, and broadens the Security Considerations residual: any well-formed
first dissemination from a Byzantine member costs the reveal, whether or not
it passes the binding checks.
iurii-ssv added a commit to ssvlabs/ssv that referenced this pull request Sep 2, 2026
Node-side surface for Gloas (ePBS) Payload Timeliness Committee duties,
per SIP ssvlabs/SIPs#94:

- types/gloas: PayloadAttestationData (42B SSZ) and PayloadAttestationMessage
  (146B SSZ), plus JSON-only PTCDuty, matching the beacon-APIs Gloas schemas;
  SSZ round-trip and golden JSON wire-format tests.
- goclient: hand-rolled HTTP for the three PTC endpoints (get duties, produce
  data, submit messages). go-eth2-client has no Gloas provider yet, so these
  are direct requests until it is rebased.
- beacon: standalone PTCCalls interface (folded into BeaconNode with the PTC
  runner later) plus regenerated MockPTCCalls; GoClient conformance asserted
  in tests.

@iurii-ssv iurii-ssv 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.

The updated local block building LGTM, left a couple suggestions to polish/finalize it below.

Comment thread sips/epbs_support.md Outdated
Comment thread sips/epbs_support.md Outdated
shane-moore and others added 4 commits September 3, 2026 15:56
Anchor implemented this in sigp/anchor#1288 and validated it on a Gloas
devnet, so the rule is known implementable before it is written down here.

Section 7 keyed the dissemination dedup on (MessageID, slot), so the first
structurally valid carrier was the only one an operator would forward or
consider. One committee member broadcasting a well-formed but decision-unbound
envelope ahead of the builder therefore made every operator abstain, costing
the cluster its reveal. Dedup is now one per (MessageID, signer, slot), and
section 6 signs the first candidate whose envelope satisfies the four bindings
rather than the first that arrives, continuing to the payload-due cutoff.

The previous commit reconciled section 6 to section 7 after a review found
them disagreeing about what first-valid meant. That resolved the conflict in
the wrong direction: section 6's original content-based reading was correct,
and section 7's per-slot dedup was what made it unreachable.

Two rules that were implicit are now stated, because an implementation that
reads them differently is exploitable or unbounded. Recording must follow the
signature check, or a forged carrier claiming another operator's identity
consumes that operator's budget and suppresses its honest message. And
forwarding is bounded by committee size, which holds only because the
signature check is scoped to committee members.

Security Considerations narrows accordingly: the binding-failing carrier is no
longer a liveness risk, and what remains is a binding-passing forgery that
differs only in the unchecked PayloadRoot, splitting operators across two
roots by node-local arrival order. That case tolerates no Byzantine member
when ExecutionRequests is empty, and the named hardening is now sign-all
relative to the new rule rather than to first-valid.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SJP7vu2EGTFknFswZpjNjJ
Section 6 named a sole disseminator and publisher without saying how an
operator learns it is that one. "The QBFT leader" does not work: after a round
change the deciding leader re-proposes an earlier leader's block, so its beacon
node did not build it. An operator is now the builder operator when the block
in its own produce response carries the decided root, the local test section 4
already uses for the Eth-Builder-Url echo, which is round-change safe.

The envelope now comes from the operator's own BlockContents, obtained by
calling produceBlockV4 with include_payload=true, so no beacon-node call sits
between the decision and the dissemination. The stateful branch is dropped: the
envelope GET answers only on the node that built the block and the publish POST
rejects a body produced elsewhere, so it forces node affinity on any operator
with more than one beacon node. Publication is the Contents form to every
beacon node, matching the section 4 block publish redundancy.
The previous commit deleted it, leaving a SHOULD on the inline produce
variant with no alternative described, so a client that fetches the envelope
from its beacon node had no path in the text at all. That is the common case
today: a validator client can own the fetch and hand the envelope to the SSV
layer to sign, which leaves the SSV side no way to choose the inline variant
on its own.

Both paths are described again, with the node affinity stated as the reason
to prefer the inline one rather than as grounds for removing the other.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SJP7vu2EGTFknFswZpjNjJ
The previous commit spelled out the beacon-node fetch as a separate allowed
path, which SHOULD already grants. Keeping both said the same thing twice and
read as an argument rather than a rule. The affinity cost now sits with the
SHOULD as its justification, and the restatement is gone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SJP7vu2EGTFknFswZpjNjJ
iurii-ssv
iurii-ssv previously approved these changes Sep 4, 2026
Comment thread sips/epbs_support.md Outdated
iurii-ssv added a commit to ssvlabs/ssv that referenced this pull request Sep 7, 2026
Node-side surface for Gloas (ePBS) Payload Timeliness Committee duties,
per SIP ssvlabs/SIPs#94:

- types/gloas: PayloadAttestationData (42B SSZ) and PayloadAttestationMessage
  (146B SSZ), plus JSON-only PTCDuty, matching the beacon-APIs Gloas schemas;
  SSZ round-trip and golden JSON wire-format tests.
- goclient: hand-rolled HTTP for the three PTC endpoints (get duties, produce
  data, submit messages). go-eth2-client has no Gloas provider yet, so these
  are direct requests until it is rebased.
- beacon: standalone PTCCalls interface (folded into BeaconNode with the PTC
  runner later) plus regenerated MockPTCCalls; GoClient conformance asserted
  in tests.

@GalRogozinski GalRogozinski 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.

Note, no special dissemination is needed on the self-building path

Comment thread sips/epbs_support.md Outdated
Comment thread sips/epbs_support.md
Comment thread sips/epbs_support.md Outdated
- [`execution_payload` gossip topic (carries `SignedExecutionPayloadEnvelope`)](https://github.com/ethereum/consensus-specs/blob/a5a1bc630401eedbe2f3d87934c99012578c113b/specs/gloas/p2p-interface.md#new-execution_payload)
- [`POST /eth/v1/beacon/execution_payload_envelopes` endpoint](https://github.com/ethereum/beacon-APIs/pull/624)

On the self-build path (`bid.builder_index == BUILDER_INDEX_SELF_BUILD` per [EIP-7732](https://eips.ethereum.org/EIPS/eip-7732)), the proposer signs `SignedExecutionPayloadEnvelope` after block publication. The [§4](#4-modified-proposer-duty) decision pins exactly one valid envelope: `bid.block_hash` commits to the whole payload, and [`verify_execution_payload_envelope`](https://github.com/ethereum/consensus-specs/blob/a5a1bc630401eedbe2f3d87934c99012578c113b/specs/gloas/fork-choice.md#new-verify_execution_payload_envelope) checks every envelope field against the committed bid and block, so this duty runs no consensus. The flow is one dissemination round plus one partial-signature round; the operator whose beacon node built the decided block (the builder operator) both disseminates and publishes.

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.

Suggestion: carry PayloadRoot in ProposerConsensusData and drop the dissemination round

If the §4 decision pins exactly one valid envelope, that value can be carried in the decision rather than distributed after it.

Prerequisite, worth doing anyway: blind execution_requests to its root too. The root formula below already shows this is free, its input is hash_tree_root(execution_requests). It also turns the binding check into a 32-byte comparison against bid.execution_requests_root, and keeps an unbounded ProgressiveList off the SSV wire. Then every envelope field is derivable from the decided block except PayloadRoot, a 32-byte gap.

Option A. Add PayloadRoot to ProposerConsensusData at Gloas, from the leader's own produce response, which it holds under §6's include_payload=true guidance. Require zero when builder_index != BUILDER_INDEX_SELF_BUILD, derivable from the same value, so an honest leader never trips it and the block value check gains no new failure mode. §6 keeps its signing round and publication rules, and drops the dissemination message type, container, four binding checks, selection rule, and §7 clauses.

Option B. As A, plus fold the envelope partial signature into the block's PostConsensusPartialSig packet as a second entry under DOMAIN_BEACON_BUILDER. Removes the round as well. Every SSV message carries a per-operator RSA signature (types/operator_signer.go), so each eliminated broadcast saves one signing and n-1 verifications per operator. Needs an exception to §7's one-entry rule for this role.

This is a Byzantine-tolerance improvement, not just bandwidth. Under a Byzantine leader both designs lose the reveal identically, since the bad root comes from the deciding leader either way. The difference is a Byzantine non-leader: the Security Considerations entry on forged dissemination concedes a binding-passing forgery splits the cluster on node-local arrival order and that the path "tolerates no Byzantine member." QBFT admits one justified value, so that entry and its hardening note both disappear.

Cost: a Gloas-conditional field in a container every proposer duty uses, plus a presence rule in §4's value check. Option B also touches a shared §7 rule.

The rationale bullet argues against consensus over the envelope, which is a different proposal. Neither option negotiates the envelope. They extend a negotiation that already has to succeed before the attestation deadline by 32 bytes.

@iurii-ssv iurii-ssv Sep 7, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I think, back when we discussed it with Shane, I proposed "dissemination" for clarity / explicitness (conceptual simplicity) ... but if you think this is easy to optimize away, I don't mind - implementation-wise both approaches are relatively comparable in terms of complexity.

With execution_requests blinded to its root, everything in the envelope except PayloadRoot is derivable from the decided block, so carrying those 32 bytes in the §4 value lets every operator compute the signing root the moment §4 decides. That removes the new MsgType, the carrier, the per-signer dedup and the forged-dissemination residual, and keeps the unbounded requests list off the SSV wire.

I'd go for Option B directly. Nothing is deployed yet, and B is where this ends up: with the envelope signature riding the block's post-consensus packet there is no role 9, no EnvelopePartialSig, no envelope runner, and no §4→§6 handoff at all — the proposer runner signs both roots after the decision, reconstructs both from the same packets, and the builder operator publishes the reveal right behind the block, one gossip hop earlier than A. Stopping at A would leave role 9 and EnvelopePartialSig on the wire for one revision and then remove them, i.e. two migrations for Anchor and go-ssv instead of one. A is acceptable only as a deliberate stop if the shared change below is contested.

What B needs spelled out:

  • Per-root domains in post-consensus verification. Today expectedPostConsensusRootsAndDomain returns roots plus one domain (spec and node alike); the packet would carry the block root under DOMAIN_BEACON_PROPOSER and the envelope root under DOMAIN_BEACON_BUILDER, so the domain becomes per root. Small per site, but it touches every runner and the post-consensus vectors.
  • §7 packet rule. The proposer's post-consensus packet carries up to two entries at Gloas slots; validation is content-agnostic, so the exception is by role and slot, not by build path.

Three things to pin down either way:

  1. Stateless produce becomes a MUST for the proposing operator. The leader has to know PayloadRoot before it proposes, so §6's SHOULD on include_payload=true turns into a requirement on whoever proposes — otherwise the envelope GET lands on the block hot path, the objection that ruled folding out in June. go-ssv already runs the stateless path; worth confirming how Anchor's VC hands the root over.
  2. Packaging. Keep ProposerConsensusData (Duty, Version, DataSSZ) as is and make the Gloas DataSSZ a {block, payload_root} wrapper decoded by the existing Version branch — the fork-swap pattern §2 already uses for the vote type, so no cross-fork wire change.
  3. Value check. payload_root MUST be zero iff bid.builder_index != BUILDER_INDEX_SELF_BUILD; beyond that it stays leader-trusted, as PayloadRoot is today.

Publication is unchanged: builder operator by produce-root match, Contents body to each beacon node.

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 in 0f7fe152, Option B as you both described. One addition: the envelope entry is best-effort. A block-only packet is valid and final, so verifyExpectedRoot's exact-count check becomes "block root required, envelope root optional, nothing else counted".

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Agreed on the receiver side: a block-only packet must stay valid and final. On the sender side I'd make it a MUST rather than a SHOULD. An operator that decided a self-build value derives the envelope root from that value with nothing extra, so omission is never a legitimate choice, and SHOULD reads as if it were; a compliant-but-omitting implementation would quietly deny the cluster its reveal. "MUST include, receivers MUST NOT require" is the usual split.

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.

@MatheusFranco99
See if you like how @iurii-ssv answered your question :-)
Being lenient means that if for whatever reason we need to change behavior, it can be done w.o a fork.
I don't think there's room for much security considerations since only the leader holds the envelope

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.

Agreed. Sender MUST include the envelope entry; receivers MUST NOT require it, so a faulty sender loses only its envelope share and, as Gal says, behavior can change later without a fork. 5d6addeb.

…o post-consensus

Adopt Option B from the section 6 review thread: the Gloas DataSSZ becomes
{block, payload_root}, execution_requests is blinded to its root, and the
envelope signature rides the proposer's post-consensus packet as a second
entry under DOMAIN_BEACON_BUILDER. The dissemination message type, role 9,
EnvelopePartialSig, and their section 7 rules are removed. include_payload=true
becomes a MUST at Gloas slots. Section 2 gains a MAY for the same-slot check.
@MatheusFranco99

MatheusFranco99 commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Great improvement folding the envelope into the block duty!

One suggestion on §4: make the envelope entry a MUST for the sender, and keep the receiver lenient.

Right now the sender "SHOULD include" it and the receiver treats it as MAY. But every field of the envelope's signing input is now derivable from the decided value (that's the point of carrying PayloadRoot), so an honest, upgraded operator on the self-build path has no legitimate reason to omit it and shouldn't do it. A SHOULD for something no correct operator has a reason to skip just leaves room for an implementation to drop it silently, and Security Considerations already names "fewer than a threshold of operators include the envelope entry" as one of the three ways the slot's envelope is missed.

About the receiver side, I'm still not sure:

  1. if MUST NOT be rejected for lacking the envelope entry, rejecting a block-only packet costs the sender's block share too.
  2. On the other hand, from the protocol, the leader must send it so how could it not be there.

What do you guys think? @shane-moore @GalRogozinski

Comment thread sips/epbs_support.md Outdated
The envelope entry in the Gloas proposer post-consensus packet is a MUST
for senders; receivers still must not require it, so a faulty sender loses
only its envelope share. Section 6 states that the payload, requests,
blobs, and proofs never cross the SSV wire, replacing the narrower blobs
sentence in section 4.
@ovidiu-ssv-labs

ovidiu-ssv-labs commented Sep 10, 2026

Copy link
Copy Markdown

Small nit: §2 and Security Considerations now disagree about the AttestationData.Index local check

The clause added in 0f7fe152 permits an operator to reject AttestationDataIndex = 1 for a same-slot block:

An operator MAY reject a value with AttestationDataIndex = 1 when its own view already establishes, at validation time, that the block at BlockRoot has slot duty.slot … The check has no false positive.

Security Considerations still reads, unchanged:

Gloas AttestationData.Index is trusted from the QBFT leader — The Gloas AttestationData.Index value check (§2) does not require the QBFT-decided value to match each operator's local BN view … Accepted tradeoff: a malicious QBFT leader can push a value contrary to the cluster's majority BN observation.

Both statements are defensible on their own — the accepted tradeoff still holds for the general non-same-slot case, and the new clause is scoped to same-slot only. But a reader who consults Security Considerations alone will conclude no local check is available, which is now incomplete. Suggest a sentence there noting the §2 same-slot exception and that it is optional.

Measurement that may be useful for the MAY/SHOULD question. We ran the same-slot case in QA on 2026-09-09 (ssv-mini, 4 operators, Gloas active): a leader flipping a valid index caused the beacon node to refuse the committee's attestations with a gossip REJECT per verify_attestation_payload_status. No chain-level corruption and no slashing, but a direct attestation-effectiveness loss that SSV's own value check cannot observe. Given the SIP states the check has no false positive, this is the size of the loss it prevents.

Implementation status for reference: not implemented on epbs-gloas at latest 88684d504. Tracked for QA at ssvlabs/ssv#3035.

Also noting @iurii-ssv raised this in the §2 thread on 2026-05-17 and @shane-moore ruled out a BN lookup on 2026-05-18 as reintroducing per-operator agreement on the hot path — the new clause appears to answer that by using only instance-start knowledge.

iurii-ssv added a commit to ssvlabs/ssv that referenced this pull request Sep 10, 2026
Node-side surface for Gloas (ePBS) Payload Timeliness Committee duties,
per SIP ssvlabs/SIPs#94:

- types/gloas: PayloadAttestationData (42B SSZ) and PayloadAttestationMessage
  (146B SSZ), plus JSON-only PTCDuty, matching the beacon-APIs Gloas schemas;
  SSZ round-trip and golden JSON wire-format tests.
- goclient: hand-rolled HTTP for the three PTC endpoints (get duties, produce
  data, submit messages). go-eth2-client has no Gloas provider yet, so these
  are direct requests until it is rebased.
- beacon: standalone PTCCalls interface (folded into BeaconNode with the PTC
  runner later) plus regenerated MockPTCCalls; GoClient conformance asserted
  in tests.
…tions

Section 2's same-slot AttestationDataIndex check becomes a SHOULD after
SSV Labs measured the loss it prevents, and the Security entry that says
the index is leader-trusted now names this check as its one exception.
@shane-moore

Copy link
Copy Markdown
Collaborator Author

Good catch, the Security entry now names the §2 exception. Your measurement settled the keyword: SHOULD in d24fd3b7. Same-slot knowledge comes from head events already held locally, no beacon-node call on the hot path.

@MatheusFranco99

Copy link
Copy Markdown
Contributor

Coming at this from implementing it in ssv-spec (ssvlabs/ssv-spec#633): I'd like to propose relaxing the one-entry rule for RequestAuthPartialSig.

Today §5 has each distinct auth signing root travel in its own single-entry packet, and §7 enforces at-most-one-entry (REJECT above 1) for the validator-scoped roles. The stated reason is isolation: a divergent builder entry should cost that builder's bids, never the slot.

I think we get that isolation more cheaply on the receive side. All the auth roots belong to the same duty, the same proposal slot and the same runner — the only thing that differs is which configured entry each one is for. So the runner can validate per entry instead of per packet: derive the root set from its own configured entries, accept the entries whose root is in that set, ignore the rest. A divergent entry then costs that builder and nothing else, which is exactly the property §5 is after, with one packet per slot instead of up to eight.

Message validation would then only have to bound the size: allow RequestAuthPartialSig to carry up to MAX_BUILDER_ENTRIES (8) entries, require the same signer and validator index across them, and keep the distinct-root budget unchanged. That stays a purely structural check.

The reason this has to be written into §7 rather than left to the runner: validation is content-agnostic, so it can't do the per-entry matching itself, and the one-entry rule fires before the runner ever sees the packet. As it stands, a multi-entry auth packet is REJECT'd on the wire regardless of what the runner would have done with it.

@MatheusFranco99

Copy link
Copy Markdown
Contributor

§3 says an operator that has seen no beacon block for the slot abstains, and §7 fixes the REJECT/IGNORE convention, but neither says what the abstaining operator should do with the PTC partials that keep arriving from its peers.

That case is worth pinning, because the two outcomes differ for the sender: the abstainer is still a forwarding peer, and a REJECT costs the gossip score of a peer whose only difference is that it did see the block. That's an honest divergence of observation, not a fault located in the message — which by §7's own rationale is the IGNORE side of the line.

Suggestion: add a line to §3 or the §7 table saying that an operator with no frozen observation IGNOREs incoming PTC partials rather than treating them as invalid. It also gives implementations something to map onto, since otherwise "I abstained" and "you signed the wrong root" surface identically.

iurii-ssv added a commit to ssvlabs/ssv that referenced this pull request Sep 17, 2026
Node-side surface for Gloas (ePBS) Payload Timeliness Committee duties,
per SIP ssvlabs/SIPs#94:

- types/gloas: PayloadAttestationData (42B SSZ) and PayloadAttestationMessage
  (146B SSZ), plus JSON-only PTCDuty, matching the beacon-APIs Gloas schemas;
  SSZ round-trip and golden JSON wire-format tests.
- goclient: hand-rolled HTTP for the three PTC endpoints (get duties, produce
  data, submit messages). go-eth2-client has no Gloas provider yet, so these
  are direct requests until it is rebased.
- beacon: standalone PTCCalls interface (folded into BeaconNode with the PTC
  runner later) plus regenerated MockPTCCalls; GoClient conformance asserted
  in tests.
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.

6 participants