Skip to content

fix: remove VM_ERROR auto-disagree in consensus voting#1513

Merged
cristiam86 merged 6 commits intomainfrom
fix/consensus-vm-error-voting
Mar 13, 2026
Merged

fix: remove VM_ERROR auto-disagree in consensus voting#1513
cristiam86 merged 6 commits intomainfrom
fix/consensus-vm-error-voting

Conversation

@MuncleUscles
Copy link
Member

@MuncleUscles MuncleUscles commented Mar 6, 2026

Summary

  • Deterministic contracts that raise exceptions (e.g. ValueError) were going UNDETERMINED because validators auto-voted DISAGREE
  • GenVM reports Python exceptions as VM_ERROR with exit_code 1, and the old _set_vote() logic short-circuited to DISAGREE for any VM_ERROR before reaching the comparison logic
  • All non-timeout VM_ERROR types (exit_code, OOM, wasm_trap, invalid_contract) are deterministic given the same inputs — timeout is already handled separately
  • Fix: remove the VM_ERROR → auto-DISAGREE shortcut so these results flow through to the normal comparison of execution_result, contract_state, and pending_transactions

Reproduction

  1. Deploy a contract that always raises ValueError
  2. Call the method — leader and all validators get identical VM_ERROR("exit_code 1")
  3. Before fix: all validators vote DISAGREE → UNDETERMINED
  4. After fix: all validators vote AGREE (matching results) → FINALIZED with ERROR

Test plan

  • Unit tests updated and passing (13/13)
  • Reproduce on local Studio instance (deploy error contract, verify FINALIZED)
  • Verify timeout VM_ERROR still correctly votes TIMEOUT
  • Verify non-deterministic disagreement still correctly votes DISAGREE

Summary by CodeRabbit

Release Notes

  • Bug Fixes
    • Refined virtual machine error handling in the validator consensus voting mechanism. Non-timeout errors are now properly evaluated when validators encounter different error states, treating systematic discrepancies as deterministic violations. When validators experience identical errors, consensus correctly resolves to agreement, improving overall network consistency and fault tolerance.

VM_ERROR was unconditionally triggering DISAGREE votes before reaching
the deterministic comparison logic. Since GenVM reports Python exceptions
(ValueError, etc.) as VM_ERROR with "exit_code 1", this caused fully
deterministic error contracts to go UNDETERMINED — validators disagreed
despite having identical results.

All non-timeout VM_ERROR types (exit_code, OOM, wasm_trap,
invalid_contract) are deterministic given the same inputs. Timeout is
already handled separately. Removing the shortcut lets VM_ERROR results
flow through to the normal comparison of execution_result,
contract_state, and pending_transactions.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 6, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ac78c2f0-795b-41f4-a482-19bf606a0e02

📥 Commits

Reviewing files that changed from the base of the PR and between 242d816 and 8336d36.

📒 Files selected for processing (1)
  • backend/node/base.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/node/base.py

📝 Walkthrough

Walkthrough

The PR refactors VM error handling in the voting logic by removing the dedicated VM_ERROR-specific DISAGREE path. VM errors now flow through deterministic violation checks, resulting in DETERMINISTIC_VIOLATION when validator and leader states differ, or AGREE when states align. Tests are updated to reflect these revised voting semantics.

Changes

Cohort / File(s) Summary
Core Voting Logic
backend/node/base.py
Removed the VM_ERROR-specific branch that unconditionally set votes to DISAGREE. VM errors now proceed through deterministic violation checks; mismatched states yield DETERMINISTIC_VIOLATION, matching states yield AGREE. Step numbering adjusted accordingly.
Test Suite Updates
tests/unit/test_set_vote.py
Renamed and updated 3 test cases to reflect new VM_ERROR semantics: mismatching errors now expect DETERMINISTIC_VIOLATION instead of DISAGREE, and matching errors now expect AGREE instead of DISAGREE.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

Poem

🐰 A rabbit hops through VM error trails,
No more the DISAGREE default prevails,
When states align, agreement rings true,
When diverged, violations shine through! 🌟

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main change: removing the VM_ERROR auto-disagree behavior in consensus voting logic.
Description check ✅ Passed The description is comprehensive, covering the problem, root cause, the fix, reproduction steps, and test status. It follows the template structure with clear 'Summary', 'Reproduction', and 'Test plan' sections addressing the 'What', 'Why', and 'Testing done' requirements.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/consensus-vm-error-voting
📝 Coding Plan
  • Generate coding plan for human review comments

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 and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
backend/node/base.py (1)

