Skip to content

Commit 1b7cd8e

Browse files
authored
Merge pull request #6552 from IntersectMBO/split-refactor-testnet-options
cardano-testnet: Split testnet options into purpose-specific types
2 parents 1f5588d + b416499 commit 1b7cd8e

52 files changed

Lines changed: 531 additions & 487 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

cardano-node-chairman/test/Spec/Chairman/Cardano.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ hprop_chairman :: H.Property
2020
hprop_chairman = integrationRetryWorkspace 2 "cardano-chairman" $ \tempAbsPath' -> H.runWithDefaultWatchdog_ $ do
2121
conf <- mkConf tempAbsPath'
2222

23-
let testnetOptions = def{ cardanoNodes = SpoNodeOptions [] :| [RelayNodeOptions [], RelayNodeOptions []] }
24-
allNodes <- testnetNodes <$> createAndRunTestnet testnetOptions def conf
23+
let creationOptions = def{ creationNodes = SpoNodeOptions [] :| [RelayNodeOptions [], RelayNodeOptions []] }
24+
allNodes <- testnetNodes <$> createAndRunTestnet creationOptions def conf
2525

2626
chairmanOver 120 50 conf allNodes
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
2+
### Removed
3+
4+
- Removed `CardanoTestnetOptions` type and `CreateEnvOptions` wrapper (replaced by purpose-specific types).
5+
- Removed dead fields `cardanoNodeLoggingFormat` and `cardanoOutputDir`.
6+
7+
### Changed
8+
9+
- Split `CardanoTestnetOptions` into `TestnetCreationOptions` and `TestnetRuntimeOptions` so each function receives only the fields it uses.
10+
- `CardanoTestnetCliOptions` is now a sum type (`NoUserProvidedEnv | StartFromEnv`), making `--node-env` and `--num-pool-nodes` structurally mutually exclusive in the CLI parser.
11+
- Simplified `CardanoTestnetCreateEnvOptions` and `createTestnetEnv` signatures (fewer arguments, genesis options and on-chain params folded into `TestnetCreationOptions`).
12+
13+
### Added
14+
15+
- `readNodeOptionsFromEnv`: scans an existing testnet environment directory to classify nodes as SPO or relay.
16+

cardano-testnet/src/Cardano/Testnet.hs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ module Cardano.Testnet (
1010
retryOnAddressInUseError,
1111

1212
-- ** Testnet options
13-
CardanoTestnetOptions(..),
13+
TestnetCreationOptions(..),
14+
TestnetRuntimeOptions(..),
15+
TestnetEnvOptions(..),
1416
RpcSupport(..),
1517
NodeOption(..),
1618
cardanoDefaultTestnetNodeOptions,

cardano-testnet/src/Parsers/Cardano.hs

Lines changed: 73 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ module Parsers.Cardano
66
) where
77

88
import Cardano.Api (AnyShelleyBasedEra (..))
9-
import Cardano.Api.Pretty
109

1110
import Cardano.CLI.EraBased.Common.Option hiding (pNetworkId)
1211
import Cardano.Prelude (readMaybe)
@@ -26,68 +25,85 @@ import Options.Applicative.Types (readerAsk)
2625
import Testnet.Defaults (defaultEra)
2726
import Testnet.Start.Cardano
2827
import Testnet.Start.Types
29-
import Testnet.Types (readNodeLoggingFormat)
28+
29+
data ModeOptions
30+
= ModeFromEnv TestnetEnvOptions
31+
| ModeNewEnv TestnetCreationOptions (Maybe FilePath)
3032

3133
optsTestnet :: Parser CardanoTestnetCliOptions
32-
optsTestnet = CardanoTestnetCliOptions
33-
<$> pCardanoTestnetCliOptions
34-
<*> pGenesisOptions
35-
<*> pNodeEnvironment
36-
<*> pUpdateTimestamps
37-
<*> pOnChainParams
34+
optsTestnet = mkCliOptions <$> pModeOptions <*> pRuntimeOptions
35+
where
36+
pModeOptions =
37+
ModeFromEnv <$> pFromEnv
38+
<|> ModeNewEnv <$> pCreationOptions <*> pScratchOutputDir
39+
mkCliOptions (ModeFromEnv envOpts) rt = StartFromEnv (StartFromEnvOptions envOpts rt)
40+
mkCliOptions (ModeNewEnv creation outDir) rt = NoUserProvidedEnv (NoUserProvidedEnvOptions creation outDir rt)
3841

3942
optsCreateTestnet :: Parser CardanoTestnetCreateEnvOptions
4043
optsCreateTestnet = CardanoTestnetCreateEnvOptions
41-
<$> pCardanoTestnetCliOptions
42-
<*> pGenesisOptions
44+
<$> pCreationOptions
4345
<*> pEnvOutputDir
44-
<*> pCreateEnvOptions
4546

