Skip to content

Rebroadcast finalize votes before proposing an empty block - #598

Open
yacovm wants to merge 1 commit into
mainfrom
rebroadcastFirst
Open

yacovm wants to merge 1 commit into
mainfrom
rebroadcastFirst

Conversation

@yacovm

@yacovm yacovm commented Sep 16, 2026

Copy link
Copy Markdown
Collaborator

When the tip is notarized but not finalized, two timers race: NotarizationTime rebroadcasts the finalize vote for the tip so peers can send back the missing finalization, and the empty-block builder gives up waiting for transactions and proposes an empty block on top of the tip.

Align them so the rebroadcast always comes first. FinalizeRebroadcastTimeout is now three network delays, so the rebroadcast fires within six network delays of entering the round, and the empty-block timeout is four times MaxProposalWait, eight network delays, which leaves peers a round trip to answer.

Add an instance-level test that partitions finalization traffic between two validators and asserts the leader rebroadcasts its finalize vote before it proposes the empty block.

When the tip is notarized but not finalized, two timers race: NotarizationTime
rebroadcasts the finalize vote for the tip so peers can send back the missing
finalization, and the empty-block builder gives up waiting for transactions and
proposes an empty block on top of the tip.

Align them so the rebroadcast always comes first. FinalizeRebroadcastTimeout is
now three network delays, so the rebroadcast fires within six network delays of
entering the round, and the empty-block timeout is four times MaxProposalWait,
eight network delays, which leaves peers a round trip to answer.

Add an instance-level test that partitions finalization traffic between two
validators and asserts the leader rebroadcasts its finalize vote before it
proposes the empty block.

Signed-off-by: Yacov Manevich <yacov.manevich@avalabs.org>
Comment thread simplex/epoch.go
e.blockBuilder = &EmptyBlockBuilder{
ShouldBuildEmptyBlock: e.haveUnFinalizedButNotarizedSuffix,
Timeout: e.MaxProposalWait,
Timeout: e.MaxProposalWait * 4,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

x3 and x4 seem like they are magic numbers that we will forget in a couple weeks. Is there a better way to set these toggles so they dont become footguns?

Comment thread instance_test.go
// so A finalizes block 2 while B only notarizes it. B then enters round 3 as leader with no pending
// transactions. Among the messages B sends after entering round 3, the finalize vote for round 2 must
// appear before the (empty) block proposal for round 3.
func TestFinalizeVoteRebroadcastPrecedesEmptyBlockProposal(t *testing.T) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i feel like this test is kind of long and hard to parse, i tried to make a simplified version of it here. lemme know what you think

Comment thread instance_helpers_test.go
}

// deliverable reports whether a message from one node to another should be delivered.
func (n *network) deliverable(from, to common.NodeID, msg *common.Message) bool {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have the messageFilter term and types in testutil, maybe we can re-use that?

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