@@ -17,7 +17,8 @@ module Testnet.Start.Cardano
1717 , TestnetCreationOptions (.. )
1818 , TestnetRuntimeOptions (.. )
1919 , TestnetEnvOptions (.. )
20- , NodeOption (.. )
20+ , TestnetNodeOptions (.. )
21+ , NodeOptions (.. )
2122 , cardanoDefaultTestnetNodeOptions
2223
2324 , TestnetRuntime (.. )
@@ -92,16 +93,6 @@ import RIO.State (put)
9293import UnliftIO.Async
9394import UnliftIO.Exception (stringException )
9495
95- -- | There are certain conditions that need to be met in order to run
96- -- a valid node cluster.
97- testMinimumConfigurationRequirements :: ()
98- => HasCallStack
99- => MonadIO m
100- => NonEmpty NodeOption -> m ()
101- testMinimumConfigurationRequirements nodes = withFrozenCallStack $ do
102- unless (any isSpoNodeOptions nodes) $ do
103- throwString " Need at least one SPO node to produce blocks, but got none."
104-
10596liftToIntegration :: HasCallStack => RIO ResourceMap a -> H. Integration a
10697liftToIntegration r = do
10798 rMap <- lift $ lift getInternalState
@@ -118,15 +109,13 @@ createTestnetEnv :: ()
118109createTestnetEnv
119110 creationOptions@ TestnetCreationOptions
120111 { creationEra= asbe
121- , creationNodes
112+ , creationNodes= TestnetNodeOptions {optSpoNodes, optRelayNodes}
122113 }
123114 Conf
124115 { genesisHashesPolicy
125116 , tempAbsPath= TmpAbsolutePath tmpAbsPath
126117 } = do
127118
128- testMinimumConfigurationRequirements creationNodes
129-
130119 AnyShelleyBasedEra sbe <- pure asbe
131120
132121 _ <- createSPOGenesisAndFiles
@@ -141,13 +130,16 @@ createTestnetEnv
141130
142131 liftIOAnnotated . LBS. writeFile configurationFile $ A. encodePretty $ Object config
143132
144- portNumbers <- forM (NEL. zip (1 :| [2 .. ]) creationNodes)
133+ let allNodes = NEL. toList optSpoNodes ++ optRelayNodes
134+ numberedNodes = zip [1 .. ] allNodes
135+ nodeIds = map fst numberedNodes
136+
137+ portNumbers <- forM numberedNodes
145138 (\ (i, _nodeOption) -> (i,) <$> H. randomPort testnetDefaultIpv4Address)
146139
147- let portNumbersMap = Map. fromList ( NEL. toList portNumbers)
140+ let portNumbersMap = Map. fromList portNumbers
148141
149142 -- Create network topology and write port files
150- let nodeIds = fst <$> NEL. zip (1 :| [2 .. ]) creationNodes
151143 forM_ nodeIds $ \ i -> do
152144 let nodeDataDir = tmpAbsPath </> Defaults. defaultNodeDataDir i
153145 liftIOAnnotated $ IO. createDirectoryIfMissing True nodeDataDir
@@ -157,7 +149,7 @@ createTestnetEnv
157149 Just port -> liftIOAnnotated $ writeFile (tmpAbsPath </> defaultPortFile i) (show port)
158150 Nothing -> throwString $ " Port not found for node " <> show i
159151
160- producers <- mapM (idToRemoteAddressP2P portNumbersMap) $ NodeId <$> NEL. filter (/= i) nodeIds
152+ producers <- mapM (idToRemoteAddressP2P portNumbersMap) $ NodeId <$> filter (/= i) nodeIds
161153 let topology = Defaults. defaultP2PTopology producers
162154 liftIOAnnotated . LBS. writeFile (nodeDataDir </> " topology.json" ) $ A. encodePretty topology
163155
@@ -232,12 +224,12 @@ cardanoTestnet
232224 => MonadResource m
233225 => MonadCatch m
234226 => MonadFail m
235- => NonEmpty NodeOption -- ^ The nodes to start
227+ => TestnetNodeOptions -- ^ The nodes to start
236228 -> TestnetRuntimeOptions -- ^ Runtime options
237229 -> Conf -- ^ Path to the test sandbox
238230 -> m TestnetRuntime
239231cardanoTestnet
240- cardanoNodes
232+ TestnetNodeOptions {optSpoNodes = cardanoSpoNodes, optRelayNodes = cardanoRelayNodes}
241233 TestnetRuntimeOptions
242234 { runtimeEnableNewEpochStateLogging= enableNewEpochStateLogging
243235 , runtimeEnableRpc= cardanoEnableRpc
@@ -247,8 +239,8 @@ cardanoTestnet
247239 { tempAbsPath= TmpAbsolutePath tmpAbsPath
248240 , updateTimestamps
249241 } = do
250- testMinimumConfigurationRequirements cardanoNodes
251- let nPools = NumPools $ length $ NEL. filter isSpoNodeOptions cardanoNodes
242+ let nPools = NumPools $ NEL. length cardanoSpoNodes
243+ allNodes = map ( True ,) ( NEL. toList cardanoSpoNodes) ++ map ( False ,) cardanoRelayNodes
252244 nodeConfigFile = tmpAbsPath </> defaultConfigFile
253245 byronGenesisFile = tmpAbsPath </> " byron-genesis.json"
254246 shelleyGenesisFile = tmpAbsPath </> " shelley-genesis.json"
@@ -279,7 +271,7 @@ cardanoTestnet
279271 }
280272
281273 -- Read port numbers from disk (written by createTestnetEnv)
282- portNumbers <- forM (NEL. zip ( 1 :| [ 2 .. ]) cardanoNodes ) $ \ (i, _nodeOption ) -> do
274+ portNumbers <- forM (zip [ 1 .. ] allNodes ) $ \ (i, _ ) -> do
283275 let nodeDataDir = tmpAbsPath </> Defaults. defaultNodeDataDir i
284276 portPath = tmpAbsPath </> defaultPortFile i
285277 portStr <- liftIOAnnotated $ readFile portPath
@@ -316,19 +308,16 @@ cardanoTestnet
316308 let shelleyGenesis' = shelleyGenesis{sgSystemStart = startTime}
317309 liftIOAnnotated . LBS. writeFile shelleyGenesisFile $ A. encodePretty shelleyGenesis'
318310
319- let portNumbersMap = Map. fromList ( NEL. toList portNumbers)
311+ let portNumbersMap = Map. fromList portNumbers
320312
321- eTestnetNodes <- forConcurrently (NEL. zip ( 1 :| [ 2 .. ]) cardanoNodes ) $ \ (i, nodeOptions) -> do
313+ eTestnetNodes <- forConcurrently (zip [ 1 .. ] allNodes ) $ \ (i, (isSpo, nodeOptions) ) -> do
322314 port <- case Map. lookup i portNumbersMap of
323315 Just p -> pure p
324316 Nothing -> throwString $ " Port not found for node " <> show i
325317 let nodeName = Defaults. defaultNodeName i
326318 nodeDataDir = tmpAbsPath </> Defaults. defaultNodeDataDir i
327319 nodePoolKeysDir = tmpAbsPath </> Defaults. defaultSpoKeysDir i
328- (mKeys, spoNodeCliArgs) <-
329- case nodeOptions of
330- RelayNodeOptions {} -> pure (Nothing , [] )
331- SpoNodeOptions {} -> do
320+ (mKeys, spoNodeCliArgs) <- if not isSpo then pure (Nothing , [] ) else do
332321 -- depending on testnet configuration, either start a 'kes-agent' or use a key from disk
333322 kesSourceCliArg <-
334323 case cardanoKESSource of
@@ -370,11 +359,11 @@ cardanoTestnet
370359 , " --database-path" , nodeDataDir </> " db"
371360 ]
372361 <> spoNodeCliArgs
373- <> extraCliArgs nodeOptions
362+ <> nodeExtraCliArgs nodeOptions
374363 <> [" --grpc-enable" | RpcEnabled <- [cardanoEnableRpc]]
375364 pure $ eRuntime <&> \ rt -> rt{poolKeys= mKeys}
376365
377- let (failedNodes, testnetNodes') = partitionEithers ( NEL. toList eTestnetNodes)
366+ let (failedNodes, testnetNodes') = partitionEithers eTestnetNodes
378367 unless (null failedNodes) $ do
379368 throwString $ " Some nodes failed to start:\n " ++ show (vsep $ prettyError <$> failedNodes)
380369
@@ -417,9 +406,6 @@ cardanoTestnet
417406
418407 pure runtime
419408 where
420- extraCliArgs = \ case
421- SpoNodeOptions args -> args
422- RelayNodeOptions args -> args
423409 -- TODO: This should come from the configuration!
424410 makePathsAbsolute :: (Element a ~ FilePath , MonoFunctor a ) => a -> a
425411 makePathsAbsolute = omap (tmpAbsPath </> )
@@ -511,19 +497,32 @@ retryOnAddressInUseError act = withFrozenCallStack $ go maximumTimeout retryTime
511497 retryTimeout = 5
512498
513499-- | Read node options from an existing testnet environment directory.
514- -- Scans @node-data/@ for node directories and checks @pools-keys/@ to
515- -- classify each node as SPO or relay.
516- readNodeOptionsFromEnv :: MonadIO m => FilePath -> m (NonEmpty NodeOption )
500+ -- Scans @node-data/@ for node directories numbered @node1, node2, ...@
501+ -- and checks @pools-keys/@ to classify each as SPO or relay.
502+ -- Validates that nodes are consecutively numbered starting from 1,
503+ -- and that all SPO nodes come before relay nodes.
504+ readNodeOptionsFromEnv :: HasCallStack => MonadIO m => FilePath -> m TestnetNodeOptions
517505readNodeOptionsFromEnv envDir = do
518506 entries <- liftIO $ IO. listDirectory (envDir </> " node-data" )
519507 let nodeNums = sort $ mapMaybe parseNodeNum entries
520- case nodeNums of
521- [] -> throwString " No node directories found in environment"
522- (n: ns) -> mapM classifyNode (n :| ns)
508+ when (null nodeNums) $
509+ throwString " No node directories found in environment"
510+ when (nodeNums /= [1 .. length nodeNums]) $
511+ throwString $ " Node directories are not consecutively numbered from 1: " <> show nodeNums
512+ isSpoFlags <- forM nodeNums $ \ i ->
513+ liftIO $ IO. doesDirectoryExist (envDir </> Defaults. defaultSpoKeysDir i)
514+ let (spoFlags, relayFlags) = span id isSpoFlags
515+ unless (all not relayFlags) $
516+ throwString " SPO nodes must come before relay nodes in the environment"
517+ when (null spoFlags) $
518+ throwString " No SPO node directories found in environment"
519+ let nSpos = length spoFlags
520+ let spoOpts = map (const (NodeOptions [] )) [1 .. nSpos]
521+ relayOpts = map (const (NodeOptions [] )) [nSpos + 1 .. length nodeNums]
522+ case spoOpts of
523+ (s: ss) -> pure $ TestnetNodeOptions { optSpoNodes = s :| ss, optRelayNodes = relayOpts }
524+ [] -> throwString " No SPO node directories found in environment"
523525 where
524526 parseNodeNum s = do
525527 rest <- stripPrefix " node" s
526528 readMaybe rest :: Maybe Int
527- classifyNode i = do
528- hasPools <- liftIO $ IO. doesDirectoryExist (envDir </> Defaults. defaultSpoKeysDir i)
529- pure $ if hasPools then SpoNodeOptions [] else RelayNodeOptions []
0 commit comments