@@ -27,12 +27,6 @@ module Ouroboros.Consensus.Block.SupportsPeras
2727 , PerasCfg (.. )
2828 , ValidatedPerasCert (.. )
2929 , ValidatedPerasVote (.. )
30- , PerasVoteAggregate (.. )
31- , PerasVoteAggregateStatus (.. )
32- , pvasMaybeCert
33- , emptyPerasVoteAggregateStatus
34- , updatePerasVoteAggregate
35- , updatePerasVoteAggregateStatus
3630 , makePerasCfg
3731 , HasId (.. )
3832 , HasPerasCertRound (.. )
@@ -132,6 +126,11 @@ instance Condense PerasWeight where
132126boostPerCert :: PerasWeight
133127boostPerCert = PerasWeight 15
134128
129+ -- | TODO: this may become a Ledger protocol parameter
130+ -- see https://github.com/tweag/cardano-peras/issues/119
131+ quorumThreshold :: PerasVoteStake
132+ quorumThreshold = PerasVoteStake 0.75
133+
135134-- TODO using 'Validated' for extra safety? Or some @.Unsafe@ module?
136135data ValidatedPerasCert blk = ValidatedPerasCert
137136 { vpcCert :: ! (PerasCert blk )
@@ -202,6 +201,8 @@ class
202201
203202 data PerasValidationErr blk
204203
204+ data PerasForgeErr blk
205+
205206 validatePerasCert ::
206207 PerasCfg blk ->
207208 PerasCert blk ->
@@ -213,105 +214,23 @@ class
213214 PerasVoteStakeDistr ->
214215 Either (PerasValidationErr blk ) (ValidatedPerasVote blk )
215216
216- const_PERAS_QUORUM_THRESHOLD :: PerasVoteStake
217- const_PERAS_QUORUM_THRESHOLD = PerasVoteStake 0.75
218-
219- data PerasVoteAggregate blk = PerasVoteAggregate
220- { pvaTarget :: ! (PerasVoteTarget blk )
221- , pvaVotes :: ! (Map (IdOf (PerasVote blk )) (WithArrivalTime (ValidatedPerasVote blk )))
222- , pvaTotalStake :: ! PerasVoteStake
223- }
224- deriving stock (Generic , Eq , Ord , Show )
225- deriving anyclass NoThunks
226-
227- data PerasVoteAggregateStatus blk
228- = PerasVoteAggregateQuorumNotReached { pvasVoteAggregate :: ! (PerasVoteAggregate blk )}
229- | PerasVoteAggregateQuorumReachedAlready
230- { pvasVoteAggregate :: ! (PerasVoteAggregate blk ), pvasCert :: ValidatedPerasCert blk }
231- deriving stock (Generic , Eq , Ord , Show )
232- deriving anyclass NoThunks
233-
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.
251- updatePerasVoteAggregate ::
252- StandardHash blk =>
253- PerasVoteAggregate blk ->
254- WithArrivalTime (ValidatedPerasVote blk ) ->
255- PerasVoteAggregate blk
256- updatePerasVoteAggregate
257- pva@ PerasVoteAggregate
258- { pvaTarget = (roundNo, point)
259- , pvaVotes = existingVotes
260- , pvaTotalStake = initialStake
261- }
262- vote =
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- }
217+ forgePerasCert ::
218+ PerasCfg blk ->
219+ PerasVoteTarget blk ->
220+ [ValidatedPerasVote blk ] ->
221+ Either (PerasForgeErr blk ) (ValidatedPerasCert blk )
304222
305223type PerasVoteTarget blk = (PerasRoundNo , Point blk )
306224
307225-- TODO: degenerate instance for all blks to get things to compile
308226-- see https://github.com/tweag/cardano-peras/issues/73
309227instance StandardHash blk => BlockSupportsPeras blk where
310- newtype PerasCfg blk = PerasCfg
228+ data PerasCfg blk = PerasCfg
311229 { -- TODO: eventually, this will come from the
312230 -- protocol parameters from the ledger state
313231 -- see https://github.com/tweag/cardano-peras/issues/119
314232 perasCfgWeightBoost :: PerasWeight
233+ , perasCfgQuorumThreshold :: PerasVoteStake
315234 }
316235 deriving stock (Show , Eq )
317236
@@ -336,6 +255,11 @@ instance StandardHash blk => BlockSupportsPeras blk where
336255 = PerasValidationErr
337256 deriving stock (Show , Eq )
338257
258+ data PerasForgeErr blk
259+ = PerasForgeErrMismatchedTarget
260+ | PerasForgeErrInsufficientVotes
261+ deriving stock (Show , Eq )
262+
339263 -- TODO: perform actual validation against all
340264 -- possible 'PerasValidationErr' variants
341265 -- see https://github.com/tweag/cardano-peras/issues/120
@@ -350,6 +274,27 @@ instance StandardHash blk => BlockSupportsPeras blk where
350274 let stake = getPerasVoteStakeOf stakeDistr (pvVoteVoterId vote)
351275 in Right (ValidatedPerasVote {vpvVote = vote, vpvVoteStake = stake})
352276
277+ forgePerasCert cfg target votes =
278+ let allMatchTarget = all (\ v -> getPerasVoteTarget v == target) votes
279+ hasSufficientStake =
280+ let totalStake = mconcat (map vpvVoteStake votes)
281+ in totalStake >= perasCfgQuorumThreshold cfg
282+ in if not allMatchTarget
283+ then Left PerasForgeErrMismatchedTarget
284+ else
285+ if not hasSufficientStake
286+ then Left PerasForgeErrInsufficientVotes
287+ else
288+ Right
289+ ValidatedPerasCert
290+ { vpcCert =
291+ PerasCert
292+ { pcCertRound = fst target
293+ , pcCertBoostedBlock = snd target
294+ }
295+ , vpcCertBoost = perasCfgWeightBoost cfg
296+ }
297+
353298instance HasId (PerasCert blk ) where
354299 type IdOf (PerasCert blk ) = PerasRoundNo
355300 getId = pcCertRound
@@ -402,6 +347,7 @@ makePerasCfg :: Maybe (BlockConfig blk) -> PerasCfg blk
402347makePerasCfg _ =
403348 PerasCfg
404349 { perasCfgWeightBoost = boostPerCert
350+ , perasCfgQuorumThreshold = quorumThreshold
405351 }
406352
407353-- | Extract the certificate round from a Peras certificate container
0 commit comments