diff --git a/cardano-testnet/changelog.d/20260420_204409_palas_make_testnet_backwards_compatible.md b/cardano-testnet/changelog.d/20260420_204409_palas_make_testnet_backwards_compatible.md new file mode 100644 index 00000000000..e9489268974 --- /dev/null +++ b/cardano-testnet/changelog.d/20260420_204409_palas_make_testnet_backwards_compatible.md @@ -0,0 +1,4 @@ +### Added + +- Support for running `cardano-testnet` with older `cardano-cli` and `cardano-node` versions. + diff --git a/cardano-testnet/src/Testnet/Components/Configuration.hs b/cardano-testnet/src/Testnet/Components/Configuration.hs index b072e38a545..a0255285603 100644 --- a/cardano-testnet/src/Testnet/Components/Configuration.hs +++ b/cardano-testnet/src/Testnet/Components/Configuration.hs @@ -53,14 +53,20 @@ import System.FilePath.Posix (takeDirectory, ()) import Testnet.Blockfrost (blockfrostToGenesis) import qualified Testnet.Defaults as Defaults import Testnet.Filepath -import Testnet.Process.RunIO (execCli_, liftIOAnnotated) +import Testnet.Process.RunIO (execCli_, getCliHelpText, liftIOAnnotated) +import Testnet.Start.Byron (ByronGenesisOptions (..), byronDefaultGenesisOptions, + createByronGenesis) import Testnet.Start.Types import qualified Hedgehog.Extras.Stock.OS as OS import qualified Hedgehog.Extras.Stock.Time as DTC +import Data.List (isInfixOf) +import Data.Maybe (fromMaybe) +import Data.Monoid.Extra (mwhen) import RIO (MonadThrow, throwM) + -- | Returns JSON encoded hashes of the era, as well as the hard fork configuration toggle. createConfigJson :: () => HasCallStack @@ -72,9 +78,9 @@ createConfigJson :: () createConfigJson (TmpAbsolutePath tempAbsPath) sbe = GHC.withFrozenCallStack $ do byronGenesisHash <- getByronGenesisHash $ tempAbsPath "byron-genesis.json" shelleyGenesisHash <- getHash ShelleyEra "ShelleyGenesisHash" - alonzoGenesisHash <- getHash AlonzoEra "AlonzoGenesisHash" - conwayGenesisHash <- getHash ConwayEra "ConwayGenesisHash" - dijkstraGenesisHash <- getHash DijkstraEra "DijkstraGenesisHash" + alonzoGenesisHash <- getHash AlonzoEra "AlonzoGenesisHash" + conwayGenesisHash <- getHash ConwayEra "ConwayGenesisHash" + dijkstraGenesisHash <- getHash DijkstraEra "DijkstraGenesisHash" pure $ mconcat [ byronGenesisHash @@ -85,7 +91,7 @@ createConfigJson (TmpAbsolutePath tempAbsPath) sbe = GHC.withFrozenCallStack $ d , Defaults.defaultYamlHardforkViaConfig sbe ] where - getHash :: MonadIO m => CardanoEra a -> Text.Text -> m (KeyMap Value) + getHash :: MonadIO m => CardanoEra a -> Text.Text -> m (KeyMap Value) getHash e = getShelleyGenesisHash (tempAbsPath Defaults.defaultGenesisFilepath e) createConfigJsonNoHash :: () @@ -119,7 +125,7 @@ getShelleyGenesisHash path key = do pure . singleton (fromText key) $ toJSON genesisHash -- | For an unknown reason, CLI commands are a lot slower on Windows than on Linux and --- MacOS. We need to allow a lot more time to set up a testnet. +-- MacOS. We need to allow a lot more time to set up a testnet. startTimeOffsetSeconds :: Int startTimeOffsetSeconds = if OS.isWin32 then 90 else 15 @@ -161,7 +167,7 @@ createSPOGenesisAndFiles createSPOGenesisAndFiles testnetOptions genesisOptions@GenesisOptions{genesisTestnetMagic} onChainParams - (TmpAbsolutePath tempAbsPath) = do + (TmpAbsolutePath tempAbsPath) = do AnyShelleyBasedEra sbe <- pure cardanoNodeEra @@ -200,16 +206,23 @@ createSPOGenesisAndFiles currentTime <- liftIOAnnotated DTC.getCurrentTime let startTime = DTC.addUTCTime (fromIntegral startTimeOffsetSeconds) currentTime + -- Probe which --spec-* flags the installed cardano-cli supports so that + -- we only pass flags it understands. To add a new era, just append a + -- line below — the rest adapts automatically. + let cliHelp = fromMaybe "" <$> + getCliHelpText [eraToString sbe, "genesis", "create-testnet-data"] + cliHas <- flip isInfixOf <$> cliHelp + execCli_ $ [ eraToString sbe, "genesis", "create-testnet-data" ] - ++ createTestnetDataFlag ShelleyEra - ++ createTestnetDataFlag AlonzoEra - ++ createTestnetDataFlag ConwayEra - ++ createTestnetDataFlag DijkstraEra + ++ specFlag cliHas ShelleyEra + ++ specFlag cliHas AlonzoEra + ++ specFlag cliHas ConwayEra + ++ specFlag cliHas DijkstraEra ++ [ "--testnet-magic", show genesisTestnetMagic , "--pools", show nPoolNodes - , "--total-supply", show cardanoMaxSupply -- Half of this will be delegated, see https://github.com/IntersectMBO/cardano-cli/pull/874 + , "--total-supply", show cardanoMaxSupply -- Half of this will be delegated, see https://github.com/IntersectMBO/cardano-cli/pull/874 , "--stake-delegators", show numStakeDelegators , "--utxo-keys", show numSeededUTxOKeys] <> monoidForEraInEon @ConwayEraOnwards era (const ["--drep-keys", show cardanoNumDReps]) @@ -217,26 +230,65 @@ createSPOGenesisAndFiles , "--out-dir", tempAbsPath ] + -- Older cardano-cli versions do not generate byron-genesis.json from + -- create-testnet-data. Create it via the legacy byron command when missing. + createByronGenesisIfMissing startTime + + -- For eras whose --spec-* flag was not supported, write a default genesis + -- file so that the node configuration and hash computation stay uniform. + let dijkstraGenesisPath = tempAbsPath Defaults.defaultGenesisFilepath DijkstraEra + unlessFileExists dijkstraGenesisPath $ + liftIOAnnotated $ LBS.writeFile dijkstraGenesisPath $ A.encodePretty dijkstraGenesis + -- Remove the input files. We don't need them anymore, since create-testnet-data wrote new versions. forM_ [ inputGenesisShelleyFp, inputGenesisAlonzoFp, inputGenesisConwayFp + , inputGenesisDijkstraFp , tempAbsPath "byron.genesis.spec.json" -- Created by create-testnet-data ] - (\fp -> liftIOAnnotated $ whenM (System.doesFileExist fp) (System.removeFile fp)) + $ \fp -> liftIOAnnotated $ whenM (System.doesFileExist fp) (System.removeFile fp) return genesisShelleyDir where inputGenesisShelleyFp = genesisInputFilepath ShelleyEra - inputGenesisAlonzoFp = genesisInputFilepath AlonzoEra - inputGenesisConwayFp = genesisInputFilepath ConwayEra - inputGenesisDijkstraFp = genesisInputFilepath DijkstraEra + inputGenesisAlonzoFp = genesisInputFilepath AlonzoEra + inputGenesisConwayFp = genesisInputFilepath ConwayEra + inputGenesisDijkstraFp = genesisInputFilepath DijkstraEra + nPoolNodes = cardanoNumPools testnetOptions + CardanoTestnetOptions{cardanoNodeEra, cardanoMaxSupply, cardanoNumDReps} = testnetOptions + genesisInputFilepath :: Pretty (eon era) => eon era -> FilePath genesisInputFilepath e = tempAbsPath ("genesis-input." <> eraToString e <> ".json") - createTestnetDataFlag :: Pretty (eon era) => eon era -> [String] - createTestnetDataFlag sbe = - ["--spec-" ++ eraToString sbe, genesisInputFilepath sbe] + + specFlag :: Pretty (eon era) => (String -> Bool) -> eon era -> [String] + specFlag supported e = + let flag = "--spec-" ++ eraToString e + in mwhen (supported flag) [flag, genesisInputFilepath e] + + createByronGenesisIfMissing startTime = do + let byronGenesisPath = tempAbsPath Defaults.defaultGenesisFilepath ByronEra + unlessFileExists byronGenesisPath $ do + let byronGenDir = tempAbsPath "byron-gen-command" + byronParamsFp = tempAbsPath "byron-params.json" + byronOpts = byronDefaultGenesisOptions + { byronNumBftNodes = fromIntegral nPoolNodes } + liftIOAnnotated $ LBS.writeFile byronParamsFp $ Aeson.encode Defaults.defaultByronProtocolParamsJsonValue + createByronGenesis genesisTestnetMagic startTime byronOpts byronParamsFp byronGenDir + liftIOAnnotated $ do + System.renameFile (byronGenDir "genesis.json") byronGenesisPath + forM_ [1..nPoolNodes] $ \i -> do + let ii = fromIntegral i :: Int + poolKeysDir = tempAbsPath Defaults.defaultSpoKeysDir ii + padIdx = let s = show (ii - 1) in replicate (3 - length s) '0' ++ s + System.copyFile (byronGenDir ("delegate-keys." ++ padIdx ++ ".key")) (poolKeysDir "byron-delegate.key") + System.copyFile (byronGenDir ("delegation-cert." ++ padIdx ++ ".json")) (poolKeysDir "byron-delegation.cert") + System.removeFile byronParamsFp + + unlessFileExists fp act = do + exists <- liftIOAnnotated $ System.doesFileExist fp + unless exists act diff --git a/cardano-testnet/src/Testnet/Defaults.hs b/cardano-testnet/src/Testnet/Defaults.hs index ef1976b7557..bedd362982f 100644 --- a/cardano-testnet/src/Testnet/Defaults.hs +++ b/cardano-testnet/src/Testnet/Defaults.hs @@ -366,6 +366,7 @@ defaultYamlConfig = -- See: https://github.com/input-output-hk/cardano-ledger/blob/master/eras/byron/ledger/impl/doc/network-magic.md , ("RequiresNetworkMagic", "RequiresMagic") + , ("EnableP2P", Aeson.Bool True) , ("PeerSharing", Aeson.Bool False) -- Logging related diff --git a/cardano-testnet/src/Testnet/Process/RunIO.hs b/cardano-testnet/src/Testnet/Process/RunIO.hs index 719e2eeb832..77385db63b6 100644 --- a/cardano-testnet/src/Testnet/Process/RunIO.hs +++ b/cardano-testnet/src/Testnet/Process/RunIO.hs @@ -8,6 +8,7 @@ module Testnet.Process.RunIO ( execCli' , execCli_ + , getCliHelpText , mkExecConfig , procNode , procKesAgent @@ -93,6 +94,16 @@ execCli -> RIO env String execCli = GHC.withFrozenCallStack $ execFlex "cardano-cli" "CARDANO_CLI" +-- | Best-effort query of the @cardano-cli@ help text for a subcommand. +-- Returns 'Nothing' if the binary can't be found or the command fails. +getCliHelpText :: MonadIO m => [String] -> m (Maybe String) +getCliHelpText args = liftIO $ do + result <- tryAny $ runRIO () $ + execFlexAny' defaultExecConfig "cardano-cli" "CARDANO_CLI" (args ++ ["--help"]) + pure $ case result of + Right (_, stdout', _) -> Just stdout' + Left _ -> Nothing + -- | Create a process returning its stdout. -- -- Being a 'flex' function means that the environment determines how the process is launched. diff --git a/cardano-testnet/src/Testnet/Start/Byron.hs b/cardano-testnet/src/Testnet/Start/Byron.hs index 053eed0db5d..eca95713661 100644 --- a/cardano-testnet/src/Testnet/Start/Byron.hs +++ b/cardano-testnet/src/Testnet/Start/Byron.hs @@ -6,19 +6,18 @@ {-# LANGUAGE NumericUnderscores #-} module Testnet.Start.Byron - ( createByronGenesis + ( ByronGenesisOptions(..) + , createByronGenesis , byronDefaultGenesisOptions ) where -import Control.Monad.Catch (MonadCatch) import Control.Monad.IO.Class (MonadIO) import Data.Time.Clock (UTCTime) import GHC.Stack -import Testnet.Process.Run +import Testnet.Process.RunIO (execCli_) import Hedgehog.Extras.Stock.Time (showUTCTimeSeconds) -import Hedgehog.Internal.Property (MonadTest) data ByronGenesisOptions = ByronGenesisOptions { byronNumBftNodes :: Int @@ -41,7 +40,7 @@ byronDefaultGenesisOptions = ByronGenesisOptions -- | Creates a default Byron genesis. This is required for any testnet, predominantly because -- we inject our ADA supply into our testnet via the Byron genesis. createByronGenesis - :: (MonadTest m, MonadCatch m, MonadIO m, HasCallStack) + :: (MonadIO m, HasCallStack) => Int -> UTCTime -> ByronGenesisOptions diff --git a/cardano-testnet/test/cardano-testnet-golden/files/golden/node_default_config.json b/cardano-testnet/test/cardano-testnet-golden/files/golden/node_default_config.json index cb0c0c45c86..f13a6d46616 100644 --- a/cardano-testnet/test/cardano-testnet-golden/files/golden/node_default_config.json +++ b/cardano-testnet/test/cardano-testnet-golden/files/golden/node_default_config.json @@ -5,6 +5,7 @@ "DijkstraGenesisFile": "dijkstra-genesis.json", "EnableLogMetrics": false, "EnableLogging": true, + "EnableP2P": true, "ExperimentalHardForksEnabled": true, "ExperimentalProtocolsEnabled": true, "LastKnownBlockVersion-Alt": 0,