Skip to content

Commit f119c14

Browse files
committed
Disallow duplicate VRF keys in stake pool registration starting with v11
1 parent 05e2559 commit f119c14

File tree

5 files changed

+95
-27
lines changed

5 files changed

+95
-27
lines changed

eras/shelley/impl/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## 1.17.0.0
44

5+
* Add `hardforkConwayDisallowDuplicatedVRFKeys`
56
* Add `VRFKeyHashAlreadyRegistered` to `ShelleyPoolPredFailure` type
67
* Add `NFData` for `NominalDiffTimeMicro`, `ShelleyGenesisStaking` and `ShelleyGenesis`
78
* Deprecate `PoolParams` in favor of `StakePoolState`. #5196

eras/shelley/impl/src/Cardano/Ledger/Shelley/Era.hs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ module Cardano.Ledger.Shelley.Era (
3333
hardforkAlonzoAllowMIRTransfer,
3434
hardforkAlonzoValidatePoolRewardAccountNetID,
3535
hardforkBabbageForgoRewardPrefilter,
36+
hardforkConwayDisallowDuplicatedVRFKeys,
3637
) where
3738

3839
import Cardano.Ledger.BaseTypes (ProtVer (pvMajor), natVersion)
@@ -155,3 +156,8 @@ hardforkAlonzoValidatePoolRewardAccountNetID pv = pvMajor pv > natVersion @4
155156
-- See the Shelley Ledger Errata 17.2.
156157
hardforkBabbageForgoRewardPrefilter :: ProtVer -> Bool
157158
hardforkBabbageForgoRewardPrefilter pv = pvMajor pv > natVersion @6
159+
160+
hardforkConwayDisallowDuplicatedVRFKeys ::
161+
ProtVer ->
162+
Bool
163+
hardforkConwayDisallowDuplicatedVRFKeys pv = pvMajor pv > natVersion @10

