Skip to content

feat(validator_store): add PTC voting context phase to metadata_service #1036

Description

@shane-moore

Goal

Add a new metadata-service phase loop in anchor/validator_store/src/metadata_service.rs that pre-fetches PayloadAttestationData from the BN shortly before LH's PayloadAttestationService starts QBFT at 75% slot. The loop publishes the fetched context on a watch channel that sign_payload_attestation (follow-up issue) reads as its QBFT initial value. Modeled on the existing Phase-2 voting context loop in metadata_service.rs.

Context

Per SIP-94 §3 (ssvlabs/SIPs#94), PTC requires one QBFT instance per cluster per slot. Each operator needs an initial value for that instance; LH's PayloadAttestationService fetches per-operator inside its own loop, which gives Anchor no place to stage that initial value at the metadata layer.

This phase produces the operator-local initial value by pre-fetching PayloadAttestationData through that operator's BeaconNodeFallback. Honest operators can still disagree on payload_present / blob_data_available near the 75% deadline because their BNs observe envelope and blob data at slightly different times; cluster coherence comes from QBFT deciding one value, not from the metadata fetch itself. Mirrors the §2 attestation pattern (per-operator pre-fetch into metadata_service).

When the BN has not yet seen a block for the slot, the honest behavior is to skip the duty (consensus-specs Gloas validator.md: "If the validator has not seen any beacon block for the assigned slot, do not submit"). The phase surfaces this to sign_payload_attestation via a sentinel on the watch channel.

Suggested approach

anchor/validator_store/src/metadata_service.rs:

  • Add a new phase loop (Phase-4-style) that fires shortly before chain_spec.get_payload_attestation_due() (default close to 75% slot, with margin for a BN round-trip; implementer tunes).
  • Fetch via BeaconNodeFallback::first_success(|bn| bn.get_validator_payload_attestation_data(slot)). Falls back to the next BN on failure.
  • Publish to a new PayloadAttestationContext watch channel carrying beacon_block_root, payload_present, blob_data_available.
  • When the BN returns no block for the slot, publish a "skip duty" sentinel that the follow-up sign_payload_attestation recognizes (sentinel shape is an open question, see below).
  • Phase-1/2/3 loops should be untouched.

Acceptance criteria

  • New phase loop fires at the configured slot fraction (close to LH's 75% PTC service start, with margin for BN round-trip).
  • BN fetch via BeaconNodeFallback::first_success; falls back to the next BN on failure.
  • Watch channel publishes a PayloadAttestationContext per slot.
  • "No block seen" BN response surfaces as a "skip duty" sentinel on the watch channel.
  • Existing Phase-1/2/3 loops unaffected.
  • Integration tests using a mocked BN:
    • ptc_phase_publishes_on_bn_success: BN returns PayloadAttestationData; slot clock advances; assert the watch channel publishes within slot.
    • ptc_phase_publishes_skip_on_bn_none: BN returns nothing for the slot; assert the watch channel publishes the "skip duty" sentinel.

Open questions

  • "Skip duty" sentinel shape. Options include Option<PayloadAttestationContext> (None = skip), an enum like PtcSlotState { Vote(ctx), Skip }, or a flag on the context struct. Decide before publishing the type so the follow-up sign_payload_attestation PR can pattern-match cleanly.
  • Phase ordering against existing loops. Phases 1/2/3 fire at different slot fractions; Phase 4 needs to fire before 75%. Confirm against the existing loop structure in metadata_service.rs whether the new phase runs as an independent task or sequences after Phase 3.
  • BN-None vs BN-error distinction. SIP-94 references the validator-spec "skip if no block seen" rule. Check the LH BN client (eth2/src/lib.rs::get_validator_payload_attestation_data) for whether "no block" surfaces as None, an explicit error variant, or both — the sentinel handling depends on it.

Risks

  • Timing tuning. Fetch too early and envelope arrival is uncertain; too late and the cluster's QBFT round budget shrinks below the 75% deadline. Default ~70% slot is a starting point; measure on ssv-mini before locking.
  • BN fallback failure. If all configured BNs fail for the slot, the cluster cannot vote PTC. Missed reward only, not slashing. Surface via a metric counter so operators can spot a degraded BN fleet.

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