717-729: ⚠️ Potential issue | 🔴 Critical

Critical: Vote.DETERMINISTIC_VIOLATION votes are silently ignored by the consensus algorithm.

The determine_consensus_from_votes() function in backend/consensus/utils.py (lines 5-35) only counts AGREE, DISAGREE, TIMEOUT, and IDLE votes:

agree_count = votes_list.count(Vote.AGREE.value)
disagree_count = votes_list.count(Vote.DISAGREE.value)
timeout_count = votes_list.count(Vote.TIMEOUT.value)
idle_count = votes_list.count(Vote.IDLE.value)

When validators emit Vote.DETERMINISTIC_VIOLATION (at backend/node/base.py:724 for state mismatches), these votes are completely dropped from consensus calculation. This causes:

  • All-DETERMINISTIC_VIOLATION scenarios (e.g., leader succeeds but all validators detect state divergence) → incorrectly return NO_MAJORITY instead of strong disagreement
  • Mixed scenarios (some validators AGREE, others DETERMINISTIC_VIOLATION) → validators detecting violations have no voice in consensus

The vote reaches the consensus function (backend/consensus/base.py:3102-3103) but is not counted. Update determine_consensus_from_votes() to treat DETERMINISTIC_VIOLATION as effective disagree, or reconsider whether DISAGREE should be used instead of the new vote type.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/node/base.py` around lines 717 - 729, determine_consensus_from_votes
currently ignores Vote.DETERMINISTIC_VIOLATION, so validators that set
Vote.DETERMINISTIC_VIOLATION (set in backend/node/base.py when leader_receipt !=
receipt) are dropped; update determine_consensus_from_votes (in
backend/consensus/utils.py) to treat Vote.DETERMINISTIC_VIOLATION as a
disagreeing vote by counting Vote.DETERMINISTIC_VIOLATION.value toward
disagree_count (or explicitly increment a disagree_count when encountering
Vote.DETERMINISTIC_VIOLATION), and add/update a short comment and unit tests to
assert that DETERMINISTIC_VIOLATION produces a disagree/majority-rejection
outcome.
tests/unit/test_set_vote.py (1)

221-270: ⚠️ Potential issue | 🔴 Critical

Critical: DETERMINISTIC_VIOLATION votes are silently dropped in consensus calculation.

The unit tests correctly verify that _set_vote() generates DETERMINISTIC_VIOLATION votes when there are mismatches. However, there is a critical bug: determine_consensus_from_votes() in backend/consensus/utils.py only counts AGREE, DISAGREE, TIMEOUT, and IDLE votes. DETERMINISTIC_VIOLATION votes are not counted at all and are silently dropped.

This means validators can emit DETERMINISTIC_VIOLATION votes, but they have no effect on the consensus outcome. Either:

  1. Add DETERMINISTIC_VIOLATION counting logic to determine_consensus_from_votes() (treating it as DISAGREE or as a separate majority category), or
  2. Change _set_vote() to emit DISAGREE instead of DETERMINISTIC_VIOLATION, or
  3. Document that DETERMINISTIC_VIOLATION is for logging only and should not affect consensus

Add a test in test_consensus_voting.py that verifies the chosen behavior when validators emit DETERMINISTIC_VIOLATION votes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/test_set_vote.py` around lines 221 - 270, The consensus function
determine_consensus_from_votes() ignores Vote.DETERMINISTIC_VIOLATION so those
votes are dropped; fix by updating determine_consensus_from_votes() to include
Vote.DETERMINISTIC_VIOLATION in the vote counting (treat it as a
disagree-equivalent for majority calculations), ensure any branching/majority
logic uses the new count, and add a unit test in test_consensus_voting.py that
constructs a vote set including Vote.DETERMINISTIC_VIOLATION and asserts the
expected consensus outcome; refer to determine_consensus_from_votes(),
Vote.DETERMINISTIC_VIOLATION, and _set_vote() to locate the relevant code and
tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@backend/node/base.py`:
- Around line 717-729: determine_consensus_from_votes currently ignores
Vote.DETERMINISTIC_VIOLATION, so validators that set
Vote.DETERMINISTIC_VIOLATION (set in backend/node/base.py when leader_receipt !=
receipt) are dropped; update determine_consensus_from_votes (in
backend/consensus/utils.py) to treat Vote.DETERMINISTIC_VIOLATION as a
disagreeing vote by counting Vote.DETERMINISTIC_VIOLATION.value toward
disagree_count (or explicitly increment a disagree_count when encountering
Vote.DETERMINISTIC_VIOLATION), and add/update a short comment and unit tests to
assert that DETERMINISTIC_VIOLATION produces a disagree/majority-rejection
outcome.

In `@tests/unit/test_set_vote.py`:
- Around line 221-270: The consensus function determine_consensus_from_votes()
ignores Vote.DETERMINISTIC_VIOLATION so those votes are dropped; fix by updating
determine_consensus_from_votes() to include Vote.DETERMINISTIC_VIOLATION in the
vote counting (treat it as a disagree-equivalent for majority calculations),
ensure any branching/majority logic uses the new count, and add a unit test in
test_consensus_voting.py that constructs a vote set including
Vote.DETERMINISTIC_VIOLATION and asserts the expected consensus outcome; refer
to determine_consensus_from_votes(), Vote.DETERMINISTIC_VIOLATION, and
_set_vote() to locate the relevant code and tests.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e2909c90-ec55-4df4-984f-1f0b48ed8411

📥 Commits

Reviewing files that changed from the base of the PR and between 925f041 and 4b2b147.

📒 Files selected for processing (2)
  • backend/node/base.py
  • tests/unit/test_set_vote.py

MuncleUscles and others added 3 commits March 6, 2026 13:26
node_factory_supplier reads len(created_nodes) to determine votes
while multiple validators are created concurrently, causing the vote
threshold to be missed intermittently. Adding a lock serializes node
creation so the count is always accurate when get_vote() is called.
The test_leader_appeal final assertion timed out at 60s when run under
pytest-xdist (-n auto). The consensus loop runs slower in xdist
subprocess workers, so the appeal rounds + finality window exceed 60s.

Increase the final FINALIZED wait timeout to 180s and revert the
unnecessary threading lock (node creation is synchronous within the
single-threaded async event loop, so no race condition exists).

Root cause: pytest 8→9 upgrade in 925f041 introduced the incompatibility.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/node/base.py (1)

720-732: ⚠️ Potential issue | 🔴 Critical

CRITICAL: DETERMINISTIC_VIOLATION votes are silently ignored in consensus determination.

The current implementation of determine_consensus_from_votes() (backend/consensus/utils.py:20-23) only counts AGREE, DISAGREE, TIMEOUT, and IDLE votes. When a validator emits Vote.DETERMINISTIC_VIOLATION, this vote is not tallied or processed—it simply vanishes from the consensus calculation. This means validators correctly detecting execution mismatches are silently dropped from the consensus outcome, undermining the entire consensus mechanism.

The vote must be explicitly counted in the consensus logic, either as a distinct outcome or mapped to one of the existing categories.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/node/base.py` around lines 720 - 732, determine_consensus_from_votes
currently ignores Vote.DETERMINISTIC_VIOLATION so validators flagging
execution/state mismatches are dropped; update determine_consensus_from_votes to
explicitly handle Vote.DETERMINISTIC_VIOLATION (either add a separate counter
for it or map it to an existing category like DISAGREE), increment that counter
when seen, and then include that counter in the consensus decision logic so
DETERMINISTIC_VIOLATION votes affect the outcome; reference the
determine_consensus_from_votes function and the Vote.DETERMINISTIC_VIOLATION
enum value when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@backend/node/base.py`:
- Around line 720-732: determine_consensus_from_votes currently ignores
Vote.DETERMINISTIC_VIOLATION so validators flagging execution/state mismatches
are dropped; update determine_consensus_from_votes to explicitly handle
Vote.DETERMINISTIC_VIOLATION (either add a separate counter for it or map it to
an existing category like DISAGREE), increment that counter when seen, and then
include that counter in the consensus decision logic so DETERMINISTIC_VIOLATION
votes affect the outcome; reference the determine_consensus_from_votes function
and the Vote.DETERMINISTIC_VIOLATION enum value when making the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 32fa2232-4e68-4d29-a8ed-43e2e8fbbbaa

📥 Commits

Reviewing files that changed from the base of the PR and between 4e8b3c9 and 242d816.

📒 Files selected for processing (2)
  • backend/node/base.py
  • tests/unit/consensus/test_base.py

@cristiam86 cristiam86 merged commit c7962ca into main Mar 13, 2026
16 checks passed
@github-actions
Copy link
Contributor

🎉 This PR is included in version 0.110.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants