Skip to content

Commit b57fa4b

Browse files
committed
Change PerasVoteAggregate{,Status} datatype to a GADT instead of a record
1 parent b5e6c96 commit b57fa4b

File tree

3 files changed

+84
-55
lines changed

3 files changed

+84
-55
lines changed

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

Lines changed: 72 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
{-# LANGUAGE TypeFamilies #-}
1313
{-# LANGUAGE UndecidableInstances #-}
1414
{-# OPTIONS_GHC -Wno-orphans #-}
15+
{-# OPTIONS_GHC -Wno-partial-fields #-}
16+
{-# OPTIONS_GHC -Wno-unused-imports #-}
1517

1618
module Ouroboros.Consensus.Block.SupportsPeras
1719
( PerasRoundNo (..)
@@ -26,9 +28,11 @@ module Ouroboros.Consensus.Block.SupportsPeras
2628
, ValidatedPerasCert (..)
2729
, ValidatedPerasVote (..)
2830
, PerasVoteAggregate (..)
29-
, emptyPerasVoteAggregate
31+
, PerasVoteAggregateStatus (..)
32+
, pvasMaybeCert
33+
, emptyPerasVoteAggregateStatus
3034
, updatePerasVoteAggregate
31-
, UpdatePerasVoteAggregateResult (..)
35+
, updatePerasVoteAggregateStatus
3236
, makePerasCfg
3337
, HasId (..)
3438
, HasPerasCertRound (..)
@@ -51,6 +55,8 @@ import Codec.Serialise.Decoding (decodeListLenOf)
5155
import Codec.Serialise.Encoding (encodeListLen)
5256
import Control.Applicative ((<|>))
5357
import Data.Coerce (coerce)
58+
import Data.Map.Strict (Map)
59+
import qualified Data.Map.Strict as Map
5460
import Data.Maybe (isNothing)
5561
import Data.Monoid (Sum (..))
5662
import Data.Proxy (Proxy (..))
@@ -212,69 +218,89 @@ const_PERAS_QUORUM_THRESHOLD = PerasVoteStake 0.75
212218

213219
data PerasVoteAggregate blk = PerasVoteAggregate
214220
{ pvaTarget :: !(PerasVoteTarget blk)
215-
, pvaVotes :: !(Set (WithArrivalTime (ValidatedPerasVote blk)))
221+
, pvaVotes :: !(Map (IdOf (PerasVote blk)) (WithArrivalTime (ValidatedPerasVote blk)))
216222
, pvaTotalStake :: !PerasVoteStake
217-
, pvaMaybeCert :: !(Maybe (ValidatedPerasCert blk))
218223
}
219224
deriving stock (Generic, Eq, Ord, Show)
220225
deriving anyclass NoThunks
221226

222-
emptyPerasVoteAggregate :: PerasVoteTarget blk -> PerasVoteAggregate blk
223-
emptyPerasVoteAggregate target =
224-
PerasVoteAggregate
225-
{ pvaTotalStake = PerasVoteStake 0
226-
, pvaTarget = target
227-
, pvaVotes = Set.empty
228-
, pvaMaybeCert = Nothing
229-
}
230-
231-
data UpdatePerasVoteAggregateResult blk
232-
= IncorrectPerasVoteTarget
233-
| AddedPerasVoteButDidntGenerateNewCert (PerasVoteAggregate blk)
234-
| AddedPerasVoteAndGeneratedNewCert (PerasVoteAggregate blk) (ValidatedPerasCert blk)
227+
data PerasVoteAggregateStatus blk
228+
= PerasVoteAggregateQuorumNotReached {pvasVoteAggregate :: !(PerasVoteAggregate blk)}
229+
| PerasVoteAggregateQuorumReachedAlready
230+
{pvasVoteAggregate :: !(PerasVoteAggregate blk), pvasCert :: ValidatedPerasCert blk}
235231
deriving stock (Generic, Eq, Ord, Show)
236232
deriving anyclass NoThunks
237233

234+
pvasMaybeCert :: PerasVoteAggregateStatus blk -> Maybe (ValidatedPerasCert blk)
235+
pvasMaybeCert aggr = case aggr of
236+
PerasVoteAggregateQuorumNotReached{} -> Nothing
237+
PerasVoteAggregateQuorumReachedAlready{pvasCert} -> Just pvasCert
238+
239+
emptyPerasVoteAggregateStatus :: PerasVoteTarget blk -> PerasVoteAggregateStatus blk
240+
emptyPerasVoteAggregateStatus target =
241+
PerasVoteAggregateQuorumNotReached $
242+
PerasVoteAggregate
243+
{ pvaTotalStake = PerasVoteStake 0
244+
, pvaTarget = target
245+
, pvaVotes = Map.empty
246+
}
247+
248+
-- | Add a vote to an existing aggregate if it isn't already present, and update
249+
-- the stake accordingly.
250+
-- PRECONDITION: the vote's target must match the aggregate's target.
238251
updatePerasVoteAggregate ::
239252
StandardHash blk =>
240253
PerasVoteAggregate blk ->
241254
WithArrivalTime (ValidatedPerasVote blk) ->
242-
UpdatePerasVoteAggregateResult blk
255+
PerasVoteAggregate blk
243256
updatePerasVoteAggregate
244257
pva@PerasVoteAggregate
245258
{ pvaTarget = (roundNo, point)
246259
, pvaVotes = existingVotes
247260
, pvaTotalStake = initialStake
248-
, pvaMaybeCert = mExistingCert
249261
}
250262
vote =
251-
if getPerasVoteRound vote == roundNo && getPerasVoteVotedBlock vote == point
252-
then
253-
let pvaTotalStake = initialStake + vpvVoteStake (forgetArrivalTime vote)
254-
pvaVotes = Set.insert vote existingVotes
255-
mNewCert =
256-
if isNothing mExistingCert && pvaTotalStake >= const_PERAS_QUORUM_THRESHOLD
257-
then
258-
Just $
259-
ValidatedPerasCert
260-
{ vpcCertBoost = boostPerCert
261-
, vpcCert =
262-
PerasCert
263-
{ pcCertRound = roundNo
264-
, pcCertBoostedBlock = point
265-
}
266-
}
267-
else Nothing
268-
pva' =
269-
pva
270-
{ pvaVotes
271-
, pvaTotalStake
272-
, pvaMaybeCert = mExistingCert <|> mNewCert
273-
}
274-
in case mNewCert of
275-
Just cert -> AddedPerasVoteAndGeneratedNewCert pva' cert
276-
Nothing -> AddedPerasVoteButDidntGenerateNewCert pva'
277-
else IncorrectPerasVoteTarget
263+
if not (getPerasVoteRound vote == roundNo && getPerasVoteVotedBlock vote == point)
264+
then error "updatePerasVoteAggregate: vote target does not match aggregate target"
265+
else
266+
let (pvaVotes', pvaTotalStake') =
267+
case Map.insertLookupWithKey
268+
(\_k old _new -> old)
269+
(getId vote)
270+
vote
271+
existingVotes of
272+
(Nothing, votes') ->
273+
-- key was NOT present → inserted and stake updated
274+
(votes', initialStake + vpvVoteStake (forgetArrivalTime vote))
275+
(Just _, _) ->
276+
-- key WAS already present → votes and stake unchanged
277+
(existingVotes, initialStake)
278+
in pva{pvaVotes = pvaVotes', pvaTotalStake = pvaTotalStake'}
279+
280+
updatePerasVoteAggregateStatus ::
281+
StandardHash blk =>
282+
PerasVoteAggregateStatus blk ->
283+
WithArrivalTime (ValidatedPerasVote blk) ->
284+
PerasVoteAggregateStatus blk
285+
updatePerasVoteAggregateStatus aggr vote = case aggr of
286+
PerasVoteAggregateQuorumNotReached{pvasVoteAggregate} ->
287+
let aggr' = updatePerasVoteAggregate pvasVoteAggregate vote
288+
in if pvaTotalStake aggr' >= const_PERAS_QUORUM_THRESHOLD
289+
then
290+
PerasVoteAggregateQuorumReachedAlready
291+
{ pvasVoteAggregate = aggr'
292+
, pvasCert =
293+
ValidatedPerasCert
294+
{ vpcCertBoost = boostPerCert
295+
, vpcCert = uncurry PerasCert (pvaTarget aggr')
296+
}
297+
}
298+
else PerasVoteAggregateQuorumNotReached{pvasVoteAggregate = aggr'}
299+
PerasVoteAggregateQuorumReachedAlready{pvasVoteAggregate, pvasCert} ->
300+
PerasVoteAggregateQuorumReachedAlready
301+
{ pvasVoteAggregate = updatePerasVoteAggregate pvasVoteAggregate vote
302+
, pvasCert
303+
}
278304

279305
type PerasVoteTarget blk = (PerasRoundNo, Point blk)
280306

ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Storage/PerasVoteDB/API.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ data AddPerasVoteResult blk
5656
deriving anyclass NoThunks
5757

5858
newtype PerasStakeSnapshot blk = PerasStakeSnapshot
59-
{unPerasStakeSnapshot :: WithFingerprint (Map (PerasVoteTarget blk) (PerasVoteAggregate blk))}
59+
{unPerasStakeSnapshot :: WithFingerprint (Map (PerasVoteTarget blk) (PerasVoteAggregateStatus blk))}
6060
deriving Generic
6161
deriving newtype NoThunks
6262

@@ -65,7 +65,7 @@ getPerasCertsFromStakeSnapshot ::
6565
PerasStakeSnapshot blk ->
6666
Set (ValidatedPerasCert blk)
6767
getPerasCertsFromStakeSnapshot (PerasStakeSnapshot mp) =
68-
Set.fromList $ Map.elems $ Map.mapMaybe pvaMaybeCert (forgetFingerprint mp)
68+
Set.fromList $ Map.elems $ Map.mapMaybe pvasMaybeCert (forgetFingerprint mp)
6969

7070
data PerasVoteSnapshot blk = PerasVoteSnapshot
7171
{ containsVote :: IdOf (PerasVote blk) -> Bool

ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Storage/PerasVoteDB/Impl.hs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import Data.Set (Set)
3636
import Data.Set qualified as Set
3737
import GHC.Generics (Generic)
3838
import NoThunks.Class
39-
import Ouroboros.Consensus.Block hiding (UpdatePerasVoteAggregateResult (..))
39+
import Ouroboros.Consensus.Block
4040
import Ouroboros.Consensus.Block.SupportsPeras qualified as UPVAR
4141
import Ouroboros.Consensus.BlockchainTime (WithArrivalTime (forgetArrivalTime))
4242
import Ouroboros.Consensus.Peras.Weight
@@ -182,11 +182,14 @@ implAddVote env vote = do
182182
(res, pvsVotesByTarget') =
183183
Map.alterF
184184
( \mExistingAggr ->
185-
let aggr = fromMaybe (emptyPerasVoteAggregate voteTarget) mExistingAggr
186-
in case updatePerasVoteAggregate aggr vote of
187-
UPVAR.IncorrectPerasVoteTarget -> error "The aggregate should match the vote target."
188-
UPVAR.AddedPerasVoteButDidntGenerateNewCert aggr' -> (AddedPerasVoteButDidntGenerateNewCert, Just aggr')
189-
UPVAR.AddedPerasVoteAndGeneratedNewCert aggr' cert -> (AddedPerasVoteAndGeneratedNewCert cert, Just aggr')
185+
let aggr = fromMaybe (emptyPerasVoteAggregateStatus voteTarget) mExistingAggr
186+
aggr' = updatePerasVoteAggregateStatus aggr vote
187+
in case (aggr, aggr') of
188+
-- if we observe a state transition, it means a certificate was emitted
189+
(PerasVoteAggregateQuorumNotReached{}, PerasVoteAggregateQuorumReachedAlready{pvasCert}) ->
190+
(AddedPerasVoteAndGeneratedNewCert pvasCert, Just aggr')
191+
_ ->
192+
(AddedPerasVoteButDidntGenerateNewCert, Just aggr')
190193
)
191194
voteTarget
192195
pvsVotesByTarget
@@ -281,7 +284,7 @@ implGarbageCollect PerasVoteDbEnv{pvdbPerasVoteStateVar} roundNo =
281284

282285
data PerasVoteState blk = PerasVoteState
283286
{ pvsVoteIds :: !(Set (IdOf (PerasVote blk)))
284-
, pvsVotesByTarget :: !(Map (PerasVoteTarget blk) (PerasVoteAggregate blk))
287+
, pvsVotesByTarget :: !(Map (PerasVoteTarget blk) (PerasVoteAggregateStatus blk))
285288
, pvsVotesByTicket :: !(Map PerasVoteTicketNo (WithArrivalTime (ValidatedPerasVote blk)))
286289
-- ^ The votes by 'PerasVoteTicketNo'.
287290
--

0 commit comments

Comments
 (0)