Skip to content

fix(consensus): deliver and process a polka block for a validator left in its round - #1600

Draft
Claudius-Maginificent wants to merge 3 commits into
devfrom
fix/consensus-serve-polka-block
Draft

Claudius-Maginificent wants to merge 3 commits into
devfrom
fix/consensus-serve-polka-block

Conversation

@Claudius-Maginificent

Copy link
Copy Markdown
Collaborator

TL;DR: A validator that fell behind can now get the block the network already agreed to vote on, and process it, so it can rejoin the round instead of leaving the network stuck.

User story

As a validator operator, I want a validator that missed a block to fetch and process it from its peers, to achieve continued block production after network trouble or validator outages.

Scenario

Base flow

In each round a proposer sends a block. Once more than two-thirds of validators prevote for it (a "polka"), validators precommit it. Precommits for a block carry vote extensions, which the application (Drive) checks before the vote is counted.

Actual behavior

Take a validator that saw a polka for a block but never received the block itself:

  • Its peers move on to the next round and stop sending that block's parts.
  • Even when the block does arrive late, the validator never hands it to the application.
  • The application therefore keeps rejecting the vote extensions in other validators' precommits for that block. Those votes are not counted, no timer starts, and the validator stays in the old round.

If the validators that went ahead cannot reach a two-thirds quorum without the stuck ones, the whole network stalls. Two ways to get there:

  • about one-third of validators stop voting after the polka;
  • about one-third crash at the same moment while another third is cut off from the block.

Expected behavior

Validators that still hold the agreed block send it to peers that ask for it. A validator that completes the block processes it, accepts the other validators' precommits, starts its timer and rejoins the round. With the round back at a two-thirds quorum, the network commits the block.

Detailed discussion

What was done

  • Serving the polka block (gossip_handlers.go, heldBlockPartsFor):
    • Data gossip normally serves parts only from rs.ProposalBlockParts, which entering a new round clears. A node then still holds the polka block as ValidBlockParts and/or LockedBlockParts, but those were never served.
    • When the normal path sends nothing, a complete ValidBlockParts or LockedBlockParts is now served to a peer that meets all of these:
      • it is a validator,
      • it is at the same height, in an earlier round,
      • its requested ProposalBlockPartSetHeader equals the held one exactly (header equality is the block identity).
    • The handler repoints only its local snapshot copy (ProposalBlockParts, and Round to the peer's round so SetHasProposalBlockPart marks the parts) and calls the existing GossipProposalBlockParts.
    • Consensus state is never mutated. Same-round peers never reach this branch.
  • Processing the polka block (state_add_prop_block.go, ProposalCompletedAction.processPolkaBlock):
    • Today a block completed in the Prevote or Precommit steps is never passed to ProcessProposal.
    • Now, when the completed block equals the non-nil +2/3 prevote majority of the node's current round and has not been processed for that round, it is processed via ensureProcess(rs, rs.Round).
    • No vote is cast or changed.
    • It runs once per block completion, so the per-precommit path has no extra cost. A failure is logged and not retried.
    • ensureOwnRound then finds the round processed, and VerifyVoteExtension accepts the peers' precommits.
    • ProposalCompletedAction gets the blockExec dependency (state_controller.go).
  • Dependency on fix(consensus): resend same-round votes the peer never acknowledged #1599 for consensus v1:

Testing

  • New tests:

    Test Kind
    TestGossipHandlerTestSuite/TestDataGossipHandlerServesHeldBlockToLaggingPeer Serves the valid block, the locked block, and the valid block while the round proposes another. Negative cases: mismatched header, other height, same/later round, non-validator, incomplete part set.
    TestStatePolkaBlockCompletedInPrecommitIsProcessed Fails without the fix.
    TestStatePolkaBlockDeliveryEndsThePrecommitStall Fails without the fix. Ends with TriggeredTimeoutPrecommit.
    TestStatePolkaBlockRejectedByTheApplicationIsNotRetried Fails without the fix.
    TestStatePolkaBlockCompletedInPrevoteWaitIsProcessedOnce Fails without the fix.
    TestStatePolkaBlockCompletedInProposeVotesAndProcessesOnce Regression guard.
    TestStateCompletedBlockWithoutItsPolkaIsNotProcessed Regression guard.
    TestStateCompletedBlockOtherThanThePolkaBlockIsNotProcessed Regression guard; a mutation check confirms it catches removal of the block-identity check.
  • The tests use a Drive-like application that accepts extensions only for a processed (height, round, hash).

  • The new tests also pass under -race. A regression subset (lock, full-round, proposal, prevote/precommit, gossip, commit-extension and reactor tests) passes, and make lint reports 0 issues.

  • An independent review found:

Breaking changes

None.

Prior work

Attribution

🤖 Generated with Claude Code

https://claude.ai/code/session_01WunYYgcoPDNh3uebhUCwig

🤖 Co-authored by Claudius the Magnificent AI Agent

lklimek and others added 3 commits November 9, 2026 13:26
…t in its round

A validator that saw a polka for a block it never received precommits nil
and, once its peers move to a later round, can no longer obtain the block:
block parts are served only from the proposal part set, which entering a
new round clears, while the valid and locked part sets that still hold the
block are never gossiped. Even when the block does arrive, one completed in
the prevote or precommit step is not processed, so an application that
verifies vote extensions against its last processed proposal, like Drive,
rejects every precommit for the block. Rejected precommits are not stored
and do not count towards the +2/3 of any kind that start the precommit
wait, so the validator stays in its round for good and a network without
spare voting power stalls with it.

Serve a validator peer at this height, in an earlier round, the complete
valid or locked part set whose header it is collecting, from the gossip
snapshot. Process the round's polka block when it completes too late for
the round to vote on it, so that the precommits for it verify; the node
casts no vote again, and a failure is not retried.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A polka block the application rejects is processed once and not retried
on the precommits for it or on its parts arriving again; a block completing
in the prevote wait is processed there and not again in the precommit step;
one completing in the propose step with its proposal takes the usual path
and is processed once; a block completing without being the round's polka
block is not processed; an incomplete held part set is not served.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A block completing while the round's polka is for another block is not
processed; a nil polka alone let the identity check go unexercised. The
rejected-block test no longer redelivers parts the part set already holds,
which completes nothing.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Nov 9, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@thepastaclaw

Copy link
Copy Markdown

🕓 Review not started yet because this PR is a draft.

  • Request normal review — click when the PR is ready for review.
  • Request priority review — click to move this review to the front of the queue.

Commit 2cc8d96. Normal review starts when eligible; priority review starts as soon as a slot is available.

This branch has not been deployed

No deployments
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.

3 participants