eras/shelley/impl/src/Cardano/Ledger/Shelley/Rules/Pool.hs

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import Cardano.Ledger.Shelley.Era (
4949
ShelleyEra,
5050
ShelleyPOOL,
5151
hardforkAlonzoValidatePoolRewardAccountNetID,
52+
hardforkConwayDisallowDuplicatedVRFKeys,
5253
)
5354
import qualified Cardano.Ledger.Shelley.SoftForks as SoftForks
5455
import Cardano.Ledger.State
@@ -67,6 +68,7 @@ import Control.State.Transition (
6768
import qualified Data.ByteString as BS
6869
import Data.Kind (Type)
6970
import qualified Data.Map as Map
71+
import qualified Data.Set as Set
7072
import Data.Word (Word8)
7173
import GHC.Generics (Generic)
7274
import Lens.Micro
@@ -213,12 +215,12 @@ poolDelegationTransition ::
213215
poolDelegationTransition = do
214216
TRC
215217
( PoolEnv cEpoch pp
216-
, ps@PState {psStakePools}
218+
, ps@PState {psStakePools, psVRFKeyHashes}
217219
, poolCert
218220
) <-
219221
judgmentContext
220222
case poolCert of
221-
RegPool poolParams@PoolParams {ppId, ppRewardAccount, ppMetadata, ppCost} -> do
223+
RegPool poolParams@PoolParams {ppId, ppVrf, ppRewardAccount, ppMetadata, ppCost} -> do
222224
let pv = pp ^. ppProtocolVersionL
223225
when (hardforkAlonzoValidatePoolRewardAccountNetID pv) $ do
224226
actualNetID <- liftSTS $ asks networkId
@@ -247,15 +249,29 @@ poolDelegationTransition = do
247249
{ mismatchSupplied = ppCost
248250
, mismatchExpected = minPoolCost
249251
}
250-
251-
if not (Map.member ppId psStakePools)
252-
then do
252+
let mbStakePoolState = Map.lookup ppId psStakePools
253+
let hasMatchingVRF = ((^. spsVrfL) <$> mbStakePoolState) == Just ppVrf
254+
when (hardforkConwayDisallowDuplicatedVRFKeys pv) $ do
255+
-- if the VRF key is not associated with this pool (either because the pool is not registered
256+
-- or because the VRF key is different from the one registered for this pool),
257+
-- then we check that this VRF key is not already in use
258+
hasMatchingVRF
259+
|| Set.notMember ppVrf psVRFKeyHashes
260+
?! VRFKeyHashAlreadyRegistered ppId ppVrf
261+
case mbStakePoolState of
262+
Nothing -> do
253263
-- register new, Pool-Reg
254264
tellEvent $ RegisterPool ppId
255265
pure $
256266
payPoolDeposit ppId pp $
257-
ps & psStakePoolsL %~ Map.insert ppId (mkStakePoolState poolParams)
258-
else do
267+
ps
268+
& psStakePoolsL %~ Map.insert ppId (mkStakePoolState poolParams)
269+
& psVRFKeyHashesL %~ Set.insert ppVrf
270+
Just _ -> do
271+
-- re-register Pool
272+
let updateVRFs
273+
| hasMatchingVRF = id
274+
| otherwise = psVRFKeyHashesL %~ Set.insert ppVrf
259275
tellEvent $ ReregisterPool ppId
260276
-- hk is already registered, so we want to reregister it. That means adding it
261277
-- to the Future pool params (if it is not there already), and overriding the
@@ -270,6 +286,7 @@ poolDelegationTransition = do
270286
ps
271287
& psFutureStakePoolsL %~ Map.insert ppId (mkStakePoolState poolParams)
272288
& psRetiringL %~ Map.delete ppId
289+
& updateVRFs
273290
RetirePool ppId e -> do
274291
Map.member ppId psStakePools ?! StakePoolNotRegisteredOnKeyPOOL ppId
275292
let maxEpoch = pp ^. ppEMaxL

eras/shelley/impl/src/Cardano/Ledger/Shelley/Rules/PoolReap.hs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -145,10 +145,20 @@ poolReapTransition = do
145145
(retiringDeposits, remainingDeposits) =
146146
Map.partitionWithKey (\k _ -> Set.member k retired) (psDeposits ps)
147147
-- collect all accounts for stake pools that will retire
148-
retiredStakePoolAccounts :: Map.Map (KeyHash 'StakePool) RewardAccount
149-
retiredStakePoolAccounts = Map.map spsRewardAccount $ Map.restrictKeys (psStakePools ps) retired
150-
retiredStakePoolAccountsWithRefund :: Map.Map (KeyHash 'StakePool) (RewardAccount, CompactForm Coin)
151-
retiredStakePoolAccountsWithRefund = Map.intersectionWith (,) retiredStakePoolAccounts retiringDeposits
148+
retiredStakePoolAccountsWithVRFs ::
149+
Map.Map (KeyHash 'StakePool) (RewardAccount, VRFVerKeyHash 'StakePoolVRF)
150+
retiredStakePoolAccountsWithVRFs =
151+
Map.map
152+
(\sps -> (spsRewardAccount sps, spsVrf sps))
153+
$ Map.restrictKeys (psStakePools ps) retired
154+
retiredVRFs = foldMap (Set.singleton . snd) retiredStakePoolAccountsWithVRFs
155+
retiredStakePoolAccountsWithRefund ::
156+
Map.Map (KeyHash 'StakePool) (RewardAccount, CompactForm Coin)
157+
retiredStakePoolAccountsWithRefund =
158+
Map.intersectionWith
159+
(\(rewardAccount, _) coin -> (rewardAccount, coin))
160+
retiredStakePoolAccountsWithVRFs
161+
retiringDeposits
152162
-- collect all of the potential refunds
153163
accountRefunds :: Map.Map (Credential 'Staking) (CompactForm Coin)
154164
accountRefunds =
@@ -192,6 +202,7 @@ poolReapTransition = do
192202
& certPStateL . psStakePoolsL %~ (`Map.withoutKeys` retired)
193203
& certPStateL . psRetiringL %~ (`Map.withoutKeys` retired)
194204
& certPStateL . psDepositsCompactL .~ remainingDeposits
205+
& certPStateL . psVRFKeyHashesL %~ (`Set.difference` retiredVRFs)
195206
)
196207

197208
renderPoolReapViolation ::

eras/shelley/impl/testlib/Test/Cardano/Ledger/Shelley/Imp/PoolSpec.hs

Lines changed: 49 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -75,21 +75,36 @@ spec = describe "POOL" $ do
7575
submitFailingTx tx [injectFailure $ PoolMedataHashTooBig kh (fromIntegral tooBigSize)]
7676

7777
it "register a new pool with an already registered VRF" $ do
78+
pv <- getsPParams ppProtocolVersionL
7879
(kh, vrf) <- registerNewPool
7980
khNew <- freshKeyHash
80-
registerPoolTx <$> poolParams khNew vrf >>= submitTx_
81-
expectPool khNew (Just vrf)
81+
registerPoolTx <$> poolParams khNew vrf >>= \tx ->
82+
if pvMajor pv < natVersion @11
83+
then do
84+
submitTx_ tx
85+
expectPool khNew (Just vrf)
86+
else do
87+
submitFailingTx tx [injectFailure $ VRFKeyHashAlreadyRegistered khNew vrf]
88+
expectPool khNew Nothing
8289
expectPool kh (Just vrf)
8390

8491
it "re-register a pool with an already registered VRF" $ do
92+
pv <- getsPParams ppProtocolVersionL
8593
(kh1, vrf1) <- registerNewPool
8694
(kh2, vrf2) <- registerNewPool
87-
registerPoolTx <$> poolParams kh1 vrf2 >>= submitTx_
88-
expectPool kh1 (Just vrf1)
89-
expectFuturePool kh1 (Just vrf2)
90-
passEpoch
91-
expectPool kh1 (Just vrf2)
92-
expectPool kh2 (Just vrf2)
95+
registerPoolTx <$> poolParams kh1 vrf2 >>= \tx ->
96+
if pvMajor pv < natVersion @11
97+
then do
98+
submitTx_ tx
99+
expectPool kh1 (Just vrf1)
100+
expectFuturePool kh1 (Just vrf2)
101+
passEpoch
102+
expectPool kh1 (Just vrf2)
103+
expectPool kh2 (Just vrf2)
104+
else do
105+
submitFailingTx tx [injectFailure $ VRFKeyHashAlreadyRegistered kh1 vrf2]
106+
expectPool kh1 (Just vrf1)
107+
expectFuturePool kh1 Nothing
93108

94109
it "re-register a pool with its own VRF" $ do
95110
(kh, vrf) <- registerNewPool
@@ -148,14 +163,22 @@ spec = describe "POOL" $ do
148163
expectRetiring False kh
149164

150165
it "re-register a retiring pool with an already registered vrf" $ do
166+
pv <- getsPParams ppProtocolVersionL
151167
(kh1, _) <- registerNewPool
152168
(_, vrf2) <- registerNewPool
153169
retirePoolTx kh1 (EpochInterval 10) >>= submitTx_
154-
registerPoolTx <$> poolParams kh1 vrf2 >>= submitTx_
155-
expectRetiring False kh1
156-
expectFuturePool kh1 (Just vrf2)
157-
passEpoch
158-
expectPool kh1 (Just vrf2)
170+
registerPoolTx <$> poolParams kh1 vrf2 >>= \tx ->
171+
if pvMajor pv < natVersion @11
172+
then do
173+
submitTx_ tx
174+
expectRetiring False kh1
175+
expectFuturePool kh1 (Just vrf2)
176+
passEpoch
177+
expectPool kh1 (Just vrf2)
178+
else do
179+
submitFailingTx tx [injectFailure $ VRFKeyHashAlreadyRegistered kh1 vrf2]
180+
expectRetiring True kh1
181+
expectFuturePool kh1 Nothing
159182

160183
it "re-register retiring pool with its own VRF" $ do
161184
(kh, vrf) <- registerNewPool
@@ -176,15 +199,21 @@ spec = describe "POOL" $ do
176199
expectPool kh (Just vrfNew)
177200

178201
it "register a pool with the VRF of a retiring pool" $ do
202+
pv <- getsPParams ppProtocolVersionL
179203
(kh, vrf) <- registerNewPool
180204
let retirement = 1
181205
retirePoolTx kh (EpochInterval retirement) >>= submitTx_
182206
khNew <- freshKeyHash
183-
registerPoolTx <$> poolParams khNew vrf >>= submitTx_
184-
expectPool khNew (Just vrf)
207+
registerPoolTx <$> poolParams khNew vrf >>= \tx ->
208+
if pvMajor pv < natVersion @11
209+
then do
210+
submitTx_ tx
211+
expectPool khNew (Just vrf)
212+
else do
213+
submitFailingTx tx [injectFailure $ VRFKeyHashAlreadyRegistered khNew vrf]
214+
expectPool khNew Nothing
185215
expectRetiring True kh
186216
passNEpochs (fromIntegral retirement)
187-
expectPool khNew (Just vrf)
188217
expectRetiring False khNew
189218
expectPool kh Nothing
190219

@@ -197,6 +226,7 @@ spec = describe "POOL" $ do
197226
expectPool kh Nothing
198227
registerPoolTx <$> poolParams kh vrf >>= submitTx_
199228
expectPool kh (Just vrf)
229+
expectVRFs [vrf]
200230

201231
it "register a pool with the VRF of a retired pool" $ do
202232
(kh, vrf) <- registerNewPool
@@ -209,6 +239,7 @@ spec = describe "POOL" $ do
209239
registerPoolTx <$> poolParams khNew vrf >>= submitTx_
210240
expectPool khNew (Just vrf)
211241
expectRetiring False khNew
242+
expectVRFs [vrf]
212243
where
213244
registerNewPool = do
214245
(kh, vrf) <- (,) <$> freshKeyHash <*> freshKeyHashVRF
@@ -235,6 +266,8 @@ spec = describe "POOL" $ do
235266
assertBool
236267
("Expected 'retiring' status of: " <> show poolKh <> " to be: " <> show isRetiring)
237268
$ Map.member poolKh retiring == isRetiring
269+
expectVRFs vrfs = do
270+
(^. psVRFKeyHashesL) <$> getPState `shouldReturn` vrfs
238271
poolParams kh vrf = do
239272
pps <- registerRewardAccount >>= freshPoolParams kh
240273
pure $ pps & ppVrfL .~ vrf

0 commit comments

Comments
 (0)