diff --git a/cardano-cli/cardano-cli.cabal b/cardano-cli/cardano-cli.cabal index 9a3be6bdf1..db2f9e4f55 100644 --- a/cardano-cli/cardano-cli.cabal +++ b/cardano-cli/cardano-cli.cabal @@ -306,6 +306,7 @@ test-suite cardano-cli-test type: exitcode-stdio-1.0 build-depends: aeson, + aeson-pretty, base16-bytestring, bech32 >=1.1.0, bytestring, diff --git a/cardano-cli/src/Cardano/CLI/EraBased/Commands/Genesis.hs b/cardano-cli/src/Cardano/CLI/EraBased/Commands/Genesis.hs index b37258c7bb..9d57db42dd 100644 --- a/cardano-cli/src/Cardano/CLI/EraBased/Commands/Genesis.hs +++ b/cardano-cli/src/Cardano/CLI/EraBased/Commands/Genesis.hs @@ -94,6 +94,8 @@ data GenesisCreateStakedCmdArgs era = GenesisCreateStakedCmdArgs data GenesisCreateTestNetDataCmdArgs era = GenesisCreateTestNetDataCmdArgs { eon :: !(ShelleyBasedEra era) + , specNodeConfig :: !(Maybe FilePath) + -- ^ Path to the node configuration file to use. If unspecified, a default one will be used. , specShelley :: !(Maybe FilePath) -- ^ Path to the @genesis-shelley@ file to use. If unspecified, a default one will be used. , specAlonzo :: !(Maybe FilePath) diff --git a/cardano-cli/src/Cardano/CLI/EraBased/Options/Genesis.hs b/cardano-cli/src/Cardano/CLI/EraBased/Options/Genesis.hs index b427bad9f3..559d3e001f 100644 --- a/cardano-cli/src/Cardano/CLI/EraBased/Options/Genesis.hs +++ b/cardano-cli/src/Cardano/CLI/EraBased/Options/Genesis.hs @@ -218,7 +218,8 @@ pGenesisCreateTestNetData :: ShelleyBasedEra era -> EnvCli -> Parser (GenesisCmd pGenesisCreateTestNetData sbe envCli = fmap GenesisCreateTestNetData $ GenesisCreateTestNetDataCmdArgs sbe - <$> optional (pSpecFile "shelley") + <$> optional pNodeFile + <*> optional (pSpecFile "shelley") <*> optional (pSpecFile "alonzo") <*> optional (pSpecFile "conway") <*> pNumGenesisKeys @@ -234,6 +235,10 @@ pGenesisCreateTestNetData sbe envCli = <*> pMaybeSystemStart <*> pOutputDir where + pNodeFile = + parseFilePath + "node-configuration" + "The node configuration file to use. Entries for hashes and paths of genesis files are checked if they exist. Otherwise they are filled in." pSpecFile eraStr = parseFilePath ("spec-" <> eraStr) diff --git a/cardano-cli/src/Cardano/CLI/EraBased/Run/Genesis.hs b/cardano-cli/src/Cardano/CLI/EraBased/Run/Genesis.hs index 8949681677..4a53013558 100644 --- a/cardano-cli/src/Cardano/CLI/EraBased/Run/Genesis.hs +++ b/cardano-cli/src/Cardano/CLI/EraBased/Run/Genesis.hs @@ -494,26 +494,15 @@ runGenesisCreateCardanoCmd writeFileGenesis (rootdir "conway-genesis.json") $ WritePretty conwayGenesis liftIO $ do - case mNodeConfigTemplate of - Nothing -> pure () - Just nodeCfg -> do - nodeConfig <- Yaml.decodeFileThrow nodeCfg - let - setHash field hash = Aeson.insert field $ String $ Crypto.hashToTextAsHex hash - updateConfig :: Yaml.Value -> Yaml.Value - updateConfig (Object obj) = - Object $ - setHash "ByronGenesisHash" byronGenesisHash $ - setHash "ShelleyGenesisHash" shelleyGenesisHash $ - setHash "AlonzoGenesisHash" alonzoGenesisHash $ - setHash - "ConwayGenesisHash" - conwayGenesisHash - obj - updateConfig x = x - newConfig :: Yaml.Value - newConfig = updateConfig nodeConfig - encodeFile (rootdir "node-config.json") newConfig + forM_ mNodeConfigTemplate $ \nodeCfg -> do + let hashes = + Map.fromList + [ ("ByronGenesisHash", byronGenesisHash) + , ("ShelleyGenesisHash", shelleyGenesisHash) + , ("AlonzoGenesisHash", alonzoGenesisHash) + , ("ConwayGenesisHash", conwayGenesisHash) + ] + writeGenesisHashesToNodeConfigFile nodeCfg hashes (rootdir "node-config.json") where convertToShelleyError = withExceptT GenesisCmdByronError convertGenesisKey :: Byron.SigningKey -> SigningKey GenesisExtendedKey @@ -572,6 +561,30 @@ runGenesisCreateCardanoCmd dlgCertMap :: Genesis.GenesisData -> Map Byron.KeyHash Dlg.Certificate dlgCertMap byronGenesis = Genesis.unGenesisDelegation $ Genesis.gdHeavyDelegation byronGenesis +-- | @writeGenesisHashesToNodeConfigFile src hashes dest@ reads the node configuration file +-- at @src@ and the writes an augmented version of this file at @dest@, with the hashes. +writeGenesisHashesToNodeConfigFile + :: () + => MonadIO m + => FilePath + -- ^ From where to read the node configuration file + -> Map.Map Aeson.Key (Crypto.Hash h a) + -- ^ Key of an era's hash (like "ByronGenesisHash", "ShelleyGenesisHash", etc.), to the hash of its genesis file + -> FilePath + -- ^ Where to write the updated node config file + -> m () +writeGenesisHashesToNodeConfigFile sourcePath hashes destinationPath = liftIO $ do + nodeConfig <- Yaml.decodeFileThrow sourcePath + let newConfig = foldr updateConfigHash nodeConfig $ Map.toList hashes + Aeson.encodeFile destinationPath newConfig + where + setHash field hash = Aeson.insert field $ Aeson.String $ Crypto.hashToTextAsHex hash + updateConfigHash :: (Aeson.Key, Crypto.Hash h a) -> Yaml.Value -> Yaml.Value + updateConfigHash (field, hash) = + \case + Aeson.Object obj -> Aeson.Object $ setHash field hash obj + v -> v + runGenesisCreateStakedCmd :: GenesisCreateStakedCmdArgs era -> ExceptT GenesisCmdError IO () diff --git a/cardano-cli/src/Cardano/CLI/EraBased/Run/Genesis/CreateTestnetData.hs b/cardano-cli/src/Cardano/CLI/EraBased/Run/Genesis/CreateTestnetData.hs index 763710cd4e..04c095eab1 100644 --- a/cardano-cli/src/Cardano/CLI/EraBased/Run/Genesis/CreateTestnetData.hs +++ b/cardano-cli/src/Cardano/CLI/EraBased/Run/Genesis/CreateTestnetData.hs @@ -49,12 +49,17 @@ import Cardano.CLI.Types.Errors.GenesisCmdError import Cardano.CLI.Types.Errors.NodeCmdError import Cardano.CLI.Types.Errors.StakePoolCmdError import Cardano.CLI.Types.Key +import qualified Cardano.Crypto.Hash as Crypto import Ouroboros.Consensus.Shelley.Node (ShelleyGenesisStaking (..)) import Control.DeepSeq (NFData, deepseq) import Control.Monad (forM, forM_, unless, void, when) import qualified Data.Aeson as Aeson +import qualified Data.Aeson.Encode.Pretty as Aeson +import qualified Data.Aeson.Key as Aeson +import qualified Data.Aeson.KeyMap as Aeson import Data.Bifunctor (Bifunctor (..)) +import qualified Data.ByteString as BS import qualified Data.ByteString.Lazy.Char8 as LBS import Data.ListMap (ListMap (..)) import Data.Map.Strict (Map) @@ -65,6 +70,7 @@ import Data.String (fromString) import qualified Data.Text as Text import Data.Tuple (swap) import Data.Word (Word64) +import qualified Data.Yaml as Yaml import GHC.Exts (IsList (..)) import GHC.Generics (Generic) import GHC.Num (Natural) @@ -170,6 +176,7 @@ runGenesisCreateTestNetDataCmd Cmd.GenesisCreateTestNetDataCmdArgs { eon , networkId + , specNodeConfig , specShelley , specAlonzo , specConway @@ -321,9 +328,37 @@ runGenesisCreateTestNetDataCmd shelleyGenesis -- Write genesis.json file to output - liftIO $ LBS.writeFile (outputDir "conway-genesis.json") $ Aeson.encode conwayGenesis' - liftIO $ LBS.writeFile (outputDir "shelley-genesis.json") $ Aeson.encode shelleyGenesis' - liftIO $ LBS.writeFile (outputDir "alonzo-genesis.json") $ Aeson.encode alonzoGenesis + let conwayGenesisFilename = "conway-genesis.json" + shelleyGenesisFilename = "shelley-genesis.json" + alonzoGenesisFilename = "alonzo-genesis.json" + conwayGenesisPath = outputDir conwayGenesisFilename + shelleyGenesisPath = outputDir shelleyGenesisFilename + alonzoGenesisPath = outputDir alonzoGenesisFilename + liftIO $ LBS.writeFile conwayGenesisPath $ Aeson.encodePretty conwayGenesis' + liftIO $ LBS.writeFile shelleyGenesisPath $ Aeson.encodePretty shelleyGenesis' + liftIO $ LBS.writeFile alonzoGenesisPath $ Aeson.encodePretty alonzoGenesis + + case specNodeConfig of + Nothing -> {- Don't do anything for now -} pure () + Just inputNodeConfigPath -> do + let outputNodeConfigPath = outputDir "configuration.json" + addOrCheckHash k v = addOrCheck inputNodeConfigPath k (Crypto.hashToTextAsHex v) + addOrCheckPath k v = addOrCheck inputNodeConfigPath k (Text.pack v) + conwayGenesisHash <- getShelleyOnwardsGenesisHash conwayGenesisPath + shelleyGenesisHash <- getShelleyOnwardsGenesisHash shelleyGenesisPath + alonzoGenesisHash <- getShelleyOnwardsGenesisHash alonzoGenesisPath + nodeConfig <- Yaml.decodeFileThrow inputNodeConfigPath + nodeConfigToWrite <- + except $ + -- Write hashs + addOrCheckHash "ConwayGenesisHash" conwayGenesisHash nodeConfig + >>= addOrCheckHash "ShelleyGenesisHash" shelleyGenesisHash + >>= addOrCheckHash "AlonzoGenesisHash" alonzoGenesisHash + -- Write paths + >>= addOrCheckPath "ConwayGenesisFile" conwayGenesisFilename + >>= addOrCheckPath "ShelleyGenesisFile" shelleyGenesisFilename + >>= addOrCheckPath "AlonzoGenesisFile" alonzoGenesisFilename + liftIO $ LBS.writeFile outputNodeConfigPath $ Aeson.encodePretty nodeConfigToWrite where genesisDir = outputDir "genesis-keys" delegateDir = outputDir "delegate-keys" @@ -335,6 +370,30 @@ runGenesisCreateTestNetDataCmd :: Delegation -> (L.KeyHash L.Staking L.StandardCrypto, L.PoolParams L.StandardCrypto) mkDelegationMapEntry d = (dDelegStaking d, dPoolParams d) + -- @addOrCheck filepath key expectedValue obj @ checks + -- if @obj@ maps @key@. If it does, it checks that the value is @expectedValue@. + -- If @key@ is not mapped, the mapping @key -> expectedValue@ is inserted. + addOrCheck :: FilePath -> Aeson.Key -> Text.Text -> Yaml.Value -> Either GenesisCmdError Yaml.Value + addOrCheck filepath key expectedValue nodeConfig@(Aeson.Object obj) = + case Aeson.lookup key obj of + Nothing -> + -- Key of hash is not there, insert it + pure $ Aeson.Object $ Aeson.insert key (Aeson.String expectedValue) obj + Just (Aeson.String seen) + | seen == expectedValue -> + -- Hash is there and it's correct: no change + pure nodeConfig + Just (Aeson.String seen) -> + -- Hash is there, but it's incorrect: fail + Left $ GenesisCmdWrongGenesisHash filepath (Aeson.toText key) seen expectedValue + _ -> + Left $ + GenesisCmdWrongNodeConfigFile + filepath + ("Expected a String at key \"" <> Aeson.toText key <> "\", but found something else") + addOrCheck filepath _ _ _ = + Left $ GenesisCmdWrongNodeConfigFile filepath "Expected Object at the top-level" + addDRepsToConwayGenesis :: [VerificationKey DRepKey] -> [VerificationKey StakeKey] @@ -408,6 +467,15 @@ runGenesisCreateTestNetDataCmd rest <- mapAccumM f a' t return $ h' : rest + --- | Read the given file and hashes it using 'Blake2b_256' + getShelleyOnwardsGenesisHash + :: MonadIO m + => FilePath + -> m (Crypto.Hash Crypto.Blake2b_256 BS.ByteString) + getShelleyOnwardsGenesisHash path = do + content <- liftIO $ BS.readFile path + return $ Crypto.hashWith @Crypto.Blake2b_256 id content + -- | The output format used all along this file desiredKeyOutputFormat :: KeyOutputFormat desiredKeyOutputFormat = KeyOutputFormatTextEnvelope diff --git a/cardano-cli/src/Cardano/CLI/Types/Errors/GenesisCmdError.hs b/cardano-cli/src/Cardano/CLI/Types/Errors/GenesisCmdError.hs index aa9e92f05c..e391d8502a 100644 --- a/cardano-cli/src/Cardano/CLI/Types/Errors/GenesisCmdError.hs +++ b/cardano-cli/src/Cardano/CLI/Types/Errors/GenesisCmdError.hs @@ -44,6 +44,14 @@ data GenesisCmdError !(VerificationKeyFile In) !Text !SomeAddressVerificationKey + | -- | @GenesisCmdWrongNodeConfigFile path error@ indicates + -- that the node configuration at @path@ is badly formed. @error@ + -- gives details about the error + GenesisCmdWrongNodeConfigFile !FilePath !Text + | -- | @GenesisCmdWrongGenesisHash path key seen expected@ indicates + -- that the node configuration at @path@ has the wrong value @seen@ for @key@. + -- The value should be @expected@ instead. + GenesisCmdWrongGenesisHash !FilePath !Text !Text !Text deriving Show instance Error GenesisCmdError where @@ -133,3 +141,18 @@ instance Error GenesisCmdError where <> "." <> "This is incorrect: the delegated supply should be less or equal to the total supply." <> " Note that the total supply can either come from --total-supply or from the default template. Please fix what you use." + GenesisCmdWrongNodeConfigFile path err -> + "Node configuration file at " + <> pretty path + <> " is badly formed: " + <> pretty err + GenesisCmdWrongGenesisHash path key seen expected -> + "Hash associated to key \"" + <> pretty key + <> "\" in file " + <> pretty path + <> " is wrong. The value in the file is " + <> pretty seen + <> " whereas " + <> pretty expected + <> " is expected." diff --git a/cardano-cli/test/cardano-cli-golden/Test/Golden/CreateTestnetData.hs b/cardano-cli/test/cardano-cli-golden/Test/Golden/CreateTestnetData.hs index a7205a38c5..d9ef15b259 100644 --- a/cardano-cli/test/cardano-cli-golden/Test/Golden/CreateTestnetData.hs +++ b/cardano-cli/test/cardano-cli-golden/Test/Golden/CreateTestnetData.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE NumericUnderscores #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TupleSections #-} @@ -11,10 +12,13 @@ import Cardano.Api.Shelley (ShelleyGenesis (..)) import qualified Cardano.Ledger.Shelley.API as L import Control.Monad +import Control.Monad.Catch (MonadCatch) +import Control.Monad.Trans.Control (MonadBaseControl) import Data.List (intercalate, sort) import qualified Data.Sequence.Strict as Seq import Data.Word (Word32) import GHC.Exts (IsList (..)) +import GHC.Stack (HasCallStack) import System.Directory import System.Directory.Extra (listDirectories) import System.FilePath @@ -83,14 +87,13 @@ tree root = do -- @cabal test cardano-cli-golden --test-options '-p "/golden create testnet data/"'@ hprop_golden_create_testnet_data :: Property hprop_golden_create_testnet_data = - golden_create_testnet_data Nothing - --- Execute this test with: --- @cabal test cardano-cli-golden --test-options '-p "/golden create testnet data with template/"'@ -hprop_golden_create_testnet_data_with_template :: Property -hprop_golden_create_testnet_data_with_template = - golden_create_testnet_data $ - Just "test/cardano-cli-golden/files/input/shelley/genesis/genesis.spec.json" + let supplyValues = [Nothing, Just "test/cardano-cli-golden/files/input/shelley/genesis/genesis.spec.json"] + in propertyOnce $ forM_ supplyValues $ \shelley -> + H.moduleWorkspace "tmp" $ \tempDir -> do + golden_create_testnet_data + tempDir + shelley + (Just "test/cardano-cli-golden/files/input/shelley/genesis/node-config.json") -- | Semaphore protecting against locked file error, when running properties concurrently. createTestnetDataOutSem :: FileSem @@ -100,56 +103,67 @@ createTestnetDataOutSem = newFileSem "test/cardano-cli-golden/files/golden/conwa -- | This test tests the non-transient case, i.e. it maximizes the files -- that can be written to disk. golden_create_testnet_data - :: () - => Maybe FilePath - -- ^ The path to the shelley template use, if any - -> Property -golden_create_testnet_data mShelleyTemplate = - propertyOnce $ moduleWorkspace "tmp" $ \tempDir -> do - let outputDir = tempDir "out" - templateArg :: [String] = - case mShelleyTemplate of - Nothing -> [] - Just shelleyTemplate -> ["--spec-shelley", shelleyTemplate] - numStakeDelegs = 4 - - void $ - execCardanoCLI $ - mkArguments outputDir <> ["--stake-delegators", show numStakeDelegs] <> templateArg - - generated <- liftIO $ tree outputDir - -- Sort output for stability, and make relative to avoid storing - -- a path that changes everytime (/tmp/nix-shell.[0-9]+/tmp-Test...) - let generated' = intercalate "\n" $ sort $ map (makeRelative outputDir) generated - -- On Windows, the path separator is backslash. Normalize it to slash, like on Unix - -- so that this test can run on all platforms. - generated'' = map (\c -> if c == '\\' then '/' else c) generated' - void $ H.note generated'' - - bracketSem createTestnetDataOutSem $ - H.diffVsGoldenFile generated'' - - shelleyGenesis :: ShelleyGenesis StandardCrypto <- - H.readJsonFileOk $ outputDir "shelley-genesis.json" - - sgNetworkMagic shelleyGenesis H.=== networkMagic - length (L.sgsPools $ sgStaking shelleyGenesis) H.=== numPools - - forM_ (L.sgsPools $ sgStaking shelleyGenesis) $ \pool -> - Seq.length (L.ppRelays pool) H.=== 1 - - actualNumDReps <- liftIO $ listDirectories $ outputDir "drep-keys" - length actualNumDReps H.=== numDReps - - actualNumUtxoKeys <- liftIO $ listDirectories $ outputDir "utxo-keys" - length actualNumUtxoKeys H.=== numUtxoKeys - - conwayGenesis :: ConwayGenesis StandardCrypto <- - H.readJsonFileOk $ outputDir "conway-genesis.json" - - length (cgInitialDReps conwayGenesis) H.=== numDReps - - length (cgDelegs conwayGenesis) H.=== numStakeDelegs + :: (MonadBaseControl IO m, H.MonadTest m, MonadIO m, MonadCatch m, HasCallStack) + => FilePath + -- ^ Temporary directory to use + -> Maybe FilePath + -- ^ The path to the shelley template to use, if any + -> Maybe FilePath + -- ^ The path to the node configuration to use, if any + -> m () +golden_create_testnet_data tempDir mShelleyTemplate mNodeConfigTemplate = do + let outputDir = tempDir "out" + shelleyTemplateArg :: [String] = + case mShelleyTemplate of + Nothing -> [] + Just shelleyTemplate -> ["--spec-shelley", shelleyTemplate] + nodeConfigTemplateArg :: [String] = + case mNodeConfigTemplate of + Nothing -> [] + Just nodeConfigTemplate -> ["--node-configuration", nodeConfigTemplate] + numStakeDelegs = 4 + + void $ + execCardanoCLI $ + mkArguments + outputDir + <> ["--stake-delegators", show numStakeDelegs] + <> shelleyTemplateArg + <> nodeConfigTemplateArg + + generated <- liftIO $ tree outputDir + -- Sort output for stability, and make relative to avoid storing + -- a path that changes everytime (/tmp/nix-shell.[0-9]+/tmp-Test...) + let generated' = intercalate "\n" $ sort $ map (makeRelative outputDir) generated + -- On Windows, the path separator is backslash. Normalize it to slash, like on Unix + -- so that this test can run on all platforms. + generated'' = map (\c -> if c == '\\' then '/' else c) generated' + H.note_ generated'' + + bracketSem createTestnetDataOutSem $ + H.diffVsGoldenFile generated'' + + shelleyGenesis :: ShelleyGenesis StandardCrypto <- + H.readJsonFileOk $ outputDir "shelley-genesis.json" + + sgNetworkMagic shelleyGenesis H.=== networkMagic + length (L.sgsPools $ sgStaking shelleyGenesis) H.=== numPools + + forM_ (L.sgsPools $ sgStaking shelleyGenesis) $ \pool -> + Seq.length (L.ppRelays pool) H.=== 1 + + actualNumDReps <- liftIO $ listDirectories $ outputDir "drep-keys" + length actualNumDReps H.=== numDReps + + actualNumUtxoKeys <- liftIO $ listDirectories $ outputDir "utxo-keys" + length actualNumUtxoKeys H.=== numUtxoKeys + + conwayGenesis :: ConwayGenesis StandardCrypto <- + H.readJsonFileOk $ outputDir "conway-genesis.json" + + length (cgInitialDReps conwayGenesis) H.=== numDReps + + length (cgDelegs conwayGenesis) H.=== numStakeDelegs -- Execute this test with: -- @cabal test cardano-cli-golden --test-options '-p "/golden create testnet data deleg non deleg/"'@ diff --git a/cardano-cli/test/cardano-cli-golden/files/golden/conway/create-testnet-data.out b/cardano-cli/test/cardano-cli-golden/files/golden/conway/create-testnet-data.out index bde7c603ad..14ad5fba59 100644 --- a/cardano-cli/test/cardano-cli-golden/files/golden/conway/create-testnet-data.out +++ b/cardano-cli/test/cardano-cli-golden/files/golden/conway/create-testnet-data.out @@ -1,4 +1,5 @@ alonzo-genesis.json +configuration.json conway-genesis.json delegate-keys/README.md delegate-keys/delegate1/kes.skey diff --git a/cardano-cli/test/cardano-cli-golden/files/golden/help.cli b/cardano-cli/test/cardano-cli-golden/files/golden/help.cli index 3c638d0375..a484484cab 100644 --- a/cardano-cli/test/cardano-cli-golden/files/golden/help.cli +++ b/cardano-cli/test/cardano-cli-golden/files/golden/help.cli @@ -807,7 +807,9 @@ Usage: cardano-cli shelley genesis create-staked [--key-output-format STRING] Create a staked Shelley genesis file from a genesis template and genesis/delegation/spending keys. -Usage: cardano-cli shelley genesis create-testnet-data [--spec-shelley FILEPATH] +Usage: cardano-cli shelley genesis create-testnet-data + [--node-configuration FILEPATH] + [--spec-shelley FILEPATH] [--spec-alonzo FILEPATH] [--spec-conway FILEPATH] [--genesis-keys INT] @@ -1872,7 +1874,9 @@ Usage: cardano-cli allegra genesis create-staked [--key-output-format STRING] Create a staked Shelley genesis file from a genesis template and genesis/delegation/spending keys. -Usage: cardano-cli allegra genesis create-testnet-data [--spec-shelley FILEPATH] +Usage: cardano-cli allegra genesis create-testnet-data + [--node-configuration FILEPATH] + [--spec-shelley FILEPATH] [--spec-alonzo FILEPATH] [--spec-conway FILEPATH] [--genesis-keys INT] @@ -2935,7 +2939,9 @@ Usage: cardano-cli mary genesis create-staked [--key-output-format STRING] Create a staked Shelley genesis file from a genesis template and genesis/delegation/spending keys. -Usage: cardano-cli mary genesis create-testnet-data [--spec-shelley FILEPATH] +Usage: cardano-cli mary genesis create-testnet-data + [--node-configuration FILEPATH] + [--spec-shelley FILEPATH] [--spec-alonzo FILEPATH] [--spec-conway FILEPATH] [--genesis-keys INT] @@ -3990,7 +3996,9 @@ Usage: cardano-cli alonzo genesis create-staked [--key-output-format STRING] Create a staked Shelley genesis file from a genesis template and genesis/delegation/spending keys. -Usage: cardano-cli alonzo genesis create-testnet-data [--spec-shelley FILEPATH] +Usage: cardano-cli alonzo genesis create-testnet-data + [--node-configuration FILEPATH] + [--spec-shelley FILEPATH] [--spec-alonzo FILEPATH] [--spec-conway FILEPATH] [--genesis-keys INT] @@ -5062,7 +5070,9 @@ Usage: cardano-cli babbage genesis create-staked [--key-output-format STRING] Create a staked Shelley genesis file from a genesis template and genesis/delegation/spending keys. -Usage: cardano-cli babbage genesis create-testnet-data [--spec-shelley FILEPATH] +Usage: cardano-cli babbage genesis create-testnet-data + [--node-configuration FILEPATH] + [--spec-shelley FILEPATH] [--spec-alonzo FILEPATH] [--spec-conway FILEPATH] [--genesis-keys INT] @@ -6410,7 +6420,9 @@ Usage: cardano-cli conway genesis create-staked [--key-output-format STRING] Create a staked Shelley genesis file from a genesis template and genesis/delegation/spending keys. -Usage: cardano-cli conway genesis create-testnet-data [--spec-shelley FILEPATH] +Usage: cardano-cli conway genesis create-testnet-data + [--node-configuration FILEPATH] + [--spec-shelley FILEPATH] [--spec-alonzo FILEPATH] [--spec-conway FILEPATH] [--genesis-keys INT] @@ -8340,7 +8352,9 @@ Usage: cardano-cli latest genesis create-staked [--key-output-format STRING] Create a staked Shelley genesis file from a genesis template and genesis/delegation/spending keys. -Usage: cardano-cli latest genesis create-testnet-data [--spec-shelley FILEPATH] +Usage: cardano-cli latest genesis create-testnet-data + [--node-configuration FILEPATH] + [--spec-shelley FILEPATH] [--spec-alonzo FILEPATH] [--spec-conway FILEPATH] [--genesis-keys INT] diff --git a/cardano-cli/test/cardano-cli-golden/files/golden/help/allegra_genesis_create-testnet-data.cli b/cardano-cli/test/cardano-cli-golden/files/golden/help/allegra_genesis_create-testnet-data.cli index 1a3618c481..571bbdf211 100644 --- a/cardano-cli/test/cardano-cli-golden/files/golden/help/allegra_genesis_create-testnet-data.cli +++ b/cardano-cli/test/cardano-cli-golden/files/golden/help/allegra_genesis_create-testnet-data.cli @@ -1,4 +1,6 @@ -Usage: cardano-cli allegra genesis create-testnet-data [--spec-shelley FILEPATH] +Usage: cardano-cli allegra genesis create-testnet-data + [--node-configuration FILEPATH] + [--spec-shelley FILEPATH] [--spec-alonzo FILEPATH] [--spec-conway FILEPATH] [--genesis-keys INT] @@ -21,6 +23,10 @@ Usage: cardano-cli allegra genesis create-testnet-data [--spec-shelley FILEPATH] Create data to use for starting a testnet. Available options: + --node-configuration FILEPATH + The node configuration file to use. Entries for + hashes and paths of genesis files are checked if they + exist. Otherwise they are filled in. --spec-shelley FILEPATH The shelley specification file to use as input. A default one is generated if omitted. --spec-alonzo FILEPATH The alonzo specification file to use as input. A diff --git a/cardano-cli/test/cardano-cli-golden/files/golden/help/alonzo_genesis_create-testnet-data.cli b/cardano-cli/test/cardano-cli-golden/files/golden/help/alonzo_genesis_create-testnet-data.cli index 8592038d19..a18ee83de4 100644 --- a/cardano-cli/test/cardano-cli-golden/files/golden/help/alonzo_genesis_create-testnet-data.cli +++ b/cardano-cli/test/cardano-cli-golden/files/golden/help/alonzo_genesis_create-testnet-data.cli @@ -1,4 +1,6 @@ -Usage: cardano-cli alonzo genesis create-testnet-data [--spec-shelley FILEPATH] +Usage: cardano-cli alonzo genesis create-testnet-data + [--node-configuration FILEPATH] + [--spec-shelley FILEPATH] [--spec-alonzo FILEPATH] [--spec-conway FILEPATH] [--genesis-keys INT] @@ -21,6 +23,10 @@ Usage: cardano-cli alonzo genesis create-testnet-data [--spec-shelley FILEPATH] Create data to use for starting a testnet. Available options: + --node-configuration FILEPATH + The node configuration file to use. Entries for + hashes and paths of genesis files are checked if they + exist. Otherwise they are filled in. --spec-shelley FILEPATH The shelley specification file to use as input. A default one is generated if omitted. --spec-alonzo FILEPATH The alonzo specification file to use as input. A diff --git a/cardano-cli/test/cardano-cli-golden/files/golden/help/babbage_genesis_create-testnet-data.cli b/cardano-cli/test/cardano-cli-golden/files/golden/help/babbage_genesis_create-testnet-data.cli index 8423981ef1..b51eb88887 100644 --- a/cardano-cli/test/cardano-cli-golden/files/golden/help/babbage_genesis_create-testnet-data.cli +++ b/cardano-cli/test/cardano-cli-golden/files/golden/help/babbage_genesis_create-testnet-data.cli @@ -1,4 +1,6 @@ -Usage: cardano-cli babbage genesis create-testnet-data [--spec-shelley FILEPATH] +Usage: cardano-cli babbage genesis create-testnet-data + [--node-configuration FILEPATH] + [--spec-shelley FILEPATH] [--spec-alonzo FILEPATH] [--spec-conway FILEPATH] [--genesis-keys INT] @@ -21,6 +23,10 @@ Usage: cardano-cli babbage genesis create-testnet-data [--spec-shelley FILEPATH] Create data to use for starting a testnet. Available options: + --node-configuration FILEPATH + The node configuration file to use. Entries for + hashes and paths of genesis files are checked if they + exist. Otherwise they are filled in. --spec-shelley FILEPATH The shelley specification file to use as input. A default one is generated if omitted. --spec-alonzo FILEPATH The alonzo specification file to use as input. A diff --git a/cardano-cli/test/cardano-cli-golden/files/golden/help/conway_genesis_create-testnet-data.cli b/cardano-cli/test/cardano-cli-golden/files/golden/help/conway_genesis_create-testnet-data.cli index 072b8491c8..8de154c970 100644 --- a/cardano-cli/test/cardano-cli-golden/files/golden/help/conway_genesis_create-testnet-data.cli +++ b/cardano-cli/test/cardano-cli-golden/files/golden/help/conway_genesis_create-testnet-data.cli @@ -1,4 +1,6 @@ -Usage: cardano-cli conway genesis create-testnet-data [--spec-shelley FILEPATH] +Usage: cardano-cli conway genesis create-testnet-data + [--node-configuration FILEPATH] + [--spec-shelley FILEPATH] [--spec-alonzo FILEPATH] [--spec-conway FILEPATH] [--genesis-keys INT] @@ -21,6 +23,10 @@ Usage: cardano-cli conway genesis create-testnet-data [--spec-shelley FILEPATH] Create data to use for starting a testnet. Available options: + --node-configuration FILEPATH + The node configuration file to use. Entries for + hashes and paths of genesis files are checked if they + exist. Otherwise they are filled in. --spec-shelley FILEPATH The shelley specification file to use as input. A default one is generated if omitted. --spec-alonzo FILEPATH The alonzo specification file to use as input. A diff --git a/cardano-cli/test/cardano-cli-golden/files/golden/help/latest_genesis_create-testnet-data.cli b/cardano-cli/test/cardano-cli-golden/files/golden/help/latest_genesis_create-testnet-data.cli index bc63feed12..4b8bc85d01 100644 --- a/cardano-cli/test/cardano-cli-golden/files/golden/help/latest_genesis_create-testnet-data.cli +++ b/cardano-cli/test/cardano-cli-golden/files/golden/help/latest_genesis_create-testnet-data.cli @@ -1,4 +1,6 @@ -Usage: cardano-cli latest genesis create-testnet-data [--spec-shelley FILEPATH] +Usage: cardano-cli latest genesis create-testnet-data + [--node-configuration FILEPATH] + [--spec-shelley FILEPATH] [--spec-alonzo FILEPATH] [--spec-conway FILEPATH] [--genesis-keys INT] @@ -21,6 +23,10 @@ Usage: cardano-cli latest genesis create-testnet-data [--spec-shelley FILEPATH] Create data to use for starting a testnet. Available options: + --node-configuration FILEPATH + The node configuration file to use. Entries for + hashes and paths of genesis files are checked if they + exist. Otherwise they are filled in. --spec-shelley FILEPATH The shelley specification file to use as input. A default one is generated if omitted. --spec-alonzo FILEPATH The alonzo specification file to use as input. A diff --git a/cardano-cli/test/cardano-cli-golden/files/golden/help/mary_genesis_create-testnet-data.cli b/cardano-cli/test/cardano-cli-golden/files/golden/help/mary_genesis_create-testnet-data.cli index 91df13ecce..a57c1197ff 100644 --- a/cardano-cli/test/cardano-cli-golden/files/golden/help/mary_genesis_create-testnet-data.cli +++ b/cardano-cli/test/cardano-cli-golden/files/golden/help/mary_genesis_create-testnet-data.cli @@ -1,4 +1,6 @@ -Usage: cardano-cli mary genesis create-testnet-data [--spec-shelley FILEPATH] +Usage: cardano-cli mary genesis create-testnet-data + [--node-configuration FILEPATH] + [--spec-shelley FILEPATH] [--spec-alonzo FILEPATH] [--spec-conway FILEPATH] [--genesis-keys INT] @@ -21,6 +23,10 @@ Usage: cardano-cli mary genesis create-testnet-data [--spec-shelley FILEPATH] Create data to use for starting a testnet. Available options: + --node-configuration FILEPATH + The node configuration file to use. Entries for + hashes and paths of genesis files are checked if they + exist. Otherwise they are filled in. --spec-shelley FILEPATH The shelley specification file to use as input. A default one is generated if omitted. --spec-alonzo FILEPATH The alonzo specification file to use as input. A diff --git a/cardano-cli/test/cardano-cli-golden/files/golden/help/shelley_genesis_create-testnet-data.cli b/cardano-cli/test/cardano-cli-golden/files/golden/help/shelley_genesis_create-testnet-data.cli index 280f8a995e..10c22f5386 100644 --- a/cardano-cli/test/cardano-cli-golden/files/golden/help/shelley_genesis_create-testnet-data.cli +++ b/cardano-cli/test/cardano-cli-golden/files/golden/help/shelley_genesis_create-testnet-data.cli @@ -1,4 +1,6 @@ -Usage: cardano-cli shelley genesis create-testnet-data [--spec-shelley FILEPATH] +Usage: cardano-cli shelley genesis create-testnet-data + [--node-configuration FILEPATH] + [--spec-shelley FILEPATH] [--spec-alonzo FILEPATH] [--spec-conway FILEPATH] [--genesis-keys INT] @@ -21,6 +23,10 @@ Usage: cardano-cli shelley genesis create-testnet-data [--spec-shelley FILEPATH] Create data to use for starting a testnet. Available options: + --node-configuration FILEPATH + The node configuration file to use. Entries for + hashes and paths of genesis files are checked if they + exist. Otherwise they are filled in. --spec-shelley FILEPATH The shelley specification file to use as input. A default one is generated if omitted. --spec-alonzo FILEPATH The alonzo specification file to use as input. A diff --git a/cardano-cli/test/cardano-cli-golden/files/input/shelley/genesis/node-config.json b/cardano-cli/test/cardano-cli-golden/files/input/shelley/genesis/node-config.json new file mode 100644 index 0000000000..715347be1e --- /dev/null +++ b/cardano-cli/test/cardano-cli-golden/files/input/shelley/genesis/node-config.json @@ -0,0 +1,88 @@ +{ + "EnableLogMetrics": false, + "EnableLogging": true, + "EnableP2P": false, + "ExperimentalHardForksEnabled": true, + "ExperimentalProtocolsEnabled": true, + "LastKnownBlockVersion-Alt": 0, + "LastKnownBlockVersion-Major": 2, + "LastKnownBlockVersion-Minor": 0, + "MaxConcurrencyBulkSync": 1, + "MaxConcurrencyDeadline": 2, + "PBftSignatureThreshold": 0.6, + "Protocol": "Cardano", + "RequiresNetworkMagic": "RequiresMagic", + "SocketPath": "db/node.socket", + "TestShelleyHardForkAtEpoch": 0, + "TraceBlockFetchClient": false, + "TraceBlockFetchDecisions": false, + "TraceBlockFetchProtocol": false, + "TraceBlockFetchProtocolSerialised": false, + "TraceBlockFetchServer": false, + "TraceBlockchainTime": true, + "TraceChainDb": true, + "TraceChainSyncBlockServer": false, + "TraceChainSyncClient": false, + "TraceChainSyncHeaderServer": false, + "TraceChainSyncProtocol": false, + "TraceConnectionManager": true, + "TraceDnsResolver": true, + "TraceDnsSubscription": true, + "TraceErrorPolicy": true, + "TraceForge": true, + "TraceHandshake": false, + "TraceIpSubscription": true, + "TraceLocalChainSyncProtocol": false, + "TraceLocalConnectionManager": false, + "TraceLocalErrorPolicy": true, + "TraceLocalHandshake": false, + "TraceLocalRootPeers": true, + "TraceLocalServer": false, + "TraceLocalTxSubmissionProtocol": false, + "TraceLocalTxSubmissionServer": false, + "TraceMempool": true, + "TraceMux": false, + "TracePeerSelection": true, + "TracePeerSelectionActions": true, + "TracePublicRootPeers": true, + "TraceServer": true, + "TraceTxInbound": false, + "TraceTxOutbound": false, + "TraceTxSubmissionProtocol": false, + "TurnOnLogMetrics": false, + "defaultBackends": [ + "KatipBK" + ], + "defaultScribes": [ + [ + "FileSK", + "logs/mainnet.log" + ], + [ + "StdoutSK", + "stdout" + ] + ], + "minSeverity": "Debug", + "options": {}, + "rotation": { + "rpKeepFilesNum": 3, + "rpLogLimitBytes": 5000000, + "rpMaxAgeHours": 24 + }, + "setupBackends": [ + "KatipBK" + ], + "setupScribes": [ + { + "scFormat": "ScJson", + "scKind": "FileSK", + "scName": "logs/node.log" + }, + { + "scFormat": "ScJson", + "scKind": "StdoutSK", + "scName": "stdout" + } + ] +} diff --git a/cardano-cli/test/cardano-cli-test/Test/Cli/CreateTestnetData.hs b/cardano-cli/test/cardano-cli-test/Test/Cli/CreateTestnetData.hs index b7cc44f4a9..3e7a0dd931 100644 --- a/cardano-cli/test/cardano-cli-test/Test/Cli/CreateTestnetData.hs +++ b/cardano-cli/test/cardano-cli-test/Test/Cli/CreateTestnetData.hs @@ -1,5 +1,6 @@ {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE NumericUnderscores #-} {-# LANGUAGE ScopedTypeVariables #-} @@ -7,7 +8,12 @@ module Test.Cli.CreateTestnetData where import Control.Monad (forM_, void) -import Data.Aeson (FromJSON, ToJSON) +import Control.Monad.IO.Class (liftIO) +import qualified Data.Aeson as Aeson +import qualified Data.Aeson.Encode.Pretty as Aeson +import qualified Data.Aeson.Key as Aeson +import qualified Data.Aeson.KeyMap as Aeson +import qualified Data.ByteString.Lazy as LBS import Data.List (isInfixOf) import Data.Map.Strict (Map) import qualified Data.Map.Strict as M @@ -118,7 +124,7 @@ data TestGenesis = TestGenesis { maxLovelaceSupply :: Int , initialFunds :: Map Text Int } - deriving (Show, Generic, ToJSON, FromJSON) + deriving (Show, Generic, Aeson.ToJSON, Aeson.FromJSON) -- | This test tests the transient case, i.e. it writes strictly -- less things to disk than 'hprop_golden_create_testnet_data'. Execute this test with: @@ -157,3 +163,98 @@ hprop_create_testnet_data_transient_stake_delegators = -- For the golden part of this test, we are anyway covered by 'hprop_golden_create_testnet_data' -- that generates strictly more stuff. + +-- Execute this test with: +-- @cabal test cardano-cli-test --test-options '-p "/create testnet wrong genesis hash/"'@ +hprop_create_testnet_wrong_genesis_hash :: Property +hprop_create_testnet_wrong_genesis_hash = + propertyOnce $ moduleWorkspace "tmp" $ \tempDir -> do + let outputDir = tempDir "out" + + (exitCode, _stdout, stderr) <- + H.noteShowM $ + execDetailCardanoCLI + [ "conway" + , "genesis" + , "create-testnet-data" + , "--testnet-magic" + , "42" + , "--node-configuration" + , "test/cardano-cli-test/files/input/conway/create-testnet-data/node-config.json" + , "--out-dir" + , outputDir + ] + + exitCode === ExitFailure 1 + H.assertWith stderr ("Hash associated to key \"ConwayGenesisHash\" in file" `isInfixOf`) + +-- Execute this test with: +-- @cabal test cardano-cli-test --test-options '-p "/create testnet creates correct hashes and paths/"'@ +hprop_create_testnet_creates_correct_hashes_and_paths :: Property +hprop_create_testnet_creates_correct_hashes_and_paths = + propertyOnce $ moduleWorkspace "tmp" $ \tempDir -> do + let outputDir = tempDir "out" + outputDir2 = tempDir "out2" + configFile = "test/cardano-cli-test/files/input/conway/create-testnet-data/node-config.json" + smallerConfigFile = tempDir "smaller-config.json" + eras = ["Alonzo", "Conway", "Shelley"] + keys = concat [[era <> "GenesisHash", era <> "GenesisFile"] | era <- eras] + + -- Copy and modify the node-config.json file + originalConfig <- H.readJsonFileOk configFile + let modifiedConfig = removeKeys keys originalConfig + liftIO $ LBS.writeFile smallerConfigFile $ Aeson.encodePretty modifiedConfig + + -- Execute create-testnet-data with the small configuration. This will make create-testnet-data + -- augment the configuration file with the hashes and paths. + H.noteShowM_ $ + execCardanoCLI + [ "conway" + , "genesis" + , "create-testnet-data" + , "--testnet-magic" + , "42" + , "--node-configuration" + , smallerConfigFile + , "--out-dir" + , outputDir + ] + + let augmentedConfigPath = outputDir "configuration.json" + augmentedConfig :: Aeson.Value <- H.readJsonFileOk augmentedConfigPath + H.assertWith augmentedConfig $ \config -> all (hasKey config) keys + + -- Execute creates-testnet-data again with the augmented configuration file + -- It should not fail, meaning the hashes and paths generated by the previous call are correct. + -- But we need to remove the ShelleyGenesisHash key first, because the content of shelley genesis file + -- is not static; because it contains hashes of delegated keys. + + let shelleyLessAugmentedConfigPath = outputDir "configuration.json" + liftIO $ + LBS.writeFile shelleyLessAugmentedConfigPath $ + Aeson.encodePretty $ + removeKeys ["ShelleyGenesisHash"] augmentedConfig + + H.noteShowM_ $ + execCardanoCLI + [ "conway" + , "genesis" + , "create-testnet-data" + , "--testnet-magic" + , "42" + , "--node-configuration" + , shelleyLessAugmentedConfigPath + , "--out-dir" + , outputDir2 + ] + where + removeKeys :: [Text] -> Aeson.Value -> Aeson.Value + removeKeys keys = + \case + Aeson.Object obj -> Aeson.Object $ foldr (Aeson.delete . Aeson.fromText) obj keys + _ -> error "Invalid JSON content: expected an Object" + hasKey :: Aeson.Value -> Text -> Bool + hasKey v key = + case v of + Aeson.Object obj -> Aeson.member (Aeson.fromText key) obj + _ -> False diff --git a/cardano-cli/test/cardano-cli-test/files/input/conway/create-testnet-data/node-config.json b/cardano-cli/test/cardano-cli-test/files/input/conway/create-testnet-data/node-config.json new file mode 100644 index 0000000000..dcfcd85f74 --- /dev/null +++ b/cardano-cli/test/cardano-cli-test/files/input/conway/create-testnet-data/node-config.json @@ -0,0 +1,92 @@ +{ + "AlonzoGenesisHash": "dbc03856c313ce69b7bbcaebcc775347cc8434b0333f67f0b744a359fab16eae", + "ByronGenesisHash": "3b580098c978f3f8654768e963ccabf1d6575480bd7ae18b6891d16f5ceeacea", + "ConwayGenesisHash": "b632b8469ea7d7ae8a66e5d1419f2992764eb6f1527c39aeaef2525f3d3fa5a9", + "EnableLogMetrics": false, + "EnableLogging": true, + "EnableP2P": false, + "ExperimentalHardForksEnabled": true, + "ExperimentalProtocolsEnabled": true, + "LastKnownBlockVersion-Alt": 0, + "LastKnownBlockVersion-Major": 2, + "LastKnownBlockVersion-Minor": 0, + "MaxConcurrencyBulkSync": 1, + "MaxConcurrencyDeadline": 2, + "PBftSignatureThreshold": 0.6, + "Protocol": "Cardano", + "RequiresNetworkMagic": "RequiresMagic", + "ShelleyGenesisHash": "00e8783a023250284b547e6d27c081c044424b56350bc5c83d95dc368efe7805", + "SocketPath": "db/node.socket", + "TestShelleyHardForkAtEpoch": 0, + "TraceBlockFetchClient": false, + "TraceBlockFetchDecisions": false, + "TraceBlockFetchProtocol": false, + "TraceBlockFetchProtocolSerialised": false, + "TraceBlockFetchServer": false, + "TraceBlockchainTime": true, + "TraceChainDb": true, + "TraceChainSyncBlockServer": false, + "TraceChainSyncClient": false, + "TraceChainSyncHeaderServer": false, + "TraceChainSyncProtocol": false, + "TraceConnectionManager": true, + "TraceDnsResolver": true, + "TraceDnsSubscription": true, + "TraceErrorPolicy": true, + "TraceForge": true, + "TraceHandshake": false, + "TraceIpSubscription": true, + "TraceLocalChainSyncProtocol": false, + "TraceLocalConnectionManager": false, + "TraceLocalErrorPolicy": true, + "TraceLocalHandshake": false, + "TraceLocalRootPeers": true, + "TraceLocalServer": false, + "TraceLocalTxSubmissionProtocol": false, + "TraceLocalTxSubmissionServer": false, + "TraceMempool": true, + "TraceMux": false, + "TracePeerSelection": true, + "TracePeerSelectionActions": true, + "TracePublicRootPeers": true, + "TraceServer": true, + "TraceTxInbound": false, + "TraceTxOutbound": false, + "TraceTxSubmissionProtocol": false, + "TurnOnLogMetrics": false, + "defaultBackends": [ + "KatipBK" + ], + "defaultScribes": [ + [ + "FileSK", + "logs/mainnet.log" + ], + [ + "StdoutSK", + "stdout" + ] + ], + "minSeverity": "Debug", + "options": {}, + "rotation": { + "rpKeepFilesNum": 3, + "rpLogLimitBytes": 5000000, + "rpMaxAgeHours": 24 + }, + "setupBackends": [ + "KatipBK" + ], + "setupScribes": [ + { + "scFormat": "ScJson", + "scKind": "FileSK", + "scName": "logs/node.log" + }, + { + "scFormat": "ScJson", + "scKind": "StdoutSK", + "scName": "stdout" + } + ] +}