Skip to content

feat(client): enable the early payload-available PTC trigger - #1329

Merged
shane-moore merged 2 commits into
sigp:epbsfrom
shane-moore:feat/ptc-payload-available-trigger
Sep 29, 2026
Merged

shane-moore merged 2 commits into
sigp:epbsfrom
shane-moore:feat/ptc-payload-available-trigger

Conversation

@shane-moore

@shane-moore shane-moore commented Sep 29, 2026 •

Copy link
Copy Markdown
Member

Problem, Evidence, and Context (Required)

  • Problem. Anchor runs Lighthouse's PayloadAttestationService for the Gloas PTC duty, but passes None for its payload-available receiver. As a result, every operator fetches and signs at 75% of the slot. The cluster's full signature exists only after threshold partials arrive, so the vote always reaches gossip after the payload attestation deadline.
  • Why now. The SIP-94 review asks operators to sign once the observation is final, not always at 75% (discussion). A vote late in the [75%, 100%] window has already missed part of what it steers: next-slot bid compatibility runs on the live tally. Lighthouse's own validator client already enables this trigger by default.
  • Closes feat(client): enable the early payload-available trigger for PTC attestations #1328

Change Overview (Required)

  • Wiring. When Gloas is scheduled, the client creates the payload-available channel and hands its sender to the beacon node fallback before the fallback is shared. The receiver goes to PayloadAttestationService.
    • Lighthouse then signs early only when the beacon node that reported the payload also returns data for that block root with payload_present and blob_data_available both true.
    • Otherwise it keeps the 75% deadline path.
  • Flag. --disable-payload-available-monitor, enabled by default, restores deadline-only signing.
  • Reading order:
    • client/src/lib.rs: the wiring, and why the gate is is_gloas_scheduled(). Lighthouse starts the monitor for any Gloas epoch, including FAR_FUTURE, where the service never runs.
    • cli.rs and config.rs: the flag.
    • validator_store: a comment update.
    • docs: regenerated.
  • Unchanged:
    • sign_payload_attestation, including its slot-end collection bound and slot check.
    • The signature collector and message validation.
    • Lighthouse code.
    • What gets signed. On the Lighthouse beacon node, payload_present ("envelope seen before 50%") and blob_data_available do not revert within a slot, so the early True/True answer matches what the same node would return at 75%.
  • Peer compatibility. Peers already accept early partials:
    • Anchor measures earliness only from slot start, and its collector buffers shares by signing root before the local operator signs.
    • go-ssv (stage) queues PTC partials that arrive before its own 75% duty and replays them once the duty starts.

Risks, Trade-offs, and Mitigations (Required)

  • Duties loaded after the early event. Lighthouse records the slot as voted before it looks up duties, and an empty duty list skips the deadline retry. So a slot whose PTC duties load after its payload-available event loses its vote. Normal operation is safe because next-epoch duties are prefetched. The two reachable cases:
    • Process start: at most one slot of PTC votes.
    • The first slot or two after the Gloas fork, because fork-epoch PTC duties cannot be fetched until the fork block imports. This happens once per network, and in an earlier ssv-mini run slot 128 was already a cluster-wide miss without this change.
    • Accepted. The fix belongs in Lighthouse. The ssv-mini run below had no managed PTC duty at slots 128 or 129 (committees [59, 4] and [30, 47]), so it does not exercise this case.
  • Multiple beacon nodes. Early data comes from the first node whose event passes the root and availability checks, not from the fallback order. A minority-fork node that fires first could have its root signed. This is the same trade-off Lighthouse makes.
  • Mixed clusters. The early benefit needs at least threshold early signers. With go-ssv members below that, quorum still forms around 75%. There is no regression: go-ssv uses the early Anchor partials when its data root matches.
  • Metrics. SIGNING_TIMES{"ssv"} will record longer PTC waits for operators that sign early and then wait for peers.
  • Unverified. Whether non-Lighthouse beacon nodes serve the execution_payload_available topic. If none of the configured nodes do, Lighthouse logs a warning each slot, and the flag turns the monitor off.

Validation (Required)

  • Local checks: make cargo-fmt-check, make lint and make cli-reference-check all pass. cargo test --release -p client -p cli -p anchor_validator_store passes (190 tests). No new unit tests: the flag only negates a boolean, and the trigger logic lives in Lighthouse.
  • ssv-mini run: 3 Anchor (this branch) + 1 go-ssv (stage 8a9623f0).
    • Setup: quorum 3, Lighthouse BN/VC unstable a93f9b0f, 12s slots, Gloas at epoch 4. Observed Gloas slots 128 to 196.
    • PTC publish time: 63 of 63 Anchor publishes landed before 75% (9.00s), across 21 PTC slots × 3 nodes. Median +0.269s into the slot, max +0.914s.
    • Baseline: an earlier ssv-mini run with the trigger off published at +9.07s (75.6%).
    • Trace, slot 133, one node: New payload from beacon node at +0.142s, then Received payload attestation data with payload_present=true and a matching root at +0.146s, then published at +0.194s.
    • go-ssv PTC outcomes: 22 of 22 succeeded, including slot 177, which had two managed validators in the PTC. Every duty needs cross-client quorum.
    • Failures: 0 early-attempt fallbacks, 0 Insufficient partial signatures for payload attestation, and 0 payload-available monitor failures.
    • Errors: 0 Anchor ERROR lines after the fork. The pre-fork errors are the known Failed to download PTC duties (IncorrectStateVariant), which ssv-mini logs every slot until the fork.
    • Not exercised: the early-fail-then-deadline path. It needs an envelope seen after 50%, but ssv-mini proposers publish within about 150ms. That path is unchanged Lighthouse code.
    • Local proposers make these timings best-case; on public networks the envelope typically arrives later in the slot.
