Skip to content

[Peras 18] Implement generic weighted Fait-Accompli with Local Sortition#1893

Closed
agustinmista wants to merge 10 commits intomainfrom
peras/committee-selection-rules
Closed

[Peras 18] Implement generic weighted Fait-Accompli with Local Sortition#1893
agustinmista wants to merge 10 commits intomainfrom
peras/committee-selection-rules

Conversation

@agustinmista
Copy link
Copy Markdown
Contributor

@agustinmista agustinmista commented Feb 24, 2026

This PR implements the initial version of a generic weighted Fait-Accompli with Local Sortition (wFA^LS) committee selection scheme. To allow for both Peras and Leios to reuse the same implementation, the code is abstracted in the following ways:

  • ElectionId type family: central to committee selection, it allows the client to provide their own different election identifier. Peras will use round numbers, whereas Leios will use something isomorphic to a slot number.
  • CryptoSupportsVoteSignature: crypto interface needed to sign/verify vote signatures using a public/private key pair.
  • CryptoSupportsVRF: crypto interface needed to compute/validate VRF outputs using a public/private key pair. This is needed by the Local Sortition fallback scheme.
  • CryptoSupportsWFALS: Wrapper on top of CryptoSupportsVoteSignature and CryptoSupportsVRF that allows us to derive both their key pairs from a single key pair. This could be used to have potentially different crypto schemes for the vote signing and VRF evaluation routines derived from the same "composite" key pair.
  • VoteSupportsWFALS: interface needed to extract the bits relevant to committee selection and signature verification from a client vote (i.e., a concrete Peras or Leios vote).

In addition, this PR also provides an initial instantiation of all these type classes for Peras using BLS crypto (the best approximation for the final one we have for now).

Other notes

  • While this PR only provides wFA^LS, the design should be modular enough to also allow one to reuse some of the interfaces (e.g., ElectionId, and CryptoSupportsVoteSigning) for other committee selection schemes (e.g. wFA^IID).
  • Conformance tests against the Rust implementation will be added after Peras 16 lands into main.
  • Because epoch boundaries are a bit special, O.C.Committee.AcrossEpochs defines a small wrapper to keep the committee selections from both the current and previous epochs at hand. This is needed to validate votes cast at the end of the previous epoch arriving at the beginning of the current one.
  • Because any instance of a weighted-Fait-Accompli-based committee selection for a given epoch would need to aggregate the input stake distribution in the same way, we have extracted this into the ExtCumulativeStakeDistr data type. This would allow both Peras and Leios to share part of the heavy work needed at the beginning of every new epoch.
  • This PR does not include any other potential optimization beyond the one described above.

@agustinmista agustinmista self-assigned this Feb 24, 2026
@agustinmista agustinmista force-pushed the peras/committee-selection-rules branch 7 times, most recently from fdbd4da to 96b1d18 Compare March 2, 2026 19:35
@agustinmista agustinmista changed the title WIP implementing committee selection rules WIP implementing generic committee selection Mar 2, 2026
@agustinmista agustinmista force-pushed the peras/committee-selection-rules branch 2 times, most recently from daa1fdd to 5e6723d Compare March 3, 2026 08:50
Comment thread ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Committee/WFA.hs Outdated
Comment thread ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Committee/WFA.hs Outdated
Comment thread ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Committee/WFA.hs Outdated
@agustinmista agustinmista force-pushed the peras/committee-selection-rules branch 3 times, most recently from 94579d3 to 21c11dc Compare March 4, 2026 11:24
@agustinmista agustinmista force-pushed the peras/committee-selection-rules branch 2 times, most recently from 4545066 to b621259 Compare March 5, 2026 13:05
@tbagrel1 tbagrel1 force-pushed the peras/committee-selection-rules branch from 92a378e to 2374824 Compare March 6, 2026 11:37
@agustinmista agustinmista force-pushed the peras/committee-selection-rules branch 5 times, most recently from f757764 to 466680c Compare March 9, 2026 13:24
@agustinmista agustinmista changed the title WIP implementing generic committee selection [Peras 18] Generic weighted Fait-Accompli committee selection scheme Mar 9, 2026
@agustinmista agustinmista force-pushed the peras/committee-selection-rules branch from 466680c to 36d9a2f Compare March 11, 2026 08:18
@agustinmista agustinmista force-pushed the peras/committee-selection-rules branch 3 times, most recently from 7f18933 to 8fb6ada Compare March 16, 2026 10:26
agustinmista and others added 4 commits March 17, 2026 15:47
This commit implements an initial version of the weigthed Fait-Accompli
with Local Sortition committee selection scheme. This implementation is
generic w.r.t. both the election and vote types, as well as the crypto
scheme used by the client.

Co-authored-by: Nicolas BACQUEY <nicolas.bacquey@tweag.io>
Co-authored-by: Thomas BAGREL <thomas.bagrel@tweag.io>
Co-authored-by: Agustin Mista <agustin.mista@moduscreate.com>
This commit defines an initial instantiation of the weighted
Fait-Accompli with Local Sortition scheme for Peras using pre-existing
BLS crypto.

Co-authored-by: Nicolas BACQUEY <nicolas.bacquey@tweag.io>
Co-authored-by: Thomas BAGREL <thomas.bagrel@tweag.io>
Co-authored-by: Agustin Mista <agustin.mista@moduscreate.com>
This commit refactors the weighted Fait-Accompli conformance test suite
into a composite unit test that accepts a function to preprocess the stake
distribution into a value that gets shared accross individual tests.

This is useful to instantiate this test suite against the real implementation,
which relies on a precomputed stake distribution array that can be
shared across all 3000 individual test steps.

