1
1
{-# LANGUAGE DerivingVia #-}
2
2
{-# LANGUAGE GADTs #-}
3
+ {-# LANGUAGE LambdaCase #-}
4
+ {-# LANGUAGE NamedFieldPuns #-}
3
5
{-# LANGUAGE OverloadedStrings #-}
4
6
{-# LANGUAGE ScopedTypeVariables #-}
5
7
{-# LANGUAGE TypeApplications #-}
@@ -12,16 +14,21 @@ module Testnet.Components.Configuration
12
14
13
15
, getByronGenesisHash
14
16
, getShelleyGenesisHash
17
+ , getDefaultAlonzoGenesis
18
+ , getDefaultShelleyGenesis
15
19
16
20
, anyEraToString
17
21
, eraToString
18
22
) where
19
23
24
+ import Cardano.Api.Ledger (StandardCrypto )
20
25
import Cardano.Api.Shelley hiding (Value , cardanoEra )
21
26
22
27
import Cardano.Chain.Genesis (GenesisHash (unGenesisHash ), readGenesisData )
23
28
import qualified Cardano.Crypto.Hash.Blake2b as Crypto
24
29
import qualified Cardano.Crypto.Hash.Class as Crypto
30
+ import Cardano.Ledger.Alonzo.Genesis (AlonzoGenesis )
31
+ import Cardano.Ledger.Conway.Genesis (ConwayGenesis )
25
32
import qualified Cardano.Node.Configuration.Topology as NonP2P
26
33
import qualified Cardano.Node.Configuration.TopologyP2P as P2P
27
34
import Ouroboros.Network.NodeToNode (DiffusionMode (.. ))
@@ -32,18 +39,21 @@ import Ouroboros.Network.PeerSelection.State.LocalRootPeers
32
39
33
40
import Control.Exception.Safe (MonadCatch )
34
41
import Control.Monad
42
+ import Control.Monad.Extra
35
43
import Data.Aeson
36
44
import qualified Data.Aeson as Aeson
37
45
import qualified Data.Aeson.Encode.Pretty as A
38
46
import Data.Aeson.Key hiding (fromString )
39
47
import Data.Aeson.KeyMap hiding (map )
40
48
import qualified Data.Aeson.Lens as L
49
+ import Data.Bifunctor (first )
41
50
import qualified Data.ByteString as BS
42
51
import qualified Data.ByteString.Lazy as LBS
43
52
import qualified Data.List as List
44
53
import Data.String
45
54
import Data.Text (Text )
46
55
import qualified Data.Text as Text
56
+ import qualified Data.Time.Clock as DTC
47
57
import Data.Word (Word64 )
48
58
import GHC.Stack (HasCallStack )
49
59
import qualified GHC.Stack as GHC
@@ -54,11 +64,11 @@ import System.FilePath.Posix (takeDirectory, (</>))
54
64
import Testnet.Defaults
55
65
import Testnet.Filepath
56
66
import Testnet.Process.Run (execCli_ )
57
- import Testnet.Start.Types (GenesisBatch (.. ), GenesisOrigin (.. ), NumDReps (.. ),
58
- NumPools (.. ), anyEraToString , anyShelleyBasedEraToString , eraToString )
67
+ import Testnet.Start.Types
59
68
60
69
import Hedgehog
61
70
import qualified Hedgehog as H
71
+ import qualified Hedgehog.Extras.Stock.OS as OS
62
72
import qualified Hedgehog.Extras.Stock.Time as DTC
63
73
import qualified Hedgehog.Extras.Test.Base as H
64
74
import qualified Hedgehog.Extras.Test.File as H
@@ -109,48 +119,76 @@ getShelleyGenesisHash path key = do
109
119
let genesisHash = Crypto. hashWith id content :: Crypto. Hash Crypto. Blake2b_256 BS. ByteString
110
120
pure . singleton (fromText key) $ toJSON genesisHash
111
121
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
+
112
150
numSeededUTxOKeys :: Int
113
151
numSeededUTxOKeys = 3
114
152
115
153
createSPOGenesisAndFiles
116
154
:: (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 )
122
160
-> TmpAbsolutePath
123
161
-> m FilePath -- ^ Shelley genesis directory
124
162
createSPOGenesisAndFiles
125
- nPoolNodes nDelReps maxSupply asbe @ ( AnyShelleyBasedEra sbe)
126
- ( GenesisBatch (shelleyGenesis, alonzoGenesis, conwayGenesis, genesisOrigin))
163
+ testnetOptions genesisOptions @ GenesisOptions {genesisTestnetMagic}
164
+ mShelleyGenesis mAlonzoGenesis mConwayGenesis
127
165
(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
139
167
140
168
let genesisShelleyDirAbs = takeDirectory inputGenesisShelleyFp
141
169
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
144
171
-- 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
147
182
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
154
192
155
193
-- TODO: create-testnet-data should have arguments for
156
194
-- Alonzo and Conway genesis that are optional and if not
@@ -159,47 +197,90 @@ createSPOGenesisAndFiles
159
197
H. note_ $ " Number of stake delegators: " <> show nPoolNodes
160
198
H. note_ $ " Number of seeded UTxO keys: " <> show numSeededUTxOKeys
161
199
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
+
164
209
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
170
216
, " --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
172
218
, " --stake-delegators" , show numStakeDelegators
173
219
, " --utxo-keys" , show numSeededUTxOKeys]
174
- <> monoidForEraInEon @ ConwayEraOnwards era (const [" --drep-keys" , show nDelReps ])
220
+ <> monoidForEraInEon @ ConwayEraOnwards era (const [" --drep-keys" , show cardanoNumDReps ])
175
221
<> [ " --start-time" , DTC. formatIso8601 startTime
176
222
, " --out-dir" , tempAbsPath
177
223
]
178
224
179
225
-- Overwrite the genesis files created by create-testnet-data with the files
180
226
-- 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
187
231
188
232
-- 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))
190
238
191
239
files <- H. listDirectory tempAbsPath
192
240
forM_ files H. note
193
241
194
242
return genesisShelleyDir
195
243
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]
197
275
-- Overwrites the genesis file created by create-testnet-data with the one provided by the user
198
276
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 ()
203
284
204
285
ifaceAddress :: String
205
286
ifaceAddress = " 127.0.0.1"
0 commit comments