core: agree on no block for payload attestation duty - #4732
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## gloas #4732 +/- ##
========================================
Coverage ? 66.35%
========================================
Files ? 247
Lines ? 31947
Branches ? 0
========================================
Hits ? 21197
Misses ? 10749
Partials ? 1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
I think i'm with opus here in terms of doing option B just because its our weak or far away bns that might let the cluster down even more than they could otherwise if we do a).
If nodes enter the participate game even though their bn saw no timely block, if the other faster nodes did see one on-time, this node can incrementally help in getting the (non slashable!) PTC duty getting signed, (e.g. if someone's good BN has a validator client down or something)
(Approving to unblock because i think as is is perfectly safe, just maybe a small opportunity lost)
For truly empty slots this is right. The spec says not to attest when you haven't seen a block (validator.md:407), and it removes the consensus-timeout noise.
The cost is the case we care about most: nodes disagree about whether they saw the block. I checked the QBFT code. Incoming messages for a duty are only buffered, and the instance only runs on Propose or Participate. So a node that got a 204 now:
- can't count towards quorum on a peer's value, and
- has already told its VC 204, so it won't sign even if the cluster agrees on data.
Before this PR, such a node joined consensus, adopted the leader's value and signed. Example: 4 nodes, 2 of them see the block late → consensus needs 3 → it used to succeed, now it fails. That's exactly the "not deterministic across nodes, so agree on it live" case. The impact is bounded: a block still unseen at 6s is rare, and PTC messages aren't slashable, so the downside is a missed PTC vote, never a safety problem. Still, it's a regression in the scenario DV is supposed to handle best.
Options:
- (a) Accept it as-is, and state the trade-off in the PR body and the Participate comment.
- (b) Keep Participate for PTC. Only answer the VC with a 204 once the consensus instance ends without a decision (or at a cutoff), and just suppress the timeout log/tracker noise when the node had no value of its own. This keeps the split-view case working.
I'd push lightly for (b), or at least a comment so the choice is explicit. Also:
- Misleading comment. // Stored data takes precedence over the no-block mark, e.g. for a late block is effectively unreachable: a node that got a 204 never runs the instance, so it never stores a decided value. The test forces the path artificially. Remove the comment or reword it.
- Separate, older question: fetching at 6s gets the final answer for payload_present, but the spec says blob_data_available should only be set false once 9s has passed (validator.md:416-417). A 6s fetch can lock in an early false. It's the usual DV trade-off, since consensus and signing need time before 9s, but worth a ticket.
281a345 to
99c9d54
Compare
99c9d54 to
815627c
Compare
815627c to
dd4b8c6
Compare
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The no-block decision can be overwritten after clients observe it, and existing query-cleanup coverage was removed.
Review effort: Balanced
Findings: 1
Open (2)
What changed in this PR
Enables cluster consensus on payload-attestation slots with no block and returns HTTP 204 to validator clients.
Changes:
- Proposes and serializes empty payload-attestation data sets through consensus.
- Stores no-block outcomes and exposes them through the validator API.
- Tracks no-block duties as no-ops and adds related tests.
| File | Description |
|---|---|
core/validatorapi/validatorapi.go |
Maps no-block outcomes to the client sentinel. |
core/validatorapi/validatorapi_test.go |
Tests sentinel propagation. |
core/validatorapi/router.go |
Produces HTTP 204 responses. |
core/validatorapi/router_internal_test.go |
Tests the 204 route response. |
core/tracker/tracker.go |
Records empty-set events as no-ops. |
core/tracker/tracker_internal_test.go |
Tests empty-set tracking. |
core/tracing.go |
Propagates the updated DutyDB result. |
core/proto.go |
Allows empty payload-attestation sets. |
core/proto_test.go |
Tests empty-set serialization. |
core/interfaces.go |
Updates DutyDB and validator API contracts. |
core/fetcher/fetcher.go |
Proposes empty sets for no-block responses. |
core/fetcher/fetcher_test.go |
Tests no-block fetching. |
core/dutydb/memory.go |
Stores and resolves no-block markers. |
core/dutydb/memory_test.go |
Tests marker behavior. |
core/dutydb/memory_internal_test.go |
Tests marker identification. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|





Lets the cluster agree that a slot has no block to attest to, instead of each node acting on its own beacon node's view. When the beacon node answers 204 for payload attestation data, the fetcher proposes an empty
UnsignedDataSetlike any other value and consensus runs as usual: the round leader's proposal is what the cluster agrees on, so the outcome follows the leader's beacon node view, as for every other duty. The agreed empty set is stored in the dutydb, which resolvesAwaitPayloadAttestationDatawithErrNoPayloadAttestationData, and the validator API answers the VC with 204 No Content (~0.2s after the fetch, no VC timeouts). The tracker records empty-set consensus/dutydb events explicitly and reports an agreed no-block as a no-op rather thanbug_fetch_error, removing the per-slotconsensus timeoutandDuty failednoise on empty slots.category: bug
ticket: #4324