Skip to content

fix(voting): report unanimous timeout and violation votes accurately - #1777

Open
HusseinAdeiza wants to merge 1 commit into
genlayerlabs:v0.123-devfrom
HusseinAdeiza:fix/voting-unanimous-timeout-violation
Open

HusseinAdeiza wants to merge 1 commit into
genlayerlabs:v0.123-devfrom
HusseinAdeiza:fix/voting-unanimous-timeout-violation

Conversation

@HusseinAdeiza

@HusseinAdeiza HusseinAdeiza commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #1769

What

The unanimity short circuit in getMajorityVote only branched on Agree, so every other unanimous vote fell through to MajorityDisagree. A round where all validators timed out, or all reported a deterministic violation, was recorded as a disagreement, which then drives the wrong fee and validator rotation handling. Timeout and DeterministicViolation are now resolved explicitly inside the unanimity branch. Unanimous Agree still returns MajorityAgree and unanimous Disagree still returns MajorityDisagree, and a unanimous NotVoted vote keeps its existing MajorityDisagree result, so no other vote type changes as a side effect.

Why

This is a bug rather than a consensus rule change because the Python mirror of the same decision procedure, determine_consensus_from_votes in backend/consensus/utils.py, already does the right thing. It checks timeout_count against the majority threshold and returns ConsensusResult.TIMEOUT. The Solidity has an extra unanimity short circuit that the Python does not have, and that short circuit is what drops the Timeout and DeterministicViolation outcomes. The two implementations now agree, and the existing tests/unit/test_consensus_voting.py expectations for the Python side are untouched.

Testing done

New hardhat/test/voting/001_voting_majority_vote.test.js covers unanimous timeout, unanimous violation, unanimous agree, unanimous disagree, unanimous not-voted, non-unanimous majority timeout and violation, and the no-majority case. 8 passing. The Python mirror suite still reports 13 passing.

Decisions made

Kept the change local to the unanimity branch rather than restructuring the whole decision procedure, so the non-unanimous path and the threshold logic stay byte-identical.

Checks

  • I have tested this code
  • I have reviewed my own PR
  • I have created an issue for this PR
  • I have set a descriptive PR title compliant with conventional commits

Reviewing tips

The two assertions worth a close look are the ones that lock in behavior that must not change: unanimous agree and unanimous disagree.

User facing release notes

Rounds where every validator times out, or where every validator reports a deterministic violation, are no longer counted as unanimous disagreement.

Summary by CodeRabbit

  • Bug Fixes
    • Unanimous validator votes for a timeout or deterministic violation now return the correct result instead of being treated as a disagreement.
  • Tests
    • Added coverage for majority-vote outcomes, including unanimous, non-unanimous, and no-majority vote sets.

@github-actions
github-actions Bot changed the base branch from main to v0.123-dev September 26, 2026 23:13
@github-actions

Copy link
Copy Markdown
Contributor

This PR targeted main, which is only the default/static branch.

I retargeted it to v0.123-dev, the active development branch. Pushes to v0.123-dev automatically fast-forward main.

@coderabbitai

coderabbitai Bot commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

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
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 244608dc-177e-4fb8-a0c3-e5dc6452224f

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
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autopilot · 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

Comment @coderabbitai help to get the list of available commands.

The unanimity short circuit in getMajorityVote only branched on Agree and
sent every other unanimous vote to MajorityDisagree. A round where all
validators timed out, or all reported a deterministic violation, was
therefore recorded as MajorityDisagree, which drives the wrong fee and
validator rotation handling downstream.

The Python mirror of the same decision procedure,
determine_consensus_from_votes in backend/consensus/utils.py, already gets
this right: it checks timeout_count against the majority threshold and
returns ConsensusResult.TIMEOUT. The Solidity has an extra unanimity
short circuit the Python does not, and that short circuit is what drops the
Timeout and DeterministicViolation outcomes. This is therefore a bug fix
rather than a consensus rule change, and the two implementations now agree.

Unanimous Agree still returns MajorityAgree and unanimous Disagree still
returns MajorityDisagree, unchanged. Those two match the existing behavior
and the Python mirror, which maps agree_count and effective_disagree above
the majority to MAJORITY_AGREE and MAJORITY_DISAGREE. A unanimous NotVoted
vote also keeps its existing MajorityDisagree result, so no other VoteType
variant changes result as a side effect.

Refs genlayerlabs#1769
@HusseinAdeiza
HusseinAdeiza force-pushed the fix/voting-unanimous-timeout-violation branch from 67244d7 to 9b169f3 Compare October 3, 2026 05:30
@HusseinAdeiza

Copy link
Copy Markdown
Contributor Author

Rebased onto current v0.123-dev (was 26 behind and diverged). Diff is byte-identical to the old head, only the base moved.

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.

Voting misclassifies unanimous Timeout/DeterministicViolation; Idleness.checkIdle trusts a caller-built Transaction; txId can collide within a block

1 participant