46-
-- We can't fill in the optional Genesis files at parse time, because we want to be in a monad
47-
-- to properly parse JSON. We delegate this task to the caller.
48-
pCreateEnvOptions :: Parser CreateEnvOptions
49-
pCreateEnvOptions = CreateEnvOptions
50-
<$> pOnChainParams
47+
pFromEnv :: Parser TestnetEnvOptions
48+
pFromEnv = TestnetEnvOptions
49+
<$> OA.strOption
50+
( OA.long "node-env"
51+
<> OA.metavar "FILEPATH"
52+
<> OA.help "Path to the node's environment (which is generated otherwise). You can generate a default environment with the 'create-env' command, then modify it and pass it with this argument."
53+
)
54+
<*> pUpdateTimestamps
5155

52-
pCardanoTestnetCliOptions :: Parser CardanoTestnetOptions
53-
pCardanoTestnetCliOptions = CardanoTestnetOptions
56+
pCreationOptions :: Parser TestnetCreationOptions
57+
pCreationOptions = TestnetCreationOptions
5458
<$> pTestnetNodeOptions
5559
<*> pure (AnyShelleyBasedEra defaultEra)
5660
<*> pMaxLovelaceSupply
57-
<*> OA.option (OA.eitherReader readNodeLoggingFormat)
58-
( OA.long "node-logging-format"
59-
<> OA.help "Node logging format (json|text)"
60-
<> OA.metavar "LOGGING_FORMAT"
61-
<> OA.showDefaultWith prettyShow
62-
<> OA.value (cardanoNodeLoggingFormat def)
63-
)
64-
<*> OA.option OA.auto
65-
( OA.long "num-dreps"
66-
<> OA.help "Number of delegate representatives (DReps) to generate. Ignored if a node environment is passed."
67-
<> OA.metavar "NUMBER"
68-
<> OA.showDefault
69-
<> OA.value 3
70-
)
71-
<*> OA.switch
72-
( OA.long "enable-new-epoch-state-logging"
73-
<> OA.help "Enable new epoch state logging to logs/ledger-epoch-state.log"
74-
<> OA.showDefault
75-
)
76-
<*> (maybe NoUserProvidedEnv UserProvidedEnv <$> optional (OA.strOption
77-
( OA.long "output-dir"
78-
<> OA.help "Directory where to store files, sockets, and so on. It is created if it doesn't exist. If unset, a temporary directory is used."
79-
<> OA.metavar "DIRECTORY"
80-
)))
81-
<*> OA.flag RpcDisabled RpcEnabled
82-
( OA.long "enable-grpc"
83-
<> OA.help "[EXPERIMENTAL] Enable gRPC endpoint on all of testnet nodes. The listening socket file will be the same directory as node's N2C socket."
84-
<> OA.showDefault
85-
)
86-
<*> OA.flag UseKesKeyFile UseKesSocket
87-
( OA.long "use-kes-agent"
88-
<> OA.help "Get Praos block forging credentials from kes-agent via the default socket path"
89-
<> OA.showDefault
90-
)
61+
<*> pNumDReps
62+
<*> pGenesisOptions
63+
<*> pOnChainParams
64+
65+
pRuntimeOptions :: Parser TestnetRuntimeOptions
66+
pRuntimeOptions = TestnetRuntimeOptions
67+
<$> pEnableNewEpochStateLogging
68+
<*> pEnableRpc
69+
<*> pKesSource
70+
71+
pScratchOutputDir :: Parser (Maybe FilePath)
72+
pScratchOutputDir = optional $ OA.strOption
73+
( OA.long "output-dir"
74+
<> OA.help "Directory where to store files, sockets, and so on. It is created if it doesn't exist. If unset, a temporary directory is used."
75+
<> OA.metavar "DIRECTORY"
76+
)
77+
78+
pNumDReps :: Parser NumDReps
79+
pNumDReps = OA.option OA.auto
80+
( OA.long "num-dreps"
81+
<> OA.help "Number of delegate representatives (DReps) to generate."
82+
<> OA.metavar "NUMBER"
83+
<> OA.showDefault
84+
<> OA.value 3
85+
)
86+
87+
pEnableNewEpochStateLogging :: Parser Bool
88+
pEnableNewEpochStateLogging = OA.switch
89+
( OA.long "enable-new-epoch-state-logging"
90+
<> OA.help "Enable new epoch state logging to logs/ledger-epoch-state.log"
91+
<> OA.showDefault
92+
)
93+
94+
pEnableRpc :: Parser RpcSupport
95+
pEnableRpc = OA.flag RpcDisabled RpcEnabled
96+
( OA.long "enable-grpc"
97+
<> OA.help "[EXPERIMENTAL] Enable gRPC endpoint on all of testnet nodes. The listening socket file will be the same directory as node's N2C socket."
98+
<> OA.showDefault
99+
)
100+
101+
pKesSource :: Parser PraosCredentialsSource
102+
pKesSource = OA.flag UseKesKeyFile UseKesSocket
103+
( OA.long "use-kes-agent"
104+
<> OA.help "Get Praos block forging credentials from kes-agent via the default socket path"
105+
<> OA.showDefault
106+
)
91107

