Skip to content

Commit 02f1563

Browse files
committed
cardano-testnet: use one origin for each genesis file + use same type for node config and genesis files
1 parent a7d7206 commit 02f1563

File tree

6 files changed

+181
-172
lines changed

6 files changed

+181
-172
lines changed

cardano-testnet/cardano-testnet.cabal

+1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ library
6060
, Diff
6161
, directory
6262
, exceptions
63+
, extra
6364
, filepath
6465
, hedgehog
6566
, hedgehog-extras ^>= 0.7

cardano-testnet/src/Cardano/Testnet.hs

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ module Cardano.Testnet (
1313
TestnetNodeOptions(..),
1414
cardanoDefaultTestnetNodeOptions,
1515
getDefaultAlonzoGenesis,
16-
getDefaultGenesisBatch,
16+
getDefaultShelleyGenesis,
1717

1818
-- * Configuration
1919
Conf(..),

cardano-testnet/src/Testnet/Components/Configuration.hs

+132-51
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
{-# LANGUAGE DerivingVia #-}
22
{-# LANGUAGE GADTs #-}
3+
{-# LANGUAGE LambdaCase #-}
4+
{-# LANGUAGE NamedFieldPuns #-}
35
{-# LANGUAGE OverloadedStrings #-}
46
{-# LANGUAGE ScopedTypeVariables #-}
57
{-# LANGUAGE TypeApplications #-}
@@ -12,16 +14,21 @@ module Testnet.Components.Configuration
1214

1315
, getByronGenesisHash
1416
, getShelleyGenesisHash
17+
, getDefaultAlonzoGenesis
18+
, getDefaultShelleyGenesis
1519

1620
, anyEraToString
1721
, eraToString
1822
) where
1923

24+
import Cardano.Api.Ledger (StandardCrypto)
2025
import Cardano.Api.Shelley hiding (Value, cardanoEra)
2126

2227
import Cardano.Chain.Genesis (GenesisHash (unGenesisHash), readGenesisData)
2328
import qualified Cardano.Crypto.Hash.Blake2b as Crypto
2429
import qualified Cardano.Crypto.Hash.Class as Crypto
30+
import Cardano.Ledger.Alonzo.Genesis (AlonzoGenesis)
31+
import Cardano.Ledger.Conway.Genesis (ConwayGenesis)
2532
import qualified Cardano.Node.Configuration.Topology as NonP2P
2633
import qualified Cardano.Node.Configuration.TopologyP2P as P2P
2734
import Ouroboros.Network.NodeToNode (DiffusionMode (..))
@@ -32,18 +39,21 @@ import Ouroboros.Network.PeerSelection.State.LocalRootPeers
3239

3340
import Control.Exception.Safe (MonadCatch)
3441
import Control.Monad
42+
import Control.Monad.Extra
3543
import Data.Aeson
3644
import qualified Data.Aeson as Aeson
3745
import qualified Data.Aeson.Encode.Pretty as A
3846
import Data.Aeson.Key hiding (fromString)
3947
import Data.Aeson.KeyMap hiding (map)
4048
import qualified Data.Aeson.Lens as L
49+
import Data.Bifunctor (first)
4150
import qualified Data.ByteString as BS
4251
import qualified Data.ByteString.Lazy as LBS
4352
import qualified Data.List as List
4453
import Data.String
4554
import Data.Text (Text)
4655
import qualified Data.Text as Text
56+
import qualified Data.Time.Clock as DTC
4757
import Data.Word (Word64)
4858
import GHC.Stack (HasCallStack)
4959
import qualified GHC.Stack as GHC
@@ -54,11 +64,11 @@ import System.FilePath.Posix (takeDirectory, (</>))
5464
import Testnet.Defaults
5565
import Testnet.Filepath
5666
import Testnet.Process.Run (execCli_)
57-
import Testnet.Start.Types (GenesisBatch (..), GenesisOrigin (..), NumDReps (..),
58-
NumPools (..), anyEraToString, anyShelleyBasedEraToString, eraToString)
67+
import Testnet.Start.Types
5968

6069
import Hedgehog
6170
import qualified Hedgehog as H
71+
import qualified Hedgehog.Extras.Stock.OS as OS
6272
import qualified Hedgehog.Extras.Stock.Time as DTC
6373
import qualified Hedgehog.Extras.Test.Base as H
6474
import qualified Hedgehog.Extras.Test.File as H
@@ -109,48 +119,76 @@ getShelleyGenesisHash path key = do
109119
let genesisHash = Crypto.hashWith id content :: Crypto.Hash Crypto.Blake2b_256 BS.ByteString
110120
pure . singleton (fromText key) $ toJSON genesisHash
111121

122+
-- | For an unknown reason, CLI commands are a lot slower on Windows than on Linux and
123+
-- MacOS. We need to allow a lot more time to set up a testnet.
124+
startTimeOffsetSeconds :: DTC.NominalDiffTime
125+
startTimeOffsetSeconds = if OS.isWin32 then 90 else 15
126+
127+
-- | A start time and 'ShelleyGenesis' value that are fit to pass to 'cardanoTestnet'
128+
getDefaultShelleyGenesis :: ()
129+
=> HasCallStack
130+
=> MonadIO m
131+
=> MonadTest m
132+
=> AnyShelleyBasedEra
133+
-> Word64 -- ^ The max supply
134+
-> GenesisOptions
135+
-> m (ShelleyGenesis StandardCrypto)
136+
getDefaultShelleyGenesis asbe maxSupply opts = do
137+
currentTime <- H.noteShowIO DTC.getCurrentTime
138+
startTime <- H.noteShow $ DTC.addUTCTime startTimeOffsetSeconds currentTime
139+
return $ defaultShelleyGenesis asbe startTime maxSupply opts
140+
141+
-- | An 'AlonzoGenesis' value that is fit to pass to 'cardanoTestnet'
142+
getDefaultAlonzoGenesis :: ()
143+
=> HasCallStack
144+
=> MonadTest m
145+
=> ShelleyBasedEra era
146+
-> m AlonzoGenesis
147+
getDefaultAlonzoGenesis sbe =
148+
H.evalEither $ first prettyError (defaultAlonzoGenesis sbe)
149+
112150
numSeededUTxOKeys :: Int
113151
numSeededUTxOKeys = 3
114152

115153
createSPOGenesisAndFiles
116154
:: (MonadTest m, MonadCatch m, MonadIO m, HasCallStack)
117-
=> NumPools -- ^ The number of pools to make
118-
-> NumDReps -- ^ The number of pools to make
119-
-> Word64 -- ^ The maximum supply
120-
-> AnyShelleyBasedEra -- ^ The era to use
121-
-> GenesisBatch -- ^ The genesis to use, with their origin (whether they ar provided by the user or defaulted by 'cardano-testnet')
155+
=> CardanoTestnetOptions -- ^ The options to use
156+
-> GenesisOptions
157+
-> UserProvidedData (ShelleyGenesis StandardCrypto)
158+
-> UserProvidedData AlonzoGenesis
159+
-> UserProvidedData (ConwayGenesis StandardCrypto)
122160
-> TmpAbsolutePath
123161
-> m FilePath -- ^ Shelley genesis directory
124162
createSPOGenesisAndFiles
125-
nPoolNodes nDelReps maxSupply asbe@(AnyShelleyBasedEra sbe)
126-
(GenesisBatch (shelleyGenesis, alonzoGenesis, conwayGenesis, genesisOrigin))
163+
testnetOptions genesisOptions@GenesisOptions{genesisTestnetMagic}
164+
mShelleyGenesis mAlonzoGenesis mConwayGenesis
127165
(TmpAbsolutePath tempAbsPath) = GHC.withFrozenCallStack $ do
128-
let inputGenesisShelleyFp = tempAbsPath </> genesisInputFilepath ShelleyEra
129-
inputGenesisAlonzoFp = tempAbsPath </> genesisInputFilepath AlonzoEra
130-
inputGenesisConwayFp = tempAbsPath </> genesisInputFilepath ConwayEra
131-
132-
-- We write the genesis files to disk, to pass them to create-testnet-data.
133-
-- Then, create-testnet-data will output (possibly augmented/modified) versions
134-
-- and we remove those input files (see below), to avoid confusion.
135-
H.evalIO $ do
136-
LBS.writeFile inputGenesisShelleyFp $ A.encodePretty shelleyGenesis
137-
LBS.writeFile inputGenesisAlonzoFp $ A.encodePretty alonzoGenesis
138-
LBS.writeFile inputGenesisConwayFp $ A.encodePretty conwayGenesis
166+
AnyShelleyBasedEra sbe <- pure cardanoNodeEra
139167

140168
let genesisShelleyDirAbs = takeDirectory inputGenesisShelleyFp
141169
genesisShelleyDir <- H.createDirectoryIfMissing genesisShelleyDirAbs
142-
let testnetMagic = sgNetworkMagic shelleyGenesis
143-
-- At least there should be a delegator per DRep
170+
let -- At least there should be a delegator per DRep
144171
-- otherwise some won't be representing anybody
145-
numStakeDelegators = max 3 (fromIntegral nDelReps) :: Int
146-
startTime = sgSystemStart shelleyGenesis
172+
numStakeDelegators = max 3 (fromIntegral cardanoNumDReps) :: Int
173+
174+
-- When the user did not specify a genesis file, we write a default one to disk,
175+
-- and then pass it to create-testnet-data (which will amend it).
176+
-- When the user specifies a genesis file, we overwrite the default genesis file
177+
-- generated by create-testnet-data with the user's file
178+
-- (we still need to call create-testnet-data to generate keys)
179+
generateGenesisIfAbsent ShelleyEra mShelleyGenesis =<< getDefaultShelleyGenesis cardanoNodeEra cardanoMaxSupply genesisOptions
180+
generateGenesisIfAbsent AlonzoEra mAlonzoGenesis =<< getDefaultAlonzoGenesis sbe
181+
generateGenesisIfAbsent ConwayEra mConwayGenesis defaultConwayGenesis
147182

148-
-- TODO: Remove this rewrite.
149-
-- 50 second epochs
150-
-- Epoch length should be "10 * k / f" where "k = securityParam, f = activeSlotsCoeff"
151-
H.rewriteJsonFile @Value inputGenesisShelleyFp $ \o -> o
152-
& L.key "securityParam" . L._Integer .~ 5
153-
& L.key "updateQuorum" . L._Integer .~ 2
183+
case mShelleyGenesis of
184+
UserProvidedData _ -> pure () -- Don't touch the user's file
185+
NoUserProvidedData -> do
186+
-- TODO: Remove this rewrite.
187+
-- 50 second epochs
188+
-- Epoch length should be "10 * k / f" where "k = securityParam, f = activeSlotsCoeff"
189+
H.rewriteJsonFile @Value inputGenesisShelleyFp $ \o -> o
190+
& L.key "securityParam" . L._Integer .~ 5
191+
& L.key "updateQuorum" . L._Integer .~ 2
154192

155193
-- TODO: create-testnet-data should have arguments for
156194
-- Alonzo and Conway genesis that are optional and if not
@@ -159,47 +197,90 @@ createSPOGenesisAndFiles
159197
H.note_ $ "Number of stake delegators: " <> show nPoolNodes
160198
H.note_ $ "Number of seeded UTxO keys: " <> show numSeededUTxOKeys
161199

162-
let eraString = anyShelleyBasedEraToString asbe
163-
era = toCardanoEra sbe
200+
let era = toCardanoEra sbe
201+
startTime <-
202+
case mShelleyGenesis of
203+
UserProvidedData shelleyGenesis ->
204+
pure $ sgSystemStart shelleyGenesis
205+
NoUserProvidedData -> do
206+
currentTime <- H.noteShowIO DTC.getCurrentTime
207+
H.noteShow $ DTC.addUTCTime startTimeOffsetSeconds currentTime
208+
164209
execCli_ $
165-
[ eraString, "genesis", "create-testnet-data"
166-
, "--spec-shelley", inputGenesisShelleyFp
167-
, "--spec-alonzo", inputGenesisAlonzoFp
168-
, "--spec-conway", inputGenesisConwayFp
169-
, "--testnet-magic", show testnetMagic
210+
[ eraToString sbe, "genesis", "create-testnet-data" ]
211+
++ userGenesisToCreateTestnetDataFlag ShelleyEra mShelleyGenesis
212+
++ userGenesisToCreateTestnetDataFlag AlonzoEra mAlonzoGenesis
213+
++ userGenesisToCreateTestnetDataFlag ConwayEra mConwayGenesis
214+
++
215+
[ "--testnet-magic", show genesisTestnetMagic
170216
, "--pools", show nPoolNodes
171-
, "--total-supply", show maxSupply -- Half of this will be delegated, see https://github.com/IntersectMBO/cardano-cli/pull/874
217+
, "--total-supply", show cardanoMaxSupply -- Half of this will be delegated, see https://github.com/IntersectMBO/cardano-cli/pull/874
172218
, "--stake-delegators", show numStakeDelegators
173219
, "--utxo-keys", show numSeededUTxOKeys]
174-
<> monoidForEraInEon @ConwayEraOnwards era (const ["--drep-keys", show nDelReps])
220+
<> monoidForEraInEon @ConwayEraOnwards era (const ["--drep-keys", show cardanoNumDReps])
175221
<> [ "--start-time", DTC.formatIso8601 startTime
176222
, "--out-dir", tempAbsPath
177223
]
178224

179225
-- Overwrite the genesis files created by create-testnet-data with the files
180226
-- specified by the user (if any)
181-
case genesisOrigin of
182-
DefaultedOrigin -> pure ()
183-
UserProvidedOrigin -> do
184-
overwriteCreateTestnetDataGenesis inputGenesisShelleyFp ShelleyEra
185-
overwriteCreateTestnetDataGenesis inputGenesisAlonzoFp AlonzoEra
186-
overwriteCreateTestnetDataGenesis inputGenesisConwayFp ConwayEra
227+
liftIO $ do
228+
overwriteCreateTestnetDataGenesis ShelleyEra mShelleyGenesis
229+
overwriteCreateTestnetDataGenesis AlonzoEra mAlonzoGenesis
230+
overwriteCreateTestnetDataGenesis ConwayEra mConwayGenesis
187231

188232
-- Remove the input files. We don't need them anymore, since create-testnet-data wrote new versions.
189-
forM_ [inputGenesisShelleyFp, inputGenesisAlonzoFp, inputGenesisConwayFp] (liftIO . System.removeFile)
233+
forM_
234+
[ inputGenesisShelleyFp, inputGenesisAlonzoFp, inputGenesisConwayFp
235+
, tempAbsPath </> "byron.genesis.spec.json" -- Created by create-testnet-data
236+
]
237+
(\fp -> liftIO $ whenM (System.doesFileExist fp) (System.removeFile fp))
190238

191239
files <- H.listDirectory tempAbsPath
192240
forM_ files H.note
193241

194242
return genesisShelleyDir
195243
where
196-
genesisInputFilepath e = "genesis-input." <> eraToString e <> ".json"
244+
inputGenesisShelleyFp = genesisInputFilepath ShelleyEra
245+
inputGenesisAlonzoFp = genesisInputFilepath AlonzoEra
246+
inputGenesisConwayFp = genesisInputFilepath ConwayEra
247+
nPoolNodes = cardanoNumPools testnetOptions
248+
CardanoTestnetOptions{cardanoNodeEra, cardanoMaxSupply, cardanoNumDReps} = testnetOptions
249+
genesisInputFilepath :: Pretty (eon era) => eon era -> FilePath
250+
genesisInputFilepath e = tempAbsPath </> ("genesis-input." <> eraToString e <> ".json")
251+
generateGenesisIfAbsent ::
252+
Pretty (eon era) => MonadTest m => MonadIO m => ToJSON b =>
253+
eon era -> UserProvidedData a -> b -> m ()
254+
generateGenesisIfAbsent sbe userData genesis =
255+
case userData of
256+
UserProvidedData _ ->
257+
-- No need to give the genesis file to create-testnet-data. We use the user's one
258+
pure ()
259+
NoUserProvidedData -> do
260+
-- Let's generate a genesis file. It is used as a template by create-testnet-data
261+
liftIO $ LBS.writeFile (genesisInputFilepath sbe) $ A.encodePretty genesis
262+
userGenesisToCreateTestnetDataFlag ::
263+
Pretty (eon era) =>
264+
eon era -> UserProvidedData a -> [String]
265+
userGenesisToCreateTestnetDataFlag sbe = \case
266+
UserProvidedData _ ->
267+
-- Genesis file is provided by the user, so we don't need to pass a template one
268+
-- to create-testnet-data (we anyway overwrite the genesis file
269+
-- created by create-testnet-data in this case, see below)
270+
[]
271+
NoUserProvidedData ->
272+
-- We're in control, so we pass our template to create-testnet-data,
273+
-- which will amend it.
274+
["--spec-" ++ eraToString sbe, genesisInputFilepath sbe]
197275
-- Overwrites the genesis file created by create-testnet-data with the one provided by the user
198276
overwriteCreateTestnetDataGenesis ::
199-
Pretty (eon era) => MonadTest m => MonadIO m =>
200-
FilePath -> eon era -> m ()
201-
overwriteCreateTestnetDataGenesis genesisFp e =
202-
H.copyFile genesisFp (tempAbsPath </> eraToString e <> "-genesis.json")
277+
Pretty (eon era) => ToJSON a =>
278+
eon era -> UserProvidedData a -> IO ()
279+
overwriteCreateTestnetDataGenesis e = \case
280+
UserProvidedData a ->
281+
LBS.writeFile (tempAbsPath </> eraToString e <> "-genesis.json") $ A.encodePretty a
282+
NoUserProvidedData ->
283+
pure ()
203284

204285
ifaceAddress :: String
205286
ifaceAddress = "127.0.0.1"

0 commit comments

Comments
 (0)