Skip to content

ePBS (EIP-7732) Support - #94

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

ePBS (EIP-7732) Support#94
shane-moore wants to merge 67 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>
@iurii-ssv

Copy link
Copy Markdown

§5 message validation: proposer-duty-view staleness tolerance (+ emission grace) — proposal from devnet findings

While validating §5 on the Gloas local_testnet (ssv epbs-gloas), we hit a liveness gap in the §5 wire rules worth encoding in the SIP so both implementations converge on it.

Why §5 is uniquely fragile here. A preference partial is broadcast exactly once per emission, and its bytes are deterministic (BLS partial over a fixed signing root, deterministic operator envelope) — so gossipsub's content-derived message-id + seen-cache make any re-broadcast a no-op. A receiver that wrongly drops the first copy can therefore never recover it; enough wrong drops and the (slot, validator) preference starves quorum permanently.

The wrong drop we observed. The duty-existence rule ("a §5 message must reference a real proposer assignment at its slot") is evaluated against the receiver's cached proposer-duty view. §5 emission is triggered by the very event (validator registration → indices change) that invalidates peers' cached views, so during a registration wave a receiver validates a just-added validator's honest partials against a view fetched before it learned of that validator → no duty → dropped, unrecoverably. On the devnet this starved the first §5-eligible proposal slot after the registration wave on all four operators, while every later slot converged cleanly.

Proposed §5 validation rule (implemented in ssv, suggesting the SIP §5 message-validation text adopts it):

  1. The duty-existence check MUST treat an assignment view that predates the node's latest validator-set change as unknown — skip the check, don't reject — until the view is refreshed. This is the same tolerance already extended to a not-yet-fetched epoch. Anti-spam remains bounded during such windows by committee membership, the per-(slot, signer) distinct-signing-root cap, and the per-epoch duty-count cap.
  2. Non-normative but recommended: emitters SHOULD delay §5 (re-)emission a couple of slots after a validator-set change, so committee peers converge on the new set before the one-shot partials go out.

The same tolerance applies to the §6 self-build envelope's assignment check (identical shape, identical failure mode).

Would be good to reflect this in the §5 text so Anchor implements the same semantics from day one — otherwise mixed-implementation committees will disagree about which §5 messages are valid exactly during registration churn.

…(§5/§7)

§5 (re-)emission is triggered by the very validator-index change that
invalidates peers' cached proposer-duty views. Because a preference
partial is a one-shot deterministic broadcast, a wrongly-dropped first
copy is unrecoverable under gossip dedup, starving that slot's quorum.