92108
pTestnetNodeOptions :: Parser (NonEmpty NodeOption)
93109
pTestnetNodeOptions =
@@ -108,14 +124,6 @@ pTestnetNodeOptions =
108124
Just n | n >= 1 -> pure n
109125
_ -> fail "Need at least one SPO node to produce blocks, but got none."
110126

111-
pNodeEnvironment :: Parser UserProvidedEnv
112-
pNodeEnvironment = fmap (maybe NoUserProvidedEnv UserProvidedEnv) <$>
113-
optional $ OA.strOption
114-
( OA.long "node-env"
115-
<> OA.metavar "FILEPATH"
116-
<> OA.help "Path to the node's environment (which is generated otherwise). You can generate a default environment with the 'create-env' command, then modify it and pass it with this argument."
117-
)
118-
119127
pOnChainParams :: Parser TestnetOnChainParams
120128
pOnChainParams = fmap (fromMaybe DefaultParams) <$> optional $
121129
pCustomParamsFile <|> pMainnetParams
@@ -161,23 +169,23 @@ pGenesisOptions =
161169
pEpochLength =
162170
OA.option OA.auto
163171
( OA.long "epoch-length"
164-
<> OA.help "Epoch length, in number of slots. Ignored if a node environment is passed."
172+
<> OA.help "Epoch length, in number of slots."
165173
<> OA.metavar "SLOTS"
166174
<> OA.showDefault
167175
<> OA.value (genesisEpochLength def)
168176
)
169177
pSlotLength =
170178
OA.option OA.auto
171179
( OA.long "slot-length"
172-
<> OA.help "Slot length. Ignored if a node environment is passed."
180+
<> OA.help "Slot length."
173181
<> OA.metavar "SECONDS"
174182
<> OA.showDefault
175183
<> OA.value (genesisSlotLength def)
176184
)
177185
pActiveSlotCoeffs =
178186
OA.option OA.auto
179187
( OA.long "active-slots-coeff"
180-
<> OA.help "Active slots coefficient. Ignored if a node environment is passed."
188+
<> OA.help "Active slots coefficient."
181189
<> OA.metavar "DOUBLE"
182190
<> OA.showDefault
183191
<> OA.value (genesisActiveSlotsCoeff def)
@@ -203,8 +211,8 @@ pMaxLovelaceSupply :: Parser Word64
203211
pMaxLovelaceSupply =
204212
OA.option OA.auto
205213
( OA.long "max-lovelace-supply"
206-
<> OA.help "Max lovelace supply that your testnet starts with. Ignored if a node environment is passed."
214+
<> OA.help "Max lovelace supply that your testnet starts with."
207215
<> OA.metavar "WORD64"
208216
<> OA.showDefault
209-
<> OA.value (cardanoMaxSupply def)
217+
<> OA.value (creationMaxSupply def)
210218
)

cardano-testnet/src/Parsers/Run.hs

Lines changed: 33 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{-# LANGUAGE GADTs #-}
22
{-# LANGUAGE LambdaCase #-}
3+
{-# LANGUAGE NamedFieldPuns #-}
34
{-# LANGUAGE NumericUnderscores #-}
45
{-# LANGUAGE OverloadedStrings #-}
56
{-# LANGUAGE ScopedTypeVariables #-}
@@ -13,9 +14,12 @@ module Parsers.Run
1314
import Cardano.CLI.Environment
1415

1516
import Control.Monad (void)
17+
import Data.Maybe (fromMaybe)
1618
import Options.Applicative
1719
import qualified Options.Applicative as Opt
20+
import System.Directory (doesDirectoryExist)
1821

22+
import Testnet.Filepath (unTmpAbsPath)
1923
import Testnet.Start.Cardano
2024
import Testnet.Start.Types
2125

@@ -24,6 +28,7 @@ import Parsers.Help
2428
import Parsers.Version
2529
import RIO (display, forever, logInfo, runSimpleApp, threadDelay)
2630
import UnliftIO.Resource (runResourceT)
31+
import Cardano.Prelude (unlessM)
2732

2833
pref :: ParserPrefs
2934
pref = Opt.prefs $ showHelpOnEmpty <> showHelpOnError
@@ -56,50 +61,43 @@ runTestnetCmd = \case
5661

5762
createEnvOptions :: CardanoTestnetCreateEnvOptions -> IO ()
5863
createEnvOptions CardanoTestnetCreateEnvOptions
59-
{ createEnvTestnetOptions=testnetOptions
60-
, createEnvGenesisOptions=genesisOptions
64+
{ createEnvCreationOptions=creationOptions
6165
, createEnvOutputDir=outputDir
62-
, createEnvCreateEnvOptions=ceOptions
6366
} = do
6467
conf <- mkConfigAbs outputDir
6568
void $ createTestnetEnv
66-
testnetOptions genesisOptions ceOptions
69+
creationOptions
6770
-- Do not add hashes to the main config file, so that genesis files
6871
-- can be modified without having to recompute hashes every time.
6972
conf{genesisHashesPolicy = WithoutHashes}
7073

7174
runCardanoOptions :: CardanoTestnetCliOptions -> IO ()
72-
runCardanoOptions CardanoTestnetCliOptions
73-
{ cliTestnetOptions=testnetOptions
74-
, cliGenesisOptions=genesisOptions
75-
, cliNodeEnvironment=env
76-
, cliUpdateTimestamps=updateTimestamps'
77-
, cliOnChainParams=onChainParams
78-
} = do
79-
case env of
80-
NoUserProvidedEnv -> do
81-
-- Create the sandbox, then run cardano-testnet.
82-
-- It is not necessary to honor `cliUpdateTimestamps` here, because
83-
-- the genesis files will be created with up-to-date stamps already.
84-
conf <- mkConfigAbs "testnet"
85-
runSimpleApp . runResourceT $ do
86-
logInfo $ "Creating environment: " <> display (tempAbsPath conf)
87-
createTestnetEnv testnetOptions genesisOptions (CreateEnvOptions onChainParams) conf
88-
logInfo $ "Starting testnet in environment: " <> display (tempAbsPath conf)
89-
void $ cardanoTestnet testnetOptions conf
90-
logInfo "Testnet started"
91-
waitForShutdown
92-
UserProvidedEnv nodeEnvPath -> do
93-
-- Run cardano-testnet in the sandbox provided by the user
94-
-- In that case, 'cardanoOutputDir' is not used
95-
conf <- mkConfigAbs nodeEnvPath
96-
runSimpleApp . runResourceT $ do
97-
logInfo $ "Starting testnet in environment: " <> display (tempAbsPath conf)
98-
void $ cardanoTestnet
99-
testnetOptions
100-
conf{updateTimestamps=updateTimestamps'}
101-
logInfo "Testnet started"
102-
waitForShutdown
75+
runCardanoOptions = \case
76+
NoUserProvidedEnv NoUserProvidedEnvOptions{noEnvCreationOptions, noEnvOutputDir, noEnvRuntimeOptions} -> do
77+
-- Create the sandbox, then run cardano-testnet.
78+
-- It is not necessary to update timestamps here, because
79+
-- the genesis files will be created with up-to-date stamps already.
80+
let dirName = fromMaybe "testnet" noEnvOutputDir
81+
conf <- mkConfigAbs dirName
82+
runSimpleApp . runResourceT $ do
83+
logInfo $ "Creating environment: " <> display (tempAbsPath conf)
84+
createTestnetEnv noEnvCreationOptions conf
85+
logInfo $ "Starting testnet in environment: " <> display (tempAbsPath conf)
86+
void $ cardanoTestnet (creationNodes noEnvCreationOptions) noEnvRuntimeOptions conf
87+
logInfo "Testnet started"
88+
waitForShutdown
89+
StartFromEnv StartFromEnvOptions{fromEnvOptions, fromEnvRuntimeOptions} -> do
90+
-- Run cardano-testnet in the sandbox provided by the user
91+
let dirName = envPath fromEnvOptions
92+
unlessM (doesDirectoryExist dirName) $ error $ "The provided path does not exist or is not a directory: " <> dirName
93+
conf <- mkConfigAbs dirName
94+
nodes <- readNodeOptionsFromEnv (unTmpAbsPath (tempAbsPath conf))
95+
runSimpleApp . runResourceT $ do
96+
logInfo $ "Starting testnet in environment: " <> display (tempAbsPath conf)
97+
void $ cardanoTestnet nodes fromEnvRuntimeOptions
98+
conf{updateTimestamps = envUpdateTimestamps fromEnvOptions}
99+
logInfo "Testnet started"
100+
waitForShutdown
103101
where
104102
waitForShutdown = do
105103
logInfo "Waiting for shutdown (Ctrl+C)"

0 commit comments

Comments
 (0)