-
Notifications
You must be signed in to change notification settings - Fork 753
Expand file tree
/
Copy pathConfiguration.hs
More file actions
306 lines (264 loc) · 11.2 KB
/
Configuration.hs
File metadata and controls
306 lines (264 loc) · 11.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE NumericUnderscores #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
module Testnet.Components.Configuration
( createConfigJson
, createConfigJsonNoHash
, createSPOGenesisAndFiles
, numSeededUTxOKeys
, getByronGenesisHash
, getShelleyGenesisHash
, getDefaultAlonzoGenesis
, getDefaultShelleyGenesis
, startTimeOffsetSeconds
, anyEraToString
, eraToString
) where
import Cardano.Api hiding (Value, cardanoEra)
import Cardano.Api.Ledger (AlonzoGenesis, ConwayGenesis)
import Cardano.Chain.Genesis (GenesisHash (unGenesisHash), readGenesisData)
import qualified Cardano.Crypto.Hash.Blake2b as Crypto
import qualified Cardano.Crypto.Hash.Class as Crypto
import Cardano.Ledger.BaseTypes (unsafeNonZero)
import Cardano.Ledger.Dijkstra.Genesis (DijkstraGenesis)
import Cardano.Node.Protocol.Byron
import Control.Concurrent (threadDelay)
import Control.Exception
import Control.Monad
import Control.Monad.Extra
import Data.Aeson
import qualified Data.Aeson as Aeson
import qualified Data.Aeson.Encode.Pretty as A
import Data.Aeson.Key hiding (fromString)
import Data.Aeson.KeyMap hiding (map)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as LBS
import qualified Data.Text as Text
import qualified Data.Time.Clock as DTC
import Data.Word (Word64)
import GHC.Stack (HasCallStack)
import qualified GHC.Stack as GHC
import qualified Network.HTTP.Simple as HTTP
import RIO ( MonadThrow, throwM)
import System.IO (hPutStrLn, stderr)
import qualified System.Directory as System
import System.FilePath.Posix (takeDirectory, (</>))
import Testnet.Blockfrost (BlockfrostParams, blockfrostToGenesis)
import qualified Testnet.Defaults as Defaults
import Testnet.Filepath
import Testnet.Process.RunIO (execCli_, liftIOAnnotated)
import Testnet.Start.Types
import qualified Hedgehog.Extras.Stock.OS as OS
import qualified Hedgehog.Extras.Stock.Time as DTC
-- | Returns JSON encoded hashes of the era, as well as the hard fork configuration toggle.
createConfigJson :: ()
=> HasCallStack
=> MonadIO m
=> MonadThrow m
=> TmpAbsolutePath
-> ShelleyBasedEra era -- ^ The era used for generating the hard fork configuration toggle
-> m (KeyMap Aeson.Value)
createConfigJson (TmpAbsolutePath tempAbsPath) sbe = GHC.withFrozenCallStack $ do
byronGenesisHash <- getByronGenesisHash $ tempAbsPath </> "byron-genesis.json"
shelleyGenesisHash <- getHash ShelleyEra "ShelleyGenesisHash"
alonzoGenesisHash <- getHash AlonzoEra "AlonzoGenesisHash"
conwayGenesisHash <- getHash ConwayEra "ConwayGenesisHash"
dijkstraGenesisHash <- getHash DijkstraEra "DijkstraGenesisHash"
pure $ mconcat
[ byronGenesisHash
, shelleyGenesisHash
, alonzoGenesisHash
, conwayGenesisHash
, dijkstraGenesisHash
, Defaults.defaultYamlHardforkViaConfig sbe
]
where
getHash :: MonadIO m => CardanoEra a -> Text.Text -> m (KeyMap Value)
getHash e = getShelleyGenesisHash (tempAbsPath </> Defaults.defaultGenesisFilepath e)
createConfigJsonNoHash :: ()
=> ShelleyBasedEra era -- ^ The era used for generating the hard fork configuration toggle
-> KeyMap Aeson.Value
createConfigJsonNoHash = Defaults.defaultYamlHardforkViaConfig
-- Generate hashes for genesis.json files
getByronGenesisHash
:: MonadIO m
=> MonadThrow m
=> FilePath
-> m (KeyMap Aeson.Value)
getByronGenesisHash path = do
e <- runExceptT $ readGenesisData path
case e of
Left err -> throwM $ GenesisReadError path err
Right (_, genesisHash) -> do
let genesisHash' = unGenesisHash genesisHash
pure . singleton "ByronGenesisHash" $ toJSON genesisHash'
getShelleyGenesisHash
:: MonadIO m
=> FilePath
-> Text
-> m (KeyMap Aeson.Value)
getShelleyGenesisHash path key = do
content <- liftIOAnnotated $ BS.readFile path
let genesisHash = Crypto.hashWith id content :: Crypto.Hash Crypto.Blake2b_256 BS.ByteString
pure . singleton (fromText key) $ toJSON genesisHash
-- | For an unknown reason, CLI commands are a lot slower on Windows than on Linux and
-- MacOS. We need to allow a lot more time to set up a testnet.
startTimeOffsetSeconds :: DTC.NominalDiffTime
startTimeOffsetSeconds = if OS.isWin32 then 90 else 15
-- | A start time and 'ShelleyGenesis' value that are fit to pass to 'cardanoTestnet'
getDefaultShelleyGenesis :: ()
=> MonadIO m
=> AnyShelleyBasedEra
-> Word64 -- ^ The max supply
-> GenesisOptions
-> m ShelleyGenesis
getDefaultShelleyGenesis asbe maxSupply opts = do
currentTime <- liftIOAnnotated DTC.getCurrentTime
let startTime = DTC.addUTCTime startTimeOffsetSeconds currentTime
return $ Defaults.defaultShelleyGenesis asbe startTime maxSupply opts
-- | An 'AlonzoGenesis' value that is fit to pass to 'cardanoTestnet'
getDefaultAlonzoGenesis :: ()
=> HasCallStack
=> MonadThrow m
=> m AlonzoGenesis
getDefaultAlonzoGenesis =
case Defaults.defaultAlonzoGenesis of
Right genesis -> return genesis
Left err -> throwM err
numSeededUTxOKeys :: Int
numSeededUTxOKeys = 3
createSPOGenesisAndFiles
:: MonadIO m
=> HasCallStack
=> MonadThrow m
=> CardanoTestnetOptions -- ^ The options to use
-> GenesisOptions
-> TestnetOnChainParams
-> TmpAbsolutePath
-> m FilePath -- ^ Shelley genesis directory
createSPOGenesisAndFiles
testnetOptions genesisOptions@GenesisOptions{genesisTestnetMagic}
onChainParams
(TmpAbsolutePath tempAbsPath) = do
AnyShelleyBasedEra sbe <- pure cardanoNodeEra
let genesisShelleyDir = takeDirectory inputGenesisShelleyFp
liftIOAnnotated $ System.createDirectoryIfMissing True genesisShelleyDir
let -- At least there should be a delegator per DRep
-- otherwise some won't be representing anybody
numStakeDelegators = max 3 (fromIntegral cardanoNumDReps) :: Int
shelleyGenesis'' <- getDefaultShelleyGenesis cardanoNodeEra cardanoMaxSupply genesisOptions
-- TODO: Remove this rewrite.
-- 50 second epochs
-- Epoch length should be "10 * k / f" where "k = securityParam, f = activeSlotsCoeff"
let shelleyGenesis' = shelleyGenesis''
{ sgSecurityParam = unsafeNonZero 5
, sgUpdateQuorum = 2
}
alonzoGenesis' <- getDefaultAlonzoGenesis
let conwayGenesis' = Defaults.defaultConwayGenesis
dijkstraGenesis' = dijkstraGenesisDefaults
(shelleyGenesis, alonzoGenesis, conwayGenesis, dijkstraGenesis)
<- resolveOnChainParams onChainParams
(shelleyGenesis', alonzoGenesis', conwayGenesis', dijkstraGenesis')
-- Write Genesis files to disk, so they can be picked up by create-testnet-data
liftIOAnnotated $ do
LBS.writeFile inputGenesisAlonzoFp $ A.encodePretty alonzoGenesis
LBS.writeFile inputGenesisConwayFp $ A.encodePretty conwayGenesis
LBS.writeFile inputGenesisShelleyFp $ A.encodePretty shelleyGenesis
LBS.writeFile inputGenesisDijkstraFp $ A.encodePretty dijkstraGenesis
let era = toCardanoEra sbe
currentTime <- liftIOAnnotated DTC.getCurrentTime
let startTime = DTC.addUTCTime startTimeOffsetSeconds currentTime
execCli_ $
[ eraToString sbe, "genesis", "create-testnet-data" ]
++ createTestnetDataFlag ShelleyEra
++ createTestnetDataFlag AlonzoEra
++ createTestnetDataFlag ConwayEra
++ createTestnetDataFlag DijkstraEra
++
[ "--testnet-magic", show genesisTestnetMagic
, "--pools", show nPoolNodes
, "--total-supply", show cardanoMaxSupply -- Half of this will be delegated, see https://github.com/IntersectMBO/cardano-cli/pull/874
, "--stake-delegators", show numStakeDelegators
, "--utxo-keys", show numSeededUTxOKeys]
<> monoidForEraInEon @ConwayEraOnwards era (const ["--drep-keys", show cardanoNumDReps])
<> [ "--start-time", DTC.formatIso8601 startTime
, "--out-dir", tempAbsPath
]
-- Remove the input files. We don't need them anymore, since create-testnet-data wrote new versions.
forM_
[ inputGenesisShelleyFp, inputGenesisAlonzoFp, inputGenesisConwayFp
, tempAbsPath </> "byron.genesis.spec.json" -- Created by create-testnet-data
]
(\fp -> liftIOAnnotated $ whenM (System.doesFileExist fp) (System.removeFile fp))
return genesisShelleyDir
where
inputGenesisShelleyFp = genesisInputFilepath ShelleyEra
inputGenesisAlonzoFp = genesisInputFilepath AlonzoEra
inputGenesisConwayFp = genesisInputFilepath ConwayEra
inputGenesisDijkstraFp = genesisInputFilepath DijkstraEra
nPoolNodes = cardanoNumPools testnetOptions
CardanoTestnetOptions{cardanoNodeEra, cardanoMaxSupply, cardanoNumDReps} = testnetOptions
genesisInputFilepath :: Pretty (eon era) => eon era -> FilePath
genesisInputFilepath e = tempAbsPath </> ("genesis-input." <> eraToString e <> ".json")
createTestnetDataFlag :: Pretty (eon era) => eon era -> [String]
createTestnetDataFlag sbe =
["--spec-" ++ eraToString sbe, genesisInputFilepath sbe]
data BlockfrostParamsError = BlockfrostParamsDecodeError FilePath String
deriving Show
instance Exception BlockfrostParamsError where
displayException (BlockfrostParamsDecodeError fp err) =
"Failed to decode Blockfrost on-chain parameters from file "
<> fp
<> ": "
<> err
newtype MainnetParamsFetchError = MainnetParamsFetchError SomeException
deriving Show
instance Exception MainnetParamsFetchError where
displayException (MainnetParamsFetchError exc) =
"Failed to fetch mainnet on-chain parameters from GitHub after retries: "
<> displayException exc
-- | Resolves different kinds of user-provided on-chain parameters
-- into a unified, consistent set of Genesis files
resolveOnChainParams :: ()
=> HasCallStack
=> MonadIO m
=> MonadThrow m
=> TestnetOnChainParams
-> (ShelleyGenesis, AlonzoGenesis, ConwayGenesis, DijkstraGenesis)
-> m (ShelleyGenesis, AlonzoGenesis, ConwayGenesis, DijkstraGenesis)
resolveOnChainParams onChainParams geneses = case onChainParams of
DefaultParams -> do
pure geneses
OnChainParamsFile file -> do
eParams <- eitherDecode <$> liftIOAnnotated (LBS.readFile file)
case eParams of
Right params -> pure $ blockfrostToGenesis geneses params
Left err -> throwM $ BlockfrostParamsDecodeError file err
OnChainParamsMainnet -> do
blockfrostToGenesis geneses <$> fetchMainnetParams
where
maxRetries = 3 :: Int
retryDelaySec = 2_000_000 -- 2 seconds in microseconds
fetchMainnetParams :: (MonadIO m, MonadThrow m) => m BlockfrostParams
fetchMainnetParams = go maxRetries
where
go n = do
result <- liftIO $ try @HTTP.HttpException $
HTTP.getResponseBody <$> HTTP.httpJSON mainnetParamsRequest
case result of
Right params -> pure params
Left exc
| n > 0 -> do
liftIO $ hPutStrLn stderr $
displayException exc
<> "\n\nFailed to fetch mainnet parameters (retrying, "
<> show n <> " attempts left)"
liftIO $ threadDelay retryDelaySec
go (n - 1)
| otherwise ->
throwM $ MainnetParamsFetchError (toException exc)