Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
b351cc3
Tweak voting committee crypto interface for aggregatable types
agustinmista Apr 27, 2026
1adb5cd
Implement pure weighted Fait-Accompli logic
agustinmista Apr 9, 2026
08c3fd1
Implement local sortition for non-persistent seats
agustinmista Apr 9, 2026
e74632c
Implement wFA^LS voting committee instance
agustinmista Apr 9, 2026
8000b05
Implement EveryoneVotes voting committee instance
agustinmista Apr 9, 2026
545a72f
Add changelog
agustinmista Apr 13, 2026
72dbffc
Remove epochNonce from EveryoneVotes committee selection
tbagrel1 Apr 27, 2026
5b9c87a
remove repetitive computation of numPoolsWithPositiveStake
tbagrel1 Apr 27, 2026
5d3a35f
Fix checkShouldVote for EveryoneVotes in the zero-stake case
tbagrel1 Apr 27, 2026
0b46c3e
Add doc on LS module header
tbagrel1 Apr 27, 2026
7013c56
Improve guards and comments in LS
tbagrel1 Apr 27, 2026
acacbbc
fix typos
tbagrel1 Apr 27, 2026
e401351
Improve doc on cumulative stake w.r.t. potential tiebreakers
tbagrel1 Apr 27, 2026
76cff08
Add fair tiebreaker implementation in WFA
tbagrel1 Apr 27, 2026
c1d197d
Clean-up imports in WFALS and EveryoneVotes
tbagrel1 Apr 27, 2026
a7a8bac
Add comment on accum stake (right to left)
tbagrel1 Apr 27, 2026
553d13e
merge partial getCandidateInSeat and predicate seatIndexWithinBounds …
tbagrel1 Apr 27, 2026
4fc545d
Consistent comment on export list of Committee.{WFALS,EveryoneVotes}
tbagrel1 Apr 28, 2026
d1c49c3
Change the error kind from `InvalidVoteSignature` to `CryptoError` wh…
tbagrel1 Apr 28, 2026
cfd0884
Change `VotesWithSameTarget` to `VotesNoDupNonEmptySameTarget` to ens…
tbagrel1 Apr 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<!--
A new scriv changelog fragment.

Uncomment the section that is right (remove the HTML comment wrapper).
For top level release notes, leave all the headers commented out.
-->

<!--
### Breaking

- A bullet item for the Breaking category.

-->
### Non-Breaking

- Implemented pure weighted Fait-Accompli logic.
- Implemented local sortition check for non-persistent seats.
- Implemented wFA^LS voting committee instance.
- Implemented EveryoneVotes voting committee instance.

<!--
### Patch

- A bullet item for the Patch category.

-->
5 changes: 5 additions & 0 deletions ouroboros-consensus.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,11 @@ library
Ouroboros.Consensus.Committee.AcrossEpochs
Ouroboros.Consensus.Committee.Class
Ouroboros.Consensus.Committee.Crypto
Ouroboros.Consensus.Committee.EveryoneVotes
Ouroboros.Consensus.Committee.LS
Ouroboros.Consensus.Committee.Types
Ouroboros.Consensus.Committee.WFA
Ouroboros.Consensus.Committee.WFALS
Ouroboros.Consensus.Config
Ouroboros.Consensus.Config.SecurityParam
Ouroboros.Consensus.Config.SupportsNode
Expand Down Expand Up @@ -351,6 +355,7 @@ library
build-depends:
FailT ^>=0.1.2,
aeson,
array,
base >=4.14 && <4.23,
base-deriving-via,
base16-bytestring >=1.0,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
Expand All @@ -9,25 +10,26 @@ module Ouroboros.Consensus.Committee.Class
CryptoSupportsVotingCommittee (..)

-- * Votes with same target
, VotesWithSameTarget
, VotesNoDupNonEmptySameTarget
, getElectionIdFromVotes
, getVoteCandidateFromVotes
, getRawVotes
, VotesWithSameTargetError (..)
, ensureSameTarget
, VotesNoDupNonEmptySameTargetError (..)
, ensureNoDupNonEmptySameTarget
) where

import Data.Containers.NonEmpty (HasNonEmpty (..))
import Data.Either (partitionEithers)
import Data.Kind (Type)
import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.Set as Set
import Ouroboros.Consensus.Committee.Crypto
( CryptoSupportsVoteSigning
, ElectionId
, PrivateKey
, VoteCandidate
)
import Ouroboros.Consensus.Committee.Types (PoolId, VoteWeight)
import Ouroboros.Consensus.Committee.Types (PoolId, SeatIndex, VoteWeight)

-- * Voting committee interface

Expand Down Expand Up @@ -102,8 +104,10 @@ class

-- | Forge a certificate attesting the winner of a given election
forgeCert ::
VotesWithSameTarget crypto committee ->
Cert crypto committee
VotesNoDupNonEmptySameTarget crypto committee SeatIndex ->
Either
(VotingCommitteeError crypto committee)
(Cert crypto committee)

