From 2c4966b63febe43298122bc8a73455933f447e32 Mon Sep 17 00:00:00 2001 From: MarcFontaine Date: Wed, 19 Oct 2022 17:36:02 +0200 Subject: [PATCH] cardano-testnet: add single entrypoint for starting Shelley Babbage and Cardano testnets --- .../test/Spec/Chairman/Cardano.hs | 3 +- .../test/Spec/Chairman/Shelley.hs | 4 +- cardano-testnet/cardano-testnet.cabal | 1 + cardano-testnet/src/Test/Process.hs | 8 + cardano-testnet/src/Test/Runtime.hs | 43 ++-- cardano-testnet/src/Testnet.hs | 27 +++ cardano-testnet/src/Testnet/Babbage.hs | 82 ++------ cardano-testnet/src/Testnet/Cardano.hs | 184 +++++------------- cardano-testnet/src/Testnet/Shelley.hs | 108 +++++----- cardano-testnet/src/Testnet/Utils.hs | 61 ++++++ .../Spec/Cli/Alonzo/LeadershipSchedule.hs | 8 +- .../Spec/Cli/Babbage/LeadershipSchedule.hs | 10 +- .../test/Spec/Cli/KesPeriodInfo.hs | 6 +- .../test/Spec/ShutdownOnSlotSynced.hs | 7 +- .../testnet/Testnet/Commands/Babbage.hs | 9 +- .../testnet/Testnet/Commands/Cardano.hs | 9 +- .../testnet/Testnet/Commands/Shelley.hs | 9 +- 17 files changed, 261 insertions(+), 318 deletions(-) create mode 100644 cardano-testnet/src/Testnet.hs diff --git a/cardano-node-chairman/test/Spec/Chairman/Cardano.hs b/cardano-node-chairman/test/Spec/Chairman/Cardano.hs index f923f5a7556..7de4a81dcd7 100644 --- a/cardano-node-chairman/test/Spec/Chairman/Cardano.hs +++ b/cardano-node-chairman/test/Spec/Chairman/Cardano.hs @@ -16,6 +16,7 @@ import qualified Hedgehog.Extras.Test.Base as H import qualified Hedgehog.Extras.Test.Process as H import qualified System.Directory as IO import qualified Test.Base as H +import qualified Test.Runtime as H import qualified Testnet.Cardano as H import qualified Testnet.Conf as H @@ -29,6 +30,6 @@ hprop_chairman = H.integration . H.runFinallies . H.workspace "chairman" $ \temp configurationTemplate <- H.noteShow $ base "configuration/defaults/byron-mainnet/configuration.yaml" conf <- H.mkConf (H.ProjectBase base) (H.YamlFilePath configurationTemplate) tempAbsPath' Nothing - allNodes <- fmap H.nodeName . H.allNodes <$> H.testnet H.defaultTestnetOptions conf + allNodes <- fmap H.nodeName . H.allNodes <$> H.cardanoTestnet H.defaultTestnetOptions conf chairmanOver 120 50 conf allNodes diff --git a/cardano-node-chairman/test/Spec/Chairman/Shelley.hs b/cardano-node-chairman/test/Spec/Chairman/Shelley.hs index c56b64c77cf..d7df183bf9d 100644 --- a/cardano-node-chairman/test/Spec/Chairman/Shelley.hs +++ b/cardano-node-chairman/test/Spec/Chairman/Shelley.hs @@ -6,6 +6,7 @@ module Spec.Chairman.Shelley import Control.Monad ((=<<)) import Data.Function +import Data.Functor import Data.Maybe import Spec.Chairman.Chairman (chairmanOver) import System.FilePath (()) @@ -15,6 +16,7 @@ import qualified Hedgehog.Extras.Test.Base as H import qualified Hedgehog.Extras.Test.Process as H import qualified System.Directory as IO import qualified Test.Base as H +import qualified Test.Runtime as H import qualified Testnet.Conf as H import qualified Testnet.Shelley as H @@ -28,6 +30,6 @@ hprop_chairman = H.integration . H.runFinallies . H.workspace "chairman" $ \temp configurationTemplate <- H.noteShow $ base "configuration/defaults/byron-mainnet/configuration.yaml" conf <- H.mkConf (H.ProjectBase base) (H.YamlFilePath configurationTemplate) tempAbsPath' Nothing - allNodes <- H.testnet H.defaultTestnetOptions conf + allNodes <- fmap H.nodeName . H.allNodes <$> H.shelleyTestnet H.defaultTestnetOptions conf chairmanOver 120 21 conf allNodes diff --git a/cardano-testnet/cardano-testnet.cabal b/cardano-testnet/cardano-testnet.cabal index d0889a14bad..4bc76af7f84 100644 --- a/cardano-testnet/cardano-testnet.cabal +++ b/cardano-testnet/cardano-testnet.cabal @@ -57,6 +57,7 @@ library Test.Base Test.Process Test.Runtime + Testnet Testnet.Babbage Testnet.Byron Testnet.Cardano diff --git a/cardano-testnet/src/Test/Process.hs b/cardano-testnet/src/Test/Process.hs index 5a12b5d08e4..ab89c792f96 100644 --- a/cardano-testnet/src/Test/Process.hs +++ b/cardano-testnet/src/Test/Process.hs @@ -3,6 +3,7 @@ module Test.Process , assertByDeadlineMCustom , bashPath , execCli + , execCli_ , execCli' , execCreateScriptContext , execCreateScriptContext' @@ -52,6 +53,13 @@ execCli -> m String execCli = GHC.withFrozenCallStack $ H.execFlex "cardano-cli" "CARDANO_CLI" +-- | Run cardano-cli, discarding return value +execCli_ + :: (MonadTest m, MonadCatch m, MonadIO m, HasCallStack) + => [String] + -> m () +execCli_ = void . execCli + -- | Run cardano-cli, returning the stdout execCli' :: (MonadTest m, MonadCatch m, MonadIO m, HasCallStack) diff --git a/cardano-testnet/src/Test/Runtime.hs b/cardano-testnet/src/Test/Runtime.hs index 1f39e646927..fd87e40b1a4 100644 --- a/cardano-testnet/src/Test/Runtime.hs +++ b/cardano-testnet/src/Test/Runtime.hs @@ -2,7 +2,6 @@ {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE LambdaCase #-} -{-# LANGUAGE NamedFieldPuns #-} module Test.Runtime ( LeadershipSlot(..) @@ -10,13 +9,15 @@ module Test.Runtime , PaymentKeyPair(..) , StakingKeyPair(..) , TestnetRuntime(..) + , NodeRuntime(..) , TestnetNode(..) , PoolNode(..) , PoolNodeKeys(..) , Delegator(..) + , allNodes , bftSprockets , poolSprockets - , poolNodeToTestnetNode + , poolNodeStdout , readNodeLoggingFormat ) where @@ -49,7 +50,7 @@ data TestnetRuntime = TestnetRuntime , delegators :: [Delegator] } -data TestnetNode = TestnetNode +data NodeRuntime = NodeRuntime { nodeName :: String , nodeSprocket :: Sprocket , nodeStdinHandle :: IO.Handle @@ -58,14 +59,11 @@ data TestnetNode = TestnetNode , nodeProcessHandle :: IO.ProcessHandle } +newtype TestnetNode = TestnetNode {unTestnetNode :: NodeRuntime} + data PoolNode = PoolNode - { poolNodeName :: String - , poolNodeSprocket :: Sprocket - , poolNodeStdinHandle :: IO.Handle - , poolNodeStdout :: FilePath - , poolNodeStderr :: FilePath - , poolNodeProcessHandle :: IO.ProcessHandle - , poolNodeKeys :: PoolNodeKeys + { poolRuntime :: NodeRuntime + , poolKeys :: PoolNodeKeys } data PoolNodeKeys = PoolNodeKeys @@ -97,31 +95,20 @@ data LeadershipSlot = LeadershipSlot , slotTime :: Text } deriving (Eq, Show, Generic, FromJSON) -poolNodeToTestnetNode :: PoolNode -> TestnetNode -poolNodeToTestnetNode PoolNode - { poolNodeName - , poolNodeSprocket - , poolNodeStdinHandle - , poolNodeStdout - , poolNodeStderr - , poolNodeProcessHandle - } = TestnetNode - { nodeName = poolNodeName - , nodeSprocket = poolNodeSprocket - , nodeStdinHandle = poolNodeStdinHandle - , nodeStdout = poolNodeStdout - , nodeStderr = poolNodeStderr - , nodeProcessHandle = poolNodeProcessHandle - } +poolNodeStdout :: PoolNode -> FilePath +poolNodeStdout = nodeStdout . poolRuntime bftSprockets :: TestnetRuntime -> [Sprocket] -bftSprockets = fmap nodeSprocket . bftNodes +bftSprockets = fmap (nodeSprocket . unTestnetNode) . bftNodes poolSprockets :: TestnetRuntime -> [Sprocket] -poolSprockets = fmap poolNodeSprocket . poolNodes +poolSprockets = fmap (nodeSprocket . poolRuntime) . poolNodes readNodeLoggingFormat :: String -> Either String NodeLoggingFormat readNodeLoggingFormat = \case "json" -> Right NodeLoggingFormatAsJson "text" -> Right NodeLoggingFormatAsText s -> Left $ "Unrecognised node logging format: " <> show s <> ". Valid options: \"json\", \"text\"" + +allNodes :: TestnetRuntime -> [NodeRuntime] +allNodes tr = fmap unTestnetNode (bftNodes tr) <> fmap poolRuntime (poolNodes tr) diff --git a/cardano-testnet/src/Testnet.hs b/cardano-testnet/src/Testnet.hs new file mode 100644 index 00000000000..6d8754ec213 --- /dev/null +++ b/cardano-testnet/src/Testnet.hs @@ -0,0 +1,27 @@ +module Testnet + ( TestnetOptions(..) + , Testnet.testnet + ) where + +import Data.Eq (Eq) +import Text.Show (Show) + +import qualified Hedgehog.Extras.Test.Base as H + +import Testnet.Babbage +import Testnet.Cardano +import Testnet.Conf +import Testnet.Shelley + +data TestnetOptions + = ShelleyOnlyTestnetOptions ShelleyTestnetOptions + | BabbageOnlyTestnetOptions BabbageTestnetOptions + | CardanoOnlyTestnetOptions CardanoTestnetOptions + deriving (Eq, Show) + +testnet :: TestnetOptions -> Conf -> H.Integration TestnetRuntime +testnet options = case options of + ShelleyOnlyTestnetOptions o -> shelleyTestnet o + BabbageOnlyTestnetOptions o -> babbageTestnet o + CardanoOnlyTestnetOptions o -> cardanoTestnet o + diff --git a/cardano-testnet/src/Testnet/Babbage.hs b/cardano-testnet/src/Testnet/Babbage.hs index bf73e8af686..a3c6349847a 100644 --- a/cardano-testnet/src/Testnet/Babbage.hs +++ b/cardano-testnet/src/Testnet/Babbage.hs @@ -8,7 +8,7 @@ {-# OPTIONS_GHC -Wno-unused-local-binds -Wno-unused-matches #-} module Testnet.Babbage - ( TestnetOptions(..) + ( BabbageTestnetOptions(..) , defaultTestnetOptions , TestnetNodeOptions(..) , defaultTestnetNodeOptions @@ -17,55 +17,48 @@ module Testnet.Babbage , TestnetNode (..) , PaymentKeyPair(..) - , testnet + , babbageTestnet ) where import Control.Applicative (Applicative (..)) -import Control.Monad (Monad (..), fmap, forM, forM_, return, void, when, (=<<)) +import Control.Monad import Data.Aeson (encode, object, toJSON, (.=)) import Data.Bool (Bool (..)) import Data.Eq (Eq) import Data.Function (flip, ($), (.)) -import Data.Functor ((<$>), (<&>)) +import Data.Functor import Data.Int (Int) -import Data.Maybe (Maybe (..)) -import Data.Ord (Ord ((<=))) import Data.Semigroup (Semigroup ((<>))) import Data.String (String) import GHC.Float (Double) -import Hedgehog.Extras.Stock.IO.Network.Sprocket (Sprocket (..)) import Hedgehog.Extras.Stock.Time (showUTCTimeSeconds) import System.FilePath.Posix (()) + import Test.Runtime (Delegator (..), NodeLoggingFormat (..), PaymentKeyPair (..), PoolNode (PoolNode), PoolNodeKeys (..), StakingKeyPair (..), TestnetNode (..), TestnetRuntime (..)) +import Testnet.Utils import Text.Show (Show (show)) import qualified Data.HashMap.Lazy as HM import qualified Data.List as L import qualified Data.Time.Clock as DTC -import qualified Hedgehog as H import qualified Hedgehog.Extras.Stock.Aeson as J -import qualified Hedgehog.Extras.Stock.IO.Network.Sprocket as IO import qualified Hedgehog.Extras.Stock.OS as OS -import qualified Hedgehog.Extras.Stock.String as S import qualified Hedgehog.Extras.Test.Base as H import qualified Hedgehog.Extras.Test.File as H -import qualified Hedgehog.Extras.Test.Process as H import qualified System.Info as OS -import qualified System.IO as IO -import qualified System.Process as IO import qualified Test.Assert as H -import qualified Test.Process as H import qualified Testnet.Conf as H +import Test.Process (execCli_) {- HLINT ignore "Reduce duplication" -} {- HLINT ignore "Redundant <&>" -} {- HLINT ignore "Redundant flip" -} {- HLINT ignore "Redundant id" -} {- HLINT ignore "Use let" -} -data TestnetOptions = TestnetOptions +data BabbageTestnetOptions = BabbageTestnetOptions { numSpoNodes :: Int , slotDuration :: Int , securityParam :: Int @@ -73,8 +66,8 @@ data TestnetOptions = TestnetOptions , nodeLoggingFormat :: NodeLoggingFormat } deriving (Eq, Show) -defaultTestnetOptions :: TestnetOptions -defaultTestnetOptions = TestnetOptions +defaultTestnetOptions :: BabbageTestnetOptions +defaultTestnetOptions = BabbageTestnetOptions { numSpoNodes = 3 , slotDuration = 1000 , securityParam = 10 @@ -92,8 +85,8 @@ defaultTestnetNodeOptions = TestnetNodeOptions startTimeOffsetSeconds :: DTC.NominalDiffTime startTimeOffsetSeconds = if OS.isWin32 then 90 else 15 -testnet :: TestnetOptions -> H.Conf -> H.Integration TestnetRuntime -testnet testnetOptions H.Conf {..} = do +babbageTestnet :: BabbageTestnetOptions -> H.Conf -> H.Integration TestnetRuntime +babbageTestnet testnetOptions H.Conf {..} = do H.createDirectoryIfMissing (tempAbsPath "logs") H.lbsWriteFile (tempAbsPath "byron.genesis.spec.json") . encode $ object @@ -124,7 +117,7 @@ testnet testnetOptions H.Conf {..} = do currentTime <- H.noteShowIO DTC.getCurrentTime startTime <- H.noteShow $ DTC.addUTCTime startTimeOffsetSeconds currentTime - void . H.execCli $ + execCli_ [ "byron", "genesis", "genesis" , "--protocol-magic", show @Int testnetMagic , "--start-time", showUTCTimeSeconds startTime @@ -180,7 +173,7 @@ testnet testnetOptions H.Conf {..} = do let numPoolNodes = 3 :: Int - void . H.execCli $ + execCli_ [ "genesis", "create-staked" , "--genesis-dir", tempAbsPath , "--testnet-magic", show @Int testnetMagic @@ -340,49 +333,19 @@ testnet testnetOptions H.Conf {..} = do ] ] - (poolSprockets, poolStdins, poolStdouts, poolStderrs, poolProcessHandles) <- fmap L.unzip5 . forM spoNodes $ \node -> do - dbDir <- H.noteShow $ tempAbsPath "db/" <> node - nodeStdoutFile <- H.noteTempFile logDir $ node <> ".stdout.log" - nodeStderrFile <- H.noteTempFile logDir $ node <> ".stderr.log" - sprocket <- H.noteShow $ Sprocket tempBaseAbsPath (socketDir node) - - H.createDirectoryIfMissing dbDir - H.createDirectoryIfMissing $ tempBaseAbsPath socketDir - - hNodeStdout <- H.openFile nodeStdoutFile IO.WriteMode - hNodeStderr <- H.openFile nodeStderrFile IO.WriteMode - - H.diff (L.length (IO.sprocketArgumentName sprocket)) (<=) IO.maxSprocketArgumentNameLength - - portString <- fmap S.strip . H.readFile $ tempAbsPath node "port" - - (Just stdIn, _, _, hProcess, _) <- H.createProcess =<< - ( H.procNode + poolNodes <- forM (L.zip spoNodes poolKeys) $ \(node,key) -> do + runtime <- startNode socketDir tempBaseAbsPath tempAbsPath logDir node [ "run" , "--config", tempAbsPath "configuration.yaml" , "--topology", tempAbsPath node "topology.json" , "--database-path", tempAbsPath node "db" - , "--socket-path", IO.sprocketArgumentName sprocket , "--shelley-kes-key", tempAbsPath node "kes.skey" , "--shelley-vrf-key", tempAbsPath node "vrf.skey" , "--byron-delegation-certificate", tempAbsPath node "byron-delegation.cert" , "--byron-signing-key", tempAbsPath node "byron-delegate.key" , "--shelley-operational-certificate", tempAbsPath node "opcert.cert" - , "--port", portString - ] <&> - ( \cp -> cp - { IO.std_in = IO.CreatePipe - , IO.std_out = IO.UseHandle hNodeStdout - , IO.std_err = IO.UseHandle hNodeStderr - , IO.cwd = Just tempBaseAbsPath - } - ) - ) - - when (OS.os `L.elem` ["darwin", "linux"]) $ do - H.onFailure . H.noteIO_ $ IO.readProcess "lsof" ["-iTCP:" <> portString, "-sTCP:LISTEN", "-n", "-P"] "" - - return (sprocket, stdIn, nodeStdoutFile, nodeStderrFile, hProcess) + ] + return $ PoolNode runtime key now <- H.noteShowIO DTC.getCurrentTime deadline <- H.noteShow $ DTC.addUTCTime 90 now @@ -401,14 +364,7 @@ testnet testnetOptions H.Conf {..} = do { configurationFile , shelleyGenesisFile = tempAbsPath "genesis/shelley/genesis.json" , testnetMagic - , poolNodes = L.zipWith7 PoolNode - spoNodes - poolSprockets - poolStdins - poolStdouts - poolStderrs - poolProcessHandles - poolKeys + , poolNodes , wallets = wallets , bftNodes = [] , delegators = delegators diff --git a/cardano-testnet/src/Testnet/Cardano.hs b/cardano-testnet/src/Testnet/Cardano.hs index cb8c90d1743..21da96b8d82 100644 --- a/cardano-testnet/src/Testnet/Cardano.hs +++ b/cardano-testnet/src/Testnet/Cardano.hs @@ -1,40 +1,35 @@ -{-# LANGUAGE CPP #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TypeApplications #-} -{-# OPTIONS_GHC -Wno-unused-local-binds -Wno-unused-matches #-} - module Testnet.Cardano ( ForkPoint(..) - , TestnetOptions(..) + , CardanoTestnetOptions(..) , defaultTestnetOptions , TestnetNodeOptions(..) , defaultTestnetNodeOptions , Era(..) , TestnetRuntime (..) - , allNodes , TestnetNode (..) , PaymentKeyPair(..) - , testnet + , cardanoTestnet ) where import Control.Applicative (pure) -import Control.Monad (Monad (..), fmap, forM, forM_, return, void, when, (=<<)) +import Control.Monad import Control.Monad.IO.Class (liftIO) import Data.Aeson ((.=)) import Data.Bool (Bool (..)) import Data.ByteString.Lazy (ByteString) import Data.Eq (Eq (..)) import Data.Function (flip, id, ($), (.)) -import Data.Functor ((<$>), (<&>)) +import Data.Functor import Data.Int (Int) import Data.List ((\\)) -import Data.Maybe (Maybe (Just), fromJust) -import Data.Ord (Ord ((<=))) +import Data.Maybe (fromJust) import Data.Semigroup (Semigroup ((<>))) import Data.String (IsString (fromString), String) import GHC.Enum (Bounded, Enum) @@ -46,11 +41,15 @@ import Hedgehog.Extras.Stock.Time (formatIso8601, showUTCTimeSeconds) import Ouroboros.Network.PeerSelection.LedgerPeers (UseLedgerAfter (..)) import Ouroboros.Network.PeerSelection.RelayAccessPoint (RelayAccessPoint (..)) import System.FilePath.Posix (()) -import Test.Runtime (NodeLoggingFormat (..), PaymentKeyPair (..), PoolNode (PoolNode), +import Test.Runtime as TR (NodeLoggingFormat (..), PaymentKeyPair (..), PoolNode (PoolNode), PoolNodeKeys (..), TestnetNode (..), TestnetRuntime (..)) import Text.Read (Read) import Text.Show (Show (show)) +import Test.Process (execCli_) + +import Testnet.Utils + import qualified Cardano.Node.Configuration.Topology as NonP2P import qualified Cardano.Node.Configuration.TopologyP2P as P2P import qualified Data.Aeson as J @@ -58,7 +57,6 @@ import qualified Data.HashMap.Lazy as HM import qualified Data.List as L import qualified Data.Map as M import qualified Data.Time.Clock as DTC -import qualified Hedgehog as H import qualified Hedgehog.Extras.Stock.Aeson as J import qualified Hedgehog.Extras.Stock.IO.Network.Socket as IO import qualified Hedgehog.Extras.Stock.IO.Network.Sprocket as IO @@ -68,14 +66,10 @@ import qualified Hedgehog.Extras.Test.Base as H import qualified Hedgehog.Extras.Test.Concurrent as H import qualified Hedgehog.Extras.Test.File as H import qualified Hedgehog.Extras.Test.Network as H -import qualified Hedgehog.Extras.Test.Process as H import qualified System.Directory as IO import qualified System.Info as OS -import qualified System.IO as IO -import qualified System.Process as IO import qualified Test.Assert as H import qualified Test.Process as H -import qualified Test.Runtime as TR import qualified Testnet.Conf as H {- HLINT ignore "Reduce duplication" -} @@ -91,7 +85,7 @@ data ForkPoint data Era = Byron | Shelley | Allegra | Mary | Alonzo deriving (Eq, Enum, Bounded, Read, Show) -data TestnetOptions = TestnetOptions +data CardanoTestnetOptions = CardanoTestnetOptions { -- | List of node options. Each option will result in a single node being -- created. bftNodeOptions :: [TestnetNodeOptions] @@ -104,8 +98,8 @@ data TestnetOptions = TestnetOptions , nodeLoggingFormat :: NodeLoggingFormat } deriving (Eq, Show) -defaultTestnetOptions :: TestnetOptions -defaultTestnetOptions = TestnetOptions +defaultTestnetOptions :: CardanoTestnetOptions +defaultTestnetOptions = CardanoTestnetOptions { bftNodeOptions = L.replicate 2 defaultTestnetNodeOptions , numPoolNodes = 1 , era = Alonzo @@ -127,9 +121,6 @@ defaultTestnetNodeOptions = TestnetNodeOptions { extraNodeCliArgs = [] } -allNodes :: TestnetRuntime -> [TestnetNode] -allNodes tr = bftNodes tr <> fmap TR.poolNodeToTestnetNode (poolNodes tr) - ifaceAddress :: String ifaceAddress = "127.0.0.1" @@ -175,8 +166,8 @@ mkTopologyConfig numNodes allPorts port True = J.encode topologyP2P [] (P2P.UseLedger DontUseLedger) -testnet :: TestnetOptions -> H.Conf -> H.Integration TestnetRuntime -testnet testnetOptions H.Conf {..} = do +cardanoTestnet :: CardanoTestnetOptions -> H.Conf -> H.Integration TestnetRuntime +cardanoTestnet testnetOptions H.Conf {..} = do void $ H.note OS.os currentTime <- H.noteShowIO DTC.getCurrentTime startTime <- H.noteShow $ DTC.addUTCTime startTimeOffsetSeconds currentTime @@ -296,7 +287,7 @@ testnet testnetOptions H.Conf {..} = do ] -- stuff - void . H.execCli $ + execCli_ [ "byron" , "genesis" , "genesis" @@ -324,7 +315,7 @@ testnet testnetOptions H.Conf {..} = do -- Create keys and addresses to withdraw the initial UTxO into forM_ bftNodesN $ \n -> do - void $ H.execCli + execCli_ [ "keygen" , "--secret", tempAbsPath "byron/payment-keys.00" <> show @Int (n - 1) <> ".key" ] @@ -348,7 +339,7 @@ testnet testnetOptions H.Conf {..} = do -- Create Byron address that moves funds out of the genesis UTxO into a regular -- address. - void $ H.execCli + execCli_ [ "issue-genesis-utxo-expenditure" , "--genesis-json", tempAbsPath "byron/genesis.json" , "--testnet-magic", show @Int testnetMagic @@ -359,7 +350,7 @@ testnet testnetOptions H.Conf {..} = do ] -- Update Proposal and votes - void $ H.execCli + execCli_ [ "byron", "governance", "create-update-proposal" , "--filepath", tempAbsPath "update-proposal" , "--testnet-magic", show @Int testnetMagic @@ -374,7 +365,7 @@ testnet testnetOptions H.Conf {..} = do ] forM_ bftNodesN $ \n -> do - void $ H.execCli + execCli_ [ "byron", "governance", "create-proposal-vote" , "--proposal-filepath", tempAbsPath "update-proposal" , "--testnet-magic", show @Int testnetMagic @@ -383,7 +374,7 @@ testnet testnetOptions H.Conf {..} = do , "--output-filepath", tempAbsPath "update-vote.00" <> show @Int (n - 1) ] - void $ H.execCli + execCli_ [ "byron", "governance", "create-update-proposal" , "--filepath", tempAbsPath "update-proposal-1" , "--testnet-magic", show @Int testnetMagic @@ -398,7 +389,7 @@ testnet testnetOptions H.Conf {..} = do ] forM_ bftNodesN $ \n -> - void $ H.execCli + execCli_ [ "byron", "governance", "create-proposal-vote" , "--proposal-filepath", tempAbsPath "update-proposal-1" , "--testnet-magic", show @Int testnetMagic @@ -419,7 +410,7 @@ testnet testnetOptions H.Conf {..} = do alonzoSpecFile <- H.noteTempFile tempAbsPath "shelley/genesis.alonzo.spec.json" liftIO $ IO.copyFile sourceAlonzoGenesisSpecFile alonzoSpecFile - void $ H.execCli + execCli_ [ "genesis", "create" , "--testnet-magic", show @Int testnetMagic , "--genesis-dir", tempAbsPath "shelley" @@ -445,7 +436,7 @@ testnet testnetOptions H.Conf {..} = do ) -- Now generate for real: - void $ H.execCli + execCli_ [ "genesis", "create" , "--testnet-magic", show @Int testnetMagic , "--genesis-dir", tempAbsPath "shelley" @@ -480,7 +471,7 @@ testnet testnetOptions H.Conf {..} = do poolKeys <- forM poolNodesN $ \i -> do let node = "node-pool" <> show @Int i - void $ H.execCli + execCli_ [ "node", "key-gen" , "--cold-verification-key-file", tempAbsPath node "shelley/operator.vkey" , "--cold-signing-key-file", tempAbsPath node "shelley/operator.skey" @@ -494,7 +485,7 @@ testnet testnetOptions H.Conf {..} = do poolNodeKeysStakingVkey <- H.note $ tempAbsPath node "shelley/staking.vkey" poolNodeKeysStakingSkey <- H.note $ tempAbsPath node "shelley/staking.skey" - void $ H.execCli + execCli_ [ "node", "key-gen-VRF" , "--verification-key-file", poolNodeKeysVrfVkey , "--signing-key-file", poolNodeKeysVrfSkey @@ -519,13 +510,13 @@ testnet testnetOptions H.Conf {..} = do -- Make hot keys and for all nodes forM_ allNodeNames $ \node -> do - void $ H.execCli + execCli_ [ "node", "key-gen-KES" , "--verification-key-file", tempAbsPath node "shelley/kes.vkey" , "--signing-key-file", tempAbsPath node "shelley/kes.skey" ] - void $ H.execCli + execCli_ [ "node", "issue-op-cert" , "--kes-period", "0" , "--kes-verification-key-file", tempAbsPath node "shelley/kes.vkey" @@ -554,38 +545,38 @@ testnet testnetOptions H.Conf {..} = do let paymentVKey = tempAbsPath "addresses/" <> addr <> ".vkey" -- Payment address keys - void $ H.execCli + execCli_ [ "address", "key-gen" , "--verification-key-file", paymentVKey , "--signing-key-file", paymentSKey ] - void $ H.execCli + execCli_ [ "address", "key-gen" , "--verification-key-file", tempAbsPath "shelley/utxo-keys/utxo2.vkey" , "--signing-key-file", tempAbsPath "shelley/utxo-keys/utxo2.skey" ] - void $ H.execCli + execCli_ [ "stake-address", "key-gen" , "--verification-key-file", tempAbsPath "addresses/" <> addr <> "-stake.vkey" , "--signing-key-file", tempAbsPath "addresses/" <> addr <> "-stake.skey" ] - void $ H.execCli + execCli_ [ "stake-address", "key-gen" , "--verification-key-file", tempAbsPath "shelley/utxo-keys/utxo-stake.vkey" , "--signing-key-file", tempAbsPath "shelley/utxo-keys/utxo-stake.skey" ] - void $ H.execCli + execCli_ [ "stake-address", "key-gen" , "--verification-key-file", tempAbsPath "shelley/utxo-keys/utxo2-stake.vkey" , "--signing-key-file", tempAbsPath "shelley/utxo-keys/utxo2-stake.skey" ] -- Payment addresses - void $ H.execCli + execCli_ [ "address", "build" , "--payment-verification-key-file", tempAbsPath "addresses/" <> addr <> ".vkey" , "--stake-verification-key-file", tempAbsPath "addresses/" <> addr <> "-stake.vkey" @@ -594,7 +585,7 @@ testnet testnetOptions H.Conf {..} = do ] -- Stake addresses - void $ H.execCli + execCli_ [ "stake-address", "build" , "--stake-verification-key-file", tempAbsPath "addresses/" <> addr <> "-stake.vkey" , "--testnet-magic", show @Int testnetMagic @@ -602,7 +593,7 @@ testnet testnetOptions H.Conf {..} = do ] -- Stake addresses registration certs - void $ H.execCli + execCli_ [ "stake-address", "registration-certificate" , "--stake-verification-key-file", tempAbsPath "addresses/" <> addr <> "-stake.vkey" , "--out-file", tempAbsPath "addresses/" <> addr <> "-stake.reg.cert" @@ -616,7 +607,7 @@ testnet testnetOptions H.Conf {..} = do -- user N will delegate to pool N forM_ userPoolN $ \n -> do -- Stake address delegation certs - void $ H.execCli + execCli_ [ "stake-address", "delegation-certificate" , "--stake-verification-key-file", tempAbsPath "addresses/user" <> show @Int n <> "-stake.vkey" , "--cold-verification-key-file", tempAbsPath "node-pool" <> show @Int n "shelley/operator.vkey" @@ -666,7 +657,7 @@ testnet testnetOptions H.Conf {..} = do user1Addr <- H.readFile $ tempAbsPath "addresses/user1.addr" - void $ H.execCli + execCli_ [ "transaction", "build-raw" , "--invalid-hereafter", "1000" , "--fee", "0" @@ -718,7 +709,7 @@ testnet testnetOptions H.Conf {..} = do -- 2. the user1 stake address key, due to the delegation cert -- 3. the pool1 owner key, due to the pool registration cert -- 3. the pool1 operator key, due to the pool registration cert - void $ H.execCli + execCli_ [ "transaction", "sign" , "--signing-key-file", tempAbsPath "shelley/utxo-keys/utxo1.skey" , "--signing-key-file", tempAbsPath "addresses/user1-stake.skey" @@ -736,96 +727,34 @@ testnet testnetOptions H.Conf {..} = do -- Launch cluster of three nodes let bftNodeNameAndOpts = L.zip bftNodeNames (bftNodeOptions testnetOptions) - (bftSprockets', bftStdins, bftStdouts, bftStderrs, bftProcessHandles) <- fmap L.unzip5 . forM bftNodeNameAndOpts $ \(node, nodeOpts) -> do - dbDir <- H.noteShow $ tempAbsPath "db/" <> node - nodeStdoutFile <- H.noteTempFile logDir $ node <> ".stdout.log" - nodeStderrFile <- H.noteTempFile logDir $ node <> ".stderr.log" - sprocket <- H.noteShow $ Sprocket tempBaseAbsPath (socketDir node) - - H.createDirectoryIfMissing dbDir - H.createDirectoryIfMissing $ tempBaseAbsPath socketDir - - hNodeStdout <- H.openFile nodeStdoutFile IO.WriteMode - hNodeStderr <- H.openFile nodeStderrFile IO.WriteMode - - H.diff (L.length (IO.sprocketArgumentName sprocket)) (<=) IO.maxSprocketArgumentNameLength - - portString <- fmap S.strip . H.readFile $ tempAbsPath node "port" - - (Just stdIn, _, _, hProcess, _) <- H.createProcess =<< - ( H.procNode - ([ "run" + bftNodes <- forM bftNodeNameAndOpts $ \(node, nodeOpts) -> do + runtime <- startNode socketDir tempBaseAbsPath tempAbsPath logDir node + ([ "run" , "--config", tempAbsPath "configuration.yaml" , "--topology", tempAbsPath node "topology.json" , "--database-path", tempAbsPath node "db" - , "--socket-path", IO.sprocketArgumentName sprocket , "--shelley-kes-key", tempAbsPath node "shelley/kes.skey" , "--shelley-vrf-key", tempAbsPath node "shelley/vrf.skey" , "--shelley-operational-certificate", tempAbsPath node "shelley/node.cert" - , "--port", portString , "--delegation-certificate", tempAbsPath node "byron/delegate.cert" , "--signing-key", tempAbsPath node "byron/delegate.key" - ] <> extraNodeCliArgs nodeOpts) <&> - ( \cp -> cp - { IO.std_in = IO.CreatePipe - , IO.std_out = IO.UseHandle hNodeStdout - , IO.std_err = IO.UseHandle hNodeStderr - , IO.cwd = Just tempBaseAbsPath - } - ) - ) - - H.noteShowM_ $ H.getPid hProcess - - when (OS.os `L.elem` ["darwin", "linux"]) $ do - H.onFailure . H.noteIO_ $ IO.readProcess "lsof" ["-iTCP:" <> portString, "-sTCP:LISTEN", "-n", "-P"] "" - - return (sprocket, stdIn, nodeStdoutFile, nodeStderrFile, hProcess) + ] <> extraNodeCliArgs nodeOpts) + return $ TestnetNode runtime H.threadDelay 100000 - (poolSprockets, poolStdins, poolStdouts, poolStderrs, poolProcessHandles) <- fmap L.unzip5 . forM poolNodeNames $ \node -> do - dbDir <- H.noteShow $ tempAbsPath "db/" <> node - nodeStdoutFile <- H.noteTempFile logDir $ node <> ".stdout.log" - nodeStderrFile <- H.noteTempFile logDir $ node <> ".stderr.log" - sprocket <- H.noteShow $ Sprocket tempBaseAbsPath (socketDir node) - - H.createDirectoryIfMissing dbDir - H.createDirectoryIfMissing $ tempBaseAbsPath socketDir - - hNodeStdout <- H.openFile nodeStdoutFile IO.WriteMode - hNodeStderr <- H.openFile nodeStderrFile IO.WriteMode - - H.diff (L.length (IO.sprocketArgumentName sprocket)) (<=) IO.maxSprocketArgumentNameLength - - portString <- fmap S.strip . H.readFile $ tempAbsPath node "port" - - (Just stdIn, _, _, hProcess, _) <- H.createProcess =<< - ( H.procNode + poolNodes <- forM (L.zip poolNodeNames poolKeys) $ \(node, key) -> do + runtime <- startNode socketDir tempBaseAbsPath tempAbsPath logDir node [ "run" , "--config", tempAbsPath "configuration.yaml" , "--topology", tempAbsPath node "topology.json" , "--database-path", tempAbsPath node "db" - , "--socket-path", IO.sprocketArgumentName sprocket , "--shelley-kes-key", tempAbsPath node "shelley/kes.skey" , "--shelley-vrf-key", tempAbsPath node "shelley/vrf.skey" , "--shelley-operational-certificate", tempAbsPath node "shelley/node.cert" , "--host-addr", ifaceAddress - , "--port", portString - ] <&> - ( \cp -> cp - { IO.std_in = IO.CreatePipe - , IO.std_out = IO.UseHandle hNodeStdout - , IO.std_err = IO.UseHandle hNodeStderr - , IO.cwd = Just tempBaseAbsPath - } - ) - ) - - when (OS.os `L.elem` ["darwin", "linux"]) $ do - H.onFailure . H.noteIO_ $ IO.readProcess "lsof" ["-iTCP:" <> portString, "-sTCP:LISTEN", "-n", "-P"] "" - - return (sprocket, stdIn, nodeStdoutFile, nodeStderrFile, hProcess) + ] + return $ PoolNode runtime key now <- H.noteShowIO DTC.getCurrentTime deadline <- H.noteShow $ DTC.addUTCTime 90 now @@ -845,21 +774,8 @@ testnet testnetOptions H.Conf {..} = do { configurationFile , shelleyGenesisFile = tempAbsPath "shelley/genesis.json" , testnetMagic - , bftNodes = L.zipWith6 TestnetNode - bftNodeNames - bftSprockets' - bftStdins - bftStdouts - bftStderrs - bftProcessHandles - , poolNodes = L.zipWith7 PoolNode - poolNodeNames - poolSprockets - poolStdins - poolStdouts - poolStderrs - poolProcessHandles - poolKeys + , bftNodes + , poolNodes , wallets , delegators = [] -- TODO this should be populated } diff --git a/cardano-testnet/src/Testnet/Shelley.hs b/cardano-testnet/src/Testnet/Shelley.hs index 27d70032c0a..f3c9982fb88 100644 --- a/cardano-testnet/src/Testnet/Shelley.hs +++ b/cardano-testnet/src/Testnet/Shelley.hs @@ -1,29 +1,28 @@ {-# LANGUAGE CPP #-} +{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TypeApplications #-} module Testnet.Shelley - ( TestnetOptions(..) + ( ShelleyTestnetOptions(..) , defaultTestnetOptions - - , testnet + , shelleyTestnet , hprop_testnet , hprop_testnet_pause ) where -import Control.Monad (Monad(..), forever, forM_, void, (=<<), when) +import Control.Monad import Control.Monad.IO.Class (MonadIO(liftIO)) import Control.Monad.Trans.Resource (MonadResource(liftResourceT), resourceForkIO) import Data.Aeson (Value, ToJSON(toJSON)) import Data.ByteString.Lazy (ByteString) import Data.Eq (Eq) import Data.Function (($), (.), flip) -import Data.Functor (Functor(fmap), (<$>), (<&>)) +import Data.Functor import Data.Int (Int) import Data.List ((\\)) -import Data.Maybe (Maybe(Nothing, Just), fromJust) -import Data.Ord (Ord((<=))) +import Data.Maybe import Data.Semigroup (Semigroup((<>))) import Data.String (String, fromString) import Data.Time.Clock (UTCTime) @@ -36,6 +35,9 @@ import Ouroboros.Network.PeerSelection.RelayAccessPoint (RelayAccessPo import Prelude (Bool(..), Integer, (-)) import System.FilePath.Posix (()) import Text.Show (Show(show)) +import Test.Process (execCli_) +import Test.Runtime hiding (allNodes) +import Testnet.Utils import qualified Cardano.Node.Configuration.Topology as NonP2P import qualified Cardano.Node.Configuration.TopologyP2P as P2P @@ -58,8 +60,6 @@ import qualified Hedgehog.Extras.Test.Network as H import qualified Hedgehog.Extras.Test.Process as H import qualified System.Directory as IO import qualified System.Info as OS -import qualified System.IO as IO -import qualified System.Process as IO import qualified Test.Base as H import qualified Test.Process as H import qualified Testnet.Conf as H @@ -68,7 +68,7 @@ import qualified Testnet.Conf as H {- HLINT ignore "Redundant <&>" -} {- HLINT ignore "Redundant flip" -} -data TestnetOptions = TestnetOptions +data ShelleyTestnetOptions = ShelleyTestnetOptions { numPraosNodes :: Int , numPoolNodes :: Int , activeSlotsCoeff :: Double @@ -79,8 +79,8 @@ data TestnetOptions = TestnetOptions , enableP2P :: Bool } deriving (Eq, Show) -defaultTestnetOptions :: TestnetOptions -defaultTestnetOptions = TestnetOptions +defaultTestnetOptions :: ShelleyTestnetOptions +defaultTestnetOptions = ShelleyTestnetOptions { numPraosNodes = 2 , numPoolNodes = 1 , activeSlotsCoeff = 0.1 @@ -102,7 +102,7 @@ rewriteConfiguration _ s = s ifaceAddress :: String ifaceAddress = "127.0.0.1" -rewriteGenesisSpec :: TestnetOptions -> UTCTime -> Value -> Value +rewriteGenesisSpec :: ShelleyTestnetOptions -> UTCTime -> Value -> Value rewriteGenesisSpec testnetOptions startTime = rewriteObject $ HM.insert "activeSlotsCoeff" (J.toJSON @Double (activeSlotsCoeff testnetOptions)) @@ -159,8 +159,8 @@ mkTopologyConfig numPraosNodes allPorts port True = J.encode topologyP2P [] (P2P.UseLedger DontUseLedger) -testnet :: TestnetOptions -> H.Conf -> H.Integration [String] -testnet testnetOptions H.Conf {..} = do +shelleyTestnet :: ShelleyTestnetOptions -> H.Conf -> H.Integration TestnetRuntime +shelleyTestnet testnetOptions H.Conf {..} = do void $ H.note OS.os let praosNodesN = show @Int <$> [1 .. numPraosNodes testnetOptions] @@ -187,7 +187,7 @@ testnet testnetOptions H.Conf {..} = do liftIO $ IO.copyFile sourceAlonzoGenesisSpecFile alonzoSpecFile -- Set up our template - void $ H.execCli + execCli_ [ "genesis", "create" , "--testnet-magic", show @Int testnetMagic , "--genesis-dir", tempAbsPath @@ -205,7 +205,7 @@ testnet testnetOptions H.Conf {..} = do H.assertIsJsonFile $ tempAbsPath "genesis.spec.json" -- Now generate for real - void $ H.execCli + execCli_ [ "genesis", "create" , "--testnet-magic", show @Int testnetMagic , "--genesis-dir", tempAbsPath @@ -219,14 +219,14 @@ testnet testnetOptions H.Conf {..} = do -- Make the pool operator cold keys -- This was done already for the BFT nodes as part of the genesis creation forM_ poolNodes $ \n -> do - void $ H.execCli + execCli_ [ "node", "key-gen" , "--cold-verification-key-file", tempAbsPath n "operator.vkey" , "--cold-signing-key-file", tempAbsPath n "operator.skey" , "--operational-certificate-issue-counter-file", tempAbsPath n "operator.counter" ] - void $ H.execCli + execCli_ [ "node", "key-gen-VRF" , "--verification-key-file", tempAbsPath n "vrf.vkey" , "--signing-key-file", tempAbsPath n "vrf.skey" @@ -241,13 +241,13 @@ testnet testnetOptions H.Conf {..} = do -- Make hot keys and for all nodes forM_ allNodes $ \node -> do - void $ H.execCli + execCli_ [ "node", "key-gen-KES" , "--verification-key-file", tempAbsPath node "kes.vkey" , "--signing-key-file", tempAbsPath node "kes.skey" ] - void $ H.execCli + execCli_ [ "node", "issue-op-cert" , "--kes-period", "0" , "--kes-verification-key-file", tempAbsPath node "kes.vkey" @@ -276,21 +276,21 @@ testnet testnetOptions H.Conf {..} = do forM_ addrs $ \addr -> do -- Payment address keys - void $ H.execCli + execCli_ [ "address", "key-gen" , "--verification-key-file", tempAbsPath "addresses/" <> addr <> ".vkey" , "--signing-key-file", tempAbsPath "addresses/" <> addr <> ".skey" ] -- Stake address keys - void $ H.execCli + execCli_ [ "stake-address", "key-gen" , "--verification-key-file", tempAbsPath "addresses/" <> addr <> "-stake.vkey" , "--signing-key-file", tempAbsPath "addresses/" <> addr <> "-stake.skey" ] -- Payment addresses - void $ H.execCli + execCli_ [ "address", "build" , "--payment-verification-key-file", tempAbsPath "addresses/" <> addr <> ".vkey" , "--stake-verification-key-file", tempAbsPath "addresses/" <> addr <> "-stake.vkey" @@ -299,7 +299,7 @@ testnet testnetOptions H.Conf {..} = do ] -- Stake addresses - void $ H.execCli + execCli_ [ "stake-address", "build" , "--stake-verification-key-file", tempAbsPath "addresses/" <> addr <> "-stake.vkey" , "--testnet-magic", show @Int testnetMagic @@ -307,7 +307,7 @@ testnet testnetOptions H.Conf {..} = do ] -- Stake addresses registration certs - void $ H.execCli + execCli_ [ "stake-address", "registration-certificate" , "--stake-verification-key-file", tempAbsPath "addresses/" <> addr <> "-stake.vkey" , "--out-file", tempAbsPath "addresses/" <> addr <> "-stake.reg.cert" @@ -315,7 +315,7 @@ testnet testnetOptions H.Conf {..} = do forM_ userPoolN $ \n -> do -- Stake address delegation certs - void $ H.execCli + execCli_ [ "stake-address", "delegation-certificate" , "--stake-verification-key-file", tempAbsPath "addresses/user" <> n <> "-stake.vkey" , "--cold-verification-key-file", tempAbsPath "node-pool" <> n "operator.vkey" @@ -331,7 +331,7 @@ testnet testnetOptions H.Conf {..} = do -- Next is to make the stake pool registration cert forM_ poolNodes $ \node -> do - void $ H.execCli + execCli_ [ "stake-pool", "registration-certificate" , "--testnet-magic", show @Int testnetMagic , "--pool-pledge", "0" @@ -365,7 +365,7 @@ testnet testnetOptions H.Conf {..} = do userNAddr <- H.readFile $ tempAbsPath "addresses/user" <> n <> ".addr" - void $ H.execCli + execCli_ [ "transaction", "build-raw" , "--invalid-hereafter", "1000" , "--fee", "0" @@ -384,7 +384,7 @@ testnet testnetOptions H.Conf {..} = do -- 3. the pool n owner key, due to the pool registration cert -- 3. the pool n operator key, due to the pool registration cert - void $ H.execCli + execCli_ [ "transaction", "sign" , "--signing-key-file", tempAbsPath "utxo-keys/utxo" <> n <> ".skey" , "--signing-key-file", tempAbsPath "addresses/user" <> n <> "-stake.skey" @@ -407,24 +407,8 @@ testnet testnetOptions H.Conf {..} = do <&> L.unlines . fmap (rewriteConfiguration (enableP2P testnetOptions)) . L.lines >>= H.writeFile (tempAbsPath "configuration.yaml") - forM_ allNodes $ \node -> do - dbDir <- H.noteShow $ tempAbsPath "db/" <> node - nodeStdoutFile <- H.noteTempFile logDir $ node <> ".stdout.log" - nodeStderrFile <- H.noteTempFile logDir $ node <> ".stderr.log" - sprocket <- H.noteShow $ Sprocket tempBaseAbsPath (socketDir node) - - H.createDirectoryIfMissing dbDir - H.createDirectoryIfMissing $ tempBaseAbsPath socketDir - - hNodeStdout <- H.openFile nodeStdoutFile IO.WriteMode - hNodeStderr <- H.openFile nodeStderrFile IO.WriteMode - - H.diff (L.length (IO.sprocketArgumentName sprocket)) (<=) IO.maxSprocketArgumentNameLength - - portString <- H.readFile $ tempAbsPath node "port" - - void $ H.createProcess =<< - ( H.procNode + allNodeRuntimes <- forM allNodes $ \node -> do + runtime <- startNode socketDir tempBaseAbsPath tempAbsPath logDir node [ "run" , "--config", tempAbsPath "configuration.yaml" , "--topology", tempAbsPath node "topology.json" @@ -433,20 +417,8 @@ testnet testnetOptions H.Conf {..} = do , "--shelley-vrf-key", tempAbsPath node "vrf.skey" , "--shelley-operational-certificate" , tempAbsPath node "node.cert" , "--host-addr", ifaceAddress - , "--port", portString - , "--socket-path", IO.sprocketArgumentName sprocket - ] <&> - ( \cp -> cp - { IO.std_in = IO.CreatePipe - , IO.std_out = IO.UseHandle hNodeStdout - , IO.std_err = IO.UseHandle hNodeStderr - , IO.cwd = Just tempBaseAbsPath - } - ) - ) - - when (OS.os `L.elem` ["darwin", "linux"]) $ do - H.onFailure . H.noteIO_ $ IO.readProcess "lsof" ["-iTCP:" <> portString, "-sTCP:LISTEN", "-n", "-P"] "" + ] + return $ TestnetNode runtime now <- H.noteShowIO DTC.getCurrentTime deadline <- H.noteShow $ DTC.addUTCTime 90 now @@ -463,7 +435,15 @@ testnet testnetOptions H.Conf {..} = do H.noteShowIO_ DTC.getCurrentTime - return allNodes + return TestnetRuntime + { configurationFile = alonzoSpecFile + , shelleyGenesisFile = tempAbsPath "genesis/shelley/genesis.json" + , testnetMagic + , poolNodes = [ ] + , wallets = [ ] + , bftNodes = allNodeRuntimes + , delegators = [ ] + } hprop_testnet :: H.Property hprop_testnet = H.integration . H.runFinallies . H.workspace "chairman" $ \tempAbsPath' -> do @@ -473,7 +453,7 @@ hprop_testnet = H.integration . H.runFinallies . H.workspace "chairman" $ \tempA void . liftResourceT . resourceForkIO . forever . liftIO $ IO.threadDelay 10000000 - void $ testnet defaultTestnetOptions conf + void $ shelleyTestnet defaultTestnetOptions conf H.failure -- Intentional failure to force failure report diff --git a/cardano-testnet/src/Testnet/Utils.hs b/cardano-testnet/src/Testnet/Utils.hs index 295c5b85b90..ffbbd3a3643 100644 --- a/cardano-testnet/src/Testnet/Utils.hs +++ b/cardano-testnet/src/Testnet/Utils.hs @@ -4,6 +4,7 @@ module Testnet.Utils ( QueryTipOutput(..) , queryTip + , startNode , waitUntilEpoch ) where @@ -15,17 +16,29 @@ import Control.Exception.Safe (MonadCatch) import Control.Monad import Control.Monad.IO.Class import Data.Aeson (fromJSON) +import Data.Functor +import qualified Data.List as L import GHC.Stack import System.Directory (doesFileExist, removeFile) +import System.FilePath.Posix (()) import Cardano.CLI.Shelley.Output +import Hedgehog.Extras.Stock.IO.Network.Sprocket (Sprocket (..)) import qualified Hedgehog.Extras.Test.Base as H import qualified Hedgehog.Extras.Test.File as H import Hedgehog.Extras.Test.Process (ExecConfig) import Hedgehog.Internal.Property (MonadTest) import qualified Test.Process as H +import qualified Hedgehog as H +import qualified Hedgehog.Extras.Stock.IO.Network.Sprocket as IO +import qualified Hedgehog.Extras.Stock.String as S +import qualified Hedgehog.Extras.Test.Process as H +import qualified System.Info as OS +import qualified System.IO as IO +import qualified System.Process as IO +import Test.Runtime (NodeRuntime (..)) -- | Submit the desired epoch to wait to. waitUntilEpoch @@ -83,3 +96,51 @@ queryTip (QueryTipOutput fp) testnetMag execConfig = do newtype QueryTipOutput = QueryTipOutput { unQueryTipOutput :: FilePath} +startNode :: + FilePath + -> String + -> FilePath + -> FilePath + -> String + -> [String] + -> H.Integration NodeRuntime +startNode socketDir tempBaseAbsPath tempAbsPath logDir node nodeCmd = do + dbDir <- H.noteShow $ tempAbsPath "db/" <> node + nodeStdoutFile <- H.noteTempFile logDir $ node <> ".stdout.log" + nodeStderrFile <- H.noteTempFile logDir $ node <> ".stderr.log" + sprocket <- H.noteShow $ Sprocket tempBaseAbsPath (socketDir node) + + H.createDirectoryIfMissing dbDir + H.createDirectoryIfMissing $ tempBaseAbsPath socketDir + + hNodeStdout <- H.openFile nodeStdoutFile IO.WriteMode + hNodeStderr <- H.openFile nodeStderrFile IO.WriteMode + + H.diff (L.length (IO.sprocketArgumentName sprocket)) (<=) IO.maxSprocketArgumentNameLength + + portString <- fmap S.strip . H.readFile $ tempAbsPath node "port" + + (Just stdIn, _, _, hProcess, _) <- H.createProcess =<< + ( H.procNode + ( nodeCmd + <> + [ "--socket-path", IO.sprocketArgumentName sprocket + , "--port", portString + ] + ) + <&> + ( \cp -> cp + { IO.std_in = IO.CreatePipe + , IO.std_out = IO.UseHandle hNodeStdout + , IO.std_err = IO.UseHandle hNodeStderr + , IO.cwd = Just tempBaseAbsPath + } + ) + ) + + H.noteShowM_ $ H.getPid hProcess + + when (OS.os `L.elem` ["darwin", "linux"]) $ do + H.onFailure . H.noteIO_ $ IO.readProcess "lsof" ["-iTCP:" <> portString, "-sTCP:LISTEN", "-n", "-P"] "" + + return $ NodeRuntime node sprocket stdIn nodeStdoutFile nodeStderrFile hProcess diff --git a/cardano-testnet/test/Spec/Cli/Alonzo/LeadershipSchedule.hs b/cardano-testnet/test/Spec/Cli/Alonzo/LeadershipSchedule.hs index f8535d22b9b..b3c17bd6605 100644 --- a/cardano-testnet/test/Spec/Cli/Alonzo/LeadershipSchedule.hs +++ b/cardano-testnet/test/Spec/Cli/Alonzo/LeadershipSchedule.hs @@ -48,7 +48,7 @@ import qualified Test.Process as H import qualified Test.Runtime as TR import Test.Runtime (LeadershipSlot (..)) import qualified Testnet.Cardano as TC -import Testnet.Cardano (TestnetOptions (..), TestnetRuntime (..)) +import Testnet.Cardano (CardanoTestnetOptions (..), TestnetRuntime (..), cardanoTestnet) import qualified Testnet.Conf as H import Testnet.Utils (waitUntilEpoch) @@ -69,7 +69,7 @@ hprop_leadershipSchedule = H.integration . H.runFinallies . H.workspace "alonzo" tr@TC.TestnetRuntime { testnetMagic , poolNodes - } <- TC.testnet fastTestnetOptions conf + } <- cardanoTestnet fastTestnetOptions conf poolNode1 <- H.headM poolNodes @@ -482,7 +482,7 @@ hprop_leadershipSchedule = H.integration . H.runFinallies . H.workspace "alonzo" H.note_ "Done" - let poolVrfSkey = TR.poolNodeKeysVrfSkey $ TR.poolNodeKeys poolNode1 + let poolVrfSkey = TR.poolNodeKeysVrfSkey $ TR.poolKeys poolNode1 scheduleFile <- H.noteTempFile tempAbsPath "schedule.log" void $ H.execCli' execConfig @@ -504,7 +504,7 @@ hprop_leadershipSchedule = H.integration . H.runFinallies . H.workspace "alonzo" leadershipDeadline <- H.noteShowM $ DTC.addUTCTime 90 <$> H.noteShowIO DTC.getCurrentTime H.assertByDeadlineMCustom "Leader schedule is correct" leadershipDeadline $ do - leaderSlots <- H.getRelevantLeaderSlots (TR.poolNodeStdout poolNode1) (minimum expectedLeadershipSlotNumbers) + leaderSlots <- H.getRelevantLeaderSlots (TR.nodeStdout $ TR.poolRuntime poolNode1) (minimum expectedLeadershipSlotNumbers) maxSlotExpected <- H.noteShow $ maximum expectedLeadershipSlotNumbers maxActualSlot <- H.noteShow $ maximum leaderSlots return $ maxActualSlot >= maxSlotExpected diff --git a/cardano-testnet/test/Spec/Cli/Babbage/LeadershipSchedule.hs b/cardano-testnet/test/Spec/Cli/Babbage/LeadershipSchedule.hs index 2efe4e4d6bb..24e6fd498c2 100644 --- a/cardano-testnet/test/Spec/Cli/Babbage/LeadershipSchedule.hs +++ b/cardano-testnet/test/Spec/Cli/Babbage/LeadershipSchedule.hs @@ -25,7 +25,7 @@ import Hedgehog (Property) import Prelude import System.Environment (getEnvironment) import System.FilePath (()) -import Testnet.Babbage (TestnetOptions (..), TestnetRuntime (..)) +import Testnet.Babbage (BabbageTestnetOptions (..), TestnetRuntime (..), babbageTestnet) import qualified Data.Aeson as J import qualified Data.Aeson.Types as J @@ -66,13 +66,13 @@ hprop_leadershipSchedule = H.integration . H.runFinallies . H.workspace "alonzo" , poolNodes -- , wallets -- , delegators - } <- TC.testnet testnetOptions conf + } <- babbageTestnet testnetOptions conf poolNode1 <- H.headM poolNodes env <- H.evalIO getEnvironment - poolSprocket1 <- H.noteShow $ TR.poolNodeSprocket poolNode1 + poolSprocket1 <- H.noteShow $ TR.nodeSprocket $ TR.poolRuntime poolNode1 execConfig <- H.noteShow H.ExecConfig { H.execConfigEnv = Last $ Just $ @@ -106,10 +106,10 @@ hprop_leadershipSchedule = H.integration . H.runFinallies . H.workspace "alonzo" stakePoolId <- filter ( /= '\n') <$> H.execCli [ "stake-pool", "id" - , "--cold-verification-key-file", TR.poolNodeKeysColdVkey $ TR.poolNodeKeys poolNode1 + , "--cold-verification-key-file", TR.poolNodeKeysColdVkey $ TR.poolKeys poolNode1 ] - let poolVrfSkey = TR.poolNodeKeysVrfSkey $ TR.poolNodeKeys poolNode1 + let poolVrfSkey = TR.poolNodeKeysVrfSkey $ TR.poolKeys poolNode1 id do scheduleFile <- H.noteTempFile tempAbsPath "schedule.log" diff --git a/cardano-testnet/test/Spec/Cli/KesPeriodInfo.hs b/cardano-testnet/test/Spec/Cli/KesPeriodInfo.hs index 0df3aa7dd87..0dc1dbdce57 100644 --- a/cardano-testnet/test/Spec/Cli/KesPeriodInfo.hs +++ b/cardano-testnet/test/Spec/Cli/KesPeriodInfo.hs @@ -41,8 +41,8 @@ import qualified Test.Base as H import qualified Test.Process as H import qualified Test.Runtime as TR import qualified Testnet.Cardano as TC -import Testnet.Cardano (TestnetOptions (..), TestnetRuntime (..), - defaultTestnetNodeOptions, defaultTestnetOptions, testnet) +import Testnet.Cardano (CardanoTestnetOptions (..), TestnetRuntime (..), + defaultTestnetNodeOptions, defaultTestnetOptions, cardanoTestnet) import qualified Testnet.Conf as H import Testnet.Conf (ProjectBase (..), YamlFilePath (..)) import Testnet.Utils (waitUntilEpoch) @@ -66,7 +66,7 @@ hprop_kes_period_info = H.integration . H.runFinallies . H.workspace "chairman" , slotLength = 0.02 , activeSlotsCoeff = 0.1 } - runTime@TC.TestnetRuntime { testnetMagic } <- testnet fastTestnetOptions conf + runTime@TC.TestnetRuntime { testnetMagic } <- cardanoTestnet fastTestnetOptions conf let sprockets = TR.bftSprockets runTime env <- H.evalIO getEnvironment diff --git a/cardano-testnet/test/Spec/ShutdownOnSlotSynced.hs b/cardano-testnet/test/Spec/ShutdownOnSlotSynced.hs index 52c5315c68f..c1a3650c004 100644 --- a/cardano-testnet/test/Spec/ShutdownOnSlotSynced.hs +++ b/cardano-testnet/test/Spec/ShutdownOnSlotSynced.hs @@ -28,9 +28,10 @@ import qualified Hedgehog.Extras.Test.File as H import qualified Hedgehog.Extras.Test.Process as H import qualified System.Directory as IO import qualified Test.Base as H +import Test.Runtime (NodeRuntime (..)) import Testnet.Cardano (TestnetNode (..), TestnetNodeOptions (TestnetNodeOptions), - TestnetOptions (..), TestnetRuntime (..), defaultTestnetNodeOptions, - defaultTestnetOptions, testnet) + CardanoTestnetOptions (..), TestnetRuntime (..), defaultTestnetNodeOptions, + defaultTestnetOptions, cardanoTestnet) import qualified Testnet.Cardano as TC import qualified Testnet.Conf as H @@ -54,7 +55,7 @@ hprop_shutdownOnSlotSynced = H.integration . H.runFinallies . H.workspace "chair , defaultTestnetNodeOptions ] } - TC.TestnetRuntime { bftNodes = node:_ } <- testnet fastTestnetOptions conf + TC.TestnetRuntime { bftNodes = (TestnetNode node):_ } <- cardanoTestnet fastTestnetOptions conf -- Wait for the node to exit let timeout :: Int diff --git a/cardano-testnet/testnet/Testnet/Commands/Babbage.hs b/cardano-testnet/testnet/Testnet/Commands/Babbage.hs index acefe4cd7a1..2a102388b03 100644 --- a/cardano-testnet/testnet/Testnet/Commands/Babbage.hs +++ b/cardano-testnet/testnet/Testnet/Commands/Babbage.hs @@ -12,6 +12,7 @@ import Data.Semigroup import Options.Applicative import System.IO (IO) import Test.Runtime (readNodeLoggingFormat) +import Testnet import Testnet.Babbage import Testnet.Run (runTestnet) import Text.Show @@ -20,11 +21,11 @@ import qualified Options.Applicative as OA data BabbageOptions = BabbageOptions { maybeTestnetMagic :: Maybe Int - , testnetOptions :: TestnetOptions + , testnetOptions :: BabbageTestnetOptions } deriving (Eq, Show) -optsTestnet :: Parser TestnetOptions -optsTestnet = TestnetOptions +optsTestnet :: Parser BabbageTestnetOptions +optsTestnet = BabbageTestnetOptions <$> OA.option auto ( OA.long "num-spo-nodes" <> OA.help "Number of SPO nodes" @@ -74,7 +75,7 @@ optsBabbage = BabbageOptions runBabbageOptions :: BabbageOptions -> IO () runBabbageOptions options = runTestnet (maybeTestnetMagic options) $ - Testnet.Babbage.testnet (testnetOptions options) + Testnet.testnet (BabbageOnlyTestnetOptions $ testnetOptions options) cmdBabbage :: Mod CommandFields (IO ()) cmdBabbage = command "babbage" $ flip info idm $ runBabbageOptions <$> optsBabbage diff --git a/cardano-testnet/testnet/Testnet/Commands/Cardano.hs b/cardano-testnet/testnet/Testnet/Commands/Cardano.hs index bedd1bc562a..4222ee6fc27 100644 --- a/cardano-testnet/testnet/Testnet/Commands/Cardano.hs +++ b/cardano-testnet/testnet/Testnet/Commands/Cardano.hs @@ -15,6 +15,7 @@ import GHC.Enum import Options.Applicative import System.IO (IO) import Test.Runtime (readNodeLoggingFormat) +import Testnet import Testnet.Cardano import Testnet.Run (runTestnet) import Text.Read @@ -25,11 +26,11 @@ import qualified Options.Applicative as OA data CardanoOptions = CardanoOptions { maybeTestnetMagic :: Maybe Int - , testnetOptions :: TestnetOptions + , testnetOptions :: CardanoTestnetOptions } deriving (Eq, Show) -optsTestnet :: Parser TestnetOptions -optsTestnet = TestnetOptions +optsTestnet :: Parser CardanoTestnetOptions +optsTestnet = CardanoTestnetOptions <$> OA.option ((`L.replicate` defaultTestnetNodeOptions) <$> auto) ( OA.long "num-bft-nodes" @@ -101,7 +102,7 @@ optsCardano = CardanoOptions runCardanoOptions :: CardanoOptions -> IO () runCardanoOptions options = runTestnet (maybeTestnetMagic options) $ - Testnet.Cardano.testnet (testnetOptions options) + Testnet.testnet (CardanoOnlyTestnetOptions $ testnetOptions options) cmdCardano :: Mod CommandFields (IO ()) cmdCardano = command "cardano" $ flip info idm $ runCardanoOptions <$> optsCardano diff --git a/cardano-testnet/testnet/Testnet/Commands/Shelley.hs b/cardano-testnet/testnet/Testnet/Commands/Shelley.hs index 88797fabfd5..a79a52fd73e 100644 --- a/cardano-testnet/testnet/Testnet/Commands/Shelley.hs +++ b/cardano-testnet/testnet/Testnet/Commands/Shelley.hs @@ -13,6 +13,7 @@ import Data.Semigroup import Options.Applicative import System.IO (IO) import Testnet.Run (runTestnet) +import Testnet import Testnet.Shelley import Text.Show @@ -20,11 +21,11 @@ import qualified Options.Applicative as OA data ShelleyOptions = ShelleyOptions { maybeTestnetMagic :: Maybe Int - , testnetOptions :: TestnetOptions + , testnetOptions :: ShelleyTestnetOptions } deriving (Eq, Show) -optsTestnet :: Parser TestnetOptions -optsTestnet = TestnetOptions +optsTestnet :: Parser ShelleyTestnetOptions +optsTestnet = ShelleyTestnetOptions <$> OA.option auto ( OA.long "num-praos-nodes" <> OA.help "Number of PRAOS nodes" @@ -95,7 +96,7 @@ optsShelley = ShelleyOptions runShelleyOptions :: ShelleyOptions -> IO () runShelleyOptions options = runTestnet (maybeTestnetMagic options) $ - Testnet.Shelley.testnet (testnetOptions options) + Testnet.testnet (ShelleyOnlyTestnetOptions $ testnetOptions options) cmdShelley :: Mod CommandFields (IO ()) cmdShelley = command "shelley" $ flip info idm $ runShelleyOptions <$> optsShelley