Per-slot publish offset (seconds into the slot)
 slot   node-0  node-1  node-2
  130    0.504   0.507   0.505
  131    0.183   0.182   0.182
  133    0.194   0.194   0.194
  138    0.226   0.224   0.224
  143    0.289   0.283   0.285
  144    0.237   0.237   0.238
  146    0.207   0.212   0.209
  149    0.591   0.591   0.594
  154    0.397   0.402   0.396
  159    0.342   0.342   0.335
  165    0.912   0.905   0.914
  171    0.269   0.270   0.269
  172    0.263   0.265   0.256
  174    0.239   0.237   0.237
  177    0.337   0.340   0.339
  178    0.319   0.320   0.320
  182    0.201   0.202   0.202
  184    0.220   0.224   0.219
  186    0.224   0.238   0.237
  192    0.867   0.863   0.842
  194    0.284   0.279   0.279

Rollback (Required for behavior or runtime changes; optional otherwise)

  • Without a rebuild: run with --disable-payload-available-monitor.
  • Revert: revert the commit. There is no config, data or wire-format impact.

Additional Info / Next Steps (Optional)

  • Signing payload_present = False early at 50% (the rest of the SIP review suggestion) needs a Lighthouse change and is out of scope.

🤖 Generated with Claude Code

Anchor passed `None` for the payload-available receiver of Lighthouse's
PayloadAttestationService, so every operator signed payload attestations
at 75% of the slot and the reconstructed vote reached gossip after the
deadline. Wire the channel so a True/True attestation is signed as soon
as the beacon node reports the payload available, with 75% as the
fallback, matching Lighthouse's validator client default.

Add --disable-payload-available-monitor to restore deadline-only signing.
The channel is created only when Gloas is scheduled, matching the payload
attestation service's own start condition.

Closes sigp#1328
@codecov-commenter

codecov-commenter commented Sep 29, 2026 •

Copy link
Copy Markdown

Codecov Report

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

Files with missing lines Patch % Lines
anchor/client/src/lib.rs 0.00% 8 Missing ⚠️
anchor/client/src/config.rs 0.00% 2 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             epbs    #1329   +/-   ##
=======================================
  Coverage        ?   80.87%           
=======================================
  Files           ?      179           
  Lines           ?    41247           
  Branches        ?        0           
=======================================
  Hits            ?    33360           
  Misses          ?     7887           
  Partials        ?        0           
Flag Coverage Δ
rust 80.87% <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.

@shane-moore shane-moore left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Review from Claude Opus 5.5 (high effort)

Reviewed 70ac1e05 against epbs at f77b5840, Lighthouse pin 8b927ced. Looks good to merge.

Checked:

  • The wiring matches Lighthouse's VC (validator_client/src/lib.rs:426-433). The is_gloas_scheduled() gate is stricter than Lighthouse's, which starts the monitor for any Some(gloas_fork_epoch), including FAR_FUTURE (beacon_node_fallback/src/lib.rs:107-108).
  • set_payload_available_send runs before the Arc wrap and before start_fallback_updater_service.
  • Early PTC partials pass peer validation: message_earliness measures from slot start for PTC (message_validator/src/lib.rs:1102-1117). The collector also keys incoming shares by signing root before the local operator signs.
  • sign_payload_attestation is unchanged and still stops collecting at slot end. The updated comment matches both Lighthouse fetch paths.

Open question (non-blocking): the startup risk in the description also covers any slot whose PTC duties load after the early event but before 75%. In that case latest_voted_slot is set with no duties, and the deadline retry is skipped (LH payload_attestation_service.rs:162-168). Steady state is safe because the next epoch is prefetched. The most likely other case is the slot after the Gloas fork, because the fork epoch's duties cannot be fetched before the fork. The ssv-mini table starts at slot 130, so it does not cover that slot.

Comment thread anchor/client/src/lib.rs
// Mirrors Lighthouse's `MAX_HEAD_EVENT_QUEUE_LEN` in `validator_client/src/lib.rs`.
const MAX_HEAD_EVENT_QUEUE_LEN: usize = 1_024;

const MAX_PAYLOAD_AVAILABLE_EVENT_QUEUE_LEN: usize = 1_024;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

nit: this copies Lighthouse's MAX_PAYLOAD_AVAILABLE_EVENT_QUEUE_LEN (validator_client/src/lib.rs:83), but it lacks the // Mirrors Lighthouse's ... comment that the constant above it has.

@shane-moore
shane-moore merged commit 6b97ae3 into sigp:epbs Sep 29, 2026
23 checks passed
@shane-moore
shane-moore deleted the feat/ptc-payload-available-trigger branch September 29, 2026 23:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants