feat(validator_store): detach non-builder envelope signing from the Lighthouse callback - #1
Merged
Conversation
Sign a non-builder's SIP-94 §6 envelope share from a task spawned in sign_block once the decided block is threshold-signed, instead of from Lighthouse's envelope callback. That callback first fetches the operator's own envelope from its beacon node and never reaches the store when the node holds none (its local bid was external while the cluster decided a self-build block), so the share was lost. The callback now returns a delegated sentinel for non-builder contexts; the builder path is unchanged. Tracks sigp#1287.
shane-moore
marked this pull request as ready for review
September 2, 2026 23:28
Lighthouse can call sign_block twice for one slot: a second block-service notification for the same slot hit three of four operators at one devnet slot, about 12 s after the first call. The non-builder envelope task is spawned only after sign_abstract_block, so the repeat is rejected by slashing protection as SameData before it can spawn a second task. Nothing pinned that placement: the existing spawn-path tests disable slashing protection and call sign_block once. Add a slashing-enabled test that calls sign_block twice and expects one envelope collection; it fails with two collections if the spawn is moved above the slashing check. Also blind the local envelope only on the builder path. The non-builder callback now returns before using it, so hashing the full payload up front was wasted work on every non-builder duty.
shane-moore
commented
Sep 3, 2026
shane-moore
left a comment
Owner
Author
There was a problem hiding this comment.
Reviewed at a64a884 against cb50756. No remaining code-review objections. The repeated sign_block test detects the load-bearing spawn placement, and local_blinded is now restricted to the builder path. The QueueFull disposition is appropriate for this PR and belongs in shared collector/processor handling. Local validator_store tests and formatting pass. Ready to merge after CI passes on this head.
Reviewed by GPT-5.6 Sol (xhigh).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on sigp#1286 (
envelope-no-qbft); opened on the fork with that base so the diff is only the delta. Re-target tosigp/anchor:epbsafter sigp#1286 merges. Tracks sigp#1287.Problem, Evidence, and Context (Required)
sign_execution_payload_envelope, and Lighthouse reaches that call only after fetching the operator's own envelope from its beacon node (block_service.rs:668-680at pine58ec88fe,?-propagated). The node serves that GET from a cache written only for locally built payloads (block_production/gloas.rs:709-741; a gossip-cached external bid wins withpayload_data: None, 1010-1020). So an operator whose node took an external bid while the cluster decided a self-build block never signs its share. Stop external-builder envelope 404 after block publication sigp/lighthouse#9948 turns that 404 into a skip; same loss.gloas.rs:916-935;--builder-boost-factoris node-wide config), not observed on any devnet (ssv-mini has no builders). Planning note: Envelope duty trigger keyed on the local block in Lighthouse block service sigp/anchor#1287.Change Overview (Required)
sign_blockspawnssign_disseminated_envelopeafter the decided block's threshold signature succeeds, when the recorded context has a self-build bid on a block another operator built. The task awaits the dissemination to the payload-due deadline, validates it, and contributes the partial signature. Detached, bounded by the deadline, terminal failure logged at warn.EnvelopeNonBuilderDelegatedsentinel immediately instead of waiting;EnvelopeNotBuiltLocallyhad no other producer and is deleted.Weak<Self>viaArc::new_cyclicgives the&selftrait method something to spawn with.Risks, Trade-offs, and Mitigations (Required)
sign_blockbullet below.sign_blockstill has no context and no task.sign_blockfor the same slot is real, not hypothetical: the devnet run below logged oneUnable to sign block: SameDataat slot 101. The guard is the spawn placement.SameDatacomes from the slashing-protection check insidesign_abstract_block, so the second call returns at the?before reaching the spawn and creates no second task. Counters confirmed no operator exceeded one outcome per duty. Spawning at decide time instead would double-sign at exactly that slot.Validation (Required)
anchor_validator_store: 166 tests green (was 162). New: mixed-bid regression (sign_blockon a local external-bid block with a fixed self-build decision spawns the task, which signs the disseminated root; a later callback returns the sentinel and adds no second share), builder and external-decision negatives with a valid dissemination pre-stored so a wrongly spawned task would be caught, callback-returns-without-waiting under a paused clock. Non-builder worker tests drive the task body directly.make cargo-fmt-check,make lintclean.signed_other + published == receivedexactly on every operator, anddelegated == lh_sentinel == signed_other: one outcome per node per duty.Signed another operator's envelopebefore its ownReceived execution payload envelope. The callback path could not produce that ordering, since it only runs after the fetch returns.sign_blockcase fired and was handled. See the risk bullet above.pr-reviews-style workbench atssv-mini/evidence-envelope-non-builder-20260902/(README with the 11-row oracle matrix and verdicts, per-duty chains, regression ticks, 89 resource samples, fault log).Rollback (Required for behavior or runtime changes; optional otherwise)
Blockers / Dependencies (Optional)
epbs.Additional Info / Next Steps (Optional)
plans/anchor/2026-09-02-issue-1287-non-builder-envelope-trigger.md(vault). The planning recommendation was to hold this until a builder-enabled network exists. The devnet run above supersedes that: the mixed-bid trigger still cannot be reproduced, but the mechanism it relies on is now validated over 23 duties plus two faults, and the repeat-sign_blockfinding turned the plan's one open concurrency question from speculative into observed and handled.