Skip to content

fix: consensus determinism, BFT quorum threshold, and digest safety - #44

Open
Alicepoltora wants to merge 1 commit into
AdvaitaLabs:mainfrom
Alicepoltora:fix/consensus-determinism-and-quorum
Open

fix: consensus determinism, BFT quorum threshold, and digest safety#44
Alicepoltora wants to merge 1 commit into
AdvaitaLabs:mainfrom
Alicepoltora:fix/consensus-determinism-and-quorum

Conversation

@Alicepoltora

Copy link
Copy Markdown

Summary

Fixes 4 consensus-breaking and correctness bugs:

1. Non-deterministic global_state_root (consensus-breaking)

consensus/src/merkle_integration.rs:136

compute_global_state_root iterates a HashMap which has non-deterministic order. Different validators compute different global_state_root from identical inputs → consensus fork.

Fix: Sort entries by subnet_id before building the SubnetAggregationTree.

2. Non-deterministic Event/Anchor/CF IDs (consensus-breaking)

types/src/event.rs:749, types/src/consensus.rs:87,311

Event::compute_id, Anchor::compute_id, and ConsensusFrame::compute_id include SystemTime::now() timestamp in the hash. Two validators creating the same logical event at different wall-clock times get different IDs → never reach consensus.

Fix: Exclude timestamp from ID computation. The VLC logical_time + creator + parent_ids already provide uniqueness and causal ordering. The timestamp field is preserved for informational purposes.

3. BFT quorum threshold wrong for 3 validators

types/src/consensus.rs:334

Previous formula (n * 2) / 3 + 1 gives threshold=3 for n=3, requiring unanimity instead of BFT quorum. This means a single offline validator blocks all finalization.

Validators Old threshold New threshold (correct)
3 3 (unanimity!) 2
4 3 3
6 5 4

Fix: (n * 2 + 2) / 3 = ceil(2n/3).

4. Object::compute_digest silently ignores BCS errors

types/src/object.rs:418

If bcs::to_bytes fails, the digest was computed from only id + version, without data. Two objects with the same id/version but different (non-serializable) data got the same digest → silent data corruption.

Fix: expect() on BCS failure with a clear message (indicates a type-level bug).

Testing

  • cargo check -p setu-types -p consensus — passes
  • cargo test -p consensus — 160/160 pass
  • cargo test -p setu-types — 178/179 pass (1 pre-existing golden-file failure unrelated to these changes)

Files changed

  • consensus/src/merkle_integration.rs — sort subnet entries
  • types/src/event.rs — exclude timestamp from Event ID, update genesis constant
  • types/src/consensus.rs — exclude timestamp from Anchor/CF ID, fix quorum formula
  • types/src/object.rs — propagate BCS serialization errors

1. Fix non-deterministic global_state_root (consensus/src/merkle_integration.rs)
   - HashMap iteration order is non-deterministic; sort entries by subnet_id
     before building SubnetAggregationTree so all validators compute the same root

2. Fix non-deterministic Event/Anchor/CF IDs (types/src/event.rs, types/src/consensus.rs)
   - Remove SystemTime::now() timestamp from compute_id hash inputs
   - Timestamp caused different validators to compute different IDs for the same
     logical event, breaking consensus
   - VLC logical_time + creator + parent_ids already ensure uniqueness

3. Fix BFT quorum threshold (types/src/consensus.rs)
   - Previous formula (n*2)/3 + 1 required unanimity for n=3 (threshold=3)
   - Correct BFT quorum is ceil(2n/3): n=3→2, n=4→3, n=6→4
   - Changed to (n*2 + 2) / 3

4. Fix Object::compute_digest silently ignoring BCS errors (types/src/object.rs)
   - If bcs::to_bytes fails, digest was computed without data bytes
   - Two objects with same id/version but different data got the same digest
   - Now panics with clear message (indicates a type-level bug)
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.

1 participant