Skip to content

Commit dfc7006

Browse files
authored
Merge pull request #4604 from IntersectMBO/lehins/fix-GovInfoEvent
Fix enacted `Set` in `GovInfoEvent`
2 parents fae3725 + 3d83339 commit dfc7006

File tree

12 files changed

+214
-95
lines changed

12 files changed

+214
-95
lines changed

eras/conway/impl/CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Version history for `cardano-ledger-conway`
22

33
## 1.17.0.0
4+
45
* Changed `ConwayWdrlNotDelegatedToDRep` to wrap `NonEmpty`
56
* Add `showGovActionType`, `acceptedByEveryone`
67
* Added `unRatifySignal`
@@ -12,6 +13,7 @@
1213
* `reDRepStateL`
1314
* `reCurrentEpochL`
1415
* `reCommitteeStateL`
16+
* Add a new field to `GovInfoEvent`
1517

1618
### `testlib`
1719

eras/conway/impl/cardano-ledger-conway.cabal

+9-9
Original file line numberDiff line numberDiff line change
@@ -141,29 +141,29 @@ library testlib
141141
build-depends:
142142
base,
143143
bytestring,
144-
cardano-data:{cardano-data, testlib},
145-
containers,
146-
cuddle >=0.3.0.0,
147-
plutus-ledger-api,
148-
deepseq,
149-
microlens,
150144
cardano-crypto-class,
145+
cardano-data:{cardano-data, testlib},
151146
cardano-ledger-allegra,
152147
cardano-ledger-alonzo:{cardano-ledger-alonzo, testlib},
153-
cardano-ledger-binary,
148+
cardano-ledger-binary:{cardano-ledger-binary, testlib},
154149
cardano-ledger-babbage:{cardano-ledger-babbage, testlib} >=1.8.2,
155150
cardano-ledger-conway,
156151
cardano-ledger-core:{cardano-ledger-core, testlib},
157152
cardano-ledger-mary,
158153
cardano-ledger-shelley,
159154
cardano-strict-containers,
155+
containers,
156+
cuddle >=0.3.0.0,
160157
data-default-class,
158+
deepseq,
161159
FailT,
162160
generic-random,
161+
microlens,
163162
microlens-mtl,
164163
mtl,
165-
text,
166-
small-steps >=1.1
164+
plutus-ledger-api,
165+
small-steps >=1.1,
166+
text
167167

168168
executable huddle-cddl
169169
main-is: Main.hs

eras/conway/impl/src/Cardano/Ledger/Conway/Governance/Proposals.hs

