Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cardano-node-chairman/test/Spec/Chairman/Cardano.hs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ hprop_chairman :: H.Property
hprop_chairman = integrationRetryWorkspace 2 "cardano-chairman" $ \tempAbsPath' -> H.runWithDefaultWatchdog_ $ do
conf <- mkConf tempAbsPath'

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

chairmanOver 120 50 conf allNodes
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

### Removed

- Removed `CardanoTestnetOptions` type and `CreateEnvOptions` wrapper (replaced by purpose-specific types).
- Removed dead fields `cardanoNodeLoggingFormat` and `cardanoOutputDir`.

### Changed

- Split `CardanoTestnetOptions` into `TestnetCreationOptions` and `TestnetRuntimeOptions` so each function receives only the fields it uses.
- `CardanoTestnetCliOptions` is now a sum type (`NoUserProvidedEnv | StartFromEnv`), making `--node-env` and `--num-pool-nodes` structurally mutually exclusive in the CLI parser.
- Simplified `CardanoTestnetCreateEnvOptions` and `createTestnetEnv` signatures (fewer arguments, genesis options and on-chain params folded into `TestnetCreationOptions`).

### Added

- `readNodeOptionsFromEnv`: scans an existing testnet environment directory to classify nodes as SPO or relay.

4 changes: 3 additions & 1 deletion cardano-testnet/src/Cardano/Testnet.hs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ module Cardano.Testnet (
retryOnAddressInUseError,

-- ** Testnet options
CardanoTestnetOptions(..),
TestnetCreationOptions(..),
TestnetRuntimeOptions(..),
TestnetEnvOptions(..),
RpcSupport(..),
NodeOption(..),
cardanoDefaultTestnetNodeOptions,
Expand Down
138 changes: 73 additions & 65 deletions cardano-testnet/src/Parsers/Cardano.hs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ module Parsers.Cardano
) where

import Cardano.Api (AnyShelleyBasedEra (..))
import Cardano.Api.Pretty

import Cardano.CLI.EraBased.Common.Option hiding (pNetworkId)
import Cardano.Prelude (readMaybe)
Expand All @@ -26,68 +25,85 @@ import Options.Applicative.Types (readerAsk)
import Testnet.Defaults (defaultEra)
import Testnet.Start.Cardano
import Testnet.Start.Types
import Testnet.Types (readNodeLoggingFormat)

data ModeOptions
= ModeFromEnv TestnetEnvOptions
| ModeNewEnv TestnetCreationOptions (Maybe FilePath)

optsTestnet :: Parser CardanoTestnetCliOptions
optsTestnet = CardanoTestnetCliOptions
<$> pCardanoTestnetCliOptions
<*> pGenesisOptions
<*> pNodeEnvironment
<*> pUpdateTimestamps
<*> pOnChainParams
optsTestnet = mkCliOptions <$> pModeOptions <*> pRuntimeOptions
where
pModeOptions =
ModeFromEnv <$> pFromEnv
<|> ModeNewEnv <$> pCreationOptions <*> pScratchOutputDir
mkCliOptions (ModeFromEnv envOpts) rt = StartFromEnv (StartFromEnvOptions envOpts rt)
mkCliOptions (ModeNewEnv creation outDir) rt = NoUserProvidedEnv (NoUserProvidedEnvOptions creation outDir rt)

optsCreateTestnet :: Parser CardanoTestnetCreateEnvOptions
optsCreateTestnet = CardanoTestnetCreateEnvOptions
<$> pCardanoTestnetCliOptions
<*> pGenesisOptions
<$> pCreationOptions
<*> pEnvOutputDir
<*> pCreateEnvOptions

-- We can't fill in the optional Genesis files at parse time, because we want to be in a monad
-- to properly parse JSON. We delegate this task to the caller.
pCreateEnvOptions :: Parser CreateEnvOptions
pCreateEnvOptions = CreateEnvOptions
<$> pOnChainParams
pFromEnv :: Parser TestnetEnvOptions
pFromEnv = TestnetEnvOptions
<$> OA.strOption
( OA.long "node-env"
<> OA.metavar "FILEPATH"
<> 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."
)
<*> pUpdateTimestamps

pCardanoTestnetCliOptions :: Parser CardanoTestnetOptions
pCardanoTestnetCliOptions = CardanoTestnetOptions
pCreationOptions :: Parser TestnetCreationOptions
pCreationOptions = TestnetCreationOptions
<$> pTestnetNodeOptions
<*> pure (AnyShelleyBasedEra defaultEra)
<*> pMaxLovelaceSupply
<*> OA.option (OA.eitherReader readNodeLoggingFormat)
( OA.long "node-logging-format"
<> OA.help "Node logging format (json|text)"
<> OA.metavar "LOGGING_FORMAT"
<> OA.showDefaultWith prettyShow
<> OA.value (cardanoNodeLoggingFormat def)
)
<*> OA.option OA.auto
( OA.long "num-dreps"
<> OA.help "Number of delegate representatives (DReps) to generate. Ignored if a node environment is passed."
<> OA.metavar "NUMBER"
<> OA.showDefault
<> OA.value 3
)
<*> OA.switch
( OA.long "enable-new-epoch-state-logging"
<> OA.help "Enable new epoch state logging to logs/ledger-epoch-state.log"
<> OA.showDefault
)
<*> (maybe NoUserProvidedEnv UserProvidedEnv <$> optional (OA.strOption
( OA.long "output-dir"
<> 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."
<> OA.metavar "DIRECTORY"
)))
<*> OA.flag RpcDisabled RpcEnabled
( OA.long "enable-grpc"
<> 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."
<> OA.showDefault
)
<*> OA.flag UseKesKeyFile UseKesSocket
( OA.long "use-kes-agent"
<> OA.help "Get Praos block forging credentials from kes-agent via the default socket path"
<> OA.showDefault
)
<*> pNumDReps
<*> pGenesisOptions
<*> pOnChainParams

pRuntimeOptions :: Parser TestnetRuntimeOptions
pRuntimeOptions = TestnetRuntimeOptions
<$> pEnableNewEpochStateLogging
<*> pEnableRpc
<*> pKesSource

pScratchOutputDir :: Parser (Maybe FilePath)
pScratchOutputDir = optional $ OA.strOption
( OA.long "output-dir"
<> 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."
<> OA.metavar "DIRECTORY"
)

pNumDReps :: Parser NumDReps
pNumDReps = OA.option OA.auto
( OA.long "num-dreps"
<> OA.help "Number of delegate representatives (DReps) to generate."
<> OA.metavar "NUMBER"
<> OA.showDefault
<> OA.value 3
)

pEnableNewEpochStateLogging :: Parser Bool
pEnableNewEpochStateLogging = OA.switch
( OA.long "enable-new-epoch-state-logging"
<> OA.help "Enable new epoch state logging to logs/ledger-epoch-state.log"
<> OA.showDefault
)

pEnableRpc :: Parser RpcSupport
pEnableRpc = OA.flag RpcDisabled RpcEnabled
( OA.long "enable-grpc"
<> 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."
<> OA.showDefault
)

pKesSource :: Parser PraosCredentialsSource
pKesSource = OA.flag UseKesKeyFile UseKesSocket
( OA.long "use-kes-agent"
<> OA.help "Get Praos block forging credentials from kes-agent via the default socket path"
<> OA.showDefault
)

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

pNodeEnvironment :: Parser UserProvidedEnv
pNodeEnvironment = fmap (maybe NoUserProvidedEnv UserProvidedEnv) <$>
optional $ OA.strOption
( OA.long "node-env"
<> OA.metavar "FILEPATH"
<> 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."
)

pOnChainParams :: Parser TestnetOnChainParams
pOnChainParams = fmap (fromMaybe DefaultParams) <$> optional $
pCustomParamsFile <|> pMainnetParams
Expand Down Expand Up @@ -161,23 +169,23 @@ pGenesisOptions =
pEpochLength =
OA.option OA.auto
( OA.long "epoch-length"
<> OA.help "Epoch length, in number of slots. Ignored if a node environment is passed."
<> OA.help "Epoch length, in number of slots."
<> OA.metavar "SLOTS"
<> OA.showDefault
<> OA.value (genesisEpochLength def)
)
pSlotLength =
OA.option OA.auto
( OA.long "slot-length"
<> OA.help "Slot length. Ignored if a node environment is passed."
<> OA.help "Slot length."
<> OA.metavar "SECONDS"
<> OA.showDefault
<> OA.value (genesisSlotLength def)
)
pActiveSlotCoeffs =
OA.option OA.auto
( OA.long "active-slots-coeff"
<> OA.help "Active slots coefficient. Ignored if a node environment is passed."
<> OA.help "Active slots coefficient."
<> OA.metavar "DOUBLE"
<> OA.showDefault
<> OA.value (genesisActiveSlotsCoeff def)
Expand All @@ -203,8 +211,8 @@ pMaxLovelaceSupply :: Parser Word64
pMaxLovelaceSupply =
OA.option OA.auto
( OA.long "max-lovelace-supply"
<> OA.help "Max lovelace supply that your testnet starts with. Ignored if a node environment is passed."
<> OA.help "Max lovelace supply that your testnet starts with."
<> OA.metavar "WORD64"
<> OA.showDefault
<> OA.value (cardanoMaxSupply def)
<> OA.value (creationMaxSupply def)
)
68 changes: 33 additions & 35 deletions cardano-testnet/src/Parsers/Run.hs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE NumericUnderscores #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
Expand All @@ -13,9 +14,12 @@ module Parsers.Run
import Cardano.CLI.Environment

import Control.Monad (void)
import Data.Maybe (fromMaybe)
import Options.Applicative
import qualified Options.Applicative as Opt
import System.Directory (doesDirectoryExist)

import Testnet.Filepath (unTmpAbsPath)
import Testnet.Start.Cardano
import Testnet.Start.Types

Expand All @@ -24,6 +28,7 @@ import Parsers.Help
import Parsers.Version
import RIO (display, forever, logInfo, runSimpleApp, threadDelay)
import UnliftIO.Resource (runResourceT)
import Cardano.Prelude (unlessM)

pref :: ParserPrefs
pref = Opt.prefs $ showHelpOnEmpty <> showHelpOnError
Expand Down Expand Up @@ -56,50 +61,43 @@ runTestnetCmd = \case

createEnvOptions :: CardanoTestnetCreateEnvOptions -> IO ()
createEnvOptions CardanoTestnetCreateEnvOptions
{ createEnvTestnetOptions=testnetOptions
, createEnvGenesisOptions=genesisOptions
{ createEnvCreationOptions=creationOptions
, createEnvOutputDir=outputDir
, createEnvCreateEnvOptions=ceOptions
} = do
conf <- mkConfigAbs outputDir
void $ createTestnetEnv
testnetOptions genesisOptions ceOptions
creationOptions
-- Do not add hashes to the main config file, so that genesis files
-- can be modified without having to recompute hashes every time.
conf{genesisHashesPolicy = WithoutHashes}

runCardanoOptions :: CardanoTestnetCliOptions -> IO ()
runCardanoOptions CardanoTestnetCliOptions
{ cliTestnetOptions=testnetOptions
, cliGenesisOptions=genesisOptions
, cliNodeEnvironment=env
, cliUpdateTimestamps=updateTimestamps'
, cliOnChainParams=onChainParams
} = do
case env of
NoUserProvidedEnv -> do
-- Create the sandbox, then run cardano-testnet.
-- It is not necessary to honor `cliUpdateTimestamps` here, because
-- the genesis files will be created with up-to-date stamps already.
conf <- mkConfigAbs "testnet"
runSimpleApp . runResourceT $ do
logInfo $ "Creating environment: " <> display (tempAbsPath conf)
createTestnetEnv testnetOptions genesisOptions (CreateEnvOptions onChainParams) conf
logInfo $ "Starting testnet in environment: " <> display (tempAbsPath conf)
void $ cardanoTestnet testnetOptions conf
logInfo "Testnet started"
waitForShutdown
UserProvidedEnv nodeEnvPath -> do
-- Run cardano-testnet in the sandbox provided by the user
-- In that case, 'cardanoOutputDir' is not used
conf <- mkConfigAbs nodeEnvPath
runSimpleApp . runResourceT $ do
logInfo $ "Starting testnet in environment: " <> display (tempAbsPath conf)
void $ cardanoTestnet
testnetOptions
conf{updateTimestamps=updateTimestamps'}
logInfo "Testnet started"
waitForShutdown
runCardanoOptions = \case
NoUserProvidedEnv NoUserProvidedEnvOptions{noEnvCreationOptions, noEnvOutputDir, noEnvRuntimeOptions} -> do
-- Create the sandbox, then run cardano-testnet.
-- It is not necessary to update timestamps here, because
-- the genesis files will be created with up-to-date stamps already.
let dirName = fromMaybe "testnet" noEnvOutputDir
conf <- mkConfigAbs dirName
runSimpleApp . runResourceT $ do
logInfo $ "Creating environment: " <> display (tempAbsPath conf)
createTestnetEnv noEnvCreationOptions conf
logInfo $ "Starting testnet in environment: " <> display (tempAbsPath conf)
void $ cardanoTestnet (creationNodes noEnvCreationOptions) noEnvRuntimeOptions conf
logInfo "Testnet started"
waitForShutdown
StartFromEnv StartFromEnvOptions{fromEnvOptions, fromEnvRuntimeOptions} -> do
-- Run cardano-testnet in the sandbox provided by the user
let dirName = envPath fromEnvOptions
unlessM (doesDirectoryExist dirName) $ error $ "The provided path does not exist or is not a directory: " <> dirName
conf <- mkConfigAbs dirName
nodes <- readNodeOptionsFromEnv (unTmpAbsPath (tempAbsPath conf))
Comment thread
palas marked this conversation as resolved.
runSimpleApp . runResourceT $ do
logInfo $ "Starting testnet in environment: " <> display (tempAbsPath conf)
void $ cardanoTestnet nodes fromEnvRuntimeOptions
conf{updateTimestamps = envUpdateTimestamps fromEnvOptions}
logInfo "Testnet started"
waitForShutdown
where
waitForShutdown = do
logInfo "Waiting for shutdown (Ctrl+C)"
Expand Down
Loading
Loading