Skip to content

research(qbft_manager): committee-duty QBFT hard-sleeps to attestation_due, negating the eager-attest head start #1092

Description

@shane-moore

Issue Addressed

Research ticket, follow-up to #986. With the head monitor now live on epbs, sign_attestations fires on the head event (~+0.25s) and VotingContext is published early, but the committee QBFT instance then hard-sleeps to the attestation deadline before starting round 1, so consensus is still decided at ~+3.0s. This ticket is to confirm the gap, validate feasibility, and settle the design. The proposed direction below is one candidate and needs more research / spec work; the outcome should be a spun-out implementation issue/PR or a documented reason not to do it.

The gap (measured on epbs, ssv-mini Gloas devnet, slot 192)

Offset Event
+0.253s Starting attestation production from_head_monitor=true
+0.416s first QBFT Received initialization message (instance seeded)
+0.4s to +3.0s nothing — the instance is sleeping
+3.005s Starting new round round=1 (sleep wakes at get_attestation_due = 3.0s Gloas)
+3.020s Reached a COMMIT consensus

Uninitialized::initialize does sleep_until(instance_start_time) before round 1 (qbft_manager/src/instance.rs:120-128). instance_start_time is dual-purpose: the same value is the sleep target AND the cumulative round-timeout anchor (qbft_manager/src/timeout.rs:21-30; round N deadline = instance_start_time + cumulative_timeout(N)). So the sleep cannot simply be moved earlier without also moving every round-change deadline, which would desync operators. The data is in hand at ~+0.4s, but consensus cannot begin until the deadline.

Why it is built this way

How go-ssv differs

go-ssv gates committee-duty execution at the scheduler level (min(block_seen + 300ms, slot_start + 1/3)), then starts QBFT round 1 immediately with no pre-round sleep; round timeouts are anchored to absolute slot time. go-ssv already decouples when consensus starts (the gate, which exploits early blocks) from the round-change cadence (the absolute anchor). Anchor conflates the two.

What this buys, and what it does not (scoping)

The publish bottleneck depends on whether any validator in the cluster is in the current sync committee. The post-consensus partial-signature batch is per-committee and bundles all the cluster's duties for the slot:

  • Attestation-only cluster-slots (no sync member): the batch flushes right after QBFT decides, so this fix alone (Anchor-only, no Lighthouse change) moves attestation publish from ~+3.1s to ~+0.7s. Common by cluster count.
  • Sync-overlap cluster-slots (a sync member, for the whole ~27h period): the batch waits on the sync partial signature, produced only when Lighthouse's timer-only SyncCommitteeService fires (4s today; 3s under Gloas once SYNC_MESSAGE_DUE_BPS_GLOAS is implemented). The start-gate fix moves the decision early but publish does not move. This is the case the slot-192 trace above measured (all 10 validators were sync members). Moving these additionally needs either a sync-message head-event arm / internal kick (produce the sync partial sig when QBFT decides) or decoupling the partial-sig batch.

So this change is a prerequisite for moving publish anywhere, sufficient alone only on attestation-only cluster-slots, and necessary-but-not-sufficient for sync-overlap slots and sync messages.

Proposed direction (preliminary, needs deeper design)

One candidate, recorded to anchor discussion. Not a committed design; spec it out further as part of this research.

Decouple the start gate from the round anchor: add an earliest_start: Instant to TimeoutMode::SlotTime, retarget the sleep to earliest_start.min(instance_start_time), and leave instance_start_time as the round-timeout anchor (unchanged, so round-change cadence stays absolute and operators stay synchronized). The .min(anchor) clamp makes it never-worse-than-today. The validator_store construction sites pass earliest_start derived from data availability (~now, optionally plus a small propagation delay). Block proposals (Relative) untouched.

Risk-reducing facts that still need confirming in review: Anchor's beacon-vote value-check (common/ssv_types/src/consensus.rs) does not compare the head block_root (epoch-only fork protection by default, plus slashing protection), so followers PREPARE on the leader's head even if their BN has not imported it. Early starts are therefore low-risk here. The existing QBFT message buffer absorbs cross-operator start skew. Starting earlier than the anchor grows the round-1 budget, so the #758 proposer failure mode cannot recur.

Open questions the research should settle

Scope

In scope: the committee-duty QBFT start gate (SlotTime). Out of scope: block proposals (Relative, already immediate-start); the partial-sig batch coupling and the Lighthouse sync trigger (separate work, but this is a prerequisite for them); Lighthouse-side attestation/sync deadline values.

Metadata

Metadata

Assignees

No one assigned

    Labels

    QBFTepbsePBS / EIP-7732 / Gloas implementationresearchAnything with unclear path forward and likely needing Go-SSV involvement

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions