Skip to content

feat(message_validator): early RANDAO receiver rules: eligibility predicate, earliness window, ordering exemption, duty tri-state #1177

Description

@shane-moore

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

Current code

At upstream/epbs 7bad23f:

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.
  • Build on fix: tolerate unknown proposer duty views and gate network-triggered QBFT spawn #1190's pubkey-keyed DutyAssignment path:
    • Assigned: duty check passes;
    • NotAssigned: wire verdict IGNORE;
    • Unknown: wire verdict IGNORE.
  • 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.
  • An eligible RANDAO received in-slot or later within ordinary Proposer lateness remains a candidate. Under Unknown it produces the same initial IGNORE and optional feat(message_receiver): quarantine Unknown-view Early RANDAO shares and promote on complete duty install #1178 handoff as an early copy.
  • 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.
  • Assigned passes, NotAssigned is 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.
  • 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.

Notes

Issues are directionally correct, not prescriptive. Verify symbols at PR time.

Metadata

Metadata

Assignees

No one assigned

    Labels

    epbsePBS / EIP-7732 / Gloas implementation

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions