From bd90e5abe27bb7826cc483cba48975c056c78112 Mon Sep 17 00:00:00 2001 From: Nicolas BACQUEY Date: Mon, 7 Jul 2025 11:22:16 +0200 Subject: [PATCH 1/6] Introduce flag in `cardano-testnet create-env` to update time stamps --- cardano-testnet/src/Parsers/Cardano.hs | 12 +++++++++- cardano-testnet/src/Parsers/Run.hs | 4 ++-- cardano-testnet/src/Testnet/Start/Cardano.hs | 7 ++++-- cardano-testnet/src/Testnet/Start/Types.hs | 23 ++++++++++++++++++- .../Cardano/Testnet/Test/P2PTopology.hs | 5 ++-- 5 files changed, 43 insertions(+), 8 deletions(-) diff --git a/cardano-testnet/src/Parsers/Cardano.hs b/cardano-testnet/src/Parsers/Cardano.hs index edc11b9c6c7..d708f5c747a 100644 --- a/cardano-testnet/src/Parsers/Cardano.hs +++ b/cardano-testnet/src/Parsers/Cardano.hs @@ -35,7 +35,10 @@ optsCreateTestnet envCli = CardanoTestnetCreateEnvOptions <$> pCardanoTestnetCliOptions envCli <*> pGenesisOptions <*> pEnvOutputDir - <*> pTopologyType + <*> ( CreateEnvOptions + <$> pTopologyType + <*> pCreateEnvUpdateTime + ) pCardanoTestnetCliOptions :: EnvCli -> Parser CardanoTestnetOptions pCardanoTestnetCliOptions envCli = CardanoTestnetOptions @@ -99,6 +102,13 @@ pTopologyType = OA.flag DirectTopology P2PTopology <> OA.showDefault ) +pCreateEnvUpdateTime :: Parser CreateEnvUpdateTime +pCreateEnvUpdateTime = OA.flag CreateEnv UpdateTimeAndExit + ( OA.long "update-time" + <> OA.help "Don't create anything, just update the time stamps in existing files" + <> OA.showDefault + ) + pEnvOutputDir :: Parser FilePath pEnvOutputDir = OA.strOption ( OA.long "output" diff --git a/cardano-testnet/src/Parsers/Run.hs b/cardano-testnet/src/Parsers/Run.hs index c527dd433b8..ce96ce2b481 100644 --- a/cardano-testnet/src/Parsers/Run.hs +++ b/cardano-testnet/src/Parsers/Run.hs @@ -62,11 +62,11 @@ createEnvOptions CardanoTestnetCreateEnvOptions { createEnvTestnetOptions=testnetOptions , createEnvGenesisOptions=genesisOptions , createEnvOutputDir=outputDir - , createEnvTopologyType=topologyType + , createEnvCreateEnvOptions=ceOptions } = testnetRoutine (UserProvidedEnv outputDir) $ \conf -> createTestnetEnv - testnetOptions genesisOptions topologyType + testnetOptions genesisOptions ceOptions -- The CLI does not provide a way to provide custom genesis data by design: -- If the user wants to have custom genesis data, they should manually -- modify the files created by this command before running the testnet. diff --git a/cardano-testnet/src/Testnet/Start/Cardano.hs b/cardano-testnet/src/Testnet/Start/Cardano.hs index 659297e98b9..00bead3e4a0 100644 --- a/cardano-testnet/src/Testnet/Start/Cardano.hs +++ b/cardano-testnet/src/Testnet/Start/Cardano.hs @@ -86,7 +86,7 @@ createTestnetEnv :: () => HasCallStack => CardanoTestnetOptions -> GenesisOptions - -> TopologyType + -> CreateEnvOptions -> UserProvidedData ShelleyGenesis -> UserProvidedData AlonzoGenesis -> UserProvidedData ConwayGenesis @@ -98,7 +98,10 @@ createTestnetEnv , cardanoNodes } genesisOptions - topologyType + CreateEnvOptions + { ceoTopologyType=topologyType + , ceoUpdateTime=_createEnvUpdateTime + } mShelley mAlonzo mConway Conf { genesisHashesPolicy diff --git a/cardano-testnet/src/Testnet/Start/Types.hs b/cardano-testnet/src/Testnet/Start/Types.hs index 2a287974d48..ec59d332316 100644 --- a/cardano-testnet/src/Testnet/Start/Types.hs +++ b/cardano-testnet/src/Testnet/Start/Types.hs @@ -21,6 +21,8 @@ module Testnet.Start.Types , anyShelleyBasedEraToString , eraToString + , CreateEnvOptions(..) + , CreateEnvUpdateTime(..) , NodeOption(..) , isRelayNodeOptions , cardanoDefaultTestnetNodeOptions @@ -88,6 +90,25 @@ data TopologyType instance Default TopologyType where def = DirectTopology +data CreateEnvUpdateTime + = CreateEnv + | UpdateTimeAndExit + deriving (Eq, Show) + +instance Default CreateEnvUpdateTime where + def = CreateEnv + +data CreateEnvOptions = CreateEnvOptions + { ceoTopologyType :: TopologyType + , ceoUpdateTime :: CreateEnvUpdateTime + } deriving (Eq, Show) + +instance Default CreateEnvOptions where + def = CreateEnvOptions + { ceoTopologyType = def + , ceoUpdateTime = def + } + -- | An abstract node id, used as placeholder in topology files -- when the actual ports/addresses aren't known yet (i.e. before runtime) newtype NodeId = NodeId Int @@ -107,7 +128,7 @@ data CardanoTestnetCreateEnvOptions = CardanoTestnetCreateEnvOptions { createEnvTestnetOptions :: CardanoTestnetOptions , createEnvGenesisOptions :: GenesisOptions , createEnvOutputDir :: FilePath - , createEnvTopologyType :: TopologyType + , createEnvCreateEnvOptions :: CreateEnvOptions } deriving (Eq, Show) -- | Options which, contrary to 'GenesisOptions' are not implemented diff --git a/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/P2PTopology.hs b/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/P2PTopology.hs index a90ea57b84e..9dbb794342c 100644 --- a/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/P2PTopology.hs +++ b/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/P2PTopology.hs @@ -23,7 +23,7 @@ import qualified System.Process as IO import Testnet.Process.Run (execCli', mkExecConfig) import Testnet.Property.Util (integrationRetryWorkspace) -import Testnet.Start.Types (GenesisOptions (..), NodeId, +import Testnet.Start.Types (CreateEnvOptions (..), GenesisOptions (..), NodeId, UserProvidedData (..), UserProvidedEnv (..), TopologyType (..)) import Hedgehog ((===)) @@ -38,12 +38,13 @@ hprop_p2p_topology = integrationRetryWorkspace 2 "p2p-topology" $ \tmpDir -> H.r let testnetOptions = def { cardanoOutputDir = UserProvidedEnv tmpDir } genesisOptions = def { genesisEpochLength = 200 } + createEnvOptions = def { ceoTopologyType = P2PTopology } someTopologyFile = tmpDir "node-data" "node1" "topology.json" -- Generate the sandbox conf <- mkConf tmpDir createTestnetEnv - testnetOptions genesisOptions P2PTopology + testnetOptions genesisOptions createEnvOptions NoUserProvidedData NoUserProvidedData NoUserProvidedData conf From 85a0e31ecdd1d2b393a9fd3ed451adb4a51215b3 Mon Sep 17 00:00:00 2001 From: Nicolas BACQUEY Date: Mon, 7 Jul 2025 13:40:08 +0200 Subject: [PATCH 2/6] Implement flag `--update-time` --- cardano-testnet/cardano-testnet.cabal | 1 + cardano-testnet/src/Testnet/Start/Cardano.hs | 97 ++++++++++++-------- 2 files changed, 61 insertions(+), 37 deletions(-) diff --git a/cardano-testnet/cardano-testnet.cabal b/cardano-testnet/cardano-testnet.cabal index 3db4af39e70..e001559a469 100644 --- a/cardano-testnet/cardano-testnet.cabal +++ b/cardano-testnet/cardano-testnet.cabal @@ -55,6 +55,7 @@ library , cardano-ledger-shelley , cardano-node , cardano-ping ^>= 0.8 + , cardano-prelude , contra-tracer , containers , data-default-class diff --git a/cardano-testnet/src/Testnet/Start/Cardano.hs b/cardano-testnet/src/Testnet/Start/Cardano.hs index 00bead3e4a0..0a3313cf08f 100644 --- a/cardano-testnet/src/Testnet/Start/Cardano.hs +++ b/cardano-testnet/src/Testnet/Start/Cardano.hs @@ -27,12 +27,15 @@ module Testnet.Start.Cardano import Cardano.Api +import Cardano.Api.Byron (GenesisData (..)) +import qualified Cardano.Api.Byron as Byron import Cardano.Ledger.Alonzo.Genesis (AlonzoGenesis) import Cardano.Ledger.Conway.Genesis (ConwayGenesis) import Cardano.Node.Configuration.Topology (RemoteAddress(..)) import qualified Cardano.Node.Configuration.Topology as Direct import qualified Cardano.Node.Configuration.TopologyP2P as P2P +import Cardano.Prelude (canonicalEncodePretty) import Ouroboros.Network.PeerSelection.RelayAccessPoint (RelayAccessPoint(..)) import Prelude hiding (lines) @@ -100,48 +103,68 @@ createTestnetEnv genesisOptions CreateEnvOptions { ceoTopologyType=topologyType - , ceoUpdateTime=_createEnvUpdateTime + , ceoUpdateTime=createEnvUpdateTime } mShelley mAlonzo mConway Conf { genesisHashesPolicy , tempAbsPath=TmpAbsolutePath tmpAbsPath - } = do - - testMinimumConfigurationRequirements testnetOptions - - AnyShelleyBasedEra sbe <- pure asbe - _ <- createSPOGenesisAndFiles - testnetOptions genesisOptions - mShelley mAlonzo mConway - (TmpAbsolutePath tmpAbsPath) - - configurationFile <- H.noteShow $ tmpAbsPath "configuration.yaml" - -- Add Byron, Shelley and Alonzo genesis hashes to node configuration - config' <- case genesisHashesPolicy of - WithHashes -> createConfigJson (TmpAbsolutePath tmpAbsPath) sbe - WithoutHashes -> pure $ createConfigJsonNoHash sbe - -- Setup P2P configuration value - let config = A.insert - "EnableP2P" - (Bool $ topologyType == P2PTopology) - config' - H.evalIO $ LBS.writeFile configurationFile $ A.encodePretty $ Object config - - -- Create network topology, with abstract IDs in lieu of addresses - let nodeIds = fst <$> zip [1..] cardanoNodes - forM_ nodeIds $ \i -> do - let nodeDataDir = tmpAbsPath Defaults.defaultNodeDataDir i - H.evalIO $ IO.createDirectoryIfMissing True nodeDataDir - - let producers = NodeId <$> filter (/= i) nodeIds - case topologyType of - DirectTopology -> - let topology = Direct.RealNodeTopology producers - in H.lbsWriteFile (nodeDataDir "topology.json") $ A.encodePretty topology - P2PTopology -> - let topology = Defaults.defaultP2PTopology producers - in H.lbsWriteFile (nodeDataDir "topology.json") $ A.encodePretty topology + } = case createEnvUpdateTime of + + CreateEnv -> do + testMinimumConfigurationRequirements testnetOptions + + AnyShelleyBasedEra sbe <- pure asbe + _ <- createSPOGenesisAndFiles + testnetOptions genesisOptions + mShelley mAlonzo mConway + (TmpAbsolutePath tmpAbsPath) + + configurationFile <- H.noteShow $ tmpAbsPath "configuration.yaml" + -- Add Byron, Shelley and Alonzo genesis hashes to node configuration + config' <- case genesisHashesPolicy of + WithHashes -> createConfigJson (TmpAbsolutePath tmpAbsPath) sbe + WithoutHashes -> pure $ createConfigJsonNoHash sbe + -- Setup P2P configuration value + let config = A.insert + "EnableP2P" + (Bool $ topologyType == P2PTopology) + config' + H.evalIO $ LBS.writeFile configurationFile $ A.encodePretty $ Object config + + -- Create network topology, with abstract IDs in lieu of addresses + let nodeIds = fst <$> zip [1..] cardanoNodes + forM_ nodeIds $ \i -> do + let nodeDataDir = tmpAbsPath Defaults.defaultNodeDataDir i + H.evalIO $ IO.createDirectoryIfMissing True nodeDataDir + + let producers = NodeId <$> filter (/= i) nodeIds + case topologyType of + DirectTopology -> + let topology = Direct.RealNodeTopology producers + in H.lbsWriteFile (nodeDataDir "topology.json") $ A.encodePretty topology + P2PTopology -> + let topology = Defaults.defaultP2PTopology producers + in H.lbsWriteFile (nodeDataDir "topology.json") $ A.encodePretty topology + + UpdateTimeAndExit -> do + let byronGenesisFile = tmpAbsPath "byron-genesis.json" + shelleyGenesisFile = tmpAbsPath "shelley-genesis.json" + + currentTime <- H.noteShowIO DTC.getCurrentTime + startTime <- H.noteShow $ DTC.addUTCTime startTimeOffsetSeconds currentTime + + -- Update start time in Byron genesis file + eByron <- runExceptT $ Byron.readGenesisData byronGenesisFile + (byronGenesis', _byronHash) <- H.leftFail eByron + let byronGenesis = byronGenesis'{gdStartTime = startTime} + H.lbsWriteFile byronGenesisFile $ canonicalEncodePretty byronGenesis + + -- Update start time in Shelley genesis file + eShelley <- H.readJsonFile shelleyGenesisFile + shelleyGenesis' :: ShelleyGenesis <- H.leftFail eShelley + let shelleyGenesis = shelleyGenesis'{sgSystemStart = startTime} + H.lbsWriteFile shelleyGenesisFile $ A.encodePretty shelleyGenesis -- | Starts a number of nodes, as configured by the value of the 'cardanoNodes' -- field in the 'CardanoTestnetOptions' argument. Regarding this field, you can either: From 7b8a6ba3da444ba1706656a64f36ca86018b698b Mon Sep 17 00:00:00 2001 From: Nicolas BACQUEY Date: Mon, 7 Jul 2025 14:40:48 +0200 Subject: [PATCH 3/6] Add test for updating time stamps --- cardano-testnet/cardano-testnet.cabal | 1 + .../Cardano/Testnet/Test/UpdateTimeStamps.hs | 105 ++++++++++++++++++ .../cardano-testnet-test.hs | 2 + 3 files changed, 108 insertions(+) create mode 100644 cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/UpdateTimeStamps.hs diff --git a/cardano-testnet/cardano-testnet.cabal b/cardano-testnet/cardano-testnet.cabal index e001559a469..3c08de9e301 100644 --- a/cardano-testnet/cardano-testnet.cabal +++ b/cardano-testnet/cardano-testnet.cabal @@ -226,6 +226,7 @@ test-suite cardano-testnet-test Cardano.Testnet.Test.SanityCheck Cardano.Testnet.Test.RunTestnet Cardano.Testnet.Test.SubmitApi.Transaction + Cardano.Testnet.Test.UpdateTimeStamps type: exitcode-stdio-1.0 diff --git a/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/UpdateTimeStamps.hs b/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/UpdateTimeStamps.hs new file mode 100644 index 00000000000..f6ed9defc46 --- /dev/null +++ b/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/UpdateTimeStamps.hs @@ -0,0 +1,105 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} + +module Cardano.Testnet.Test.UpdateTimeStamps + ( hprop_update_time_stamps + ) where + +import Cardano.Api (BlockNo (..), ChainTip (..)) + +import Cardano.CLI.Type.Output (QueryTipLocalStateOutput (..)) +import Cardano.Testnet hiding (shelleyGenesisFile) + +import Prelude + +import qualified Data.Aeson as A +import qualified Data.ByteString.Lazy.Char8 as B +import Data.Default.Class (def) +import GHC.Float (double2Int) +import System.Exit (ExitCode (..)) +import System.FilePath (()) +import qualified System.Process as IO + +import Testnet.Components.Configuration (startTimeOffsetSeconds) +import Testnet.Process.Run (execCli', mkExecConfig) +import Testnet.Property.Util (integrationRetryWorkspace) +import Testnet.Start.Types (CreateEnvOptions (..), CreateEnvUpdateTime (..), + GenesisHashesPolicy (..), GenesisOptions (..), + UserProvidedData (..), UserProvidedEnv (..)) + +import Hedgehog ((===)) +import qualified Hedgehog as H +import qualified Hedgehog.Extras as H + +-- | Execute me with: +-- @DISABLE_RETRIES=1 cabal test cardano-testnet-test --test-options '-p "/Can have its start time modified/"'@ +hprop_update_time_stamps :: H.Property +hprop_update_time_stamps = integrationRetryWorkspace 2 "update-time-stamps" $ \tmpDir -> H.runWithDefaultWatchdog_ $ do + + let testnetOptions = def { cardanoOutputDir = UserProvidedEnv tmpDir } + genesisOptions = def { genesisEpochLength = 200 } + byronGenesisFile = tmpDir "byron-genesis.json" + shelleyGenesisFile = tmpDir "shelley-genesis.json" + + -- Generate the sandbox + conf <- mkConf tmpDir + createTestnetEnv + testnetOptions genesisOptions def + NoUserProvidedData NoUserProvidedData NoUserProvidedData + -- 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} + + -- Wait for 20% more than `startTimeOffsetSeconds`, to ensure that + -- the time bounds in the sandbox' config files are no longer valid + H.threadDelay $ double2Int $ realToFrac startTimeOffsetSeconds * 1_000_000 * 1.2 + + -- Call `createTestnetEnv` again to update the time stamps + createTestnetEnv + testnetOptions genesisOptions + def{ceoUpdateTime = UpdateTimeAndExit} + NoUserProvidedData NoUserProvidedData NoUserProvidedData + conf + + -- Run testnet with generated config + TestnetRuntime + { testnetNodes + , testnetMagic + } <- cardanoTestnet testnetOptions genesisOptions conf + + -- There should only be one SPO node among three + TestnetNode + { nodeProcessHandle + , nodeSprocket + } <- case testnetNodes of + [spoNode, _relayNode1, _relayNode2] -> do + (isTestnetNodeSpo <$> testnetNodes) === [True, False, False] + pure spoNode + _ -> H.failure + + -- Check that blocks have been produced on the chain after 2 minutes at most + H.byDurationM 5 120 "Expected blocks to be minted" $ do + execConfig <- mkExecConfig tmpDir nodeSprocket testnetMagic + tipStr <- H.noteM $ execCli' execConfig + [ "query", "tip" + , "--output-json" + ] + QueryTipLocalStateOutput + { localStateChainTip = tip + } <- H.nothingFail $ A.decode $ B.pack tipStr + + case tip of + ChainTipAtGenesis -> H.failure + ChainTip _ _ (BlockNo blockNo) -> + -- Blocks have been produced if the tip of the chain is > 0 + H.assertWith blockNo (> 0) + + -- If everything went fine, terminate the node and exit with success + exit <- H.evalIO $ do + IO.terminateProcess nodeProcessHandle + IO.waitForProcess nodeProcessHandle + -- Nodes are expected to exit successfully when terminated + exit === ExitSuccess diff --git a/cardano-testnet/test/cardano-testnet-test/cardano-testnet-test.hs b/cardano-testnet/test/cardano-testnet-test/cardano-testnet-test.hs index 44996327deb..f84552d093c 100644 --- a/cardano-testnet/test/cardano-testnet-test/cardano-testnet-test.hs +++ b/cardano-testnet/test/cardano-testnet-test/cardano-testnet-test.hs @@ -31,6 +31,7 @@ import qualified Cardano.Testnet.Test.P2PTopology import qualified Cardano.Testnet.Test.RunTestnet import qualified Cardano.Testnet.Test.SanityCheck as LedgerEvents import qualified Cardano.Testnet.Test.SubmitApi.Transaction +import qualified Cardano.Testnet.Test.UpdateTimeStamps import Prelude @@ -116,6 +117,7 @@ tests = do [ ignoreOnWindows "Produces blocks" Cardano.Testnet.Test.RunTestnet.hprop_run_testnet , ignoreOnMacAndWindows "Supports dumping/loading config files" Cardano.Testnet.Test.DumpConfig.hprop_dump_config , ignoreOnMacAndWindows "Can be started with P2P topology file" Cardano.Testnet.Test.P2PTopology.hprop_p2p_topology + , ignoreOnMacAndWindows "Can have its start time modified" Cardano.Testnet.Test.UpdateTimeStamps.hprop_update_time_stamps ] , T.testGroup "SubmitApi" [ ignoreOnMacAndWindows "transaction" Cardano.Testnet.Test.SubmitApi.Transaction.hprop_transaction From 9abc42f83977ca16b31c01056df2595db47e2345 Mon Sep 17 00:00:00 2001 From: Nicolas BACQUEY Date: Mon, 7 Jul 2025 15:20:20 +0200 Subject: [PATCH 4/6] Share function that checks testnet nodes health --- cardano-testnet/cardano-testnet.cabal | 1 + .../Cardano/Testnet/Test/DumpConfig.hs | 53 ++------------ .../Cardano/Testnet/Test/P2PTopology.hs | 52 ++----------- .../Cardano/Testnet/Test/RunTestnet.hs | 51 +------------ .../Cardano/Testnet/Test/UpdateTimeStamps.hs | 55 +------------- .../Cardano/Testnet/Test/Utils.hs | 73 +++++++++++++++++++ 6 files changed, 92 insertions(+), 193 deletions(-) create mode 100644 cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Utils.hs diff --git a/cardano-testnet/cardano-testnet.cabal b/cardano-testnet/cardano-testnet.cabal index 3c08de9e301..710ef6b854b 100644 --- a/cardano-testnet/cardano-testnet.cabal +++ b/cardano-testnet/cardano-testnet.cabal @@ -227,6 +227,7 @@ test-suite cardano-testnet-test Cardano.Testnet.Test.RunTestnet Cardano.Testnet.Test.SubmitApi.Transaction Cardano.Testnet.Test.UpdateTimeStamps + Cardano.Testnet.Test.Utils type: exitcode-stdio-1.0 diff --git a/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/DumpConfig.hs b/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/DumpConfig.hs index 953097e1139..e5e1751c74c 100644 --- a/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/DumpConfig.hs +++ b/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/DumpConfig.hs @@ -1,5 +1,4 @@ {-# LANGUAGE DataKinds #-} -{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE NumericUnderscores #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} @@ -8,34 +7,27 @@ module Cardano.Testnet.Test.DumpConfig ( hprop_dump_config ) where -import Cardano.Api (BlockNo (..), ChainTip (..), ShelleyGenesis, runExceptT, - sgSystemStart) +import Cardano.Api (ShelleyGenesis, runExceptT, sgSystemStart) import Cardano.Api.Byron (GenesisData (..)) import qualified Cardano.Api.Byron as Byron -import Cardano.CLI.Type.Output (QueryTipLocalStateOutput (..)) import Cardano.Prelude (canonicalEncodePretty) import Cardano.Testnet hiding (shelleyGenesisFile) +import Cardano.Testnet.Test.Utils (nodesProduceBlocks) import Prelude -import qualified Data.Aeson as A import Data.Aeson.Encode.Pretty (encodePretty) -import qualified Data.ByteString.Lazy.Char8 as B import Data.Default.Class (def) import qualified Data.Time.Clock as Time import GHC.Float (double2Int) -import System.Exit (ExitCode (..)) import System.FilePath (()) -import qualified System.Process as IO import Testnet.Components.Configuration (startTimeOffsetSeconds) -import Testnet.Process.Run (execCli', mkExecConfig) import Testnet.Property.Util (integrationRetryWorkspace) import Testnet.Start.Types (GenesisHashesPolicy (..), GenesisOptions (..), UserProvidedData (..), UserProvidedEnv (..)) -import Hedgehog ((===)) import qualified Hedgehog as H import qualified Hedgehog.Extras as H @@ -78,41 +70,6 @@ hprop_dump_config = integrationRetryWorkspace 2 "dump-config-files" $ \tmpDir -> H.lbsWriteFile shelleyGenesisFile $ encodePretty shelleyGenesis -- Run testnet with generated config - TestnetRuntime - { testnetNodes - , testnetMagic - } <- cardanoTestnet testnetOptions genesisOptions conf - - -- There should only be one SPO node among three - TestnetNode - { nodeProcessHandle - , nodeSprocket - } <- case testnetNodes of - [spoNode, _relayNode1, _relayNode2] -> do - (isTestnetNodeSpo <$> testnetNodes) === [True, False, False] - pure spoNode - _ -> H.failure - - -- Check that blocks have been produced on the chain after 2 minutes at most - H.byDurationM 5 120 "Expected blocks to be minted" $ do - execConfig <- mkExecConfig tmpDir nodeSprocket testnetMagic - tipStr <- H.noteM $ execCli' execConfig - [ "query", "tip" - , "--output-json" - ] - QueryTipLocalStateOutput - { localStateChainTip = tip - } <- H.nothingFail $ A.decode $ B.pack tipStr - - case tip of - ChainTipAtGenesis -> H.failure - ChainTip _ _ (BlockNo blockNo) -> - -- Blocks have been produced if the tip of the chain is > 0 - H.assertWith blockNo (> 0) - - -- If everything went fine, terminate the node and exit with success - exit <- H.evalIO $ do - IO.terminateProcess nodeProcessHandle - IO.waitForProcess nodeProcessHandle - -- Nodes are expected to exit successfully when terminated - exit === ExitSuccess + runtime <- cardanoTestnet testnetOptions genesisOptions conf + + nodesProduceBlocks tmpDir runtime diff --git a/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/P2PTopology.hs b/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/P2PTopology.hs index 9dbb794342c..87a60133ce0 100644 --- a/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/P2PTopology.hs +++ b/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/P2PTopology.hs @@ -1,5 +1,4 @@ {-# LANGUAGE DataKinds #-} -{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} @@ -7,26 +6,20 @@ module Cardano.Testnet.Test.P2PTopology ( hprop_p2p_topology ) where -import Cardano.Api (BlockNo (..), ChainTip (..)) -import Cardano.CLI.Type.Output (QueryTipLocalStateOutput (..)) import qualified Cardano.Node.Configuration.TopologyP2P as P2P -import Cardano.Testnet hiding (shelleyGenesisFile) +import Cardano.Testnet (CardanoTestnetOptions (..), cardanoTestnet, + createTestnetEnv, mkConf) +import Cardano.Testnet.Test.Utils (nodesProduceBlocks) import Prelude -import qualified Data.Aeson as A -import qualified Data.ByteString.Lazy.Char8 as B import Data.Default.Class (def) -import System.Exit (ExitCode (..)) import System.FilePath (()) -import qualified System.Process as IO -import Testnet.Process.Run (execCli', mkExecConfig) import Testnet.Property.Util (integrationRetryWorkspace) import Testnet.Start.Types (CreateEnvOptions (..), GenesisOptions (..), NodeId, UserProvidedData (..), UserProvidedEnv (..), TopologyType (..)) -import Hedgehog ((===)) import qualified Hedgehog as H import qualified Hedgehog.Extras as H @@ -53,41 +46,6 @@ hprop_p2p_topology = integrationRetryWorkspace 2 "p2p-topology" $ \tmpDir -> H.r (_topology :: P2P.NetworkTopology NodeId) <- H.leftFail eTopology -- Run testnet with generated config - TestnetRuntime - { testnetNodes - , testnetMagic - } <- cardanoTestnet testnetOptions genesisOptions conf + runtime <- cardanoTestnet testnetOptions genesisOptions conf - -- There should only be one SPO node among three - TestnetNode - { nodeProcessHandle - , nodeSprocket - } <- case testnetNodes of - [spoNode, _relayNode1, _relayNode2] -> do - (isTestnetNodeSpo <$> testnetNodes) === [True, False, False] - pure spoNode - _ -> H.failure - - -- Check that blocks have been produced on the chain after 2 minutes at most - H.byDurationM 5 120 "Expected blocks to be minted" $ do - execConfig <- mkExecConfig tmpDir nodeSprocket testnetMagic - tipStr <- H.noteM $ execCli' execConfig - [ "query", "tip" - , "--output-json" - ] - QueryTipLocalStateOutput - { localStateChainTip = tip - } <- H.nothingFail $ A.decode $ B.pack tipStr - - case tip of - ChainTipAtGenesis -> H.failure - ChainTip _ _ (BlockNo blockNo) -> - -- Blocks have been produced if the tip of the chain is > 0 - H.assertWith blockNo (> 0) - - -- If everything went fine, terminate the node and exit with success - exit <- H.evalIO $ do - IO.terminateProcess nodeProcessHandle - IO.waitForProcess nodeProcessHandle - -- Nodes are expected to exit successfully when terminated - exit === ExitSuccess + nodesProduceBlocks tmpDir runtime diff --git a/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/RunTestnet.hs b/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/RunTestnet.hs index a103c0cce56..4f043827944 100644 --- a/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/RunTestnet.hs +++ b/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/RunTestnet.hs @@ -1,5 +1,4 @@ {-# LANGUAGE DataKinds #-} -{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} @@ -9,22 +8,15 @@ module Cardano.Testnet.Test.RunTestnet import Prelude -import Cardano.Api (BlockNo (..), ChainTip (..)) -import Cardano.CLI.Type.Output (QueryTipLocalStateOutput (..)) -import qualified Data.Aeson as A -import qualified Data.ByteString.Lazy.Char8 as B import Data.Default.Class (def) -import System.Exit (ExitCode (..)) -import qualified System.Process as IO -import Cardano.Testnet +import Cardano.Testnet (mkConf, createAndRunTestnet) +import Cardano.Testnet.Test.Utils (nodesProduceBlocks) import Testnet.Property.Util (integrationRetryWorkspace) import Testnet.Start.Types (GenesisOptions (..)) -import Hedgehog ((===)) import qualified Hedgehog as H import qualified Hedgehog.Extras as H -import Testnet.Process.Run (execCli',mkExecConfig) -- | Execute me with: -- @DISABLE_RETRIES=1 cabal test cardano-testnet-test --test-options '-p "/Produces blocks/"'@ @@ -35,41 +27,6 @@ hprop_run_testnet = integrationRetryWorkspace 2 "run-testnet" $ \tmpDir -> H.run testnetOptions = def conf <- mkConf tmpDir - TestnetRuntime - { testnetNodes - , testnetMagic - } <- createAndRunTestnet testnetOptions shelleyOptions conf + runtime <- createAndRunTestnet testnetOptions shelleyOptions conf - -- There should only be one SPO node among three - TestnetNode - { nodeProcessHandle - , nodeSprocket - } <- case testnetNodes of - [spoNode, _relayNode1, _relayNode2] -> do - (isTestnetNodeSpo <$> testnetNodes) === [True, False, False] - pure spoNode - _ -> H.failure - - -- Check that blocks have been produced on the chain after 2 minutes at most - H.byDurationM 5 120 "Expected blocks to be minted" $ do - execConfig <- mkExecConfig tmpDir nodeSprocket testnetMagic - tipStr <- H.noteM $ execCli' execConfig - [ "query", "tip" - , "--output-json" - ] - QueryTipLocalStateOutput - { localStateChainTip = tip - } <- H.nothingFail $ A.decode $ B.pack tipStr - - case tip of - ChainTipAtGenesis -> H.failure - ChainTip _ _ (BlockNo blockNo) -> - -- Blocks have been produced if the tip of the chain is > 0 - H.assertWith blockNo (> 0) - - -- If everything went fine, terminate the node and exit with success - exit <- H.evalIO $ do - IO.terminateProcess nodeProcessHandle - IO.waitForProcess nodeProcessHandle - -- Nodes are expected to exit successfully when terminated - exit === ExitSuccess + nodesProduceBlocks tmpDir runtime diff --git a/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/UpdateTimeStamps.hs b/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/UpdateTimeStamps.hs index f6ed9defc46..cd066bee8a6 100644 --- a/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/UpdateTimeStamps.hs +++ b/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/UpdateTimeStamps.hs @@ -1,5 +1,4 @@ {-# LANGUAGE DataKinds #-} -{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE NumericUnderscores #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} @@ -8,29 +7,20 @@ module Cardano.Testnet.Test.UpdateTimeStamps ( hprop_update_time_stamps ) where -import Cardano.Api (BlockNo (..), ChainTip (..)) - -import Cardano.CLI.Type.Output (QueryTipLocalStateOutput (..)) -import Cardano.Testnet hiding (shelleyGenesisFile) +import Cardano.Testnet +import Cardano.Testnet.Test.Utils (nodesProduceBlocks) import Prelude -import qualified Data.Aeson as A -import qualified Data.ByteString.Lazy.Char8 as B import Data.Default.Class (def) import GHC.Float (double2Int) -import System.Exit (ExitCode (..)) -import System.FilePath (()) -import qualified System.Process as IO import Testnet.Components.Configuration (startTimeOffsetSeconds) -import Testnet.Process.Run (execCli', mkExecConfig) import Testnet.Property.Util (integrationRetryWorkspace) import Testnet.Start.Types (CreateEnvOptions (..), CreateEnvUpdateTime (..), GenesisHashesPolicy (..), GenesisOptions (..), UserProvidedData (..), UserProvidedEnv (..)) -import Hedgehog ((===)) import qualified Hedgehog as H import qualified Hedgehog.Extras as H @@ -41,8 +31,6 @@ hprop_update_time_stamps = integrationRetryWorkspace 2 "update-time-stamps" $ \t let testnetOptions = def { cardanoOutputDir = UserProvidedEnv tmpDir } genesisOptions = def { genesisEpochLength = 200 } - byronGenesisFile = tmpDir "byron-genesis.json" - shelleyGenesisFile = tmpDir "shelley-genesis.json" -- Generate the sandbox conf <- mkConf tmpDir @@ -65,41 +53,6 @@ hprop_update_time_stamps = integrationRetryWorkspace 2 "update-time-stamps" $ \t conf -- Run testnet with generated config - TestnetRuntime - { testnetNodes - , testnetMagic - } <- cardanoTestnet testnetOptions genesisOptions conf - - -- There should only be one SPO node among three - TestnetNode - { nodeProcessHandle - , nodeSprocket - } <- case testnetNodes of - [spoNode, _relayNode1, _relayNode2] -> do - (isTestnetNodeSpo <$> testnetNodes) === [True, False, False] - pure spoNode - _ -> H.failure - - -- Check that blocks have been produced on the chain after 2 minutes at most - H.byDurationM 5 120 "Expected blocks to be minted" $ do - execConfig <- mkExecConfig tmpDir nodeSprocket testnetMagic - tipStr <- H.noteM $ execCli' execConfig - [ "query", "tip" - , "--output-json" - ] - QueryTipLocalStateOutput - { localStateChainTip = tip - } <- H.nothingFail $ A.decode $ B.pack tipStr - - case tip of - ChainTipAtGenesis -> H.failure - ChainTip _ _ (BlockNo blockNo) -> - -- Blocks have been produced if the tip of the chain is > 0 - H.assertWith blockNo (> 0) + runtime <- cardanoTestnet testnetOptions genesisOptions conf - -- If everything went fine, terminate the node and exit with success - exit <- H.evalIO $ do - IO.terminateProcess nodeProcessHandle - IO.waitForProcess nodeProcessHandle - -- Nodes are expected to exit successfully when terminated - exit === ExitSuccess + nodesProduceBlocks tmpDir runtime diff --git a/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Utils.hs b/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Utils.hs new file mode 100644 index 00000000000..a9338d2d47b --- /dev/null +++ b/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Utils.hs @@ -0,0 +1,73 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} + +module Cardano.Testnet.Test.Utils + ( nodesProduceBlocks + ) where + +import Cardano.Api (BlockNo (..), ChainTip (..), MonadIO) +import Cardano.CLI.Type.Output (QueryTipLocalStateOutput (..)) + +import Prelude + +import Control.Monad.Catch (MonadCatch) +import qualified Data.Aeson as A +import qualified Data.ByteString.Lazy.Char8 as B +import GHC.Stack.Types (HasCallStack) +import System.Exit (ExitCode (..)) +import qualified System.Process as IO + +import Testnet.Process.Run (execCli', mkExecConfig) +import Testnet.Types (TestnetNode(..), TestnetRuntime (..), isTestnetNodeSpo) + +import Hedgehog ((===)) +import qualified Hedgehog as H +import qualified Hedgehog.Extras as H + +-- | Checks that nodes are running as expected and producing blocks +nodesProduceBlocks :: () + => HasCallStack + => H.MonadAssertion m + => H.MonadTest m + => MonadCatch m + => MonadIO m + => FilePath + -> TestnetRuntime + -> m () +nodesProduceBlocks envDir TestnetRuntime{testnetNodes, testnetMagic} = do + + -- There should only be one SPO node among three + TestnetNode + { nodeProcessHandle + , nodeSprocket + } <- case testnetNodes of + [spoNode, _relayNode1, _relayNode2] -> do + (isTestnetNodeSpo <$> testnetNodes) === [True, False, False] + pure spoNode + _ -> H.failure + + -- Check that blocks have been produced on the chain after 2 minutes at most + H.byDurationM 5 120 "Expected blocks to be minted" $ do + execConfig <- mkExecConfig envDir nodeSprocket testnetMagic + tipStr <- H.noteM $ execCli' execConfig + [ "query", "tip" + , "--output-json" + ] + QueryTipLocalStateOutput + { localStateChainTip = tip + } <- H.nothingFail $ A.decode $ B.pack tipStr + + case tip of + ChainTipAtGenesis -> H.failure + ChainTip _ _ (BlockNo blockNo) -> + -- Blocks have been produced if the tip of the chain is > 0 + H.assertWith blockNo (> 0) + + -- If everything went fine, terminate the node and exit with success + exit <- H.evalIO $ do + IO.terminateProcess nodeProcessHandle + IO.waitForProcess nodeProcessHandle + -- Nodes are expected to exit successfully when terminated + exit === ExitSuccess From 4558de83ceaba22f2ce16e471a2f8ba86c693951 Mon Sep 17 00:00:00 2001 From: Nicolas BACQUEY Date: Mon, 7 Jul 2025 15:24:57 +0200 Subject: [PATCH 5/6] chore: update changelog --- cardano-testnet/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/cardano-testnet/CHANGELOG.md b/cardano-testnet/CHANGELOG.md index e3393956061..0d5bfdfad1f 100644 --- a/cardano-testnet/CHANGELOG.md +++ b/cardano-testnet/CHANGELOG.md @@ -5,6 +5,7 @@ * [Fix discrepancy in security parameter between Byron and Shelley genesis files](https://github.com/IntersectMBO/cardano-node/pull/6188) * [Add an option to dump/load configuration sandbox](https://github.com/IntersectMBO/cardano-node/pull/6239) * [Add flag to support P2P topology](https://github.com/IntersectMBO/cardano-node/pull/6263) +* [Add flag to update time stamps in custom environment](https://github.com/IntersectMBO/cardano-node/pull/6275) ## 10.0.0 From 84629f1fcf86720b6e0d1b2ea800f580a817f7c1 Mon Sep 17 00:00:00 2001 From: Nicolas BACQUEY Date: Wed, 9 Jul 2025 12:40:15 +0200 Subject: [PATCH 6/6] Update golden files --- .../test/cardano-testnet-golden/files/golden/help.cli | 1 + .../cardano-testnet-golden/files/golden/help/create-env.cli | 3 +++ 2 files changed, 4 insertions(+) diff --git a/cardano-testnet/test/cardano-testnet-golden/files/golden/help.cli b/cardano-testnet/test/cardano-testnet-golden/files/golden/help.cli index 0fd04300cda..6cc882494d7 100644 --- a/cardano-testnet/test/cardano-testnet-golden/files/golden/help.cli +++ b/cardano-testnet/test/cardano-testnet-golden/files/golden/help.cli @@ -40,6 +40,7 @@ Usage: cardano-testnet create-env [--num-pool-nodes COUNT] [--active-slots-coeff DOUBLE] --output DIRECTORY [--p2p-topology] + [--update-time] Create a sandbox for Cardano testnet diff --git a/cardano-testnet/test/cardano-testnet-golden/files/golden/help/create-env.cli b/cardano-testnet/test/cardano-testnet-golden/files/golden/help/create-env.cli index 47803c18335..eb1b302039f 100644 --- a/cardano-testnet/test/cardano-testnet-golden/files/golden/help/create-env.cli +++ b/cardano-testnet/test/cardano-testnet-golden/files/golden/help/create-env.cli @@ -17,6 +17,7 @@ Usage: cardano-testnet create-env [--num-pool-nodes COUNT] [--active-slots-coeff DOUBLE] --output DIRECTORY [--p2p-topology] + [--update-time] Create a sandbox for Cardano testnet @@ -61,4 +62,6 @@ Available options: --output DIRECTORY Directory where to create the sandbox environment. --p2p-topology Use P2P topology files instead of "direct" topology files + --update-time Don't create anything, just update the time stamps in + existing files -h,--help Show this help text