Skip to content

Conversation

@agustinmista
Copy link
Contributor

@agustinmista agustinmista commented Dec 8, 2025

This PR comes in preparation to the implementation of the PerasVoteDB, and introduces a couple of new data types related to Peras votes and the certificate forging API:

  • The main PerasVote type, and its corresponding ValidatedPerasVote.
  • The auxiliary types PerasVoteTarget, PerasVoterId, PerasVoteStake, PerasVoteStakeDistr
  • The new Peras parameter PerasQuorumStakeThreshold (corresponding to perasQuorum in the design document, section 2.1)

@agustinmista agustinmista self-assigned this Dec 8, 2025
@agustinmista agustinmista force-pushed the peras/main-pr/votedb-api branch from bf3a52c to 4063507 Compare December 9, 2025 10:39
@agustinmista agustinmista changed the title [Peras 13] Add votes and voting API [Peras 13] Add votes and vote forging API Dec 9, 2025
@agustinmista agustinmista changed the title [Peras 13] Add votes and vote forging API [Peras 13] Add votes and certificate forging API Dec 9, 2025
@agustinmista agustinmista force-pushed the peras/main-pr/votedb-api branch from 4063507 to 06f4e61 Compare December 9, 2025 10:53
@agustinmista agustinmista changed the title [Peras 13] Add votes and certificate forging API [Peras 13] Introduce votes and certificate forging API Dec 9, 2025
Copy link
Contributor

@tbagrel1 tbagrel1 left a comment

Choose a reason for hiding this comment

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

Looks perfect to me! 😃

@agustinmista agustinmista marked this pull request as ready for review December 9, 2025 14:48
@agustinmista agustinmista force-pushed the peras/main-pr/votedb-api branch from 06f4e61 to 42bb662 Compare December 12, 2025 13:42
@agustinmista agustinmista force-pushed the peras/main-pr/new-defs-and-plumbing branch 2 times, most recently from c314f45 to bb97f3f Compare December 16, 2025 09:40
@agustinmista agustinmista force-pushed the peras/main-pr/votedb-api branch from 42bb662 to 4f2f0cc Compare December 16, 2025 10:08
pcCertBoostedBlock <- decode
pure $ PerasCert{pcCertRound, pcCertBoostedBlock}

instance Serialise (HeaderHash blk) => Serialise (PerasVote blk) where
Copy link
Contributor

Choose a reason for hiding this comment

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

Why chose Serialise instead of ToCBOR/FromCBOR?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@tbagrel1 maybe you have a better idea?

Copy link
Contributor

Choose a reason for hiding this comment

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

Ouroboros.Consensus.Network.NodeToNode uses SerialiseNodeToNode class to serialise stuff. For types whose serialisation doesn't depend on a particular NodeToNodeVersion, the instances often fallback to encode/decode from Serialise class. For Peras types we've just followed the same existing pattern :)

Comment on lines +213 to +228
-- Consistent with the 'Serialise' instance for 'PerasVote' defined in Ouroboros.Consensus.Block.SupportsPeras
encodeNodeToNode ccfg version PerasVote{..} =
encodeListLen 3
<> encodeNodeToNode ccfg version pvVoteRound
<> encodeNodeToNode ccfg version pvVoteBlock
<> encodeNodeToNode ccfg version pvVoteVoterId
decodeNodeToNode ccfg version = do
decodeListLenOf 3
pvVoteRound <- decodeNodeToNode ccfg version
pvVoteBlock <- decodeNodeToNode ccfg version
pvVoteVoterId <- decodeNodeToNode ccfg version
pure $ PerasVote pvVoteRound pvVoteBlock pvVoteVoterId

instance SerialiseNodeToNode blk PerasVoterId where
encodeNodeToNode _ccfg _version = KeyHash.toCBOR . unPerasVoterId
decodeNodeToNode _ccfg _version = PerasVoterId <$> KeyHash.fromCBOR
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess the Serialise instance could change as it is used for storing on disk (confirm?), while this one would require a new NTN version etc etc. So if any I would put the comment in the Serialise instance saying that it matches the NTN one.

Also I think I would expose a function

-- Ouroboros.Consensus.Block.SupportsPeras

encodeVoteNodeToNode :: CodecConfig blk -> NodeToNodeVersion -> PerasVote blk -> Encoding
encodeVoteNodeToNode _ccfg _version = encode

maybe? I should check how we do this in other places but I think this is the pattern we usually use.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Bystander comment: I'm not super familiar with how these two type classes interact with each other but, in any case, we should remember to also apply any transformation to the instance for PerasCert defined above.

@agustinmista agustinmista force-pushed the peras/main-pr/new-defs-and-plumbing branch from bb97f3f to 43c1fdc Compare December 16, 2025 10:44
@agustinmista agustinmista force-pushed the peras/main-pr/votedb-api branch from 4f2f0cc to fd401fc Compare December 16, 2025 10:59
@agustinmista agustinmista force-pushed the peras/main-pr/new-defs-and-plumbing branch from 43c1fdc to e7eadb3 Compare December 16, 2025 12:00
@agustinmista agustinmista force-pushed the peras/main-pr/votedb-api branch from fd401fc to 0006522 Compare December 16, 2025 12:06
@agustinmista agustinmista force-pushed the peras/main-pr/new-defs-and-plumbing branch from e7eadb3 to ecff70d Compare December 19, 2025 09:25
@agustinmista agustinmista force-pushed the peras/main-pr/votedb-api branch from 0006522 to d94393a Compare December 19, 2025 12:09
@agustinmista agustinmista force-pushed the peras/main-pr/votedb-api branch from d94393a to dff3d9b Compare December 19, 2025 12:17
agustinmista and others added 2 commits December 19, 2025 13:21
This commit introduces a couple of new types to represent Peras votes
and their corresponding certificate forging API. Notably, this requires
an initial representation of notions like vote targets, vote stakes and stake
distributions over multiple stake pools.

Co-authored-by: Agustin Mista <agustin.mista@moduscreate.com>
Co-authored-by: Alexander Esgen <alexander.esgen@iohk.io>
Co-authored-by: Georgy Lukyanov <georgy.lukyanov@iohk.io>
Co-authored-by: Thomas BAGREL <thomas.bagrel@tweag.io>
Co-authored-by: Nicolas BACQUEY <nicolas.bacquey@tweag.io>
Co-authored-by: Agustin Mista <agustin.mista@moduscreate.com>
Co-authored-by: Alexander Esgen <alexander.esgen@iohk.io>
Co-authored-by: Georgy Lukyanov <georgy.lukyanov@iohk.io>
Co-authored-by: Thomas BAGREL <thomas.bagrel@tweag.io>
Co-authored-by: Nicolas BACQUEY <nicolas.bacquey@tweag.io>
@agustinmista agustinmista force-pushed the peras/main-pr/votedb-api branch from dff3d9b to 4b0c1af Compare December 19, 2025 12:23
@agustinmista
Copy link
Contributor Author

This PR is now approved — merge pending integration of changes in upstream ouroboros-network.

Copy link
Contributor

@bladyjoker bladyjoker left a comment

Choose a reason for hiding this comment

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

Heyoo, this all looks nice and aligned with how things are done elsewhere!

I do have a couple of questions that probably require some historical forensics.

Why do we do things like this?

class
  ( Show (PerasCfg blk)
  , NoThunks (PerasCert blk)
  ) =>
  BlockSupportsPeras blk
  where
  data PerasCert blk

instance StandardHash blk => BlockSupportsPeras blk where
  type PerasCfg blk = PerasParams

  data PerasCert blk = PerasCert
    { pcCertRound :: PerasRoundNo
    , pcCertBoostedBlock :: Point blk
    }


class HasPerasCertRound cert where
  getPerasCertRound :: cert -> PerasRoundNo

instance HasPerasCertRound (PerasCert blk) where
  getPerasCertRound = pcCertRound

instance HasPerasCertRound (ValidatedPerasCert blk) where
  getPerasCertRound = getPerasCertRound . vpcCert

instance
  HasPerasCertRound cert =>
  HasPerasCertRound (WithArrivalTime cert)
  where
  getPerasCertRound = getPerasCertRound . forgetArrivalTime

-- | Extract the boosted block point from a Peras certificate container
class HasPerasCertBoostedBlock cert blk | cert -> blk where
  getPerasCertBoostedBlock :: cert -> Point blk

instance HasPerasCertBoostedBlock (PerasCert blk) blk where
  getPerasCertBoostedBlock = pcCertBoostedBlock

instance HasPerasCertBoostedBlock (ValidatedPerasCert blk) blk where
  getPerasCertBoostedBlock = getPerasCertBoostedBlock . vpcCert

instance
  HasPerasCertBoostedBlock cert blk =>
  HasPerasCertBoostedBlock (WithArrivalTime cert) blk
  where
  getPerasCertBoostedBlock = getPerasCertBoostedBlock . forgetArrivalTime

-- | Extract the certificate boost from a Peras certificate container
class HasPerasCertBoost cert where
  getPerasCertBoost :: cert -> PerasWeight

instance HasPerasCertBoost (ValidatedPerasCert blk) where
  getPerasCertBoost = vpcCertBoost

instance
  HasPerasCertBoost cert =>
  HasPerasCertBoost (WithArrivalTime cert)
  where
  getPerasCertBoost = getPerasCertBoost . forgetArrivalTime

Instead why not commit to the fact that PerasCert has BoostedBlock and Round and simply use

  data PerasCert blk = PerasCert
    { pcCertRound :: PerasRoundNo
    , pcCertBoostedBlock :: Point blk
    }

I understand some parts will perhaps only want to work with the 'round' or 'boosted block' separately, so HasFoo classes allow for that, perhaps it saves you from implementing a HasBar that you don't need in a particular test. But honestly, that's a lot of plumbing for something that doesn't look so needed.

I guess my take is that associated type/data families are overused sometimes, and it looks like we're avoiding committing to a core structure of Peras. I say, why not commit? Why not say in Peras a certificate is a thing that has a round and boosted block, rather than 'type classing' every possible cert getters which we'll end up using together anyway?

Curious to hear your thoughts!

@tbagrel1
Copy link
Contributor

@bladyjoker Good question! Actually it's a hot topic atm in our team, cf. #1829 for which we are still iterating the design.

The idea is that in the future, the actual type of Peras{Cert,Vote} will depend on blk:

  • for blk ~ TestBlock, we may omit the cryptographic stuff (that isn't part of the concrete Peras{Cert,Vote} data type atm, but will be for the actual ledger blocks soon)
  • from my (very partial) understanding, the main type of block we will have to deal with is HardForkBlock eras, which is a n-ary sum over the different eras and their associated concrete block type. We will probably have to give a BlockSupportsPeras instance for this HardForkBlock eras type. This may imply having to give an instance BlockSupportsPeras even for block types that don't actually support Peras, in which case it will be quite handy to resolve Peras{Cert,Vote} blk to Void/()
  • in the future, we want to keep the possibility to expand Peras{Cert,Vote} with new fields, even though we don't think their concrete content will change very often.

But in general, I agree, at the moment indeed, we only have a StandardHash blk => BlockSupportsPeras blk, so we don't use much the benefit of type/data families. This is supposed to change in a near future, and we are also considering consolidating all the "getter typeclasses" in a single one (one for vote, one for cert).

@bladyjoker
Copy link
Contributor

@bladyjoker Good question! Actually it's a hot topic atm in our team, cf. #1829 for which we are still iterating the design.

The idea is that in the future, the actual type of Peras{Cert,Vote} will depend on blk:

* for  `blk ~ TestBlock`, we may omit the cryptographic stuff (that isn't part of the concrete `Peras{Cert,Vote}` data type atm, but will be for the actual ledger blocks soon)

* from my (very partial) understanding, the main type of block we will have to deal with is `HardForkBlock eras`, which is a n-ary sum over the different `eras` and their associated concrete block type. We will probably have to give a `BlockSupportsPeras` instance for this `HardForkBlock eras` type. This may imply having to give an instance `BlockSupportsPeras` even for block types that don't actually support Peras, in which case it will be quite handy to resolve `Peras{Cert,Vote} blk` to `Void`/`()`

* in the future, we want to keep the possibility to expand `Peras{Cert,Vote}` with new fields, even though we don't think their concrete content will change very often.

But in general, I agree, at the moment indeed, we only have a StandardHash blk => BlockSupportsPeras blk, so we don't use much the benefit of type/data families. This is supposed to change in a near future, and we are also considering consolidating all the "getter typeclasses" in a single one (one for vote, one for cert).

Perfect! Curious what you'll come up with because this pattern has proliferated and we should re-evaluate it in some places. I suspect we can have much less wiring overhead in many places (for example Mempool etc).

I'm also curious about HFC aspects of Peras. Is there ever a moment in which one deals with certs/votes ... basically Objects that are in different blk (certA created in era A and certB created in eraB, and now you have to are in eraC and need to work with these).

That's for example what we have with UTxOs.

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.

7 participants