Co-authored-by: Nicolas BACQUEY <nicolas.bacquey@tweag.io>
Co-authored-by: Thomas BAGREL <thomas.bagrel@tweag.io>
Co-authored-by: Agustin Mista <agustin.mista@moduscreate.com>
Co-authored-by: Nicolas BACQUEY <nicolas.bacquey@tweag.io>
Co-authored-by: Thomas BAGREL <thomas.bagrel@tweag.io>
Co-authored-by: Agustin Mista <agustin.mista@moduscreate.com>
@agustinmista agustinmista force-pushed the peras/committee-selection-rules branch from 8fb6ada to 22e70f4 Compare March 17, 2026 14:51
Copy link
Copy Markdown
Member

@perturbing perturbing left a comment

Choose a reason for hiding this comment

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

Functionally, it looks good 👍

Some small details and remarks that we have to crystallize.

-- * total non-persistent stake
--
-- TODO: this can be more efficient if we compute the values directly on the array.
weightedFaitAccompliSplitSeats ::
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I agree with @tbagrel1 that we should at least prevent in some way this request (committee size larger than the number of pools that have non-zero stake).

expectedSeats =
fromMaybe 0 $
taylorExpCmpFirstNonLower
3
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This value should be scrutinized. That is, we need to understand why this was set at 3

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added a TODO here as well.


-- | Persistent committee size
newtype PersistentCommitteeSize = PersistentCommitteeSize
{ unPersistentCommitteeSize :: Word64
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why Word64?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Also for the others below.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think Word64 is the de-facto type for natural numbers in the codebase. At least I haven't come across any arbitrary-length Natural being used for long-lived values.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I ask because we can also use Word16 with values range from 0 to 65535.

I do not expect the committee size and other so exceed that. Feels like Word16 is overkill, but if it is the defacto choice, I yield ;)

Copy link
Copy Markdown
Contributor Author

@agustinmista agustinmista Mar 19, 2026

Choose a reason for hiding this comment

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

Oh, right. You were actually suggesting to go in the opposite direction haha.

As for Word16, I don't think it matters much unless we're sending these values over the wire and control the memory/packet layout (which we are not).

My rationale here being that GHC will probably not use the remaining 48bits on something else, and instead it will just dedicate the same amount of memory as if it were a Word64 due to memory alignment. See the "Basic Types" section of this old wiki entry.

Comment thread ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Committee/LS.hs Outdated
Comment thread ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Peras/Crypto.hs Outdated
@agustinmista agustinmista force-pushed the peras/committee-selection-rules branch from 6d6d5d1 to 412f4df Compare March 19, 2026 11:45
@agustinmista
Copy link
Copy Markdown
Contributor Author

FYI: this PR is already large enough, so I started working on its continuation in Peras 20, which includes:

  • Tiebreaker for pools with the same stake
  • Conformance tests between model and (Haskell) implementation

@perturbing
Copy link
Copy Markdown
Member

@agustinmista and @tbagrel1 , please be aware of this cardano-scaling/leios-wfa-ls-demo#4

Copy link
Copy Markdown
Member

@dnadales dnadales left a comment

Choose a reason for hiding this comment

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

Commenting for now waiting for a response on:

  • Underflow checking (mkExtWFAStakeDistr)
  • Possibly missing isPersistentMember check.

ElectionId c ->
VoteMessage c ->
VoteSignature c ->
Either String ()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why did we decide to use String here instead of a custom error type?

Copy link
Copy Markdown
Contributor Author

@agustinmista agustinmista Mar 20, 2026

Choose a reason for hiding this comment

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

We made it easy to instantiate this class directly with some of the crypto primitives from cardano-crypto-class, which all seem to have Either String () as the return type for verification operations.

That said, I don't have strong opinions, and we can wrap this with a ComitteeSelectionCryptoError newtype if you prefer.

EDIT: these errors are also already wrapped by the InvalidVoteSignature variant of CommitteeSelectionError.

verifyVote vote selection =
getVoteView @c vote $ \case
PersistentVote seatIndex electionId message sig
| seatIndexWithinBounds seatIndex selection -> do
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Don't we also need to check isPersistentMember seatIndex selection?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, good catch!

Likewise, we also need to check in the NonPersistentVote variant that, after retrieving the seat index of someone who claims to be a non-persistent voter, that seat index does not correspond to a persistent member:

NonPersistentVote poolId electionId message vrfOutput sig
  | Just seatIndex <- Map.lookup poolId (candidateSeats selection)
  , not (isPersistentMember seatIndex selection) ->
      ...

-- number of seats granted by local sortition and their stake (normalized
-- by the total non-persistent stake)
NonPersistentCommitteeMember
(NonPersistentMemberProof numSeats _sig _vrfOutput)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It seems names are swapped 🤔

Image

Copy link
Copy Markdown
Contributor Author

@agustinmista agustinmista Mar 20, 2026

Choose a reason for hiding this comment

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

Not sure I understand what you mean but, to make it more consistent I renamed these constructors to (Non)PersistentMembershipProof.

Hopefully that sparks joy.

stakeDistrArray =
listArray
( SeatIndex 0
, SeatIndex (fromIntegral (Map.size pools) - 1)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could there be a potential underflow here if Map.size pools == 0?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, I was planning on discharging this case elsewhere and use a non-empty map as input here, but we may as well do it directly while constructing this structure.

I tweaked this function to return an Either WFAError (ExtWFAStakeDistr a).

@agustinmista
Copy link
Copy Markdown
Contributor Author

Thanks @dnadales for the review!

I think I addressed all your feedback now (last 5 commits in this PR, to be squashed) 🤞

@agustinmista
Copy link
Copy Markdown
Contributor Author

Superseded by #1974 and #1975

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Voting Committee selection logic Mocked Vote and Cert Cryptography

5 participants