Skip to content

core: proposer preferences signed duty (gloas ePBS) #4691

Description

@KaloyanTanev

🎯 Problem to be solved

From the Gloas fork onwards, prepare_beacon_proposer and register_validator are superseded by validator-signed SignedProposerPreferences, gossiped on the proposer_preferences topic, as the source of proposer fee_recipient and target_gas_limit. For a distributed validator this becomes a new ahead-of-time signed duty: aggregate the VCs' partial signatures and gossip the reconstructed signature before the proposal slot.

Sub-task of the gloas epic #4324 (Block production / ePBS). Gated upstream on attestantio/go-eth2-client#316 (submit proposer preferences).

🛠️ Proposed solution

Treat proposer preferences like sync committee messages: ungated (no consensus round), accept the VC's partial signature, aggregate on threshold, broadcast — returning 2XX to the VC on submission without blocking for threshold.

Key design facts (spec + Lighthouse impl)

  • Signed message: ProposerPreferences{dependent_root, proposal_slot, validator_index, fee_recipient, target_gas_limit}, domain DOMAIN_PROPOSER_PREFERENCES at proposal_epoch. There is no wall-clock timestamp — unlike ValidatorRegistrationV1.timestamp (the field that forces us to pre-sign registrations and ignore VC ones), so the VCs' partial signatures are aggregatable.
  • Only path is aggregating VC partials at runtime. Charon can't self-sign (no key material in the running process) and can't pre-sign offline (proposal_slot + dependent_root are live consensus values).
  • dependent_root = shuffling anchor, compute_shuffling_dependent_slot(E) = start_slot(E-1)-1 = last block of epoch E-2 (MIN_SEED_LOOKAHEAD=1). It points ~1–2 epochs into the past, so it's stable and agreed cluster-wide except on a pathological deep reorg. Charon serves it via the v2 proposer-duties response, so the cluster converges on identical messages by construction.
  • Ahead-of-time, not at-trigger. Broadcast window is the current epoch up to MIN_SEED_LOOKAHEAD epochs ahead; gossip validation [IGNORE] The proposal slot has not started yet. Missing it means the validator "will not accept any trustless bids for that slot" → self-build → lost MEV.
  • VC data source = v2 proposer duties (apis/validator/duties/proposer.v2.yaml; v1 proposer.yaml is deprecated). v2 dependent_root = get_block_root_at_slot(state, start_slot(epoch-1)-1) = last of E-2 = exactly the anchor the preference needs (v1 used start_slot(epoch)-1 = E-1, wrong here). Confirmed in Lighthouse validator_services/src/proposer_preferences_service.rs (v8.2.0): reads (dependent_root, duties) from the proposer-duties cache, copies dependent_root verbatim, signs per-duty, POSTs SSZ to one BN; dedup republishes only when dependent_root changes.
  • Aggregation identity includes dependent_root. On reorg it changes and VCs resubmit, so ParSigDB keying must treat a changed dependent_root for a proposal_slot as a fresh aggregation, not a collision with the stale one.

Content consistency (fee_recipient / gas_limit)

The VC signs fee_recipient and target_gas_limit from its own local config, not from the lock (Charon has no BN→VC channel for these). For the partials to aggregate they must match cluster-wide. Rather than a hard reject (which fragments intake during a staggered gas_limit bump — each node validates against its own lock), warn + emit a metric on mismatch vs the lock value and aggregate whatever reaches threshold (majority wins, self-heals). Getting the correct values onto the VCs in the first place is #4692.

Tasks

  • Adopt go-eth2-client proposer-preferences interfaces (gated on feat: submit Gloas proposer preferences attestantio/go-eth2-client#316)
  • DutyProposerPreferences duty type + DOMAIN_PROPOSER_PREFERENCES signing domain
  • Core (Versioned)ProposerPreferences type + NewPartialSignedProposerPreferences (SSZ/JSON/proto encode-decode, parsig wire carries version)
  • Serve v2 proposer duties (GET /eth/v2/validator/duties/proposer/{epoch}) with the E-2 shuffling dependent_root so cluster VCs sign identical messages
  • validatorapi.SubmitProposerPreferences intake — sync-message shaped: verify partial sig, group, fan out to subscribers, return 2XX non-blocking
  • ParSigDB / ParSigEx / SigAgg wiring; aggregation keyed by (validator, proposal_slot, dependent_root); treat a changed dependent_root (reorg) as a fresh aggregation
  • Bcast: submit the reconstructed SignedProposerPreferences to the BN (→ gossip) ahead of the slot
  • Content check: warn + metric when VC-submitted fee_recipient / target_gas_limit differ from the lock value (do not reject)
  • Tracker / reasons integration for the new duty
  • validatormock support + simnet e2e test

🔗 References

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

protocolProtocol Team tickets

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions