1+ {-# LANGUAGE DataKinds #-}
12{-# LANGUAGE DeriveAnyClass #-}
23{-# LANGUAGE DeriveGeneric #-}
34{-# LANGUAGE DerivingVia #-}
1415module Ouroboros.Consensus.Block.SupportsPeras
1516 ( PerasRoundNo (.. )
1617 , onPerasRoundNo
18+ , PerasVoteId (.. )
19+ , PerasVoteTarget (.. )
20+ , PerasVoterId (.. )
21+ , PerasVoteStake (.. )
22+ , PerasVoteStakeDistr (.. )
23+ , lookupPerasVoteStake
1724 , BlockSupportsPeras (.. )
1825 , PerasCert (.. )
26+ , PerasVote (.. )
1927 , ValidatedPerasCert (.. )
28+ , ValidatedPerasVote (.. )
2029 , HasPerasCertRound (.. )
2130 , HasPerasCertBoostedBlock (.. )
2231 , HasPerasCertBoost (.. )
32+ , HasPerasVoteRound (.. )
33+ , HasPerasVoteBlock (.. )
34+ , HasPerasVoteVoterId (.. )
35+ , HasPerasVoteStake (.. )
36+ , HasPerasVoteTarget (.. )
37+ , HasPerasVoteId (.. )
2338
2439 -- * Convenience re-exports
2540 , module Ouroboros.Consensus.Peras.Params
2641 ) where
2742
43+ import qualified Cardano.Binary as KeyHash
44+ import Cardano.Ledger.Hashes (KeyHash , KeyRole (.. ))
2845import Codec.Serialise (Serialise (.. ))
2946import Codec.Serialise.Decoding (decodeListLenOf )
3047import Codec.Serialise.Encoding (encodeListLen )
3148import Data.Coerce (coerce )
49+ import qualified Data.Map as Map
50+ import Data.Map.Strict (Map )
51+ import Data.Monoid (Sum (.. ))
3252import Data.Proxy (Proxy (.. ))
3353import Data.Word (Word64 )
3454import GHC.Generics (Generic )
@@ -40,6 +60,12 @@ import Ouroboros.Consensus.Util
4060import Ouroboros.Consensus.Util.Condense
4161import Quiet (Quiet (.. ))
4262
63+ {- ------------------------------------------------------------------------------
64+ -- * Peras types
65+ -------------------------------------------------------------------------------}
66+
67+ -- ** Round numbers
68+
4369newtype PerasRoundNo = PerasRoundNo { unPerasRoundNo :: Word64 }
4470 deriving Show via Quiet PerasRoundNo
4571 deriving stock Generic
@@ -57,14 +83,70 @@ onPerasRoundNo ::
5783 (PerasRoundNo -> PerasRoundNo -> PerasRoundNo )
5884onPerasRoundNo = coerce
5985
60- -- TODO using 'Validated' for extra safety? Or some @.Unsafe@ module?
86+ -- ** Stake pool distributions
87+
88+ newtype PerasVoterId = PerasVoterId
89+ { unPerasVoterId :: KeyHash StakePool
90+ }
91+ deriving newtype NoThunks
92+ deriving stock (Eq , Ord , Generic )
93+ deriving Show via Quiet PerasVoterId
94+
95+ newtype PerasVoteStake = PerasVoteStake
96+ { unPerasVoteStake :: Rational
97+ }
98+ deriving newtype (Enum , Eq , Ord , Num , Fractional , NoThunks , Serialise )
99+ deriving stock Generic
100+ deriving Show via Quiet PerasVoteStake
101+ deriving Semigroup via Sum Rational
102+ deriving Monoid via Sum Rational
103+
104+ newtype PerasVoteStakeDistr = PerasVoteStakeDistr
105+ { unPerasVoteStakeDistr :: Map PerasVoterId PerasVoteStake
106+ }
107+ deriving newtype NoThunks
108+ deriving stock (Show , Eq , Generic )
109+
110+ data PerasVoteTarget blk = PerasVoteTarget
111+ { pvtRoundNo :: ! PerasRoundNo
112+ , pvtBlock :: ! (Point blk )
113+ }
114+ deriving stock (Show , Eq , Ord , Generic )
115+ deriving anyclass NoThunks
116+
117+ data PerasVoteId blk = PerasVoteId
118+ { pviRoundNo :: ! PerasRoundNo
119+ , pviVoterId :: ! PerasVoterId
120+ }
121+ deriving stock (Show , Eq , Ord , Generic )
122+ deriving anyclass NoThunks
123+
124+ -- | Lookup the stake of vote casted by a member of a given stake distribution.
125+ lookupPerasVoteStake ::
126+ PerasVote blk ->
127+ PerasVoteStakeDistr ->
128+ Maybe PerasVoteStake
129+ lookupPerasVoteStake vote distr =
130+ Map. lookup
131+ (pvVoteVoterId vote)
132+ (unPerasVoteStakeDistr distr)
133+
134+ -- ** Validated types
135+
61136data ValidatedPerasCert blk = ValidatedPerasCert
62137 { vpcCert :: ! (PerasCert blk )
63138 , vpcCertBoost :: ! PerasWeight
64139 }
65140 deriving stock (Show , Eq , Ord , Generic )
66141 deriving anyclass NoThunks
67142
143+ data ValidatedPerasVote blk = ValidatedPerasVote
144+ { vpvVote :: ! (PerasVote blk )
145+ , vpvVoteStake :: ! PerasVoteStake
146+ }
147+ deriving stock (Show , Eq , Ord , Generic )
148+ deriving anyclass NoThunks
149+
68150{- ------------------------------------------------------------------------------
69151-- * BlockSupportsPeras class
70152-------------------------------------------------------------------------------}
@@ -79,13 +161,29 @@ class
79161
80162 data PerasCert blk
81163
164+ data PerasVote blk
165+
82166 data PerasValidationErr blk
83167
168+ data PerasForgeErr blk
169+
84170 validatePerasCert ::
85171 PerasCfg blk ->
86172 PerasCert blk ->
87173 Either (PerasValidationErr blk ) (ValidatedPerasCert blk )
88174
175+ validatePerasVote ::
176+ PerasCfg blk ->
177+ PerasVoteStakeDistr ->
178+ PerasVote blk ->
179+ Either (PerasValidationErr blk ) (ValidatedPerasVote blk )
180+
181+ forgePerasCert ::
182+ PerasCfg blk ->
183+ PerasVoteTarget blk ->
184+ [ValidatedPerasVote blk ] ->
185+ Either (PerasForgeErr blk ) (ValidatedPerasCert blk )
186+
89187-- TODO: degenerate instance for all blks to get things to compile
90188-- see https://github.com/tweag/cardano-peras/issues/73
91189instance StandardHash blk => BlockSupportsPeras blk where
@@ -98,12 +196,27 @@ instance StandardHash blk => BlockSupportsPeras blk where
98196 deriving stock (Generic , Eq , Ord , Show )
99197 deriving anyclass NoThunks
100198
199+ data PerasVote blk = PerasVote
200+ { pvVoteRound :: PerasRoundNo
201+ , pvVoteBlock :: Point blk
202+ , pvVoteVoterId :: PerasVoterId
203+ }
204+ deriving stock (Generic , Eq , Ord , Show )
205+ deriving anyclass NoThunks
206+
101207 -- TODO: enrich with actual error types
102208 -- see https://github.com/tweag/cardano-peras/issues/120
103209 data PerasValidationErr blk
104210 = PerasValidationErr
105211 deriving stock (Show , Eq )
106212
213+ -- TODO: enrich with actual error types
214+ -- see https://github.com/tweag/cardano-peras/issues/120
215+ data PerasForgeErr blk
216+ = PerasForgeErrInsufficientVotes
217+ | PerasForgeErrTargetMismatch
218+ deriving stock (Show , Eq )
219+
107220 -- TODO: perform actual validation against all
108221 -- possible 'PerasValidationErr' variants
109222 -- see https://github.com/tweag/cardano-peras/issues/120
@@ -114,9 +227,54 @@ instance StandardHash blk => BlockSupportsPeras blk where
114227 , vpcCertBoost = perasWeight params
115228 }
116229
230+ -- TODO: perform actual validation against all
231+ -- possible 'PerasValidationErr' variants
232+ -- see https://github.com/tweag/cardano-peras/issues/120
233+ validatePerasVote _params stakeDistr vote
234+ | Just stake <- lookupPerasVoteStake vote stakeDistr =
235+ Right
236+ ValidatedPerasVote
237+ { vpvVote = vote
238+ , vpvVoteStake = stake
239+ }
240+ | otherwise =
241+ Left PerasValidationErr
242+
243+ -- TODO: perform actual validation against all
244+ -- possible 'PerasForgeErr' variants
245+ -- see https://github.com/tweag/cardano-peras/issues/120
246+ forgePerasCert params target votes
247+ | not allVotersMatchTarget =
248+ Left PerasForgeErrTargetMismatch
249+ | not votesHaveEnoughStake =
250+ Left PerasForgeErrInsufficientVotes
251+ | otherwise =
252+ return $
253+ ValidatedPerasCert
254+ { vpcCert =
255+ PerasCert
256+ { pcCertRound = pvtRoundNo target
257+ , pcCertBoostedBlock = pvtBlock target
258+ }
259+ , vpcCertBoost = perasWeight params
260+ }
261+ where
262+ totalVotesStake =
263+ mconcat (vpvVoteStake <$> votes)
264+
265+ votesHaveEnoughStake =
266+ unPerasVoteStake totalVotesStake
267+ >= unPerasQuorumStakeThreshold (perasQuorumStakeThreshold params)
268+
269+ allVotersMatchTarget =
270+ all ((target == ) . getPerasVoteTarget) votes
271+
117272instance ShowProxy blk => ShowProxy (PerasCert blk ) where
118273 showProxy _ = " PerasCert " <> showProxy (Proxy @ blk )
119274
275+ instance ShowProxy blk => ShowProxy (PerasVote blk ) where
276+ showProxy _ = " PerasVote " <> showProxy (Proxy @ blk )
277+
120278instance Serialise (HeaderHash blk ) => Serialise (PerasCert blk ) where
121279 encode PerasCert {pcCertRound, pcCertBoostedBlock} =
122280 encodeListLen 2
@@ -128,6 +286,19 @@ instance Serialise (HeaderHash blk) => Serialise (PerasCert blk) where
128286 pcCertBoostedBlock <- decode
129287 pure $ PerasCert {pcCertRound, pcCertBoostedBlock}
130288
289+ instance Serialise (HeaderHash blk ) => Serialise (PerasVote blk ) where
290+ encode PerasVote {pvVoteRound, pvVoteBlock, pvVoteVoterId} =
291+ encodeListLen 3
292+ <> encode pvVoteRound
293+ <> encode pvVoteBlock
294+ <> KeyHash. toCBOR (unPerasVoterId pvVoteVoterId)
295+ decode = do
296+ decodeListLenOf 3
297+ pvVoteRound <- decode
298+ pvVoteBlock <- decode
299+ pvVoteVoterId <- PerasVoterId <$> KeyHash. fromCBOR
300+ pure $ PerasVote {pvVoteRound, pvVoteBlock, pvVoteVoterId}
301+
131302-- | Extract the certificate round from a Peras certificate container
132303class HasPerasCertRound cert where
133304 getPerasCertRound :: cert -> PerasRoundNo
@@ -172,3 +343,104 @@ instance
172343 HasPerasCertBoost (WithArrivalTime cert )
173344 where
174345 getPerasCertBoost = getPerasCertBoost . forgetArrivalTime
346+
347+ -- | Extract the vote round from a Peras vote container
348+ class HasPerasVoteRound vote where
349+ getPerasVoteRound :: vote -> PerasRoundNo
350+
351+ instance HasPerasVoteRound (PerasVote blk ) where
352+ getPerasVoteRound = pvVoteRound
353+
354+ instance HasPerasVoteRound (ValidatedPerasVote blk ) where
355+ getPerasVoteRound = getPerasVoteRound . vpvVote
356+
357+ instance
358+ HasPerasVoteRound vote =>
359+ HasPerasVoteRound (WithArrivalTime vote )
360+ where
361+ getPerasVoteRound = getPerasVoteRound . forgetArrivalTime
362+
363+ -- | Extract the vote block point from a Peras vote container
364+ class HasPerasVoteBlock vote blk | vote -> blk where
365+ getPerasVoteBlock :: vote -> Point blk
366+
367+ instance HasPerasVoteBlock (PerasVote blk ) blk where
368+ getPerasVoteBlock = pvVoteBlock
369+
370+ instance HasPerasVoteBlock (ValidatedPerasVote blk ) blk where
371+ getPerasVoteBlock = getPerasVoteBlock . vpvVote
372+
373+ instance
374+ HasPerasVoteBlock vote blk =>
375+ HasPerasVoteBlock (WithArrivalTime vote ) blk
376+ where
377+ getPerasVoteBlock = getPerasVoteBlock . forgetArrivalTime
378+
379+ -- | Extract the stake pool ID from a Peras vote container
380+ class HasPerasVoteVoterId vote where
381+ getPerasVoteVoterId :: vote -> PerasVoterId
382+
383+ instance HasPerasVoteVoterId (PerasVote blk ) where
384+ getPerasVoteVoterId = pvVoteVoterId
385+
386+ instance HasPerasVoteVoterId (ValidatedPerasVote blk ) where
387+ getPerasVoteVoterId = getPerasVoteVoterId . vpvVote
388+
389+ instance
390+ HasPerasVoteVoterId vote =>
391+ HasPerasVoteVoterId (WithArrivalTime vote )
392+ where
393+ getPerasVoteVoterId = getPerasVoteVoterId . forgetArrivalTime
394+
395+ -- | Extract the vote stake from a validated Peras vote container
396+ class HasPerasVoteStake vote where
397+ getPerasVoteStake :: vote -> PerasVoteStake
398+
399+ instance HasPerasVoteStake (ValidatedPerasVote blk ) where
400+ getPerasVoteStake = vpvVoteStake
401+
402+ instance
403+ HasPerasVoteStake vote =>
404+ HasPerasVoteStake (WithArrivalTime vote )
405+ where
406+ getPerasVoteStake = getPerasVoteStake . forgetArrivalTime
407+
408+ -- | Extract the vote target from a Peras vote container
409+ class HasPerasVoteTarget vote blk | vote -> blk where
410+ getPerasVoteTarget :: vote -> PerasVoteTarget blk
411+
412+ instance HasPerasVoteTarget (PerasVote blk ) blk where
413+ getPerasVoteTarget vote =
414+ PerasVoteTarget
415+ { pvtRoundNo = pvVoteRound vote
416+ , pvtBlock = pvVoteBlock vote
417+ }
418+
419+ instance HasPerasVoteTarget (ValidatedPerasVote blk ) blk where
420+ getPerasVoteTarget = getPerasVoteTarget . vpvVote
421+
422+ instance
423+ HasPerasVoteTarget vote blk =>
424+ HasPerasVoteTarget (WithArrivalTime vote ) blk
425+ where
426+ getPerasVoteTarget = getPerasVoteTarget . forgetArrivalTime
427+
428+ -- | Extract the vote ID from a Peras vote container
429+ class HasPerasVoteId vote blk | vote -> blk where
430+ getPerasVoteId :: vote -> PerasVoteId blk
431+
432+ instance HasPerasVoteId (PerasVote blk ) blk where
433+ getPerasVoteId vote =
434+ PerasVoteId
435+ { pviRoundNo = pvVoteRound vote
436+ , pviVoterId = pvVoteVoterId vote
437+ }
438+
439+ instance HasPerasVoteId (ValidatedPerasVote blk ) blk where
440+ getPerasVoteId = getPerasVoteId . vpvVote
441+
442+ instance
443+ HasPerasVoteId vote blk =>
444+ HasPerasVoteId (WithArrivalTime vote ) blk
445+ where
446+ getPerasVoteId = getPerasVoteId . forgetArrivalTime
0 commit comments