Skip to content

Commit fa3fa2f

Browse files
authored
Merge pull request #6148 from IntersectMBO/smelc/testnet-pass-config-files-2
cardano-testnet: allow to take node configuration file as input
2 parents 39077c0 + 3ae3a62 commit fa3fa2f

File tree

13 files changed

+223
-100
lines changed

13 files changed

+223
-100
lines changed

cardano-testnet/cardano-testnet.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ library
9191
, transformers
9292
, transformers-except
9393
, vector
94+
, yaml
9495

9596
hs-source-dirs: src
9697
exposed-modules: Cardano.Testnet

cardano-testnet/src/Cardano/Testnet.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ module Cardano.Testnet (
1111
-- ** Testnet options
1212
CardanoTestnetOptions(..),
1313
TestnetNodeOptions(..),
14+
AutomaticNodeOption(..),
1415
cardanoDefaultTestnetNodeOptions,
1516
getDefaultAlonzoGenesis,
1617
getDefaultShelleyGenesis,

cardano-testnet/src/Parsers/Cardano.hs

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
{-# LANGUAGE ScopedTypeVariables #-}
2+
13
module Parsers.Cardano
24
( cmdCardano
35
) where
@@ -9,6 +11,7 @@ import Cardano.CLI.EraBased.Options.Common hiding (pNetworkId)
911

1012
import Prelude
1113

14+
import Control.Applicative
1215
import Data.Default.Class
1316
import Data.Functor
1417
import qualified Data.List as L
@@ -20,6 +23,7 @@ import Testnet.Start.Cardano
2023
import Testnet.Start.Types
2124
import Testnet.Types (readNodeLoggingFormat)
2225

26+
{- HLINT ignore "Use asum" -}
2327

2428
optsTestnet :: EnvCli -> Parser CardanoTestnetCliOptions
2529
optsTestnet envCli = CardanoTestnetCliOptions
@@ -28,7 +32,7 @@ optsTestnet envCli = CardanoTestnetCliOptions
2832

2933
pCardanoTestnetCliOptions :: EnvCli -> Parser CardanoTestnetOptions
3034
pCardanoTestnetCliOptions envCli = CardanoTestnetOptions
31-
<$> pNumSpoNodes
35+
<$> pTestnetNodeOptions
3236
<*> pAnyShelleyBasedEra'
3337
<*> pMaxLovelaceSupply
3438
<*> OA.option auto
@@ -47,7 +51,7 @@ pCardanoTestnetCliOptions envCli = CardanoTestnetOptions
4751
)
4852
<*> OA.option auto
4953
( OA.long "num-dreps"
50-
<> OA.help "Number of delegate representatives (DReps) to generate"
54+
<> OA.help "Number of delegate representatives (DReps) to generate. Ignored if a custom Conway genesis file is passed."
5155
<> OA.metavar "NUMBER"
5256
<> OA.showDefault
5357
<> OA.value 3
@@ -67,19 +71,27 @@ pCardanoTestnetCliOptions envCli = CardanoTestnetOptions
6771
pAnyShelleyBasedEra' =
6872
pAnyShelleyBasedEra envCli <&> (\(EraInEon x) -> AnyShelleyBasedEra x)
6973

70-
pNumSpoNodes :: Parser [TestnetNodeOptions]
71-
pNumSpoNodes =
72-
-- We don't support passing custom node configurations files on the CLI.
73-
-- So we use a default node configuration for all nodes.
74-
(`L.replicate` defaultSpoOptions) <$>
75-
OA.option auto
76-
( OA.long "num-pool-nodes"
77-
<> OA.help "Number of pool nodes. Note this uses a default node configuration for all nodes."
78-
<> OA.metavar "COUNT"
79-
<> OA.showDefault
80-
<> OA.value 1)
74+
pTestnetNodeOptions :: Parser TestnetNodeOptions
75+
pTestnetNodeOptions =
76+
asum' [
77+
AutomaticNodeOptions . (`L.replicate` defaultSpoOptions) <$>
78+
OA.option auto
79+
( OA.long "num-pool-nodes"
80+
<> OA.help "Number of pool nodes. Note this uses a default node configuration for all nodes."
81+
<> OA.metavar "COUNT"
82+
<> OA.showDefault
83+
<> OA.value 1)
84+
, UserProvidedNodeOptions
85+
<$> strOption ( long "node-config"
86+
<> metavar "FILEPATH"
87+
<> help "Path to the node's configuration file (which is generated otherwise). If you use this option, you should also pass all the genesis files (files pointed to by the fields \"AlonzoGenesisFile\", \"ShelleyGenesisFile\", etc.).")
88+
]
8189
where
8290
defaultSpoOptions = SpoNodeOptions []
91+
-- \| Because asum is not available GHC 8.10.7's base (4.14.3.0). This can be removed
92+
-- when oldest version of GHC we use is >= 9.0 (base >= 4.15)
93+
asum' :: (Foldable t, Alternative f) => t (f a) -> f a
94+
asum' = foldr (<|>) empty
8395

8496
pGenesisOptions :: Parser GenesisOptions
8597
pGenesisOptions =
@@ -92,23 +104,26 @@ pGenesisOptions =
92104
pEpochLength =
93105
OA.option auto
94106
( OA.long "epoch-length"
95-
<> OA.help "Epoch length, in number of slots"
107+
-- TODO Check that this flag is not used when a custom Shelley genesis file is passed
108+
<> OA.help "Epoch length, in number of slots. Ignored if a custom Shelley genesis file is passed."
96109
<> OA.metavar "SLOTS"
97110
<> OA.showDefault
98111
<> OA.value (genesisEpochLength def)
99112
)
100113
pSlotLength =
101114
OA.option auto
102115
( OA.long "slot-length"
103-
<> OA.help "Slot length"
116+
-- TODO Check that this flag is not used when a custom Shelley genesis file is passed
117+
<> OA.help "Slot length. Ignored if a custom Shelley genesis file is passed."
104118
<> OA.metavar "SECONDS"
105119
<> OA.showDefault
106120
<> OA.value (genesisSlotLength def)
107121
)
108122
pActiveSlotCoeffs =
109123
OA.option auto
110124
( OA.long "active-slots-coeff"
111-
<> OA.help "Active slots co-efficient"
125+
-- TODO Check that this flag is not used when a custom Shelley genesis file is passed
126+
<> OA.help "Active slots coefficient. Ignored if a custom Shelley genesis file is passed."
112127
<> OA.metavar "DOUBLE"
113128
<> OA.showDefault
114129
<> OA.value (genesisActiveSlotsCoeff def)
@@ -129,7 +144,8 @@ pMaxLovelaceSupply :: Parser Word64
129144
pMaxLovelaceSupply =
130145
option auto
131146
( long "max-lovelace-supply"
132-
<> help "Max lovelace supply that your testnet starts with."
147+
-- TODO Check that this flag is not used when a custom Shelley genesis file is passed
148+
<> help "Max lovelace supply that your testnet starts with. Ignored if a custom Shelley genesis file is passed."
133149
<> metavar "WORD64"
134150
<> showDefault
135151
<> value (cardanoMaxSupply def)

cardano-testnet/src/Parsers/Run.hs

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{-# LANGUAGE GADTs #-}
22
{-# LANGUAGE LambdaCase #-}
3+
{-# LANGUAGE ScopedTypeVariables #-}
34

45
module Parsers.Run
56
( commands
@@ -8,14 +9,27 @@ module Parsers.Run
89
, opts
910
) where
1011

12+
import Cardano.Api.Ledger (StandardCrypto)
13+
import Cardano.Api.Shelley (ShelleyGenesis)
14+
1115
import Cardano.CLI.Environment
16+
import Cardano.Ledger.Alonzo.Genesis (AlonzoGenesis)
17+
import Cardano.Ledger.Conway.Genesis (ConwayGenesis)
18+
import Cardano.Node.Configuration.POM
19+
import Cardano.Node.Types
1220

21+
import qualified Data.Aeson as Aeson
1322
import Data.Foldable
23+
import Data.Monoid
24+
import Data.Yaml (decodeFileThrow)
1425
import Options.Applicative
1526
import qualified Options.Applicative as Opt
27+
import qualified System.Directory as System
28+
import System.FilePath (takeDirectory, (</>))
1629

1730
import Testnet.Property.Run
1831
import Testnet.Start.Cardano
32+
import Testnet.Start.Types
1933

2034
import Parsers.Cardano
2135
import Parsers.Help
@@ -52,5 +66,49 @@ runTestnetCmd = \case
5266

5367

5468
runCardanoOptions :: CardanoTestnetCliOptions -> IO ()
55-
runCardanoOptions (CardanoTestnetCliOptions testnetOptions shelleyOptions) =
56-
runTestnet testnetOptions $ cardanoTestnetDefault testnetOptions shelleyOptions
69+
runCardanoOptions (CardanoTestnetCliOptions testnetOptions genesisOptions) =
70+
case cardanoNodes testnetOptions of
71+
AutomaticNodeOptions _ -> runTestnet testnetOptions $ cardanoTestnetDefault testnetOptions genesisOptions
72+
UserProvidedNodeOptions nodeInputConfigFile -> do
73+
nodeConfigFile <- readNodeConfigurationFile nodeInputConfigFile
74+
let protocolConfig :: NodeProtocolConfiguration =
75+
case getLast $ pncProtocolConfig nodeConfigFile of
76+
Nothing -> error $ "Genesis files not specified in node configuration file: " <> nodeInputConfigFile
77+
Just x -> x
78+
adjustedProtocolConfig =
79+
-- Make all the files be relative to the location of the config file.
80+
adjustFilePaths (takeDirectory nodeInputConfigFile </>) protocolConfig
81+
(shelley, alonzo, conway) = getShelleyGenesises adjustedProtocolConfig
82+
shelleyGenesis :: UserProvidedData (ShelleyGenesis StandardCrypto) <-
83+
genesisFilepathToData $ npcShelleyGenesisFile shelley
84+
alonzoGenesis :: UserProvidedData AlonzoGenesis <-
85+
genesisFilepathToData $ npcAlonzoGenesisFile alonzo
86+
conwayGenesis :: UserProvidedData (ConwayGenesis StandardCrypto) <-
87+
genesisFilepathToData $ npcConwayGenesisFile conway
88+
runTestnet testnetOptions $ cardanoTestnet
89+
testnetOptions
90+
genesisOptions
91+
shelleyGenesis
92+
alonzoGenesis
93+
conwayGenesis
94+
where
95+
getShelleyGenesises (NodeProtocolConfigurationCardano _byron shelley alonzo conway _hardForkConfig _checkPointConfig) =
96+
(shelley, alonzo, conway)
97+
readNodeConfigurationFile :: FilePath -> IO PartialNodeConfiguration
98+
readNodeConfigurationFile file = do
99+
errOrNodeConfig <- Aeson.eitherDecodeFileStrict' file
100+
case errOrNodeConfig of
101+
Left err -> error $ "Error reading node configuration file: " <> err
102+
Right nodeConfig -> pure nodeConfig
103+
genesisFilepathToData :: Aeson.FromJSON a => GenesisFile -> IO (UserProvidedData a)
104+
genesisFilepathToData (GenesisFile filepath) = do
105+
exists <- System.doesFileExist filepath
106+
if exists
107+
then UserProvidedData <$> decodeFileThrow filepath
108+
else
109+
-- Here we forbid mixing defaulting of genesis files with providing a user node configuration file:
110+
--
111+
-- Because of this check, either the user passes a node configuration file AND he passes all the genesis files.
112+
-- Or the user doesn't pass a node configuration file AND cardano-testnet generates all the genesis files.
113+
-- See the discussion here: https://github.com/IntersectMBO/cardano-node/pull/6103#discussion_r1995431437
114+
error $ "Genesis file specified in node configuration file does not exist: " <> filepath

cardano-testnet/src/Testnet/Runtime.hs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -142,16 +142,13 @@ startNode tp node ipv4 port _testnetMagic nodeCmd = GHC.withFrozenCallStack $ do
142142
left MaxSprocketLengthExceededError
143143

144144
let socketAbsPath = H.sprocketSystemName sprocket
145+
completeNodeCmd = nodeCmd ++
146+
[ "--socket-path", H.sprocketArgumentName sprocket
147+
, "--port", show port
148+
, "--host-addr", showIpv4Address ipv4
149+
]
145150

146-
nodeProcess
147-
<- newExceptT . fmap (first ExecutableRelatedFailure) . try
148-
$ procNode $ mconcat
149-
[ nodeCmd
150-
, [ "--socket-path", H.sprocketArgumentName sprocket
151-
, "--port", show port
152-
, "--host-addr", showIpv4Address ipv4
153-
]
154-
]
151+
nodeProcess <- newExceptT . fmap (first ExecutableRelatedFailure) . try $ procNode completeNodeCmd
155152

156153
-- The port number if it is obtained using 'H.randomPort', it is firstly bound to and then closed. The closing
157154
-- and release in the operating system is done asynchronously and can be slow. Here we wait until the port

0 commit comments

Comments
 (0)