2
{-# LANGUAGE GeneralisedNewtypeDeriving #-}
4
#if !defined(mingw32_HOST_OS)
8
module Cardano.CLI.Byron.Genesis
9
( ByronGenesisError(..)
10
, GenesisParameters(..)
15
, renderByronGenesisError
19
import Cardano.Prelude hiding (option, show, trace)
20
import Prelude (String)
22
import Control.Monad.Trans.Except.Extra (firstExceptT, left, right)
23
import qualified Data.ByteString as BS
24
import qualified Data.ByteString.Lazy as LB
25
import qualified Data.Map.Strict as Map
26
import Data.Text.Lazy.Builder (toLazyText)
27
import Data.Time (UTCTime)
28
import Formatting.Buildable
29
import Text.Printf (printf)
31
import System.Directory (createDirectory, doesPathExist)
32
import System.FilePath ((</>))
34
import System.Posix.Files (ownerReadMode, setFileMode)
36
import System.Directory (emptyPermissions, readable, setPermissions)
38
import Cardano.Api (Key (..), NetworkId)
39
import Cardano.Api.Byron (ByronKey, SerialiseAsRawBytes (..), SigningKey (..),
40
toByronRequiresNetworkMagic)
42
import qualified Cardano.Chain.Common as Common
43
import Cardano.Chain.Delegation hiding (Map, epoch)
44
import Cardano.Chain.Genesis (GeneratedSecrets (..))
45
import qualified Cardano.Chain.Genesis as Genesis
46
import qualified Cardano.Chain.UTxO as UTxO
48
import qualified Cardano.Crypto as Crypto
50
import Cardano.CLI.Byron.Delegation
51
import Cardano.CLI.Byron.Key
52
import Cardano.CLI.Helpers (textShow)
53
import Cardano.CLI.Types (GenesisFile (..))
55
data ByronGenesisError
56
= ByronDelegationCertSerializationError !ByronDelegationError
57
| ByronDelegationKeySerializationError ByronDelegationError
58
| GenesisGenerationError !Genesis.GenesisDataGenerationError
59
| GenesisOutputDirAlreadyExists FilePath
60
| GenesisReadError !FilePath !Genesis.GenesisDataError
61
| GenesisSpecError !Text
62
| MakeGenesisDelegationError !Genesis.GenesisDelegationError
63
| NoGenesisDelegationForKey !Text
64
| ProtocolParametersParseFailed !FilePath !Text
65
| PoorKeyFailure !ByronKeyFailure
69
renderByronGenesisError :: ByronGenesisError -> Text
70
renderByronGenesisError err =
72
ProtocolParametersParseFailed pParamFp parseError ->
73
"Protocol parameters parse failed at: " <> textShow pParamFp <> " Error: " <> parseError
74
ByronDelegationCertSerializationError bDelegSerErr ->
75
"Error while serializing the delegation certificate: " <> textShow bDelegSerErr
76
ByronDelegationKeySerializationError bKeySerErr ->
77
"Error while serializing the delegation key: " <> textShow bKeySerErr
78
PoorKeyFailure bKeyFailure ->
79
"Error creating poor keys: " <> textShow bKeyFailure
80
MakeGenesisDelegationError genDelegError ->
81
"Error creating genesis delegation: " <> textShow genDelegError
82
GenesisGenerationError genDataGenError ->
83
"Error generating genesis: " <> textShow genDataGenError
84
GenesisOutputDirAlreadyExists genOutDir ->
85
"Genesis output directory already exists: " <> textShow genOutDir
86
GenesisReadError genFp genDataError ->
87
"Error while reading genesis file at: " <> textShow genFp <> " Error: " <> textShow genDataError
88
GenesisSpecError genSpecError ->
89
"Error while creating genesis spec" <> textShow genSpecError
90
NoGenesisDelegationForKey verKey ->
91
"Error while creating genesis, no delegation certificate for this verification key:" <> textShow verKey
93
newtype NewDirectory =
95
deriving (Eq, Ord, Show, IsString)
97
-- | Parameters required for generation of new genesis.
98
data GenesisParameters = GenesisParameters
99
{ gpStartTime :: !UTCTime
100
, gpProtocolParamsFile :: !FilePath
101
, gpK :: !Common.BlockCount
102
, gpProtocolMagic :: !Crypto.ProtocolMagic
103
, gpTestnetBalance :: !Genesis.TestnetBalanceOptions
104
, gpFakeAvvmOptions :: !Genesis.FakeAvvmOptions
105
, gpAvvmBalanceFactor :: !Common.LovelacePortion
106
, gpSeed :: !(Maybe Integer)
110
mkGenesisSpec :: GenesisParameters -> ExceptT ByronGenesisError IO Genesis.GenesisSpec
111
mkGenesisSpec gp = do
112
protoParamsRaw <- lift . LB.readFile $ gpProtocolParamsFile gp
114
protocolParameters <- withExceptT
115
(ProtocolParametersParseFailed (gpProtocolParamsFile gp)) $
116
ExceptT . pure $ canonicalDecodePretty protoParamsRaw
118
-- We're relying on the generator to fake AVVM and delegation.
119
genesisDelegation <- withExceptT MakeGenesisDelegationError $
120
Genesis.mkGenesisDelegation []
122
withExceptT GenesisSpecError $
123
ExceptT . pure $ Genesis.mkGenesisSpec
124
(Genesis.GenesisAvvmBalances mempty)
129
(mkGenesisInitialiser True)
132
mkGenesisInitialiser :: Bool -> Genesis.GenesisInitializer
133
mkGenesisInitialiser useHeavyDlg =
134
Genesis.GenesisInitializer
135
(gpTestnetBalance gp)
136
(gpFakeAvvmOptions gp)
137
(Common.lovelacePortionToRational (gpAvvmBalanceFactor gp))
140
-- | Generate a genesis, for given blockchain start time, protocol parameters,
141
-- security parameter, protocol magic, testnet balance options, fake AVVM options,
142
-- AVVM balance factor and seed. Throw an error in the following cases: if the
143
-- protocol parameters file can't be read or fails parse, if genesis delegation
144
-- couldn't be generated, if the parameter-derived genesis specification is wrong,
145
-- or if the genesis fails generation.
148
-> ExceptT ByronGenesisError IO (Genesis.GenesisData, Genesis.GeneratedSecrets)
150
genesisSpec <- mkGenesisSpec gp
152
withExceptT GenesisGenerationError $
153
Genesis.generateGenesisData (gpStartTime gp) genesisSpec
155
-- | Read genesis from a file.
156
readGenesis :: GenesisFile
158
-> ExceptT ByronGenesisError IO Genesis.Config
159
readGenesis (GenesisFile file) nw =
160
firstExceptT (GenesisReadError file) $ do
161
(genesisData, genesisHash) <- Genesis.readGenesisData file
162
return Genesis.Config {
163
Genesis.configGenesisData = genesisData,
164
Genesis.configGenesisHash = genesisHash,
165
Genesis.configReqNetMagic = toByronRequiresNetworkMagic nw,
166
Genesis.configUTxOConfiguration = UTxO.defaultUTxOConfiguration
169
-- | Write out genesis into a directory that must not yet exist. An error is
170
-- thrown if the directory already exists, or the genesis has delegate keys that
171
-- are not delegated to.
174
-> Genesis.GenesisData
175
-> Genesis.GeneratedSecrets
176
-> ExceptT ByronGenesisError IO ()
177
dumpGenesis (NewDirectory outDir) genesisData gs = do
178
exists <- liftIO $ doesPathExist outDir
180
then left $ GenesisOutputDirAlreadyExists outDir
181
else liftIO $ createDirectory outDir
182
liftIO $ LB.writeFile genesisJSONFile (canonicalEncodePretty genesisData)
184
dlgCerts <- mapM findDelegateCert . map ByronSigningKey $ gsRichSecrets gs
186
liftIO $ wOut "genesis-keys" "key"
188
(map ByronSigningKey $ gsDlgIssuersSecrets gs)
189
liftIO $ wOut "delegate-keys" "key"
191
(map ByronSigningKey $ gsRichSecrets gs)
192
liftIO $ wOut "poor-keys" "key"
194
(map (ByronSigningKey . Genesis.poorSecretToKey) $ gsPoorSecrets gs)
195
liftIO $ wOut "delegation-cert" "json" serialiseDelegationCert dlgCerts
196
liftIO $ wOut "avvm-secrets" "secret" printFakeAvvmSecrets $ gsFakeAvvmSecrets gs
198
dlgCertMap :: Map Common.KeyHash Certificate
199
dlgCertMap = Genesis.unGenesisDelegation $ Genesis.gdHeavyDelegation genesisData
201
findDelegateCert :: SigningKey ByronKey -> ExceptT ByronGenesisError IO Certificate
202
findDelegateCert bSkey@(ByronSigningKey sk) =
203
case find (isCertForSK sk) (Map.elems dlgCertMap) of
204
Nothing -> left . NoGenesisDelegationForKey
205
. prettyPublicKey $ getVerificationKey bSkey
208
genesisJSONFile :: FilePath
209
genesisJSONFile = outDir <> "/genesis.json"
211
printFakeAvvmSecrets :: Crypto.RedeemSigningKey -> ByteString
212
printFakeAvvmSecrets rskey = encodeUtf8 . toStrict . toLazyText $ build rskey
214
-- Compare a given 'SigningKey' with a 'Certificate' 'VerificationKey'
215
isCertForSK :: Crypto.SigningKey -> Certificate -> Bool
216
isCertForSK sk cert = delegateVK cert == Crypto.toVerification sk
218
wOut :: String -> String -> (a -> ByteString) -> [a] -> IO ()
219
wOut = writeSecrets outDir
221
writeSecrets :: FilePath -> String -> String -> (a -> ByteString) -> [a] -> IO ()
222
writeSecrets outDir prefix suffix secretOp xs =
223
forM_ (zip xs [0::Int ..]) $
225
let filename = outDir </> prefix <> "." <> printf "%03d" nr <> "." <> suffix
226
BS.writeFile filename $ secretOp secret
228
setFileMode filename ownerReadMode
230
setPermissions filename (emptyPermissions {readable = True})