Skip to content

Commit 0d20d71

Browse files
authored
Merge pull request #4813 from IntersectMBO/lehins/sanitize-non-elected-cc-votes
Add a check to `MEMPOOL` rule that prevents unelected CC from voting
2 parents ea1d436 + 4e084d5 commit 0d20d71

File tree

4 files changed

+112
-8
lines changed

4 files changed

+112
-8
lines changed

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

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
{-# LANGUAGE BangPatterns #-}
12
{-# LANGUAGE DataKinds #-}
23
{-# LANGUAGE DeriveGeneric #-}
34
{-# LANGUAGE FlexibleContexts #-}
5+
{-# LANGUAGE LambdaCase #-}
46
{-# LANGUAGE NamedFieldPuns #-}
57
{-# LANGUAGE OverloadedStrings #-}
68
{-# LANGUAGE RecordWildCards #-}
@@ -24,6 +26,7 @@ module Cardano.Ledger.Conway.Governance (
2426
Committee (..),
2527
committeeMembersL,
2628
committeeThresholdL,
29+
authorizedElectedHotCommitteeCredentials,
2730
GovAction (..),
2831
GovActionState (..),
2932
GovActionIx (..),
@@ -195,7 +198,12 @@ import Cardano.Ledger.Binary.Coders (
195198
(!>),
196199
(<!),
197200
)
198-
import Cardano.Ledger.CertState (Obligations (..))
201+
import Cardano.Ledger.CertState (
202+
CommitteeAuthorization (..),
203+
Obligations (..),
204+
certVStateL,
205+
csCommitteeCreds,
206+
)
199207
import Cardano.Ledger.Coin (Coin (..))
200208
import Cardano.Ledger.Conway.Era (ConwayEra)
201209
import Cardano.Ledger.Conway.Governance.DRepPulser
@@ -210,6 +218,7 @@ import Cardano.Ledger.PoolParams (PoolParams (ppRewardAccount))
210218
import Cardano.Ledger.Shelley.Governance
211219
import Cardano.Ledger.Shelley.LedgerState (
212220
EpochState (..),
221+
LedgerState,
213222
NewEpochState (..),
214223
certDState,
215224
certVState,
@@ -220,10 +229,14 @@ import Cardano.Ledger.Shelley.LedgerState (
220229
epochStateTreasuryL,
221230
esLStateL,
222231
lsCertState,
232+
lsCertStateL,
223233
lsUTxOState,
234+
lsUTxOStateL,
224235
newEpochStateGovStateL,
236+
utxosGovStateL,
225237
utxosStakeDistr,
226238
vsCommitteeState,
239+
vsCommitteeStateL,
227240
vsDReps,
228241
)
229242
import Cardano.Ledger.UMap
@@ -234,8 +247,10 @@ import Control.Monad.Trans.Reader (ReaderT, ask)
234247
import Data.Aeson (KeyValue, ToJSON (..), object, pairs, (.=))
235248
import Data.Default (Default (..))
236249
import Data.Foldable (Foldable (..))
250+
import qualified Data.Foldable as F (foldl')
237251
import Data.Map.Strict (Map)
238252
import qualified Data.Map.Strict as Map
253+
import qualified Data.Set as Set
239254
import Data.Word (Word64)
240255
import GHC.Generics (Generic)
241256
import Lens.Micro
@@ -539,3 +554,21 @@ defaultStakePoolVote poolId poolParams dRepDelegations =
539554
toDefaultVote (Just DRepAlwaysAbstain) = DefaultAbstain
540555
toDefaultVote (Just DRepAlwaysNoConfidence) = DefaultNoConfidence
541556
toDefaultVote _ = DefaultNo
557+
558+
authorizedElectedHotCommitteeCredentials ::
559+
ConwayEraGov era =>
560+
LedgerState era ->
561+
Set.Set (Credential 'HotCommitteeRole)
562+
authorizedElectedHotCommitteeCredentials ledgerState =
563+
case ledgerState ^. lsUTxOStateL . utxosGovStateL . committeeGovStateL of
564+
SNothing -> Set.empty
565+
SJust electedCommiteee ->
566+
collectAuthorizedHotCreds $
567+
csCommitteeCreds committeeState `Map.intersection` committeeMembers electedCommiteee
568+
where
569+
committeeState = ledgerState ^. lsCertStateL . certVStateL . vsCommitteeStateL
570+
collectAuthorizedHotCreds =
571+
let toHotCredSet !acc = \case
572+
CommitteeHotCredential hotCred -> Set.insert hotCred acc
573+
CommitteeMemberResigned {} -> acc
574+
in F.foldl' toHotCredSet Set.empty

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -587,8 +587,9 @@ instance
587587
wrapEvent = CertsEvent . CertEvent . DelegEvent
588588

589589
instance
590-
( EraGov era
591-
, EraTx era
590+
( EraTx era
591+
, ConwayEraGov era
592+
, ConwayEraTxBody era
592593
, EraRule "MEMPOOL" era ~ ConwayMEMPOOL era
593594
, PredicateFailure (EraRule "MEMPOOL" era) ~ ConwayMempoolPredFailure era
594595
, Event (EraRule "MEMPOOL" era) ~ ConwayMempoolEvent era

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

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
{-# LANGUAGE BangPatterns #-}
12
{-# LANGUAGE DataKinds #-}
23
{-# LANGUAGE DeriveGeneric #-}
34
{-# LANGUAGE DerivingStrategies #-}
@@ -23,6 +24,12 @@ import Cardano.Ledger.BaseTypes (ShelleyBase)
2324
import Cardano.Ledger.Binary (DecCBOR (..), EncCBOR (..), FromCBOR, ToCBOR)
2425
import Cardano.Ledger.Conway.Core
2526
import Cardano.Ledger.Conway.Era (ConwayEra, ConwayMEMPOOL)
27+
import Cardano.Ledger.Conway.Governance (
28+
ConwayEraGov,
29+
Voter (..),
30+
authorizedElectedHotCommitteeCredentials,
31+
unVotingProcedures,
32+
)
2633
import Cardano.Ledger.Shelley.LedgerState
2734
import Cardano.Ledger.Shelley.Rules (LedgerEnv (..))
2835
import Control.DeepSeq (NFData)
@@ -36,12 +43,17 @@ import Control.State.Transition (
3643
State,
3744
TRC (TRC),
3845
TransitionRule,
46+
failOnNonEmpty,
3947
judgmentContext,
4048
tellEvent,
4149
transitionRules,
4250
)
43-
import Data.Text (Text, pack)
51+
import qualified Data.List.NonEmpty as NE
52+
import qualified Data.Map.Strict as Map
53+
import qualified Data.Set as Set
54+
import Data.Text as T (Text, pack)
4455
import GHC.Generics (Generic)
56+
import Lens.Micro ((^.))
4557
import NoThunks.Class (NoThunks)
4658

4759
newtype ConwayMempoolPredFailure era = ConwayMempoolPredFailure Text
@@ -58,7 +70,7 @@ newtype ConwayMempoolEvent era = ConwayMempoolEvent Text
5870
type instance EraRuleEvent "MEMPOOL" ConwayEra = ConwayMempoolEvent ConwayEra
5971

6072
instance
61-
(EraTx era, EraGov era) =>
73+
(EraTx era, ConwayEraTxBody era, ConwayEraGov era) =>
6274
STS (ConwayMEMPOOL era)
6375
where
6476
type State (ConwayMEMPOOL era) = LedgerState era
@@ -70,11 +82,27 @@ instance
7082

7183
transitionRules = [mempoolTransition @era]
7284

73-
mempoolTransition :: EraTx era => TransitionRule (ConwayMEMPOOL era)
85+
mempoolTransition ::
86+
(EraTx era, ConwayEraTxBody era, ConwayEraGov era) => TransitionRule (ConwayMEMPOOL era)
7487
mempoolTransition = do
7588
TRC (_ledgerEnv, ledgerState, tx) <-
7689
judgmentContext
7790
-- This rule only gets invoked on transactions within the mempool.
7891
-- Add checks here that sanitize undesired transactions.
79-
tellEvent . ConwayMempoolEvent . ("Mempool rule for tx " <>) . pack . show . txIdTx $ tx
92+
tellEvent . ConwayMempoolEvent . ("Mempool rule for tx " <>) . T.pack . show $ txIdTx tx
93+
let
94+
authorizedElectedHotCreds = authorizedElectedHotCommitteeCredentials ledgerState
95+
collectUnelectedCommitteeVotes !unelectedHotCreds voter _ =
96+
case voter of
97+
CommitteeVoter hotCred
98+
| hotCred `Set.notMember` authorizedElectedHotCreds ->
99+
Set.insert hotCred unelectedHotCreds
100+
_ -> unelectedHotCreds
101+
unelectedCommitteeVoters =
102+
Map.foldlWithKey' collectUnelectedCommitteeVotes Set.empty $
103+
unVotingProcedures (tx ^. bodyTxL . votingProceduresTxBodyL)
104+
addPrefix =
105+
("Unelected committee members are not allowed to cast votes: " <>)
106+
failOnNonEmpty unelectedCommitteeVoters $
107+
ConwayMempoolPredFailure . addPrefix . T.pack . show . NE.toList
80108
pure ledgerState

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

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ module Test.Cardano.Ledger.Conway.Imp.LedgerSpec (spec) where
1212
import Cardano.Ledger.BaseTypes
1313
import Cardano.Ledger.Coin (Coin (..))
1414
import Cardano.Ledger.Conway.Core
15+
import Cardano.Ledger.Conway.Governance
1516
import Cardano.Ledger.Conway.Rules (
1617
ConwayLedgerEvent (..),
1718
ConwayLedgerPredFailure (..),
@@ -21,16 +22,18 @@ import Cardano.Ledger.Conway.Rules (
2122
import Cardano.Ledger.Credential (Credential (..))
2223
import Cardano.Ledger.DRep
2324
import Cardano.Ledger.Plutus (SLanguage (..), hashPlutusScript)
24-
import Cardano.Ledger.Shelley.API.Mempool (ApplyTx (..), mkMempoolEnv)
25+
import Cardano.Ledger.Shelley.API.Mempool (ApplyTx (..), applyTx, mkMempoolEnv)
2526
import qualified Cardano.Ledger.Shelley.HardForks as HF (bootstrapPhase)
2627
import Cardano.Ledger.Shelley.LedgerState
2728
import Cardano.Ledger.Shelley.Rules (ShelleyLedgersEnv (..), ShelleyLedgersEvent (..))
2829
import Control.State.Transition.Extended
30+
import qualified Data.Map.Strict as Map
2931
import qualified Data.Sequence as Seq
3032
import qualified Data.Set as Set
3133
import Lens.Micro ((&), (.~), (^.))
3234
import Lens.Micro.Mtl (use)
3335
import Test.Cardano.Ledger.Conway.ImpTest
36+
import Test.Cardano.Ledger.Core.Rational (IsRatio (..))
3437
import Test.Cardano.Ledger.Imp.Common
3538
import Test.Cardano.Ledger.Plutus.Examples (
3639
alwaysFailsWithDatum,
@@ -254,3 +257,42 @@ spec = do
254257
assertFailure $ "Unexpected failure while applyingTx: " <> show tx <> ": " <> show e
255258
Right (_, evs) ->
256259
length [ev | ev@(MempoolEvent (ConwayMempoolEvent _)) <- evs] `shouldBe` 1
260+
261+
it "Unelected Committee voting" $ whenPostBootstrap $ do
262+
globals <- use impGlobalsL
263+
slotNo <- use impLastTickG
264+
_ <- registerInitialCommittee
265+
ccCold <- KeyHashObj <$> freshKeyHash
266+
curEpochNo <- getsNES nesELL
267+
let action =
268+
UpdateCommittee
269+
SNothing
270+
mempty
271+
(Map.singleton ccCold (addEpochInterval curEpochNo (EpochInterval 7)))
272+
(1 %! 1)
273+
proposal <- mkProposal action
274+
submitTx_ $
275+
mkBasicTx (mkBasicTxBody & proposalProceduresTxBodyL .~ [proposal])
276+
ccHot <- registerCommitteeHotKey ccCold
277+
govActionId <- do
278+
rewardAccount <- registerRewardAccount
279+
submitTreasuryWithdrawals [(rewardAccount, Coin 1)]
280+
281+
nes <- use impNESL
282+
let ls = nes ^. nesEsL . esLStateL
283+
mempoolEnv = mkMempoolEnv nes slotNo
284+
tx <-
285+
fixupTx $
286+
mkBasicTx $
287+
mkBasicTxBody
288+
& votingProceduresTxBodyL
289+
.~ VotingProcedures
290+
( Map.singleton
291+
(CommitteeVoter ccHot)
292+
(Map.singleton govActionId (VotingProcedure VoteYes SNothing))
293+
)
294+
295+
case applyTx globals mempoolEnv ls tx of
296+
Left _ -> pure ()
297+
Right _ -> assertFailure $ "Expected failure due to an unallowed vote: " <> show tx
298+
withNoFixup $ submitTx_ tx

0 commit comments

Comments
 (0)