You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Implement the mandatory receiver rules in SIP-101 at 8279853: qualifying-message classification, target-slot activation, the two-slot earliness window and dedicated clock tolerance, message-kind-scoped slot-ordering exemption, complete-schedule duty authority, and the correct gossip verdicts.
Optional local retention is implemented separately in #1178. SIP-101 permits retention when either Unknown or Known-unassigned is the sole non-passing check, but Anchor initially chooses an Unknown-only subset. This issue supplies the internal, candidate-specific Unknown outcome that #1178 may consume. Known-unassigned still receives the mandatory wire-visible IGNORE, but is not offered to retention under Anchor's current local policy.
Protocol classification
The receiver behavior in this issue is SIP MUST behavior:
Determine eligibility from the stamped proposal slot S, with S >= EARLY_RANDAO_LEAD and epoch(S) >= GLOAS_FORK_EPOCH.
Enable the receiver path in time for a candidate targeting the first Gloas slot F to arrive at receiver-local slot_start(F - 2) - EARLY_RANDAO_CLOCK_TOLERANCE.
Apply the candidate timing bound of 2 * SLOT_DURATION + 1000ms, inclusive on the accept side. This replaces, rather than adds to, the generic tolerance for this rule.
Exempt qualifying RANDAO candidates from the per-operator, per-MessageID high-water rule in both directions. A candidate below the high-water mark is not rejected for ordering, and accepting it does not advance the high-water mark applied to other proposer traffic.
Treat Known-unassigned and Unknown duty views as IGNORE, never REJECT solely because of the duty view.
Mutate ordinary validation state only on acceptance or successful promotion, never on IGNORE or initial retention.
Candidate classification is based on message structure and the stamped target slot, not on whether the message arrived before slot_start(S). Every structurally and canonically valid RandaoPartialSig satisfying the eligibility predicate is a candidate, including the unconditional in-slot fallback and any later copy still inside the ordinary Proposer lateness window. "Early" names the additional receiver allowance, not an arrival-time condition. RANDAO that does not satisfy the target-slot predicate remains ordinary traffic.
A view is Known only when #1183 and #1184 retain a complete schedule with exactly SLOTS_PER_EPOCH distinct in-epoch slots. It is authoritative for every validator, independent of the local registry. Unknown means no complete retained schedule for that epoch. There are no provisional, optimistic, root-stale, disputed, or cause-based Unknown variants. Failed or malformed refreshes and local validator changes preserve a prior complete view; a reorg changes authority only when a new complete replacement is retained.
validate_slot_time has no Early RANDAO message-class input and cannot yet express the SIP timing rule.
Partial-signature validation and signer state are currently arranged around role-level ordering. The Early RANDAO exemption must be message-kind scoped because the Proposer MessageID also carries QBFT and post-consensus traffic that remains ordered.
validate_decoded_message obtains duty state through the creating get_duty_state path before partial-signature validation, and validate_partial_sig_messages_by_duty_logic calls get_or_create_operator before the duty outcome is known. Both allocations must be deferred so an IGNORE or initial-retention result does not create ordinary validation state.
Anchor gossipsub message IDs hash the full message bytes, while MessageCounts enforces one pre-consensus or post-consensus message per signer and slot. Receiver tests must exercise both layers rather than assuming gossipsub catches every distinct encoding.
Suggested approach
Add the shared protocol constants and one pure early_randao_eligible(S, chain_spec) predicate in a crate used by both receiver and producer. The SSV fork schedule is not an input.
After structural and canonical checks, classify each partial-signature message once. A RandaoPartialSig satisfying the target-slot predicate is an Early RANDAO candidate regardless of arrival time, including the in-slot fallback. Only RANDAO that fails the eligibility predicate remains ordinary RANDAO. Thread that classification through every candidate-specific rule instead of recomputing it.
Enforce the single-entry structural rule before contextual candidate handling. Operator-signature verification and non-mutating contextual checks may be ordered for local denial-of-service policy, consistent with the SIP, but any retained or accepted result must first pass operator-signature verification.
Keep the entire pre-acceptance candidate path allocation-free with respect to ordinary validation state. Use non-creating duty-state and per-operator lookups while classifying Unknown, NotAssigned, too-early, too-late, invalid, or retainable candidates. Create or mutate the duty-state map entry and operator state only when normal acceptance or successful feat(message_receiver): quarantine Unknown-view Early RANDAO shares and promote on complete duty install #1178 promotion commits the message.
For Anchor's chosen feat(message_receiver): quarantine Unknown-view Early RANDAO shares and promote on complete duty install #1178 path, carry an otherwise-valid candidate under Unknown through every remaining non-duty check without mutating state. Only after those checks pass, surface an internal UnknownProposerEpoch { slot, validator_pubkey, operator_id } reason to message_receiver::manager. This is an Anchor handoff seam, not a new protocol verdict. Known-unassigned remains wire-visible IGNORE without a retention handoff under Anchor's current policy. If another check fails, do not offer the candidate for retention.
Keep structural and canonical checks first. Preserve the SIP's allowed precedence between operator-signature failure and non-retaining contextual outcomes, while ensuring an invalid operator signature can never be retained, accepted, forwarded, collected, or state-mutating.
Implement the earliness bound in the shared slot-time path using EARLY_RANDAO_LEAD * SLOT_DURATION + EARLY_RANDAO_CLOCK_TOLERANCE. Do not stack the existing 50ms tolerance on it. Lateness remains the ordinary Proposer-role rule.
Make the slot-ordering exemption candidate-specific in both directions. Do not weaken ordering for QBFT, post-consensus, non-qualifying RANDAO, or other Proposer traffic.
Refactor activity tracking so accepted ordinary traffic still advances max_slot, accepted candidates do not, and candidate-only state is not removed while its duplicate and lateness rules must still bind. A separate last_active_slot or equivalent activity field may drive cleanup.
Keep the RANDAO duplicate limit at one per signer and slot.
Activation
Let F be the first slot of a non-genesis Gloas epoch. F and F + 1 are eligible targets even though their full receive windows begin before wall-clock Gloas. The receiver logic is gated by epoch(S), not the current epoch or current fork. No warm-up epoch and no SSV fork gate is added.
Acceptance criteria
A qualifying candidate exactly 2 * SLOT_DURATION + 1000ms early passes timing; one millisecond earlier is IGNORE. Non-qualifying traffic keeps existing timing.
A non-RANDAO Proposer message received two slots early keeps existing timing and ordering. It does not inherit the Early RANDAO allowance or high-water exemption.
Slots below EARLY_RANDAO_LEAD are ineligible at genesis. A target before F is ineligible, while targets F and F + 1 enter this path during their pre-fork receive windows.
Structural and canonical failures are handled before candidate-specific contextual rules.
Only an otherwise-valid Unknown candidate produces the internal UnknownProposerEpoch handoff under Anchor's current policy. An otherwise-valid Known-unassigned candidate produces the same wire-visible IGNORE without that handoff. Neither outcome causes ordinary validation-state mutation or allocation.
Unknown, Known-unassigned, too-early, too-late, invalid, and initially retained candidates leave both the duty-state map and its per-operator entries unchanged.
A complete schedule is authoritative for all validators. Missing, duplicate-slot, out-of-epoch, failed, or malformed responses establish no new view. A prior complete view remains Known until a complete replacement is retained.
Candidates are exempt from high-water ordering in both directions, while QBFT, post-consensus, and other proposer messages remain ordered and continue advancing their high-water state.
Candidate-only state survives cleanup for the necessary lateness and duplicate window.
The duplicate limit remains one per signer and slot.
Byte-identical copies normally share a gossip message ID and are suppressed before application validation. Distinct operator-authenticated bytes for the same (MessageID, signer, slot) key may have different gossip IDs, but only one may pass the existing application-level pre-consensus or post-consensus limit.
Tests
Cover the SIP cross-client vectors for timing boundaries, genesis eligibility, target-slot activation across F, the SSV-fork non-input, Known-assigned, Known-unassigned, Unknown with retention enabled and disabled, Anchor's Unknown-only handoff selection, an eligible in-slot fallback under Unknown, complete-view authority and malformed schedules, stale Known replacement behavior, both ordering-exemption directions with consecutive proposal slots, structural multi-fault precedence, invalid operator signature, the two duplicate layers where equal bytes produce the same gossip ID while distinct authenticated bytes for the same message key produce different gossip IDs but only one pre-consensus message is admitted, the receiver half of a same-epoch duty move where a complete view assigning X is replaced by one assigning Y, late mesh join, cross-epoch stamps, candidate-only cleanup, a non-RANDAO Proposer message two slots early, unchanged timing and ordering behavior for QBFT and post-consensus traffic, and unchanged duty-state map and operator-entry counts for every non-accepting candidate outcome.
Run the focused ssv_types and message_validator suites at PR time.
Blocked by: #1183, #1184, and #1190
Goal
Implement the mandatory receiver rules in SIP-101 at 8279853: qualifying-message classification, target-slot activation, the two-slot earliness window and dedicated clock tolerance, message-kind-scoped slot-ordering exemption, complete-schedule duty authority, and the correct gossip verdicts.
Optional local retention is implemented separately in #1178. SIP-101 permits retention when either Unknown or Known-unassigned is the sole non-passing check, but Anchor initially chooses an Unknown-only subset. This issue supplies the internal, candidate-specific Unknown outcome that #1178 may consume. Known-unassigned still receives the mandatory wire-visible IGNORE, but is not offered to retention under Anchor's current local policy.
Protocol classification
The receiver behavior in this issue is SIP
MUSTbehavior:S, withS >= EARLY_RANDAO_LEADandepoch(S) >= GLOAS_FORK_EPOCH.Fto arrive at receiver-localslot_start(F - 2) - EARLY_RANDAO_CLOCK_TOLERANCE.2 * SLOT_DURATION + 1000ms, inclusive on the accept side. This replaces, rather than adds to, the generic tolerance for this rule.MessageIDhigh-water rule in both directions. A candidate below the high-water mark is not rejected for ordering, and accepting it does not advance the high-water mark applied to other proposer traffic.Candidate classification is based on message structure and the stamped target slot, not on whether the message arrived before
slot_start(S). Every structurally and canonically validRandaoPartialSigsatisfying the eligibility predicate is a candidate, including the unconditional in-slot fallback and any later copy still inside the ordinary Proposer lateness window. "Early" names the additional receiver allowance, not an arrival-time condition. RANDAO that does not satisfy the target-slot predicate remains ordinary traffic.A view is Known only when #1183 and #1184 retain a complete schedule with exactly
SLOTS_PER_EPOCHdistinct in-epoch slots. It is authoritative for every validator, independent of the local registry. Unknown means no complete retained schedule for that epoch. There are no provisional, optimistic, root-stale, disputed, or cause-based Unknown variants. Failed or malformed refreshes and local validator changes preserve a prior complete view; a reorg changes authority only when a new complete replacement is retained.Current code
At
upstream/epbs7bad23f:Role::Proposerduty validation still contains the legacy index path and first-slot carve-out that fix: tolerate unknown proposer duty views and gate network-triggered QBFT spawn #1190 removes.validate_slot_timehas no Early RANDAO message-class input and cannot yet express the SIP timing rule.MessageIDalso carries QBFT and post-consensus traffic that remains ordered.validate_decoded_messageobtains duty state through the creatingget_duty_statepath before partial-signature validation, andvalidate_partial_sig_messages_by_duty_logiccallsget_or_create_operatorbefore the duty outcome is known. Both allocations must be deferred so an IGNORE or initial-retention result does not create ordinary validation state.Suggested approach
early_randao_eligible(S, chain_spec)predicate in a crate used by both receiver and producer. The SSV fork schedule is not an input.RandaoPartialSigsatisfying the target-slot predicate is an Early RANDAO candidate regardless of arrival time, including the in-slot fallback. Only RANDAO that fails the eligibility predicate remains ordinary RANDAO. Thread that classification through every candidate-specific rule instead of recomputing it.DutyAssignmentpath:Assigned: duty check passes;NotAssigned: wire verdict IGNORE;Unknown: wire verdict IGNORE.UnknownProposerEpoch { slot, validator_pubkey, operator_id }reason tomessage_receiver::manager. This is an Anchor handoff seam, not a new protocol verdict. Known-unassigned remains wire-visible IGNORE without a retention handoff under Anchor's current policy. If another check fails, do not offer the candidate for retention.EARLY_RANDAO_LEAD * SLOT_DURATION + EARLY_RANDAO_CLOCK_TOLERANCE. Do not stack the existing 50ms tolerance on it. Lateness remains the ordinary Proposer-role rule.max_slot, accepted candidates do not, and candidate-only state is not removed while its duplicate and lateness rules must still bind. A separatelast_active_slotor equivalent activity field may drive cleanup.Activation
Let
Fbe the first slot of a non-genesis Gloas epoch.FandF + 1are eligible targets even though their full receive windows begin before wall-clock Gloas. The receiver logic is gated byepoch(S), not the current epoch or current fork. No warm-up epoch and no SSV fork gate is added.Acceptance criteria
2 * SLOT_DURATION + 1000msearly passes timing; one millisecond earlier is IGNORE. Non-qualifying traffic keeps existing timing.EARLY_RANDAO_LEADare ineligible at genesis. A target beforeFis ineligible, while targetsFandF + 1enter this path during their pre-fork receive windows.Assignedpasses,NotAssignedis IGNORE, and Unknown is the same initial IGNORE whether feat(message_receiver): quarantine Unknown-view Early RANDAO shares and promote on complete duty install #1178 is enabled or disabled.UnknownProposerEpochhandoff under Anchor's current policy. An otherwise-valid Known-unassigned candidate produces the same wire-visible IGNORE without that handoff. Neither outcome causes ordinary validation-state mutation or allocation.Tests
Cover the SIP cross-client vectors for timing boundaries, genesis eligibility, target-slot activation across
F, the SSV-fork non-input, Known-assigned, Known-unassigned, Unknown with retention enabled and disabled, Anchor's Unknown-only handoff selection, an eligible in-slot fallback under Unknown, complete-view authority and malformed schedules, stale Known replacement behavior, both ordering-exemption directions with consecutive proposal slots, structural multi-fault precedence, invalid operator signature, the two duplicate layers where equal bytes produce the same gossip ID while distinct authenticated bytes for the same message key produce different gossip IDs but only one pre-consensus message is admitted, the receiver half of a same-epoch duty move where a complete view assigningXis replaced by one assigningY, late mesh join, cross-epoch stamps, candidate-only cleanup, a non-RANDAO Proposer message two slots early, unchanged timing and ordering behavior for QBFT and post-consensus traffic, and unchanged duty-state map and operator-entry counts for every non-accepting candidate outcome.Run the focused
ssv_typesandmessage_validatorsuites at PR time.Notes
XtoYduty-move vector. This issue owns complete-view replacement and receiver verdicts.Issues are directionally correct, not prescriptive. Verify symbols at PR time.