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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
### Added

- Support for running `cardano-testnet` with older `cardano-cli` and `cardano-node` versions.

90 changes: 71 additions & 19 deletions cardano-testnet/src/Testnet/Components/Configuration.hs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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 :: ()
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -161,7 +167,7 @@ createSPOGenesisAndFiles
createSPOGenesisAndFiles
testnetOptions genesisOptions@GenesisOptions{genesisTestnetMagic}
onChainParams
(TmpAbsolutePath tempAbsPath) = do
(TmpAbsolutePath tempAbsPath) = do
AnyShelleyBasedEra sbe <- pure cardanoNodeEra


Expand Down Expand Up @@ -200,43 +206,89 @@ 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])
<> [ "--start-time", DTC.formatIso8601 startTime
, "--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



Expand Down
1 change: 1 addition & 0 deletions cardano-testnet/src/Testnet/Defaults.hs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions cardano-testnet/src/Testnet/Process/RunIO.hs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
module Testnet.Process.RunIO
( execCli'
, execCli_
, getCliHelpText
, mkExecConfig
, procNode
, procKesAgent
Expand Down Expand Up @@ -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.
Expand Down
9 changes: 4 additions & 5 deletions cardano-testnet/src/Testnet/Start/Byron.hs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"DijkstraGenesisFile": "dijkstra-genesis.json",
"EnableLogMetrics": false,
"EnableLogging": true,
"EnableP2P": true,
"ExperimentalHardForksEnabled": true,
"ExperimentalProtocolsEnabled": true,
"LastKnownBlockVersion-Alt": 0,
Expand Down
Loading