Skip to content

fix: keep submitting full epoch proofs once the proven tip passes the epoch - #25277

Open
spalladino wants to merge 3 commits into
merge-train/spartan-v5from
spl/a-1795-full-epoch-proof-past-proven-tip
Open

fix: keep submitting full epoch proofs once the proven tip passes the epoch#25277
spalladino wants to merge 3 commits into
merge-train/spartan-v5from
spl/a-1795-full-epoch-proof-past-proven-tip

Conversation

@spalladino

@spalladino spalladino commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Context

A prover node proving epoch N never submits its proof if another prover posts a partial epoch proof for epoch N+1 while epoch N's submission window is still open, so it earns nothing for an epoch it fully proved.

validateEpochProofSubmission refused any proof whose range ended below the global proven checkpoint tip. That is only a valid "publishing this earns nothing" test while the tip sits inside our own epoch. Partial proofs need only an epoch's first checkpoints, so another prover can advance the tip into epoch N+1 mid-epoch — and epoch N's proof is dropped even though L1 still accepts it (assertAcceptable requires only _start - 1 <= proven, and handleRewardsAndFees runs whether or not the tip moves).

The cost is real: claimProverRewards pays only provers holding shares in the epoch's longest proven length, and shares are registered on submission. Not submitting means zero for the epoch. The publishing service already encodes the right policy — full candidates are never auto-superseded by the proven tip — and the publisher was contradicting it.

Approach

Thread the candidate's kind from PublishCandidate into the publisher and apply the tip check only to partial candidates, which genuinely cannot reach the epoch's longest proven length. Full-epoch proofs always match that length, so they stay worth sending until the window closes.

That removes what was incidentally stopping a repeat submission from reaching L1, so the publisher now also skips a proof this prover has already submitted for the same epoch and length, which the rollup reverts (Rollup__ProverHaveAlreadySubmitted).

Covered by single-node/proving/proof_after_tip_advances: two prover nodes, one held at its top-tree gate while the other proves the same epoch and then partially proves the next, taking the tip past the gated epoch. Releasing the gate must yield a published proof and an on-chain submission record; before the fix the session settled as failed.

Same bug is present on next; this targets the v5 line first.

Note for reviewers: the tests were not run locally — this checkout cannot build (node_modules/@aztec/bb.js points at the next-line layout), so type-checking and the suite are unverified beyond CI.

Fixes A-1795

… epoch

The publisher refused to submit any proof whose range ended below the global
proven checkpoint tip. That is only a valid "publishing this earns nothing"
test while the tip sits inside our own epoch. Since partial proofs let a prover
advance the tip into epoch N+1 mid-epoch, a full proof for epoch N was dropped
while its submission window was still open, costing the node the epoch's
rewards: the rollup pays only provers holding shares in the epoch's longest
proven length, and shares are registered on submission.

Scope the check to partial candidates, which genuinely cannot reach that
longest length, and let full-epoch proofs through. Since that removes what was
incidentally stopping a repeat submission from reaching L1, also skip a proof
this prover has already submitted for the same epoch and length, which the
rollup reverts.
@github-actions github-actions Bot added the port-to-next Forward-port this merged PR into next label Aug 20, 2026
@spalladino spalladino removed the port-to-next Forward-port this merged PR into next label Aug 20, 2026
Two prover nodes on one sequencer. The slow one is held at its top-tree gate
while the fast one proves the same epoch and then partially proves the next,
taking the proven tip past the gated epoch's last checkpoint. Releasing the
gate must produce a published proof and an on-chain submission record for the
slow prover; before the publisher fix its session settled as failed.
attestations: ViemCommitteeAttestation[];
headers: CheckpointHeader[];
/** Whether the range covers the whole epoch. Governs whether an already-overtaken proof is still worth sending. */
kind: 'full' | 'partial';

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.

I don't see where this is passed from the prover node. Was it already an field that was being sent?

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