Goal
Spawn Lighthouse's PayloadAttestationService with AnchorValidatorStore as the backend in anchor/client/src/lib.rs (the service is absent from client/ today). This is the final wiring step for the PTC duty. Reworks the not-yet-implemented #1038.
Context
The LH service drives the per-slot PTC loop: at the 75% cutoff it fetches PayloadAttestationData, abstains on no-block, and calls sign_payload_attestation per validator (implemented in #1077). PTC duties are populated by LH's poll_beacon_ptc_attesters under the existing duties_service::start_update_service (Gloas-gated), so no extra duty wiring is needed. Dep: #1077. Part of milestone #6.
Construction shape (verified against the pin): there is no PayloadAttestationServiceBuilder. Construct with PayloadAttestationService::new(...) (6 positional args, the 6th being chain_spec, which SyncCommitteeService::new does NOT take), and start with start_update_service() which takes no args (consumes self), NOT start_update_service(&spec). Mirror SyncCommitteeService::new (client/src/lib.rs:682-687) for the constructor only. Gate the start on spec.is_gloas_scheduled(), matching LH's own VC (validator_client/src/lib.rs:657), which starts payload_attestation_service (and proposer_preferences_service) only when Gloas is scheduled. Do NOT mirror the ungated sync_committee_service start: the per-slot gloas_enabled() self-gate makes an ungated start harmless, but it spawns a perpetual idle task pre-Gloas, which diverges from LH.
Suggested approach
anchor/client/src/lib.rs (lead with the symbol; line hints drift):
| Symbol (line hint) |
change |
validator_services::{...} use block (~54-63) |
add payload_attestation_service::PayloadAttestationService |
after the sync_committee_service construction (~687) |
construct (below) |
after metadata_service.start_update_service() (~730) |
start (below) |
let payload_attestation_service = PayloadAttestationService::new(
duties_service.clone(),
validator_store.clone(),
slot_clock.clone(),
beacon_nodes.clone(),
executor.clone(),
spec.clone(),
);
// Match LH's VC (validator_client/src/lib.rs:657): start only when Gloas is scheduled.
if spec.is_gloas_scheduled() {
payload_attestation_service
.start_update_service()
.map_err(|e| format!("Unable to start payload attestation service: {e}"))?;
}
(Confirm the spec binding/type in scope here; is_gloas_scheduled() is on ChainSpec at the pin, called as …spec.is_gloas_scheduled() in LH's VC.)
LH constructor (verified, …/validator_services/src/payload_attestation_service.rs:42-49): new(duties_service: Arc<DutiesService<S,T>>, validator_store: Arc<S>, slot_clock: T, beacon_nodes: Arc<BeaconNodeFallback<T>>, executor: TaskExecutor, chain_spec: Arc<ChainSpec>). Start: start_update_service(self) -> Result<(), String> (:63).
Acceptance criteria
PayloadAttestationService imported and constructed with 6 positional args ending in spec.clone(); started via .start_update_service() (no &spec); start error wrapped into the String error chain like sibling services.
- Start wrapped in
if spec.is_gloas_scheduled() { … }, matching LH's VC (validator_client/src/lib.rs:657); not started ungated.
cargo build --workspace green; the ssv-mini smoke test shows PTC duties attempted under a Gloas-scheduled spec.
Tests
Client startup is wired/integration-tested, not unit-tested per service (no per-service spawn unit test exists for SyncCommitteeService). Mirror that convention: rely on cargo check / build + existing client integration tests; if a startup smoke test exists, extend it to assert the service spawns under a Gloas spec.
Notes
Dep #1077 (the service drives sign_payload_attestation; the trait is satisfied by the stub so it technically compiles on the role/kind change in #1075 alone, but should land after #1077). The LH types are already in the pin, so no LH bump. Issues are directionally correct, not prescriptive; verify symbols at PR time.
Goal
Spawn Lighthouse's
PayloadAttestationServicewithAnchorValidatorStoreas the backend inanchor/client/src/lib.rs(the service is absent fromclient/today). This is the final wiring step for the PTC duty. Reworks the not-yet-implemented #1038.Context
The LH service drives the per-slot PTC loop: at the 75% cutoff it fetches
PayloadAttestationData, abstains on no-block, and callssign_payload_attestationper validator (implemented in #1077). PTC duties are populated by LH'spoll_beacon_ptc_attestersunder the existingduties_service::start_update_service(Gloas-gated), so no extra duty wiring is needed. Dep: #1077. Part of milestone #6.Construction shape (verified against the pin): there is no
PayloadAttestationServiceBuilder. Construct withPayloadAttestationService::new(...)(6 positional args, the 6th beingchain_spec, whichSyncCommitteeService::newdoes NOT take), and start withstart_update_service()which takes no args (consumesself), NOTstart_update_service(&spec). MirrorSyncCommitteeService::new(client/src/lib.rs:682-687) for the constructor only. Gate the start onspec.is_gloas_scheduled(), matching LH's own VC (validator_client/src/lib.rs:657), which startspayload_attestation_service(andproposer_preferences_service) only when Gloas is scheduled. Do NOT mirror the ungatedsync_committee_servicestart: the per-slotgloas_enabled()self-gate makes an ungated start harmless, but it spawns a perpetual idle task pre-Gloas, which diverges from LH.Suggested approach
anchor/client/src/lib.rs(lead with the symbol; line hints drift):validator_services::{...}use block (~54-63)payload_attestation_service::PayloadAttestationServicesync_committee_serviceconstruction (~687)metadata_service.start_update_service()(~730)(Confirm the
specbinding/type in scope here;is_gloas_scheduled()is onChainSpecat the pin, called as…spec.is_gloas_scheduled()in LH's VC.)LH constructor (verified,
…/validator_services/src/payload_attestation_service.rs:42-49):new(duties_service: Arc<DutiesService<S,T>>, validator_store: Arc<S>, slot_clock: T, beacon_nodes: Arc<BeaconNodeFallback<T>>, executor: TaskExecutor, chain_spec: Arc<ChainSpec>). Start:start_update_service(self) -> Result<(), String>(:63).Acceptance criteria
PayloadAttestationServiceimported and constructed with 6 positional args ending inspec.clone(); started via.start_update_service()(no&spec); start error wrapped into theStringerror chain like sibling services.if spec.is_gloas_scheduled() { … }, matching LH's VC (validator_client/src/lib.rs:657); not started ungated.cargo build --workspacegreen; the ssv-mini smoke test shows PTC duties attempted under a Gloas-scheduled spec.Tests
Client startup is wired/integration-tested, not unit-tested per service (no per-service spawn unit test exists for
SyncCommitteeService). Mirror that convention: rely oncargo check/build+ existing client integration tests; if a startup smoke test exists, extend it to assert the service spawns under a Gloas spec.Notes
Dep #1077 (the service drives
sign_payload_attestation; the trait is satisfied by the stub so it technically compiles on the role/kind change in #1075 alone, but should land after #1077). The LH types are already in the pin, so no LH bump. Issues are directionally correct, not prescriptive; verify symbols at PR time.