+13-8
Original file line numberDiff line numberDiff line change
@@ -449,35 +449,39 @@ proposalsApplyEnactment ::
449449
Set (GovActionId (EraCrypto era)) ->
450450
Proposals era ->
451451
( Proposals era
452+
, Map (GovActionId (EraCrypto era)) (GovActionState era) -- Enacted actions
452453
, Map (GovActionId (EraCrypto era)) (GovActionState era) -- Removed due to enactment
453454
, Map (GovActionId (EraCrypto era)) (GovActionState era) -- Removed due to expiry
454455
)
455456
proposalsApplyEnactment enactedGass expiredGais props =
456457
let (unexpiredProposals, expiredRemoved) = proposalsRemoveWithDescendants expiredGais props
457-
(enactedProposalsState, removedDueToEnactment) =
458-
F.foldl' enact (unexpiredProposals, Map.empty) enactedGass
459-
in (enactedProposalsState, removedDueToEnactment, expiredRemoved)
458+
(enactedProposalsState, enacted, removedDueToEnactment) =
459+
F.foldl' enact (unexpiredProposals, Map.empty, Map.empty) enactedGass
460+
in (enactedProposalsState, enacted, removedDueToEnactment, expiredRemoved)
460461
where
461-
enact (!ps, !removed) gas = withGovActionParent gas enactWithoutRoot enactFromRoot
462+
enact (!ps, !enacted, !removed) gas = withGovActionParent gas enactWithoutRoot enactFromRoot
462463
where
463464
gai = gas ^. gasIdL
464465
enactWithoutRoot ::
465466
( Proposals era
466467
, Map (GovActionId (EraCrypto era)) (GovActionState era)
468+
, Map (GovActionId (EraCrypto era)) (GovActionState era)
467469
)
468470
enactWithoutRoot =
469-
let (newOMap, removedActions) = OMap.extractKeys (Set.singleton gai) $ ps ^. pPropsL
471+
let (newOMap, enactedAction) = OMap.extractKeys (Set.singleton gai) $ ps ^. pPropsL
470472
in assert -- we want an AssertionFailure here for exhaustive property-testing
471-
(not $ Map.null removedActions)
473+
(not $ Map.null enactedAction)
472474
( ps & pPropsL .~ newOMap
473-
, removed `Map.union` removedActions
475+
, enacted `Map.union` enactedAction
476+
, removed
474477
)
475478
enactFromRoot ::
476479
(forall f. Lens' (GovRelation f era) (f (GovPurposeId p era))) ->
477480
StrictMaybe (GovPurposeId p era) ->
478481
GovPurposeId p era ->
479482
( Proposals era
480483
, Map (GovActionId (EraCrypto era)) (GovActionState era)
484+
, Map (GovActionId (EraCrypto era)) (GovActionState era)
481485
)
482486
enactFromRoot govRelationL parent gpi =
483487
let siblings =
@@ -500,7 +504,8 @@ proposalsApplyEnactment enactedGass expiredGais props =
500504
in assert
501505
(ps ^. pRootsL . govRelationL . prRootL == parent)
502506
( checkInvariantAfterDeletion (Set.singleton gai) withoutSiblings newProposals
503-
, removed `Map.union` removedActions `Map.union` enactedAction
507+
, enacted `Map.union` enactedAction
508+
, removed `Map.union` removedActions
504509
)
505510

506511
-- | Get the sequence of `GovActionState`s

eras/conway/impl/src/Cardano/Ledger/Conway/Rules/Epoch.hs

+12-5
Original file line numberDiff line numberDiff line change
@@ -131,9 +131,15 @@ data ConwayEpochEvent era
131131
| SnapEvent (Event (EraRule "SNAP" era))
132132
| EpochBoundaryRatifyState (RatifyState era)
133133
| GovInfoEvent
134-
(Set (GovActionState era)) -- enacted
135-
(Set (GovActionState era)) -- expired
136-
(Set (GovActionId (EraCrypto era))) -- unclaimed
134+
-- | Enacted actions
135+
(Set (GovActionState era))
136+
-- | Actions that were removed as conflicting due to enactment
137+
(Set (GovActionState era))
138+
-- | Actions that were removed due to expiration together with their dependees
139+
(Set (GovActionState era))
140+
-- | Ids of removed governance actions that had an unregistered reward account, thus
141+
-- leading to unclaimed deposits being transfered to the treasury.
142+
(Set (GovActionId (EraCrypto era)))
137143
deriving (Generic)
138144

139145
type instance EraRuleEvent "EPOCH" (ConwayEra c) = ConwayEpochEvent (ConwayEra c)
@@ -325,7 +331,7 @@ epochTransition = do
325331
-- enacted actions and their sibling subtrees, as well as expired
326332
-- actions and their subtrees, removed, and with all the votes
327333
-- intact for the rest of them.
328-
(newProposals, enactedActions, expiredActions) =
334+
(newProposals, enactedActions, removedDueToEnactment, expiredActions) =
329335
proposalsApplyEnactment rsEnacted rsExpired (govState0 ^. proposalsGovStateL)
330336

331337
-- Apply the values from the computed EnactState to the GovState
@@ -338,14 +344,15 @@ epochTransition = do
338344
& cgsPrevPParamsL .~ curPParams
339345
& cgsFuturePParamsL .~ PotentialPParamsUpdate Nothing
340346

341-
allRemovedGovActions = expiredActions `Map.union` enactedActions
347+
allRemovedGovActions = Map.unions [expiredActions, enactedActions, removedDueToEnactment]
342348
(newUMap, unclaimed) =
343349
returnProposalDeposits allRemovedGovActions $
344350
dState2 ^. dsUnifiedL
345351

346352
tellEvent $
347353
GovInfoEvent
348354
(Set.fromList $ Map.elems enactedActions)
355+
(Set.fromList $ Map.elems removedDueToEnactment)
349356
(Set.fromList $ Map.elems expiredActions)
350357
(Map.keysSet unclaimed)
351358

eras/conway/impl/testlib/Test/Cardano/Ledger/Conway/Arbitrary.hs

+33-7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
{-# LANGUAGE BangPatterns #-}
12
{-# LANGUAGE DataKinds #-}
23
{-# LANGUAGE FlexibleContexts #-}
34
{-# LANGUAGE FlexibleInstances #-}
@@ -69,7 +70,9 @@ import Test.Cardano.Data (genNonEmptyMap)
6970
import Test.Cardano.Data.Arbitrary ()
7071
import Test.Cardano.Ledger.Alonzo.Arbitrary (genValidAndUnknownCostModels, genValidCostModel)
7172
import Test.Cardano.Ledger.Babbage.Arbitrary ()
73+
import Test.Cardano.Ledger.Binary.Random (QC (..))
7274
import Test.Cardano.Ledger.Common
75+
import Test.Cardano.Ledger.Core.Arbitrary (uniformSubMap)
7376

7477
instance
7578
(Era era, Arbitrary (PParamsUpdate era)) =>
@@ -248,9 +251,12 @@ instance
248251

249252
data ProposalsForEnactment era
250253
= ProposalsForEnactment
251-
(Proposals era)
252-
(Seq.Seq (GovActionState era))
253-
(Set.Set (GovActionId (EraCrypto era)))
254+
{ pfeProposals :: Proposals era
255+
, pfeToEnact :: Seq.Seq (GovActionState era)
256+
, pfeToRemove :: Set.Set (GovActionId (EraCrypto era))
257+
, pfeToRetain :: Set.Set (GovActionId (EraCrypto era))
258+
-- ^ Those that are retained can only be the ones that don't have any lineage
259+
}
254260
deriving (Show, Eq)
255261

256262
instance
@@ -259,22 +265,41 @@ instance
259265
where
260266
arbitrary = do
261267
ps <- genProposals @era (2, 50)
268+
let gasHasNoLineage gas =
269+
case gasAction gas of
270+
InfoAction {} -> True
271+
TreasuryWithdrawals {} -> True
272+
_ -> False
273+
hasNoLineage gaId =
274+
case proposalsLookupId gaId ps of
275+
Nothing -> error $ "Expected " ++ show gaId ++ " in generated proposals"
276+
Just gas -> gasHasNoLineage gas
262277
pparamUpdates <- chooseLineage grPParamUpdateL ps Seq.Empty
263278
hardForks <- chooseLineage grHardForkL ps Seq.Empty
264279
committees <- chooseLineage grCommitteeL ps Seq.Empty
265280
constitutions <- chooseLineage grConstitutionL ps Seq.Empty
281+
noLineageMap <- uniformSubMap Nothing (Map.filter gasHasNoLineage $ proposalsActionsMap ps) QC
282+
noLineage <- Seq.fromList <$> shuffle (Map.elems noLineageMap)
266283
sequencedGass <-
267284
sequenceLineages
268285
( Seq.filter
269286
(not . Seq.null)
270-
(Seq.fromList [pparamUpdates, hardForks, committees, constitutions])
287+
(Seq.fromList [pparamUpdates, hardForks, committees, constitutions, noLineage])
271288
)
272289
Seq.Empty
273-
let expiredGais =
290+
let notEnacted =
274291
Set.fromList (toList $ proposalsIds ps)
275292
`Set.difference` Set.fromList (gasId <$> toList sequencedGass)
276-
pure $ ProposalsForEnactment ps sequencedGass expiredGais
293+
let (retained, removedDueToConflict) = Set.partition hasNoLineage notEnacted
294+
pure $
295+
ProposalsForEnactment
296+
{ pfeProposals = ps
297+
, pfeToEnact = sequencedGass
298+
, pfeToRemove = removedDueToConflict
299+
, pfeToRetain = retained
300+
}
277301
where
302+
-- Starting from the root select a path in the tree until a leaf is reached.
278303
chooseLineage ::
279304
(forall f. Lens' (GovRelation f era) (f (GovPurposeId p era))) ->
280305
Proposals era ->
@@ -297,6 +322,7 @@ instance
297322
chooseLineage govRelL ps (lineage Seq.:|> (proposalsActionsMap ps Map.! unGovPurposeId child))
298323
consumeHeadAtIndex :: Int -> Seq.Seq (Seq.Seq a) -> (a, Seq.Seq (Seq.Seq a))
299324
consumeHeadAtIndex idx ss = (ss `Seq.index` idx `Seq.index` 0, Seq.adjust' (Seq.drop 1) idx ss)
325+
-- Mix lineages at random, while preserving relative order of each lineage
300326
sequenceLineages :: Seq.Seq (Seq.Seq a) -> Seq.Seq a -> Gen (Seq.Seq a)
301327
sequenceLineages lineages sequenced = case lineages of
302328
Seq.Empty -> pure sequenced
@@ -338,7 +364,7 @@ genProposals range = do
338364
go (def & pRootsL .~ fromPrevGovActionIds pgais) i
339365
where
340366
go :: Proposals era -> Int -> Gen (Proposals era)
341-
go ps n
367+
go !ps n
342368
| n <= 0 = pure ps
343369
| otherwise = do
344370
gas <- genGovActionState @era =<< genGovAction ps

eras/conway/impl/testlib/Test/Cardano/Ledger/Conway/Imp/EpochSpec.hs

+12-7
Original file line numberDiff line numberDiff line change
@@ -490,12 +490,15 @@ eventsSpec = describe "Events" $ do
490490
whenPostBootstrap (modifyPParams $ ppDRepVotingThresholdsL . dvtPPEconomicGroupL .~ def)
491491
propDeposit <- getsNES $ nesEsL . curPParamsEpochStateL . ppGovActionDepositL
492492
let
493-
proposeCostModel = do
493+
proposeParameterChange = do
494494
newVal <- arbitrary
495-
submitParameterChange SNothing $ def & ppuCoinsPerUTxOByteL .~ SJust newVal
496-
proposalA <- impAnn "proposalA" proposeCostModel
497-
proposalB <- impAnn "proposalB" proposeCostModel
495+
proposal <- submitParameterChange SNothing $ def & ppuCoinsPerUTxOByteL .~ SJust newVal
496+
pure
497+
(proposal, (getsNES $ nesEsL . curPParamsEpochStateL . ppCoinsPerUTxOByteL) `shouldReturn` newVal)
498+
(proposalA, checkProposedParameterA) <- proposeParameterChange
499+
(proposalB, _) <- proposeParameterChange
498500
rewardAccount@(RewardAccount _ rewardCred) <- registerRewardAccount
501+
passEpoch -- prevent proposalC expiry and force it's deletion due to conflit.
499502
proposalC <- impAnn "proposalC" $ do
500503
newVal <- arbitrary
501504
submitProposal
@@ -517,9 +520,9 @@ eventsSpec = describe "Events" $ do
517520
(_, evs) <- listen passEpoch
518521
filter isGovInfoEvent evs
519522
`shouldBeExpr` [ SomeSTSEvent @era @"TICK" . injectEvent $
520-
GovInfoEvent mempty mempty mempty
523+
GovInfoEvent mempty mempty mempty mempty
521524
]
522-
replicateM_ (fromIntegral actionLifetime) passEpochWithNoDroppedActions
525+
replicateM_ (fromIntegral actionLifetime - 1) passEpochWithNoDroppedActions
523526
logAcceptedRatio proposalA
524527
submitYesVote_ (StakePoolVoter spoCred) proposalA
525528
submitYesVoteCCs_ ccCreds proposalA
@@ -532,12 +535,14 @@ eventsSpec = describe "Events" $ do
532535
.~ SSeq.singleton (UnRegTxCert rewardCred)
533536
passEpochWithNoDroppedActions
534537
(_, evs) <- listen passEpoch
538+
checkProposedParameterA
535539
let
536540
filteredEvs = filter isGovInfoEvent evs
537541
filteredEvs
538542
`shouldBeExpr` [ SomeSTSEvent @era @"TICK" . injectEvent $
539543
GovInfoEvent
540544
(Set.singleton gasA)
541-
(Set.fromList [gasB, gasC])
545+
(Set.singleton gasC)
546+
(Set.singleton gasB)
542547
(Set.singleton proposalC)
543548
]

eras/conway/impl/testlib/Test/Cardano/Ledger/Conway/Proposals.hs

+39-20
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{-# LANGUAGE LambdaCase #-}
2+
{-# LANGUAGE NamedFieldPuns #-}
23
{-# LANGUAGE ScopedTypeVariables #-}
34

45
module Test.Cardano.Ledger.Conway.Proposals where
@@ -10,6 +11,7 @@ import Control.Exception (AssertionFailed (..), evaluate)
1011
import Data.Either (isRight)
1112
import Data.Foldable as F (foldl', toList)
1213
import qualified Data.Map.Strict as Map
14+
import Data.MapExtras (fromElems)
1315
import Data.Maybe (fromMaybe)
1416
import Data.Sequence (fromList)
1517
import qualified Data.Sequence as Seq
@@ -51,33 +53,50 @@ spec = do
5153
`shouldThrow` \AssertionFailed {} -> True
5254
describe "Enactment" $ do
5355
prop "Adding votes preserves consistency" $
54-
\(ProposalsForEnactment ps gass _ :: ProposalsForEnactment Conway, voter :: Voter era, vote :: Vote) -> do
55-
case gass of
56-
gas Seq.:<| _gass -> isRight . toGovRelationTreeEither $ proposalsAddVote voter vote (gasId gas) ps
57-
_ -> True
56+
\( ProposalsForEnactment {pfeProposals, pfeToEnact} :: ProposalsForEnactment Conway
57+
, voter :: Voter era
58+
, vote :: Vote
59+
) -> do
60+
case pfeToEnact of
61+
gas Seq.:<| _gass -> isRight . toGovRelationTreeEither $ proposalsAddVote voter vote (gasId gas) pfeProposals
62+
_ -> True
5863
prop "Enacting exhaustive lineages reduces Proposals to their roots" $
59-
\(ProposalsForEnactment ps gass _ :: ProposalsForEnactment Conway) -> do
60-
let toEnact = Set.fromList $ toList gass
61-
(_ps', enactedRemoved, expiredRemoved) = proposalsApplyEnactment gass Set.empty ps
62-
expiredRemoved `shouldSatisfy` Map.null
63-
toEnact `shouldSatisfy` (`Set.isSubsetOf` Set.fromList (Map.elems enactedRemoved))
64+
\( ProposalsForEnactment {pfeProposals, pfeToEnact, pfeToRemove, pfeToRetain} ::
65+
ProposalsForEnactment Conway
66+
) -> do
67+
let (ps', enacted, removedDueToEnactment, expiredRemoved) = proposalsApplyEnactment pfeToEnact Set.empty pfeProposals
68+
expiredRemoved `shouldSatisfy` Map.null
69+
enacted `shouldBe` fromElems gasId pfeToEnact
70+
Map.keysSet removedDueToEnactment `shouldBe` pfeToRemove
71+
proposalsSize ps' `shouldBe` Set.size pfeToRetain
6472
prop "Enacting non-member nodes throws an AssertionFailure" $
6573
\(ProposalsNewActions ps actions :: ProposalsNewActions Conway) ->
6674
(evaluate . force) (proposalsApplyEnactment (fromList actions) Set.empty ps)
6775
`shouldThrow` \AssertionFailed {} -> True
6876
prop "Expiring compliments of exhaustive lineages keeps proposals consistent" $
69-
\(ProposalsForEnactment ps _ gais :: ProposalsForEnactment Conway) -> do
70-
let (_ps', enactedRemoved, expiredRemoved) = proposalsApplyEnactment Seq.Empty gais ps
71-
enactedRemoved `shouldSatisfy` Map.null
72-
gais `shouldSatisfy` (`Set.isSubsetOf` Map.keysSet expiredRemoved)
77+
\( ProposalsForEnactment {pfeProposals, pfeToEnact, pfeToRemove, pfeToRetain} ::
78+
ProposalsForEnactment Conway
79+
) -> do
80+
let (ps', enacted, removedDueToEnactment, expiredRemoved) =
81+
proposalsApplyEnactment Seq.Empty pfeToRemove pfeProposals
82+
enacted `shouldBe` mempty
83+
removedDueToEnactment `shouldBe` mempty
84+
Map.keysSet expiredRemoved `shouldBe` pfeToRemove
85+
ps' `shouldBe` fst (proposalsRemoveWithDescendants pfeToRemove pfeProposals)
86+
let enactMap = fromElems gasId pfeToEnact
87+
let (emptyProposals, enactedMap) = proposalsRemoveWithDescendants (Map.keysSet enactMap) ps'
88+
proposalsSize emptyProposals `shouldBe` Set.size pfeToRetain
89+
enactedMap `shouldBe` enactMap
7390
prop "Expiring non-member nodes throws an AssertionFailure" $
7491
\(ProposalsNewActions ps actions :: ProposalsNewActions Conway) ->
7592
(evaluate . force) (proposalsApplyEnactment Seq.Empty (Set.fromList $ gasId <$> actions) ps)
7693
`shouldThrow` \AssertionFailed {} -> True
77-
prop "Enacting and expiring exhaustive lineages reduces Proposals to their roots" $
78-
\(ProposalsForEnactment ps toEnact toExpire :: ProposalsForEnactment Conway) -> do
79-
let (ps', enactedRemoved, expiredRemoved) = proposalsApplyEnactment toEnact toExpire ps
80-
Set.fromList (toList toEnact)
81-
`shouldSatisfy` (`Set.isSubsetOf` Set.fromList (Map.elems enactedRemoved))
82-
Set.fromList (toList toExpire) `shouldSatisfy` (`Set.isSubsetOf` Map.keysSet expiredRemoved)
83-
proposalsSize ps' `shouldBe` 0
94+
prop "Enacting and expiring conflicting proposals does not lead to removal due to enactment" $
95+
\( ProposalsForEnactment {pfeProposals, pfeToEnact, pfeToRemove, pfeToRetain} ::
96+
ProposalsForEnactment Conway
97+
) -> do
98+
let (ps', enacted, enactedRemoved, expiredRemoved) = proposalsApplyEnactment pfeToEnact pfeToRemove pfeProposals
99+
Map.keysSet expiredRemoved `shouldBe` pfeToRemove
100+
enactedRemoved `shouldBe` mempty
101+
enacted `shouldBe` fromElems gasId pfeToEnact
102+
proposalsSize ps' `shouldBe` Set.size pfeToRetain

0 commit comments

Comments
 (0)