Skip to content

Commit 016a937

Browse files
committed
Store number of occurrences of VRF key hashes in CertState
in order to avoid pools retiring after the HardFork from removing the record of a known (but duplicate) VRF key hash
1 parent 545babb commit 016a937

File tree

11 files changed

+86
-37
lines changed

11 files changed

+86
-37
lines changed

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

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
{-# LANGUAGE EmptyDataDeriving #-}
55
{-# LANGUAGE FlexibleContexts #-}
66
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
7+
{-# LANGUAGE LambdaCase #-}
78
{-# LANGUAGE MultiParamTypeClasses #-}
89
{-# LANGUAGE ScopedTypeVariables #-}
910
{-# LANGUAGE TypeApplications #-}
@@ -16,7 +17,7 @@ module Cardano.Ledger.Conway.Rules.HardFork (
1617
ConwayHardForkEvent (..),
1718
) where
1819

19-
import Cardano.Ledger.BaseTypes (ProtVer (..), ShelleyBase, natVersion)
20+
import Cardano.Ledger.BaseTypes
2021
import Cardano.Ledger.Conway.Core
2122
import Cardano.Ledger.Conway.Era (ConwayEra, ConwayHARDFORK)
2223
import Cardano.Ledger.Conway.State
@@ -36,9 +37,12 @@ import Control.State.Transition (
3637
tellEvent,
3738
transitionRules,
3839
)
40+
import Data.Map.Strict (Map)
3941
import qualified Data.Map.Strict as Map
42+
import Data.Maybe (fromMaybe)
4043
import qualified Data.Set as Set
4144
import Data.Void (Void)
45+
import Data.Word (Word64)
4246
import GHC.Generics (Generic)
4347
import Lens.Micro
4448

@@ -102,8 +106,19 @@ updateDRepDelegations certState =
102106

103107
populateVRFKeyHashes :: PState era -> PState era
104108
populateVRFKeyHashes pState =
105-
let allVRFKeyHashes =
106-
spsVrf
107-
<$> Map.elems (pState ^. psStakePoolsL)
108-
<> Map.elems (pState ^. psFutureStakePoolsL)
109-
in pState & psVRFKeyHashesL .~ Set.fromList allVRFKeyHashes
109+
pState
110+
& psVRFKeyHashesL
111+
%~ accumulateVRFKeyHashes (pState ^. psStakePoolsL)
112+
. accumulateVRFKeyHashes (pState ^. psFutureStakePoolsL)
113+
where
114+
accumulateVRFKeyHashes ::
115+
Map (KeyHash 'StakePool) StakePoolState ->
116+
Map (VRFVerKeyHash 'StakePoolVRF) (NonZero Word64) ->
117+
Map (VRFVerKeyHash 'StakePoolVRF) (NonZero Word64)
118+
accumulateVRFKeyHashes spsMap acc =
119+
Map.foldr' (\sps -> addVRFKeyHashOccurrence (sps ^. spsVrfL)) acc spsMap
120+
addVRFKeyHashOccurrence vrfKeyHash =
121+
Map.insertWith combine vrfKeyHash (knownNonZeroBounded @1)
122+
where
123+
-- Saturates at maxBound: if (+1) would overflow to 0, keep existing value
124+
combine _ oldVal = fromMaybe oldVal $ mapNonZero (+ 1) oldVal

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

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import Cardano.Ledger.Conway.PParams
1515
import Cardano.Ledger.Conway.State
1616
import Cardano.Ledger.Shelley.LedgerState
1717
import Cardano.Ledger.Shelley.Rules (ShelleyPoolPredFailure (..))
18-
import qualified Data.Set as Set
18+
import qualified Data.Map.Strict as Map
1919
import Lens.Micro
2020
import Test.Cardano.Ledger.Conway.ImpTest
2121
import Test.Cardano.Ledger.Core.Rational
@@ -52,7 +52,7 @@ spec = do
5252

5353
expectVRFs [] -- VRF keyhashes in PState is not yet populated
5454
enactHardForkV11
55-
expectVRFs [vrf2, vrf3, vrf5]
55+
expectVRFs [(vrf2, 2), (vrf3, 1), (vrf5, 1)]
5656

5757
it "Retiring a stake pool with a duplicate VRF Keyhash after v11 HardFork" $ do
5858
whenMajorVersion @10 $ do
@@ -61,21 +61,31 @@ spec = do
6161
registerStakePool kh1 vrf
6262
kh2 <- freshKeyHash
6363
registerStakePool kh2 vrf
64+
kh3 <- freshKeyHash
65+
registerStakePool kh3 vrf
6466

6567
enactHardForkV11
66-
expectVRFs [vrf]
68+
expectVRFs [(vrf, 3)]
6769
-- retire one of the pools after the hard fork
6870
retireStakePool kh1 (EpochInterval 1)
71+
retireStakePool kh2 (EpochInterval 1)
6972
passEpoch
7073
-- the vrf keyhash should still be present, since another pool is registered with it
71-
expectVRFs [vrf]
74+
expectVRFs [(vrf, 1)]
7275

7376
-- registration of the same vrf should be disallowed
74-
kh3 <- freshKeyHash
75-
registerStakePoolTx kh3 vrf >>= \tx ->
77+
kh4 <- freshKeyHash
78+
registerStakePoolTx kh4 vrf >>= \tx ->
7679
submitFailingTx
7780
tx
78-
[injectFailure $ VRFKeyHashAlreadyRegistered kh3 vrf]
81+
[injectFailure $ VRFKeyHashAlreadyRegistered kh4 vrf]
82+
83+
retireStakePool kh3 (EpochInterval 1)
84+
passEpoch
85+
expectVRFs []
86+
87+
registerStakePool kh4 vrf
88+
expectVRFs [(vrf, 1)]
7989
where
8090
enactHardForkV11 = do
8191
modifyPParams $ \pp ->
@@ -102,5 +112,7 @@ spec = do
102112
mkBasicTx mkBasicTxBody
103113
& bodyTxL . certsTxBodyL .~ [RetirePoolTxCert kh retirement]
104114
expectVRFs vrfs =
105-
psVRFKeyHashes <$> getPState `shouldReturn` Set.fromList vrfs
115+
psVRFKeyHashes
116+
<$> getPState
117+
`shouldReturn` Map.fromList [(k, unsafeNonZero v) | (k, v) <- vrfs]
106118
getPState = getsNES @era $ nesEsL . esLStateL . lsCertStateL . certPStateL

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

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import Cardano.Ledger.BaseTypes (
3434
ShelleyBase,
3535
addEpochInterval,
3636
invalidKey,
37+
knownNonZeroBounded,
3738
networkId,
3839
)
3940
import Cardano.Ledger.Binary (
@@ -68,7 +69,6 @@ import Control.State.Transition (
6869
import qualified Data.ByteString as BS
6970
import Data.Kind (Type)
7071
import qualified Data.Map as Map
71-
import qualified Data.Set as Set
7272
import Data.Word (Word8)
7373
import GHC.Generics (Generic)
7474
import Lens.Micro
@@ -253,9 +253,9 @@ poolDelegationTransition = do
253253
-- register new, Pool-Reg
254254
Nothing -> do
255255
when (hardforkConwayDisallowDuplicatedVRFKeys pv) $ do
256-
Set.notMember ppVrf psVRFKeyHashes ?! VRFKeyHashAlreadyRegistered ppId ppVrf
256+
Map.notMember ppVrf psVRFKeyHashes ?! VRFKeyHashAlreadyRegistered ppId ppVrf
257257
let updateVRFKeyHash
258-
| hardforkConwayDisallowDuplicatedVRFKeys pv = Set.insert ppVrf
258+
| hardforkConwayDisallowDuplicatedVRFKeys pv = Map.insert ppVrf (knownNonZeroBounded @1)
259259
| otherwise = id
260260
tellEvent $ RegisterPool ppId
261261
pure $
@@ -266,17 +266,18 @@ poolDelegationTransition = do
266266
Just stakePoolState -> do
267267
when (hardforkConwayDisallowDuplicatedVRFKeys pv) $ do
268268
ppVrf == stakePoolState ^. spsVrfL
269-
|| Set.notMember ppVrf psVRFKeyHashes ?! VRFKeyHashAlreadyRegistered ppId ppVrf
269+
|| Map.notMember ppVrf psVRFKeyHashes ?! VRFKeyHashAlreadyRegistered ppId ppVrf
270270
let updateFutureVRFKeyHash
271271
| hardforkConwayDisallowDuplicatedVRFKeys pv =
272-
-- If a pool re-registers with a fresh VRF, we have to add it to the list,
272+
-- If a pool re-registers with a fresh VRF, we have to record it in the map,
273273
-- but also remove the previous VRFHashKey potentially stored in previous re-registration within the same epoch,
274274
-- which we retrieve from futureStakePools.
275275
case Map.lookup ppId psFutureStakePools of
276-
Nothing -> Set.insert ppVrf
276+
Nothing -> Map.insert ppVrf (knownNonZeroBounded @1)
277277
Just futureStakePoolState
278278
| futureStakePoolState ^. spsVrfL /= ppVrf ->
279-
Set.insert ppVrf . Set.delete (futureStakePoolState ^. spsVrfL)
279+
(Map.insert ppVrf (knownNonZeroBounded @1))
280+
. Map.delete (futureStakePoolState ^. spsVrfL)
280281
| otherwise -> id
281282
| otherwise = id
282283
tellEvent $ ReregisterPool ppId

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

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ module Cardano.Ledger.Shelley.Rules.PoolReap (
2323
) where
2424

2525
import Cardano.Ledger.Address
26-
import Cardano.Ledger.BaseTypes (ShelleyBase)
26+
import Cardano.Ledger.BaseTypes
2727
import Cardano.Ledger.Coin (Coin, CompactForm)
2828
import Cardano.Ledger.Compactible (fromCompact)
2929
import Cardano.Ledger.Core
@@ -35,7 +35,6 @@ import Cardano.Ledger.Shelley.LedgerState (
3535
utxosGovStateL,
3636
)
3737
import Cardano.Ledger.Shelley.LedgerState.Types (potEqualsObligation)
38-
import Cardano.Ledger.Slot (EpochNo (..))
3938
import Cardano.Ledger.State
4039
import Cardano.Ledger.Val ((<+>), (<->))
4140
import Control.DeepSeq (NFData)
@@ -50,11 +49,14 @@ import Control.State.Transition (
5049
)
5150
import Data.Default (Default, def)
5251
import Data.Foldable (fold)
52+
import Data.Foldable as F (foldl')
5353
import qualified Data.Map.Merge.Strict as Map
54+
import Data.Map.Strict (Map)
5455
import qualified Data.Map.Strict as Map
5556
import Data.Set (Set)
5657
import qualified Data.Set as Set
5758
import Data.Void (Void)
59+
import Data.Word (Word64)
5860
import GHC.Generics (Generic)
5961
import Lens.Micro
6062

@@ -135,7 +137,7 @@ poolReapTransition = do
135137
ps0 = cs0 ^. certPStateL
136138
-- find the set of VRF key hashes that are no longer relevant, since they have been overwritten
137139
-- via pool re-registration
138-
danglingVrfKeyHashes =
140+
danglingVRFKeyHashes =
139141
Set.fromList $
140142
Map.elems $
141143
Map.merge
@@ -163,7 +165,8 @@ poolReapTransition = do
163165
retiringPools :: Map.Map (KeyHash 'StakePool) StakePoolState
164166
retiringPools = Map.restrictKeys (psStakePools ps) retired
165167
-- collect all accounts for stake pools that will retire
166-
retiredVRFs = foldMap (Set.singleton . spsVrf) retiringPools
168+
retiredVRFKeyHashes = spsVrf <$> Map.elems retiringPools
169+
167170
-- collect all of the potential refunds
168171
accountRefunds :: Map.Map (Credential 'Staking) (CompactForm Coin)
169172
accountRefunds =
@@ -209,8 +212,19 @@ poolReapTransition = do
209212
& certPStateL . psStakePoolsL %~ (`Map.withoutKeys` retired)
210213
& certPStateL . psRetiringL %~ (`Map.withoutKeys` retired)
211214
& certPStateL . psVRFKeyHashesL
212-
%~ ((`Set.difference` retiredVRFs) . (`Set.difference` danglingVrfKeyHashes))
215+
%~ ( removeVRFKeyHashOccurrences (retiredVRFKeyHashes)
216+
. (`Map.withoutKeys` danglingVRFKeyHashes)
217+
)
213218
)
219+
where
220+
removeVRFKeyHashOccurrences ::
221+
[VRFVerKeyHash 'StakePoolVRF] ->
222+
Map (VRFVerKeyHash 'StakePoolVRF) (NonZero Word64) ->
223+
Map (VRFVerKeyHash 'StakePoolVRF) (NonZero Word64)
224+
removeVRFKeyHashOccurrences vrfs vrfsMap = F.foldl' (flip removeVRFKeyHashOccurrence) vrfsMap vrfs
225+
removeVRFKeyHashOccurrence =
226+
-- Removes the key from the map if the value drops to 0
227+
Map.update (mapNonZero (\n -> n - 1))
214228

215229
renderPoolReapViolation ::
216230
( EraGov era

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@ spec = describe "POOL" $ do
344344
$ Map.member poolKh retiring == isRetiring
345345
expectVRFs vrfs = do
346346
whenMajorVersionAtLeast @11 $
347-
psVRFKeyHashes <$> getPState `shouldReturn` vrfs
347+
Map.keysSet . psVRFKeyHashes <$> getPState `shouldReturn` vrfs
348348
poolParams kh vrf = do
349349
pps <- registerRewardAccount >>= freshPoolParams kh
350350
pure $ pps & ppVrfL .~ vrf

eras/shelley/test-suite/test/Test/Cardano/Ledger/Shelley/Examples/Combinators.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ reapPool pool cs = cs {chainNes = nes'}
354354
ps
355355
{ psRetiring = Map.delete poolId (psRetiring ps)
356356
, psStakePools = Map.delete poolId (psStakePools ps)
357-
, psVRFKeyHashes = Set.delete (ppVrf pool) (psVRFKeyHashes ps)
357+
, psVRFKeyHashes = Map.delete (ppVrf pool) (psVRFKeyHashes ps)
358358
}
359359
pp = es ^. curPParamsEpochStateL
360360
ds = dps ^. certDStateL

eras/shelley/test-suite/test/Test/Cardano/Ledger/Shelley/Serialisation/Golden/Encoding.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1085,7 +1085,7 @@ tests =
10851085
expectedHex =
10861086
mconcat
10871087
[ "8700a1581ce0a714319812c3f773ba04ec5d6b3ffcd5aad85006805b047b0825410aa1581ca64647"
1088-
, "4b8f5431261506b6c273d307c7569a4eb6c96b42dd4a29520a0384821927101903e882828480a0a0"
1088+
, "4b8f5431261506b6c273d307c7569a4eb6c96b42dd4a29520a0384821927101903e8828284a0a0a0"
10891089
, "a08482a0a0a0a084a0a0000086a15822ee155ace9c40292074cb6aff8c9ccdd273c81648ff1149ef"
10901090
, "36bcea6ebb8a3e250000583d003900cb9358529df4729c3246a2a033cb9821abbfd16de488800590"
10911091
, "4abc410d6a577e9441ad8ed9663931906e4d43ece8f82c712b1d0235affb06000a1903e80185a0a0"

libs/cardano-ledger-core/src/Cardano/Ledger/State/CertState.hs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import Cardano.Ledger.BaseTypes (
4848
Anchor (..),
4949
AnchorData,
5050
KeyValuePairs (..),
51+
NonZero,
5152
StrictMaybe,
5253
ToKeyValuePairs (..),
5354
)
@@ -84,8 +85,8 @@ import qualified Data.Foldable as F
8485
import Data.Kind (Type)
8586
import Data.Map.Strict (Map)
8687
import qualified Data.Map.Strict as Map
87-
import Data.Set (Set)
8888
import qualified Data.Set as Set
89+
import Data.Word (Word64)
8990
import GHC.Generics (Generic)
9091
import Lens.Micro
9192
import NoThunks.Class (NoThunks (..))
@@ -221,7 +222,7 @@ lookupRewardDState DState {dsAccounts} cred = do
221222

222223
-- | The state used by the POOL rule, which tracks stake pool information.
223224
data PState era = PState
224-
{ psVRFKeyHashes :: !(Set (VRFVerKeyHash 'StakePoolVRF))
225+
{ psVRFKeyHashes :: !(Map (VRFVerKeyHash 'StakePoolVRF) (NonZero Word64))
225226
-- ^ VRF key hashes that have been registered via PoolParams
226227
, psStakePools :: !(Map (KeyHash 'StakePool) StakePoolState)
227228
-- ^ The state of current stake pools.
@@ -386,7 +387,7 @@ instance Default (Accounts era) => Default (DState era) where
386387
def = DState def Map.empty (GenDelegs Map.empty) def
387388

388389
instance Default (PState era) where
389-
def = PState Set.empty Map.empty Map.empty Map.empty
390+
def = PState Map.empty Map.empty Map.empty Map.empty
390391

391392
-- | A composite of all the Deposits the system is obligated to eventually pay back.
392393
data Obligations = Obligations
@@ -443,5 +444,5 @@ psFutureStakePoolsL = lens psFutureStakePools (\ps u -> ps {psFutureStakePools =
443444
psRetiringL :: Lens' (PState era) (Map (KeyHash 'StakePool) EpochNo)
444445
psRetiringL = lens psRetiring (\ps u -> ps {psRetiring = u})
445446

446-
psVRFKeyHashesL :: Lens' (PState era) (Set (VRFVerKeyHash 'StakePoolVRF))
447+
psVRFKeyHashesL :: Lens' (PState era) (Map (VRFVerKeyHash 'StakePoolVRF) (NonZero Word64))
447448
psVRFKeyHashesL = lens psVRFKeyHashes (\ps u -> ps {psVRFKeyHashes = u})

libs/cardano-ledger-test/src/Test/Cardano/Ledger/Constrained/Conway/Instances/Basic.hs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,3 +418,10 @@ class
418418
ppToSubset :: PParams era -> SimplePParams era
419419
updateToPPU :: SimplePPUpdate -> PParamsUpdate era
420420
ppuToUpdate :: PParamsUpdate era -> SimplePPUpdate
421+
422+
instance HasSimpleRep (NonZero Word64) where
423+
type SimpleRep (NonZero Word64) = Word64
424+
toSimpleRep = unNonZero
425+
fromSimpleRep = unsafeNonZero
426+
427+
instance HasSpec (NonZero Word64)

libs/cardano-ledger-test/src/Test/Cardano/Ledger/Generic/GenState.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -669,7 +669,7 @@ initialLedgerState gstate = LedgerState utxostate dpstate
669669
instantaneousRewardsZero
670670
pstate =
671671
PState
672-
Set.empty
672+
Map.empty
673673
(mkStakePoolState poolDeposit <$> pools)
674674
Map.empty
675675
Map.empty

0 commit comments

Comments
 (0)