-- | Verify a certificate attesting the winner of a given election
verifyCert ::
Expand All @@ -115,74 +119,105 @@ class

-- * Votes with same target

-- | Collection of votes all targeting the same election and candidate
data VotesWithSameTarget crypto committee
= VotesWithSameTarget
-- | Non-empty collection of votes all targeting the same election and candidate
-- with unique voter IDs.
data VotesNoDupNonEmptySameTarget crypto committee voterId
= VotesNoDupNonEmptySameTarget
(ElectionId crypto)
(VoteCandidate crypto)
(NE [Vote crypto committee])

-- | Get the election identifier targeted by a collection of votes
getElectionIdFromVotes ::
VotesWithSameTarget crypto committee ->
VotesNoDupNonEmptySameTarget crypto committee voterId ->
ElectionId crypto
getElectionIdFromVotes (VotesWithSameTarget electionId _ _) =
getElectionIdFromVotes (VotesNoDupNonEmptySameTarget electionId _ _) =
electionId

-- | Get the vote candidate targeted by a collection of votes
getVoteCandidateFromVotes ::
VotesWithSameTarget crypto committee ->
VotesNoDupNonEmptySameTarget crypto committee voterId ->
VoteCandidate crypto
getVoteCandidateFromVotes (VotesWithSameTarget _ candidate _) =
getVoteCandidateFromVotes (VotesNoDupNonEmptySameTarget _ candidate _) =
candidate

-- | Get the raw votes from a collection of votes with the same target.
--
-- NOTE: this returns votes in ascending seat index order.
getRawVotes ::
VotesWithSameTarget crypto committee ->
VotesNoDupNonEmptySameTarget crypto committee voterId ->
NE [Vote crypto committee]
getRawVotes (VotesWithSameTarget _ _ votes) =
getRawVotes (VotesNoDupNonEmptySameTarget _ _ votes) =
votes

-- | Errors when votes do not all target the same election and candidate
data VotesWithSameTargetError crypto committee
-- | Errors when votes do not respect the requirements to be grouped together to
-- eventually forge a certificate.
data VotesNoDupNonEmptySameTargetError crypto committee voterId
= EmptyVotes
| TargetMismatch
-- First vote and the rest of the votes that match its target
(NE [Vote crypto committee])
-- Votes that do not match the target of the first vote
(NE [Vote crypto committee])
| DuplicateVoter voterId

-- | Check that a list of votes all target the same election and candidate
ensureSameTarget ::
-- | Check:
-- + that a list of votes is non-empty,
-- + that all votes target the same election and candidate,
-- + and that no two votes come from the same voter.
ensureNoDupNonEmptySameTarget ::
( Eq (ElectionId crypto)
, Eq (VoteCandidate crypto)
, Ord voterId
) =>
(Vote crypto committee -> (ElectionId crypto, VoteCandidate crypto)) ->
(Vote crypto committee -> (ElectionId crypto, VoteCandidate crypto, voterId)) ->
[Vote crypto committee] ->
Either
(VotesWithSameTargetError crypto committee)
(VotesWithSameTarget crypto committee)
ensureSameTarget getTarget = \case
(VotesNoDupNonEmptySameTargetError crypto committee voterId)
(VotesNoDupNonEmptySameTarget crypto committee voterId)
ensureNoDupNonEmptySameTarget getVoteInfo = \case
[] ->
Left EmptyVotes
(firstVote : nextVotes) -> do
case partitionEithers (fmap matchesTarget nextVotes) of
([], matchingVotes) ->
Right $
VotesWithSameTarget
electionId
candidate
(firstVote :| matchingVotes)
([], matchingVotes) -> do
let allVotes = firstVote :| matchingVotes
case findDuplicate (\v -> let (_, _, vid) = getVoteInfo v in vid) allVotes of
Just dup -> Left (DuplicateVoter dup)
Nothing ->
Right $
VotesNoDupNonEmptySameTarget
electionId
candidate
allVotes
(firstMismatchingVote : nextMismatchingVotes, matchingVotes) ->
Left $
TargetMismatch
(firstVote :| matchingVotes)
(firstMismatchingVote :| nextMismatchingVotes)
where
target@(electionId, candidate) =
getTarget firstVote
(electionId, candidate, _) =
getVoteInfo firstVote
target = (electionId, candidate)
matchesTarget v'
| getTarget v' /= target = Left v'
| let (eid, vc, _) = getVoteInfo v'
, (eid, vc) /= target =
Left v'
| otherwise = Right v'

-- | Find the first duplicate voter ID in a non-empty list of votes.
findDuplicate ::
Ord voterId =>
(vote -> voterId) ->
NonEmpty vote ->
Maybe voterId
findDuplicate getId =
go Set.empty
where
go !seen (v :| rest) =
let vid = getId v
in if Set.member vid seen
then Just vid
else case rest of
[] -> Nothing
(next : more) -> go (Set.insert vid seen) (next :| more)
Loading