Skip to content

feat(client): spawn PayloadAttestationService gated on Gloas - #1093

Merged
mergify[bot] merged 1 commit into
sigp:epbsfrom
shane-moore:feat/ptc-service-spawn
Jun 17, 2026
Merged

mergify[bot] merged 1 commit into
sigp:epbsfrom
shane-moore:feat/ptc-service-spawn

Conversation

@shane-moore

Copy link
Copy Markdown
Member

Problem, Evidence, and Context

Lighthouse's PayloadAttestationService exists in the pinned LH but was never spawned in Anchor's client/, so the PTC (Payload Timeliness Committee) duty never ran. This is the final wiring step: with it, a validator holding a PTC seat under a Gloas-scheduled spec signs and publishes its PayloadAttestationMessage.

Change Overview

Construct and start LH's PayloadAttestationService in Client::run, gated on spec.is_gloas_scheduled() to mirror LH's own validator client (validator_client/src/lib.rs:657). The service owns the per-slot PTC loop (fetch data at the 75% cutoff, abstain on no block, sign per validator, submit to the BN); the SSV partial-signature exchange and threshold reconstruction happen inside the existing collect_signature path, invisible to the LH service.

The change is one import plus one gated block. ProposerPreferences (#1064) is left a TODO(gloas) to join the same block once implemented.

Unchanged: the sign path, duty population, and every sibling service. No new types, no LH bump.

Risks, Trade-offs, and Mitigations

  • Blast radius is ~zero on live networks. is_gloas_scheduled() is false wherever no Gloas fork is configured (mainnet / hoodi / holesky today), so nothing is constructed or spawned; the new path only activates under a Gloas-scheduled spec.
  • The start is gated (rather than relying on the service's own per-slot gloas_enabled() self-gate) to avoid spawning a perpetual idle task pre-Gloas, matching LH.
  • Pre-existing LH property, not introduced here and out of scope for wiring: PTC duties are signed sequentially per validator (immaterial at current PTC seat counts).

Validation

  • make cargo-fmt-check: clean.
  • make lint (cargo clippy --workspace --tests -- -D warnings): green, zero warnings, including after rebasing onto the just-merged refactor!: gate ePBS on Ethereum Gloas fork, remove SSV Fork::CStar #1090 (Fork::CStar removal).
  • No new unit test: client startup is not unit-tested per service (mirrors SyncCommitteeService). Behavioral verification (the service starts and attempts PTC at the 75% cutoff under a Gloas-scheduled spec) belongs on a devnet.

Rollback

Revert the single commit. No config, data, or operational impact (no-op until Gloas is scheduled).

Blockers / Dependencies

Deps #1080 and #1082 are merged. Independent of #1090 (already merged; this branch is rebased on top).

@shane-moore

Copy link
Copy Markdown
Member Author

@claude, please take a look

@claude-code-actions-sigp

claude-code-actions-sigp Bot commented Jun 16, 2026 •

Copy link
Copy Markdown

Claude finished @shane-moore's task in 5m 49s —— View job


Review

  • Read PR diff vs origin/epbs
  • Inspect surrounding Client::run wiring in anchor/client/src/lib.rs
  • Cross-check call shape against sibling services
  • Post review

Summary

Tight, well-scoped wiring change. The construction shape and arg order mirror the sibling services already in Client::run — SyncCommitteeService::new (anchor/client/src/lib.rs:761) and MetadataService::new (anchor/client/src/lib.rs:777) — so it slots in idiomatically. Gating on spec.is_gloas_scheduled() (rather than relying solely on the service's per-slot gloas_enabled() self-gate) is the right call: it avoids spawning an idle long-lived task on networks where Gloas is not scheduled, and the rationale is captured in-line.

I could not independently verify the upstream LH PayloadAttestationService::new signature (LH source lives outside the allowed working tree for this session and external fetches were not permitted). The PR reports make cargo-fmt-check and make lint clean, which implies the call compiles against the pinned LH rev 1a6863118.

Observations (non-blocking)

  1. Sibling-service parity in start_update_service arg shape. attestation_service.start_update_service(&spec) (line 800), sync_committee_service.start_update_service(&spec) (line 804), and preparation_service.start_proposer_prepare_service(&spec) (line 812) all take &spec, while PayloadAttestationService::start_update_service() takes none. That is fine iff the LH service stores spec on construction (as the new(...) arg list here suggests it does) — just worth a sanity check that no per-call spec/fork-version is needed. If make lint passed, the signature matches; flagging only because it stood out against the surrounding pattern.

  2. proposer_nodes not threaded in. Matches AttestationServiceBuilder and SyncCommitteeService, which also use only beacon_nodes. PTC publication going to the same BN fallback as attestations is the expected shape; calling it out so it's explicit if users with a separate proposer-BN setup later ask why PA messages don't fan out via proposer nodes.

  3. TODO(gloas) for ProposerPreferences. Tracker comment for feat(client): spawn LH ProposerPreferencesService with AnchorValidatorStore backend #1064 is appropriate here — keeps the integration point obvious without expanding scope. Consider linking the issue number directly in the comment (e.g. TODO(gloas, #1064)) so it's grep-able.

  4. No new test. Reasoning in the PR body matches repo precedent (SyncCommitteeService startup is similarly not unit-tested at the client layer). Behavioral verification on a Gloas-scheduled devnet is the right place for this.

Risk

Effectively zero on currently-supported networks — the gated branch is dead code wherever is_gloas_scheduled() returns false, which is everywhere Gloas is not configured. Rollback is a single-commit revert as stated. No data, config, or migration concerns.

Verdict

LGTM as a wiring-only change. None of the observations above are blocking.
· branch: feat/ptc-service-spawn

@codecov-commenter

codecov-commenter commented Jun 16, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 11 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (epbs@a7c0b84). Learn more about missing BASE report.

Files with missing lines Patch % Lines
anchor/client/src/lib.rs 0.00% 11 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             epbs    #1093   +/-   ##
=======================================
  Coverage        ?   63.90%           
=======================================
  Files           ?      163           
  Lines           ?    28263           
  Branches        ?        0           
=======================================
  Hits            ?    18061           
  Misses          ?    10202           
  Partials        ?        0           
Flag Coverage Δ
rust 63.90% <0.00%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Wire Lighthouse's PayloadAttestationService into client startup so locally
assigned validators sign and publish PTC payload attestations. Gate the start
on spec.is_gloas_scheduled() to mirror LH's validator client and avoid a
perpetual idle task on networks where Gloas is not scheduled.

PTC duties are already populated by the Gloas-gated poll_beacon_ptc_attesters
inside the existing duties service, and sign_payload_attestation landed in
sigp#1082, so this is the final wiring step.

Closes sigp#1078
@shane-moore
shane-moore force-pushed the feat/ptc-service-spawn branch from 9e89ff6 to a3a7c38 Compare June 16, 2026 18:30
@shane-moore

shane-moore commented Jun 16, 2026 •

Copy link
Copy Markdown
Member Author

@claude, thanks for the review. Addressed:

Obs 1 (no &spec on start_update_service) — Correct, and verified against the pin (1a6863118): the LH service stores chain_spec in Inner at construction (payload_attestation_service.rs:19) and derives everything per-slot from it — slot/payload-attestation timing (:64-65), the gloas_enabled() gate (:87-90), and the submission fork_name_at_slot(slot) (:200). No per-call spec/fork is needed, so the asymmetry with the sibling services is just an upstream API-shape difference. new(...) is 6 args ending in chain_spec: Arc<ChainSpec> (:43-50), i.e. SyncCommitteeService's args plus spec.

Obs 2 (proposer_nodes not threaded in) — Intended. PTC is attestation-class (a pool submission), and Anchor's proposer_nodes is block-proposal-only (used solely by block_service; see the note at client/src/lib.rs:348). LH's PayloadAttestationService::new only accepts beacon_nodes anyway, matching its own VC.

Obs 3 (TODO(gloas, #1064)) — Done in a3a7c38; the issue link is consistent with the existing TODO(#1027) style in the tree.

Obs 4 (no test) — Agreed; mirrors the SyncCommitteeService startup precedent, with behavioral verification left to a Gloas-scheduled devnet.

@claude-code-actions-sigp

claude-code-actions-sigp Bot commented Jun 16, 2026 •

Copy link
Copy Markdown

Claude finished @shane-moore's task in 56s —— View job


Acknowledgement

Thanks — all four are addressed and consistent with what's on the branch.

  • Confirm TODO(gloas, #1064) landed
  • Sanity-check Obs 1 reasoning against pinned LH service shape
  • Verify Obs 2 against client/src/lib.rs:348
  • Post acknowledgement

Notes on the responses

Obs 1 — Storing chain_spec in Inner and deriving timing / gloas_enabled() / fork_name_at_slot per slot from it is exactly the pattern that justifies the missing &spec on start_update_service. Anchor's call site already passes spec.clone() into new(...) (anchor/client/src/lib.rs:823), so the API-shape difference vs. siblings is upstream-driven and not something to paper over locally. Agreed — no change needed.

Obs 2 — Confirmed against anchor/client/src/lib.rs:348: "Only the beacon_nodes are used for attestation duties, so proposer_nodes do not need a head_send ref." PTC submission being attestation-class fits cleanly under that invariant, and LH's PayloadAttestationService::new only takes beacon_nodes regardless. Good.

Obs 3 — TODO(gloas, #1064) is on the diff at anchor/client/src/lib.rs:821. Matches the existing TODO(#1027) style in tree.

Obs 4 — Agreed; devnet behavioral verification is the right bar for a wiring-only gated start.

Nothing further from me — LGTM stands.
· branch: feat/ptc-service-spawn

@jnhsigmap jnhsigmap left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good, great work on this

@mergify
mergify Bot merged commit 3519152 into sigp:epbs Jun 17, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants