Skip to content

ePBS (EIP-7732) Support - #94

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

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

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