Skip to content

Commit e7580bc

Browse files
agustinmistaamesgengeo2atbagrel1nbacquey
committed
Introduce Peras votes and certificate forging API
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>
1 parent bb97f3f commit e7580bc

File tree

3 files changed

+304
-1
lines changed

3 files changed

+304
-1
lines changed

ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Block/SupportsPeras.hs

Lines changed: 273 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
{-# LANGUAGE DataKinds #-}
12
{-# LANGUAGE DeriveAnyClass #-}
23
{-# LANGUAGE DeriveGeneric #-}
34
{-# LANGUAGE DerivingVia #-}
@@ -14,21 +15,40 @@
1415
module 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 (..))
2845
import Codec.Serialise (Serialise (..))
2946
import Codec.Serialise.Decoding (decodeListLenOf)
3047
import Codec.Serialise.Encoding (encodeListLen)
3148
import Data.Coerce (coerce)
49+
import qualified Data.Map as Map
50+
import Data.Map.Strict (Map)
51+
import Data.Monoid (Sum (..))
3252
import Data.Proxy (Proxy (..))
3353
import Data.Word (Word64)
3454
import GHC.Generics (Generic)
@@ -40,6 +60,12 @@ import Ouroboros.Consensus.Util
4060
import Ouroboros.Consensus.Util.Condense
4161
import Quiet (Quiet (..))
4262

63+
{-------------------------------------------------------------------------------
64+
-- * Peras types
65+
-------------------------------------------------------------------------------}
66+
67+
-- ** Round numbers
68+
4369
newtype PerasRoundNo = PerasRoundNo {unPerasRoundNo :: Word64}
4470
deriving Show via Quiet PerasRoundNo
4571
deriving stock Generic
@@ -57,14 +83,70 @@ onPerasRoundNo ::
5783
(PerasRoundNo -> PerasRoundNo -> PerasRoundNo)
5884
onPerasRoundNo = 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+
61136
data 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
91189
instance 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+
117272
instance 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+
120278
instance 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
132303
class 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

Comments
 (0)