§7: a duty-assignment view predating the node's latest validator-set
change is treated as unknown (skip the check, don't IGNORE) until it
refreshes, extending the not-yet-fetched-epoch tolerance. Applies to
the RoleProposerPreferences (8) and RoleEnvelopeProposer (9) checks.

§5: emitters SHOULD delay (re-)emission a couple slots after a
validator-set change so peers converge before the one-shot partials go
out.
@shane-moore

shane-moore commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks @iurii-ssv, great find! Folded both parts into the SIP (0a8de9e): §7 now skips (doesn't IGNORE) the duty-assignment check when the cached view predates the latest validator-set change, until it refreshes; §5 gets the non-normative "delay re-emission a couple slots after a validator-set change" note. Anti-spam bound (committee membership + distinct-root cap + duty-count cap) kept explicit. Confirmed Anchor would hit the same wrong-drop without it, so we'll match.

shane-moore@0a8de9e

shane-moore added a commit to shane-moore/SIPs that referenced this pull request Jul 27, 2026
The summary said the SIP activates at the Gloas-aligned SSV fork, but the
ePBS SIP (ssvlabs#94, checked at d5197bc) defines no SSV-network fork; its
validation rules gate on epoch(msg.slot) >= GLOAS_FORK_EPOCH. This SIP now
matches that model explicitly: activation is Ethereum-gated by the
epoch(S) >= GLOAS_FORK_EPOCH conjunct, the SSV fork-equality conjunct is
window-boundary protection only (it keeps the 2-slot emission window from
spanning any scheduled SSV fork), and a new S >= EARLY_RANDAO_LEAD conjunct
resolves the genesis underflow normatively, surfaced by the slot-zero unit
test in the Anchor implementation. Vectors spell out the boundary outcomes.
Comment thread sips/epbs_support.md
|---|---|---|---|---|---|---|---|
| `RolePTCAttester` (7) | REJECT | `PTCAttesterPartialSig` (7); 1 | PTC attestation slot | none | 3 slots | PTC assignment at slot (IGNORE) | 2 (IGNORE) |
| `RoleProposerPreferences` (8) | REJECT | `ProposerPreferencesPartialSig` (8); up to 4 distinct signing roots, repeat of a seen root = duplicate | `proposal_slot` | `(1 + MIN_SEED_LOOKAHEAD) * SLOTS_PER_EPOCH` slots | 2 slots | proposer assignment at `proposal_slot` (IGNORE) | `SLOTS_PER_EPOCH` (IGNORE) |
| `RoleEnvelopeProposer` (9) | QBFT: 1 proposal / prepare / commit / round-change per (signer, slot, round); round cut-off 2 | `PostConsensusPartialSig` (0); 1 | proposal slot | none | 3 slots | proposer assignment at slot (IGNORE) | `SLOTS_PER_EPOCH` (IGNORE) |

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.

@yurii-ssv

just a reminder that this and other message validation rules should be fixed in https://github.com/ssvlabs/knowledge-base/blob/main/p2p/MessageValidation/Rules.md and spec

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

@GalRogozinski not sure what you mean, you want us to update https://github.com/ssvlabs/knowledge-base/blob/main/p2p/MessageValidation/Rules.md to align it with the SIP ? I can create PR for it, I guess. Just maybe lets approve/merge the SIP first, so we know it is final.

And I also think we plan/want to add the "trusted bids flow/duty" to this SIP (once it's finalized on the Ethereum side), right @shane-moore ?

@shane-moore shane-moore Jul 28, 2026

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.

Yes, we'll add a duty for RequestAuth once the beacon api/builder-spec pr's are a bit more solid:
ethereum/builder-specs#165
ethereum/beacon-APIs#630
ethereum/beacon-APIs#625

similarly, it shows EIP-7688 Progressive Containers as still CFI status in the glamsterdam meta EIP, but they verbally SFI'd it on last ACDC call, so once that's documented, I'll update the SIP accordingly

Comment thread sips/epbs_support.md Outdated
| `RoleProposerPreferences` (8) | REJECT | `ProposerPreferencesPartialSig` (8); up to 4 distinct signing roots, repeat of a seen root = duplicate | `proposal_slot` | `(1 + MIN_SEED_LOOKAHEAD) * SLOTS_PER_EPOCH` slots | 2 slots | proposer assignment at `proposal_slot` (IGNORE) | `SLOTS_PER_EPOCH` (IGNORE) |
| `RoleEnvelopeProposer` (9) | QBFT: 1 proposal / prepare / commit / round-change per (signer, slot, round); round cut-off 2 | `PostConsensusPartialSig` (0); 1 | proposal slot | none | 3 slots | proposer assignment at slot (IGNORE) | `SLOTS_PER_EPOCH` (IGNORE) |

Lateness TTL uses the existing deadline convention: a message is late once received after `slot_start(slot + TTL)` plus the clock-error margin. Duty limits count distinct duty slots per (signer, epoch of the message slot). Duty assignment checks apply only once the local node knows the duties for the message slot's epoch (a not-yet-fetched epoch is tolerated rather than IGNORE'd, since the message can legitimately arrive first).

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.

§7: duty-limit/count keying drops the MessageID (validator) dimension.

"Duty limits count distinct duty slots per (signer, epoch of the message slot)" — read literally (per signer + role + epoch), the PTC limit of 2 breaks for real clusters: PTC selection gives each validator roughly PTC_SIZE / per-slot-committee-size ≈ 1.6% probability per epoch (at ~1M active validators), so e.g. a 200-validator cluster expects ~3 PTC duties per epoch across its validators, and honest partials past the second get IGNORE'd. The count column has the same issue ("1 per (signer, slot)"): two of a cluster's validators can hold PTC seats in the same slot, which is two containers, not one.

The intent is clearly per-validator — the PTC derivation says "one PTC duty per validator per epoch", and existing message validation keys its state per MessageID, which carries the validator pubkey for validator-scoped roles. Suggest making the keying explicit: "per (signer, MessageID, epoch)" for duty limits and "1 per (signer, MessageID, slot)" for the count column, so an implementer can't read it as a global per-operator budget.

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.

Good catch, the shorthand was ambiguous. Clarified both keys in 9343090: partial-signature counts are keyed by (MessageID, signer, slot), and duty limits by (MessageID, signer, epoch of the message slot).

Comment thread sips/epbs_support.md Outdated

Lateness TTL uses the existing deadline convention: a message is late once received after `slot_start(slot + TTL)` plus the clock-error margin. Duty limits count distinct duty slots per (signer, epoch of the message slot). Duty assignment checks apply only once the local node knows the duties for the message slot's epoch (a not-yet-fetched epoch is tolerated rather than IGNORE'd, since the message can legitimately arrive first).

A view that predates the node's most recent validator-set change is treated the same way: skip the check, don't IGNORE, until it refreshes. This is load-bearing for the one-shot `RoleProposerPreferences` (8) and `RoleEnvelopeProposer` (9) checks: their (re-)emission is triggered by the very validator-index change that invalidates a peer's cached view, and a deterministic partial dropped once is unrecoverable (gossip's seen-cache suppresses any re-broadcast), so a wrongly-dropped first copy permanently starves that (`proposal_slot`, `validator_index`) from quorum. Anti-spam stays bounded by committee membership, the distinct-signing-root cap, and the per-epoch duty-count cap.

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.

§7: the stale-view tolerance covers validator-set changes but not dependent-root (reorg) reshuffles.

§3/§5 make dependent-root changes the canonical duty-refresh and re-emission trigger, and Security Considerations flags the late-reorg race — but the assignment-check tolerance here covers only (a) a not-yet-fetched epoch and (b) a view predating the node's most recent validator-set change. A node that has observed a reorg but hasn't refetched duties holds a fetched, post-validator-set-change view that is simply stale: it will IGNORE honest partials/QBFT messages for newly-assigned slots (roles 7, 8, and 9 all carry assignment checks).

The unrecoverability argument this paragraph already makes — deterministic one-shot partial + gossip seen-cache — applies identically here, so a wrongly-dropped first copy permanently starves that (proposal_slot, validator_index) from quorum.

Suggest the symmetric rule: after a local dependent-root change affecting an epoch, skip assignment checks for that epoch until the duty refetch lands — the same treatment as the validator-set-change window. Anti-spam stays bounded by the same caps cited at the end of this paragraph.

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.

Good catch, agreed. Updated §7 so a cached duty view whose dependent_root no longer matches the current local root for that epoch is treated like an unfetched or validator-set-stale view. Assignment checks resume only after a successful refresh matching the current validator set and root. This applies to roles 7, 8, and 9: dee67f5

Mechanical re-pin of every consensus-specs permalink from 801a38e15
(reviewed 2026-07-06) to master head 46d3d3513 (reviewed 2026-08-04),
plus citation and context accuracy fixes for upstream changes since the
old pin. No SSV implementation impact in this commit.

- Re-anchor the PTC security citation from a p2p-interface.md line
  anchor to the stable payload_attestation_message gossip-validation
  heading (the file was rewritten as executable functions upstream).
- Update the proposer_preferences gossip dedup key to
  (dependent_root, proposal_slot): upstream dropped validator_index,
  which the proposer check already determines. Matching key-language
  updates in the two related security entries.
- State the REJECT severity of dependent_root sourcing and the
  network-wide IGNORE of re-emissions that change only fee_recipient
  or target_gas_limit under an unchanged key.
- Flatten PR-history narration (#5414, #5429) into plain statements of
  current spec behavior.
EIP-7688 was ratified Scheduled for Inclusion for Glamsterdam at ACDC
183, so the SIP drops its describe-positional-until-SFI stance and
specifies the progressive Gloas roots. Serialization, the QBFT values,
and all SSV containers are unchanged; only signing-root derivation
moves.

- Summary/Motivation/Rationale: state the same-bytes different-roots
  boundary and enumerate exactly which SSV signing paths change
  (aggregate, block, self-build envelope) and which do not.
- §2: the aggregate serialization stays Electra-shaped, but the Gloas
  Attestation is progressive, so the AggregateAndProof signing root
  requires the Gloas type in both aggregation paths.
- §4: the decided block bytes are unchanged while the block root
  commits to the progressive BeaconBlockBody; positional merkleization
  is invalid at Gloas slots.
- §6: the blinded envelope is a five-field progressive container with
  an explicit mix_in_active_fields root derivation, progressive
  payload/request roots, ProgressiveList bounds guidance, and a
  fixed-expected-root test requirement for blinded-to-full parity.
- Security: mixed positional/progressive merkleization splits
  threshold signatures on identical decided bytes.
- Watchlist: track the EIP-7773 status update and the client type
  implementations instead of waiting on SFI.
- §3: payload_attestation_data moved its slot from path to query
  parameter after v5.0.0-alpha.2; re-pin that one link to beacon-APIs
  master 4b4d89a2 and note the 204 no-block response as the explicit
  abstain signal.
- Watchlist: beacon-APIs #625 closed 2026-08-04, superseded by #630
  (BuilderEntry preferences, max_execution_payment,
  submitBuilderPreferences, request_auth types); reserve
  DOMAIN_REQUEST_AUTH 0x0B000001 and partial-sig type 9 for the
  request-auth amendment pending builder-specs #165.
…nale

- §4: point the GetBlockData permalink at ssv-spec ac6b4233, which
  contains the roles and partial-sig types this SIP defines; the old
  85ee4f32 tree predates them and contradicted the SIP for readers
  following the link. Same file and line range, verified unchanged.
- §6: state the settled design rule that only payload is blinded (the
  SSV form deviates from the consensus-spec envelope in exactly one
  field; the request lists are tiny relative to the payload), and link
  EIP-8282, previously named with no link.
iurii-ssv added a commit to ssvlabs/ssv that referenced this pull request Aug 5, 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.
EIP-7773 now lists EIP-7688 as Scheduled for Inclusion, so the watch
trigger is gone; the normative progressive-root text stands on its own.
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.

4 participants