Skip to content

Commit

Permalink
Reduce memory usage of the create-staked command
Browse files Browse the repository at this point in the history
  • Loading branch information
newhoggy committed Jun 9, 2022
1 parent 48af8a2 commit 60eae8c
Show file tree
Hide file tree
Showing 4 changed files with 237 additions and 6 deletions.
3 changes: 3 additions & 0 deletions cardano-cli/cardano-cli.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ library
Cardano.CLI.Shelley.Run.Address
Cardano.CLI.Shelley.Run.Address.Info
Cardano.CLI.Shelley.Run.Genesis
Cardano.CLI.Shelley.Run.Genesis.ListMap
Cardano.CLI.Shelley.Run.Genesis.Types
Cardano.CLI.Shelley.Run.Governance
Cardano.CLI.Shelley.Run.Key
Cardano.CLI.Shelley.Run.Node
Expand Down Expand Up @@ -122,6 +124,7 @@ library
, containers
, cryptonite
, directory
, dlist
, filepath
, formatting
, iproute
Expand Down
107 changes: 101 additions & 6 deletions cardano-cli/src/Cardano/CLI/Shelley/Run/Genesis.hs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ import Data.Fixed (Fixed(MkFixed))
import qualified Data.Yaml as Yaml
import Text.JSON.Canonical (parseCanonicalJSON, renderCanonicalJSON)

import qualified Cardano.CLI.Shelley.Run.Genesis.Types as OT
import Cardano.CLI.Shelley.Run.Genesis.ListMap (ListMap(..))
import Cardano.CLI.Shelley.Run.Genesis.Types (toOutputTemplate)

import qualified System.IO.Unsafe as IO

{- HLINT ignore "Reduce duplication" -}

data ShelleyGenesisCmdError
Expand Down Expand Up @@ -699,20 +705,19 @@ runGenesisCreateStaked (GenesisDir rootdir)
uncurry (computeDelegation network stdeldir)

genDlgs <- readGenDelegsMap gendir deldir
nonDelegAddrs <- readInitialFundAddresses utxodir network
nonDelegAddrs :: [AddressInEra ShelleyEra] <- readInitialFundAddresses utxodir network
start <- maybe (SystemStart <$> getCurrentTimePlus30) pure mStart

stuffedUtxoAddrs <- liftIO $ replicateM (fromIntegral numStuffedUtxo)
genStuffedAddress
stuffedUtxoAddrs :: [AddressInEra ShelleyEra] <- liftIO $ lazyReplicateM (fromIntegral numStuffedUtxo) $ genStuffedAddress

let poolMap :: Map (Ledger.KeyHash Ledger.Staking StandardCrypto) (Ledger.PoolParams StandardCrypto)
poolMap = Map.fromList $ mkDelegationMapEntry <$> delegations
delegAddrs = dInitialUtxoAddr <$> delegations
delegAddrs :: [AddressInEra ShelleyEra] = dInitialUtxoAddr <$> delegations
shelleyGenesis =
updateTemplate
updateOutputTemplate
-- Shelley genesis parameters
start genDlgs mNonDlgAmount nonDelegAddrs poolMap
stDlgAmount delegAddrs stuffedUtxoAddrs template
stDlgAmount delegAddrs stuffedUtxoAddrs (toOutputTemplate template)

writeFileGenesis (rootdir </> "genesis.json") shelleyGenesis
writeFileGenesis (rootdir </> "genesis.alonzo.json") alonzoGenesis
Expand Down Expand Up @@ -1069,6 +1074,86 @@ updateTemplate (SystemStart start)
unLovelace :: Integral a => Lovelace -> a
unLovelace (Lovelace coin) = fromIntegral coin

updateOutputTemplate
:: SystemStart
-- Genesis delegation (not stake-based):
-> Map (Hash GenesisKey) (Hash GenesisDelegateKey, Hash VrfKey)
-- Non-delegated initial UTxO spec:
-> Maybe Lovelace
-> [AddressInEra ShelleyEra]
-- Genesis staking: pools/delegation map & delegated initial UTxO spec:
-> Map (Ledger.KeyHash 'Ledger.Staking StandardCrypto) (Ledger.PoolParams StandardCrypto)
-> Lovelace
-> [AddressInEra ShelleyEra]
-> [AddressInEra ShelleyEra]
-> OT.OutputShelleyGenesis StandardShelley
-> OT.OutputShelleyGenesis StandardShelley
updateOutputTemplate (SystemStart start)
genDelegMap mAmountNonDeleg utxoAddrsNonDeleg
poolSpecs (Lovelace amountDeleg) utxoAddrsDeleg stuffedUtxoAddrs
template = do

let pparamsFromTemplate = OT.sgProtocolParams template
shelleyGenesis = template
{ OT.sgSystemStart = start
, OT.sgMaxLovelaceSupply = fromIntegral $ nonDelegCoin + delegCoin
, OT.sgGenDelegs = shelleyDelKeys
, OT.sgInitialFunds = ListMap
[ (toShelleyAddr addr, toShelleyLovelace v)
| (addr, v) <-
distribute (nonDelegCoin - subtractForTreasury) utxoAddrsNonDeleg ++
distribute (delegCoin - subtractForTreasury) utxoAddrsDeleg ++
mkStuffedUtxo stuffedUtxoAddrs ]
, OT.sgStaking =
ShelleyGenesisStaking
{ sgsPools = Map.fromList
[ (Ledger._poolId poolParams, poolParams)
| poolParams <- Map.elems poolSpecs ]
, sgsStake = Ledger._poolId <$> poolSpecs
}
, OT.sgProtocolParams = pparamsFromTemplate
}
shelleyGenesis
where
maximumLovelaceSupply :: Word64
maximumLovelaceSupply = OT.sgMaxLovelaceSupply template
-- If the initial funds are equal to the maximum funds, rewards cannot be created.
subtractForTreasury :: Integer
subtractForTreasury = nonDelegCoin `quot` 10
nonDelegCoin, delegCoin :: Integer
nonDelegCoin = fromIntegral (fromMaybe maximumLovelaceSupply (unLovelace <$> mAmountNonDeleg))
delegCoin = fromIntegral amountDeleg

distribute :: Integer -> [AddressInEra ShelleyEra] -> [(AddressInEra ShelleyEra, Lovelace)]
distribute funds addrs =
fst $ List.foldl' folder ([], fromIntegral funds) addrs
where
nAddrs, coinPerAddr, splitThreshold :: Integer
nAddrs = fromIntegral $ length addrs
coinPerAddr = funds `div` nAddrs
splitThreshold = coinPerAddr + nAddrs

folder :: ([(AddressInEra ShelleyEra, Lovelace)], Integer)
-> AddressInEra ShelleyEra
-> ([(AddressInEra ShelleyEra, Lovelace)], Integer)
folder (acc, rest) addr
| rest > splitThreshold =
((addr, Lovelace coinPerAddr) : acc, rest - coinPerAddr)
| otherwise = ((addr, Lovelace rest) : acc, 0)

mkStuffedUtxo :: [AddressInEra ShelleyEra] -> [(AddressInEra ShelleyEra, Lovelace)]
mkStuffedUtxo xs = (, Lovelace minUtxoVal) <$> xs
where (Coin minUtxoVal) = Shelley._minUTxOValue $ OT.sgProtocolParams template

shelleyDelKeys = ListMap
[ (gh, Ledger.GenDelegPair gdh h)
| (GenesisKeyHash gh,
(GenesisDelegateKeyHash gdh, VrfKeyHash h)) <- Map.toList genDelegMap
]

unLovelace :: Integral a => Lovelace -> a
unLovelace (Lovelace coin) = fromIntegral coin

writeFileGenesis
:: ToJSON genesis
=> FilePath
Expand Down Expand Up @@ -1230,3 +1315,13 @@ readAlonzoGenesis fpath = do
firstExceptT (ShelleyGenesisCmdAesonDecodeError fpath . Text.pack)
. hoistEither $ Aeson.eitherDecode' lbs

lazyReplicateM :: forall a. Int -> IO a -> IO [a]
lazyReplicateM n f = IO.unsafeInterleaveIO (go n)
where
go :: Int -> IO [a]
go i = if i > 0
then do
a <- f
as <- IO.unsafeInterleaveIO (go (i - 1))
return (a:as)
else return []
47 changes: 47 additions & 0 deletions cardano-cli/src/Cardano/CLI/Shelley/Run/Genesis/ListMap.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
module Cardano.CLI.Shelley.Run.Genesis.ListMap
( ListMap(..)
) where

import Data.Aeson (Value(..), ToJSON(..), ToJSON1(..), ToJSON2(..), ToJSONKey(..), ToJSONKeyFunction(..))
import Data.Aeson.Types ( listEncoding, listValue )
import Data.Aeson.Encoding ( dict )
import Data.Eq (Eq(..))
import Data.Function ((.), id)
import Prelude (uncurry)
import Text.Show (Show(..))

import qualified Data.Aeson as J
import qualified Data.Aeson.Encoding as E
import qualified Data.Aeson.KeyMap as KM
import qualified Data.List as L
import qualified Data.Vector as V

newtype ListMap k v = ListMap
{ unListMap :: [(k, v)]
} deriving (Eq, Show)

instance ToJSONKey k => ToJSON1 (ListMap k) where
liftToJSON g _ = case toJSONKey of
ToJSONKeyText f _ -> Object . KM.fromList . unListMap . mapKeyValO f g
ToJSONKeyValue f _ -> Array . V.fromList . L.map (toJSONPair f g) . unListMap

liftToEncoding g _ = case toJSONKey of
ToJSONKeyText _ f -> dict f g (foldrWithKey . uncurry)
ToJSONKeyValue _ f -> listEncoding (pairEncoding f) . unListMap
where
pairEncoding f (a, b) = E.list id [f a, g b]

instance (ToJSON v, ToJSONKey k) => ToJSON (ListMap k v) where
toJSON = J.toJSON1
toEncoding = J.toEncoding1

foldrWithKey :: ((k, a) -> b -> b) -> b -> ListMap k a -> b
foldrWithKey f z = L.foldr f z . unListMap

-- | Transform the keys and values of a 'M.Map'.
mapKeyValO :: (k1 -> k2) -> (v1 -> v2) -> ListMap k1 v1 -> ListMap k2 v2
mapKeyValO fk kv = ListMap . foldrWithKey (\(k, v) -> ((fk k, kv v):)) []
{-# INLINE mapKeyValO #-}

toJSONPair :: (a -> Value) -> (b -> Value) -> (a, b) -> Value
toJSONPair a b = liftToJSON2 a (listValue a) b (listValue b)
86 changes: 86 additions & 0 deletions cardano-cli/src/Cardano/CLI/Shelley/Run/Genesis/Types.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}

module Cardano.CLI.Shelley.Run.Genesis.Types
( OutputShelleyGenesis(..)
, ListMap(..)
, toOutputTemplate
) where

import Cardano.CLI.Shelley.Run.Genesis.ListMap (ListMap(..))
import Cardano.Ledger.Address (Addr)
import Cardano.Ledger.BaseTypes (PositiveUnitInterval, Network)
import Cardano.Ledger.Coin (Coin)
import Cardano.Ledger.Era (Era(Crypto))
import Cardano.Ledger.Keys (KeyHash, KeyRole(Genesis), GenDelegPair)
import Cardano.Ledger.Shelley.Genesis (ShelleyGenesisStaking)
import Cardano.Ledger.Shelley.PParams ( PParams )
import Cardano.Slotting.Slot (EpochSize(..))
import Data.Aeson (ToJSON(..), (.=))
import Data.Eq (Eq)
import Data.Time (NominalDiffTime, UTCTime(..))
import Data.Word (Word32, Word64)
import GHC.Generics (Generic)
import Text.Show (Show)

import qualified Data.Aeson as Aeson
import qualified Data.Map as Map
import qualified Cardano.Ledger.Shelley.Genesis as Ledger

data OutputShelleyGenesis era = OutputShelleyGenesis
{ sgSystemStart :: !UTCTime
, sgNetworkMagic :: !Word32
, sgNetworkId :: !Network
, sgActiveSlotsCoeff :: !PositiveUnitInterval
, sgSecurityParam :: !Word64
, sgEpochLength :: !EpochSize
, sgSlotsPerKESPeriod :: !Word64
, sgMaxKESEvolutions :: !Word64
, sgSlotLength :: !NominalDiffTime
, sgUpdateQuorum :: !Word64
, sgMaxLovelaceSupply :: !Word64
, sgProtocolParams :: !(PParams era)
, sgGenDelegs :: !(ListMap (KeyHash 'Genesis (Crypto era)) (GenDelegPair (Crypto era)))
, sgInitialFunds :: !(ListMap (Addr (Crypto era)) Coin)
, sgStaking :: !(ShelleyGenesisStaking (Crypto era))
}
deriving stock (Eq, Show, Generic)

instance Era era => ToJSON (OutputShelleyGenesis era) where
toJSON sg = Aeson.object
[ "systemStart" .= sgSystemStart sg,
"networkMagic" .= sgNetworkMagic sg,
"networkId" .= sgNetworkId sg,
"activeSlotsCoeff" .= sgActiveSlotsCoeff sg,
"securityParam" .= sgSecurityParam sg,
"epochLength" .= sgEpochLength sg,
"slotsPerKESPeriod" .= sgSlotsPerKESPeriod sg,
"maxKESEvolutions" .= sgMaxKESEvolutions sg,
"slotLength" .= sgSlotLength sg,
"updateQuorum" .= sgUpdateQuorum sg,
"maxLovelaceSupply" .= sgMaxLovelaceSupply sg,
"protocolParams" .= sgProtocolParams sg,
"genDelegs" .= sgGenDelegs sg,
"initialFunds" .= sgInitialFunds sg,
"staking" .= sgStaking sg
]

toOutputTemplate :: Ledger.ShelleyGenesis era -> OutputShelleyGenesis era
toOutputTemplate template = OutputShelleyGenesis
{ sgSystemStart = Ledger.sgSystemStart template
, sgNetworkMagic = Ledger.sgNetworkMagic template
, sgNetworkId = Ledger.sgNetworkId template
, sgActiveSlotsCoeff = Ledger.sgActiveSlotsCoeff template
, sgSecurityParam = Ledger.sgSecurityParam template
, sgEpochLength = Ledger.sgEpochLength template
, sgSlotsPerKESPeriod = Ledger.sgSlotsPerKESPeriod template
, sgMaxKESEvolutions = Ledger.sgMaxKESEvolutions template
, sgSlotLength = Ledger.sgSlotLength template
, sgUpdateQuorum = Ledger.sgUpdateQuorum template
, sgMaxLovelaceSupply = Ledger.sgMaxLovelaceSupply template
, sgProtocolParams = Ledger.sgProtocolParams template
, sgGenDelegs = ListMap (Map.toList (Ledger.sgGenDelegs template))
, sgInitialFunds = ListMap (Map.toList (Ledger.sgInitialFunds template))
, sgStaking = Ledger.sgStaking template
}

0 comments on commit 60eae8c

Please sign in to comment.