From 36704ea2dd50ed54e5466c9369807bb6108567c4 Mon Sep 17 00:00:00 2001 From: Robert 'Probie' Offner Date: Tue, 8 Nov 2022 21:58:25 +1100 Subject: [PATCH] Handle pipes Fixes https://github.com/input-output-hk/cardano-node/issues/4235 --- cardano-api/src/Cardano/Api.hs | 1 + .../src/Cardano/Api/SerialiseLedgerCddl.hs | 5 +- cardano-api/src/Cardano/Api/Utils.hs | 1 - .../src/Cardano/CLI/Shelley/Run/Read.hs | 138 +++++++++++++++--- .../Cardano/CLI/Shelley/Run/Transaction.hs | 33 +++-- cardano-cli/test/Test/OptParse.hs | 4 +- cardano-testnet/cardano-testnet.cabal | 7 +- cardano-testnet/test/Main.hs | 2 + .../test/Test/Cli/Babbage/Pipes.hs | 102 +++++++++++++ 9 files changed, 255 insertions(+), 38 deletions(-) create mode 100644 cardano-testnet/test/Test/Cli/Babbage/Pipes.hs diff --git a/cardano-api/src/Cardano/Api.hs b/cardano-api/src/Cardano/Api.hs index 9f8cac2d175..69e4d953d32 100644 --- a/cardano-api/src/Cardano/Api.hs +++ b/cardano-api/src/Cardano/Api.hs @@ -501,6 +501,7 @@ module Cardano.Api ( -- single API. FromSomeTypeCDDL(..), readFileTextEnvelopeCddlAnyOf, + deserialiseFromTextEnvelopeCddlAnyOf, writeTxFileTextEnvelopeCddl, writeTxWitnessFileTextEnvelopeCddl, serialiseTxLedgerCddl, diff --git a/cardano-api/src/Cardano/Api/SerialiseLedgerCddl.hs b/cardano-api/src/Cardano/Api/SerialiseLedgerCddl.hs index c25a26cd089..3222e2d8f64 100644 --- a/cardano-api/src/Cardano/Api/SerialiseLedgerCddl.hs +++ b/cardano-api/src/Cardano/Api/SerialiseLedgerCddl.hs @@ -15,6 +15,7 @@ module Cardano.Api.SerialiseLedgerCddl -- * Reading one of several transaction or -- key witness types , readFileTextEnvelopeCddlAnyOf + , deserialiseFromTextEnvelopeCddlAnyOf , writeTxFileTextEnvelopeCddl , writeTxWitnessFileTextEnvelopeCddl @@ -36,7 +37,6 @@ import qualified Data.Aeson as Aeson import Data.Aeson.Encode.Pretty (Config (..), defConfig, encodePretty', keyOrder) import Data.Bifunctor (first) import Data.ByteString (ByteString) -import qualified Data.ByteString as BS import qualified Data.ByteString.Base16 as Base16 import qualified Data.ByteString.Lazy as LBS import qualified Data.List as List @@ -52,6 +52,7 @@ import Cardano.Api.Error import Cardano.Api.HasTypeProxy import Cardano.Api.SerialiseCBOR import Cardano.Api.Tx +import Cardano.Api.Utils -- Why have we gone this route? The serialization format of `TxBody era` @@ -319,6 +320,6 @@ readTextEnvelopeCddlFromFile readTextEnvelopeCddlFromFile path = runExceptT $ do bs <- handleIOExceptT (FileIOError path) $ - BS.readFile path + readFileBlocking path firstExceptT (FileError path . TextEnvelopeCddlAesonDecodeError path) . hoistEither $ Aeson.eitherDecodeStrict' bs diff --git a/cardano-api/src/Cardano/Api/Utils.hs b/cardano-api/src/Cardano/Api/Utils.hs index ef4b0a77990..d554ebec0fa 100644 --- a/cardano-api/src/Cardano/Api/Utils.hs +++ b/cardano-api/src/Cardano/Api/Utils.hs @@ -133,4 +133,3 @@ renderEra (AnyCardanoEra AllegraEra) = "Allegra" renderEra (AnyCardanoEra MaryEra) = "Mary" renderEra (AnyCardanoEra AlonzoEra) = "Alonzo" renderEra (AnyCardanoEra BabbageEra) = "Babbage" - diff --git a/cardano-cli/src/Cardano/CLI/Shelley/Run/Read.hs b/cardano-cli/src/Cardano/CLI/Shelley/Run/Read.hs index e15ab96d171..cfccb489da5 100644 --- a/cardano-cli/src/Cardano/CLI/Shelley/Run/Read.hs +++ b/cardano-cli/src/Cardano/CLI/Shelley/Run/Read.hs @@ -52,19 +52,31 @@ module Cardano.CLI.Shelley.Run.Read , RequiredSignerError(..) , categoriseSomeWitness , readRequiredSigner + + -- * FileOrPipe + , FileOrPipe + , fileOrPipe + , readFileOrPipe ) where import Prelude +import Control.Exception (bracket) +import Control.Monad (unless) import Control.Monad.Trans.Except (ExceptT (..), runExceptT) -import Control.Monad.Trans.Except.Extra (firstExceptT, handleIOExceptT, hoistEither, left) +import Control.Monad.Trans.Except.Extra (firstExceptT, handleIOExceptT, hoistEither, left, newExceptT) import qualified Data.Aeson as Aeson import Data.Bifunctor (first) +import qualified Data.ByteString.Builder as Builder import qualified Data.ByteString.Char8 as BS import qualified Data.ByteString.Lazy.Char8 as LBS +import Data.IORef (IORef, newIORef, readIORef, writeIORef) import qualified Data.List as List import qualified Data.Text as Text import Data.Word +import GHC.IO.Handle (hClose, hIsSeekable) +import GHC.IO.Handle.FD (openFileBlocking) +import System.IO (IOMode (ReadMode)) import Cardano.Api @@ -445,11 +457,11 @@ deserialiseScriptInAnyLang bs = newtype CddlTx = CddlTx {unCddlTx :: InAnyCardanoEra Tx} deriving (Show, Eq) -readFileTx :: FilePath -> IO (Either CddlError (InAnyCardanoEra Tx)) -readFileTx fp = do - eAnyTx <- readFileInAnyCardanoEra AsTx fp +readFileTx :: FileOrPipe -> IO (Either CddlError (InAnyCardanoEra Tx)) +readFileTx file = do + eAnyTx <- readFileInAnyCardanoEra AsTx file case eAnyTx of - Left e -> fmap unCddlTx <$> acceptTxCDDLSerialisation e + Left e -> fmap unCddlTx <$> acceptTxCDDLSerialisation file e Right tx -> return $ Right tx -- IncompleteCddlFormattedTx is an CDDL formatted tx or partial tx @@ -461,11 +473,11 @@ data IncompleteTx = UnwitnessedCliFormattedTxBody (InAnyCardanoEra TxBody) | IncompleteCddlFormattedTx (InAnyCardanoEra Tx) -readFileTxBody :: FilePath -> IO (Either CddlError IncompleteTx) -readFileTxBody fp = do - eTxBody <- readFileInAnyCardanoEra AsTxBody fp +readFileTxBody :: FileOrPipe -> IO (Either CddlError IncompleteTx) +readFileTxBody file = do + eTxBody <- readFileInAnyCardanoEra AsTxBody file case eTxBody of - Left e -> fmap (IncompleteCddlFormattedTx . unCddlTx) <$> acceptTxCDDLSerialisation e + Left e -> fmap (IncompleteCddlFormattedTx . unCddlTx) <$> acceptTxCDDLSerialisation file e Right txBody -> return $ Right $ UnwitnessedCliFormattedTxBody txBody data CddlError = CddlErrorTextEnv @@ -482,21 +494,22 @@ instance Error CddlError where displayError (CddlIOError e) = displayError e acceptTxCDDLSerialisation - :: FileError TextEnvelopeError + :: FileOrPipe + -> FileError TextEnvelopeError -> IO (Either CddlError CddlTx) -acceptTxCDDLSerialisation err = +acceptTxCDDLSerialisation file err = case err of - e@(FileError fp (TextEnvelopeDecodeError _)) -> - first (CddlErrorTextEnv e) <$> readCddlTx fp - e@(FileError fp (TextEnvelopeAesonDecodeError _)) -> - first (CddlErrorTextEnv e) <$> readCddlTx fp - e@(FileError fp (TextEnvelopeTypeError _ _)) -> - first (CddlErrorTextEnv e) <$> readCddlTx fp + e@(FileError _ (TextEnvelopeDecodeError _)) -> + first (CddlErrorTextEnv e) <$> readCddlTx file + e@(FileError _ (TextEnvelopeAesonDecodeError _)) -> + first (CddlErrorTextEnv e) <$> readCddlTx file + e@(FileError _ (TextEnvelopeTypeError _ _)) -> + first (CddlErrorTextEnv e) <$> readCddlTx file e@FileErrorTempFile{} -> return . Left $ CddlIOError e e@FileIOError{} -> return . Left $ CddlIOError e -readCddlTx :: FilePath -> IO (Either (FileError TextEnvelopeCddlError) CddlTx) -readCddlTx = readFileTextEnvelopeCddlAnyOf teTypes +readCddlTx :: FileOrPipe -> IO (Either (FileError TextEnvelopeCddlError) CddlTx) +readCddlTx = readFileOrPipeTextEnvelopeCddlAnyOf teTypes where teTypes = [ FromCDDLTx "Witnessed Tx ByronEra" CddlTx , FromCDDLTx "Witnessed Tx ShelleyEra" CddlTx @@ -519,7 +532,8 @@ newtype CddlWitness = CddlWitness { unCddlWitness :: InAnyCardanoEra KeyWitness} readFileTxKeyWitness :: FilePath -> IO (Either CddlWitnessError (InAnyCardanoEra KeyWitness)) readFileTxKeyWitness fp = do - eWitness <- readFileInAnyCardanoEra AsKeyWitness fp + file <- fileOrPipe fp + eWitness <- readFileInAnyCardanoEra AsKeyWitness file case eWitness of Left e -> fmap unCddlWitness <$> acceptKeyWitnessCDDLSerialisation e Right keyWit -> return $ Right keyWit @@ -741,10 +755,10 @@ readFileInAnyCardanoEra , HasTextEnvelope (thing BabbageEra) ) => (forall era. AsType era -> AsType (thing era)) - -> FilePath + -> FileOrPipe -> IO (Either (FileError TextEnvelopeError) (InAnyCardanoEra thing)) readFileInAnyCardanoEra asThing = - readFileTextEnvelopeAnyOf + readFileOrPipeTextEnvelopeAnyOf [ FromSomeType (asThing AsByronEra) (InAnyCardanoEra ByronEra) , FromSomeType (asThing AsShelleyEra) (InAnyCardanoEra ShelleyEra) , FromSomeType (asThing AsAllegraEra) (InAnyCardanoEra AllegraEra) @@ -752,3 +766,83 @@ readFileInAnyCardanoEra asThing = , FromSomeType (asThing AsAlonzoEra) (InAnyCardanoEra AlonzoEra) , FromSomeType (asThing AsBabbageEra) (InAnyCardanoEra BabbageEra) ] + +-- | We need a type for handling files that may be actually be things like +-- pipes. Currently the CLI makes no guarantee that a "file" will only +-- be read once. This is a problem for a user who who expects to be able to pass +-- a pipe. To handle this, we have a type for representing either files or pipes +-- where the contents will be saved in memory if what we're reading is a pipe (so +-- it can be re-read later). Unfortunately this means we can't easily stream data +-- from pipes, but at present that's not an issue. +data FileOrPipe = FileOrPipe FilePath (IORef (Maybe LBS.ByteString)) + +instance Show FileOrPipe where + show (FileOrPipe fp _) = show fp + +fileOrPipe :: FilePath -> IO FileOrPipe +fileOrPipe fp = FileOrPipe fp <$> newIORef Nothing + +-- | Get the path backing a FileOrPipe. This should primarily be used when +-- generating error messages for a user. A user should not call directly +-- call a function like readFile on the result of this function +fileOrPipePath :: FileOrPipe -> FilePath +fileOrPipePath (FileOrPipe fp _) = fp + +-- | Get the contents of a file or pipe. This function reads the entire +-- contents of the file or pipe, and is blocking. +readFileOrPipe :: FileOrPipe -> IO LBS.ByteString +readFileOrPipe (FileOrPipe fp cacheRef) = do + cached <- readIORef cacheRef + case cached of + Just dat -> pure dat + Nothing -> bracket + (openFileBlocking fp ReadMode) + hClose + (\handle -> do + -- An arbitrary block size. + let blockSize = 4096 + let go acc = do + next <- BS.hGet handle blockSize + if BS.null next + then pure acc + else go (acc <> Builder.byteString next) + contents <- go mempty + let dat = Builder.toLazyByteString contents + -- If our file is not seekable, it's likely a pipe, so we need to + -- save the result for subsequent calls + seekable <- hIsSeekable handle + unless seekable (writeIORef cacheRef (Just dat)) + pure dat) + +readFileOrPipeTextEnvelopeAnyOf :: [FromSomeType HasTextEnvelope b] + -> FileOrPipe + -> IO (Either (FileError TextEnvelopeError) b) +readFileOrPipeTextEnvelopeAnyOf types file = do + let path = fileOrPipePath file + runExceptT $ do + content <- handleIOExceptT (FileIOError path) $ readFileOrPipe file + firstExceptT (FileError path) $ hoistEither $ do + te <- first TextEnvelopeAesonDecodeError $ Aeson.eitherDecode' content + deserialiseFromTextEnvelopeAnyOf types te + +readFileOrPipeTextEnvelopeCddlAnyOf + :: [FromSomeTypeCDDL TextEnvelopeCddl b] + -> FileOrPipe + -> IO (Either (FileError TextEnvelopeCddlError) b) +readFileOrPipeTextEnvelopeCddlAnyOf types file = do + let path = fileOrPipePath file + runExceptT $ do + te <- newExceptT $ readTextEnvelopeCddlFromFileOrPipe file + firstExceptT (FileError path) $ hoistEither $ do + deserialiseFromTextEnvelopeCddlAnyOf types te + +readTextEnvelopeCddlFromFileOrPipe + :: FileOrPipe + -> IO (Either (FileError TextEnvelopeCddlError) TextEnvelopeCddl) +readTextEnvelopeCddlFromFileOrPipe file = do + let path = fileOrPipePath file + runExceptT $ do + bs <- handleIOExceptT (FileIOError path) $ + readFileOrPipe file + firstExceptT (FileError path . TextEnvelopeCddlAesonDecodeError path) + . hoistEither $ Aeson.eitherDecode' bs diff --git a/cardano-cli/src/Cardano/CLI/Shelley/Run/Transaction.hs b/cardano-cli/src/Cardano/CLI/Shelley/Run/Transaction.hs index eeeee81ddeb..383bd347a81 100644 --- a/cardano-cli/src/Cardano/CLI/Shelley/Run/Transaction.hs +++ b/cardano-cli/src/Cardano/CLI/Shelley/Run/Transaction.hs @@ -1045,7 +1045,8 @@ runTxSign txOrTxBody witSigningData mnw (TxFile outTxFile) = do let (sksByron, sksShelley) = partitionSomeWitnesses $ map categoriseSomeWitness sks case txOrTxBody of - (InputTxFile (TxFile inputTxFile)) -> do + (InputTxFile (TxFile inputTxFilePath)) -> do + inputTxFile <- liftIO $ fileOrPipe inputTxFilePath anyTx <- firstExceptT ShelleyTxCmdCddlError . newExceptT $ readFileTx inputTxFile InAnyShelleyBasedEra _era tx <- @@ -1064,7 +1065,8 @@ runTxSign txOrTxBody witSigningData mnw (TxFile outTxFile) = do firstExceptT ShelleyTxCmdWriteFileError . newExceptT $ writeTxFileTextEnvelopeCddl outTxFile signedTx - (InputTxBodyFile (TxBodyFile txbodyFile)) -> do + (InputTxBodyFile (TxBodyFile txbodyFilePath)) -> do + txbodyFile <- liftIO $ fileOrPipe txbodyFilePath unwitnessed <- firstExceptT ShelleyTxCmdCddlError . newExceptT $ readFileTxBody txbodyFile @@ -1112,15 +1114,17 @@ runTxSubmit -> NetworkId -> FilePath -> ExceptT ShelleyTxCmdError IO () -runTxSubmit (AnyConsensusModeParams cModeParams) network txFile = do +runTxSubmit (AnyConsensusModeParams cModeParams) network txFilePath = do + SocketPath sockPath <- firstExceptT ShelleyTxCmdSocketEnvError $ newExceptT readEnvSocketPath + txFile <- liftIO $ fileOrPipe txFilePath InAnyCardanoEra era tx <- firstExceptT ShelleyTxCmdCddlError . newExceptT $ readFileTx txFile let cMode = AnyConsensusMode $ consensusModeOnly cModeParams eraInMode <- hoistMaybe - (ShelleyTxCmdEraConsensusModeMismatch (Just txFile) cMode (AnyCardanoEra era)) + (ShelleyTxCmdEraConsensusModeMismatch (Just txFilePath) cMode (AnyCardanoEra era)) (toEraInMode era $ consensusModeOnly cModeParams) let txInMode = TxInMode tx eraInMode localNodeConnInfo = LocalNodeConnectInfo @@ -1150,11 +1154,12 @@ runTxCalculateMinFee -> TxShelleyWitnessCount -> TxByronWitnessCount -> ExceptT ShelleyTxCmdError IO () -runTxCalculateMinFee (TxBodyFile txbodyFile) nw protocolParamsSourceSpec +runTxCalculateMinFee (TxBodyFile txbodyFilePath) nw protocolParamsSourceSpec (TxInCount nInputs) (TxOutCount nOutputs) (TxShelleyWitnessCount nShelleyKeyWitnesses) (TxByronWitnessCount nByronKeyWitnesses) = do + txbodyFile <- liftIO $ fileOrPipe txbodyFilePath unwitnessed <- firstExceptT ShelleyTxCmdCddlError . newExceptT $ readFileTxBody txbodyFile pparams <- firstExceptT ShelleyTxCmdProtocolParamsError @@ -1297,7 +1302,8 @@ runTxGetTxId :: InputTxBodyOrTxFile -> ExceptT ShelleyTxCmdError IO () runTxGetTxId txfile = do InAnyCardanoEra _era txbody <- case txfile of - InputTxBodyFile (TxBodyFile txbodyFile) -> do + InputTxBodyFile (TxBodyFile txbodyFilePath) -> do + txbodyFile <- liftIO $ fileOrPipe txbodyFilePath unwitnessed <- firstExceptT ShelleyTxCmdCddlError . newExceptT $ readFileTxBody txbodyFile case unwitnessed of @@ -1305,7 +1311,8 @@ runTxGetTxId txfile = do IncompleteCddlFormattedTx (InAnyCardanoEra era tx) -> return (InAnyCardanoEra era (getTxBody tx)) - InputTxFile (TxFile txFile) -> do + InputTxFile (TxFile txFilePath) -> do + txFile <- liftIO $ fileOrPipe txFilePath InAnyCardanoEra era tx <- firstExceptT ShelleyTxCmdCddlError . newExceptT $ readFileTx txFile return . InAnyCardanoEra era $ getTxBody tx @@ -1314,7 +1321,8 @@ runTxGetTxId txfile = do runTxView :: InputTxBodyOrTxFile -> ExceptT ShelleyTxCmdError IO () runTxView = \case - InputTxBodyFile (TxBodyFile txbodyFile) -> do + InputTxBodyFile (TxBodyFile txbodyFilePath) -> do + txbodyFile <- liftIO $ fileOrPipe txbodyFilePath unwitnessed <- firstExceptT ShelleyTxCmdCddlError . newExceptT $ readFileTxBody txbodyFile InAnyCardanoEra era txbody <- @@ -1326,7 +1334,8 @@ runTxView = \case -- In the case of a transaction body, we can simply call makeSignedTransaction [] -- to get a transaction which allows us to reuse friendlyTxBS! liftIO $ BS.putStr $ friendlyTxBodyBS era txbody - InputTxFile (TxFile txFile) -> do + InputTxFile (TxFile txFilePath) -> do + txFile <- liftIO $ fileOrPipe txFilePath InAnyCardanoEra era tx <- firstExceptT ShelleyTxCmdCddlError . newExceptT $ readFileTx txFile liftIO $ BS.putStr $ friendlyTxBS era tx @@ -1342,7 +1351,8 @@ runTxCreateWitness -> Maybe NetworkId -> OutputFile -> ExceptT ShelleyTxCmdError IO () -runTxCreateWitness (TxBodyFile txbodyFile) witSignData mbNw (OutputFile oFile) = do +runTxCreateWitness (TxBodyFile txbodyFilePath) witSignData mbNw (OutputFile oFile) = do + txbodyFile <- liftIO $ fileOrPipe txbodyFilePath unwitnessed <- firstExceptT ShelleyTxCmdCddlError . newExceptT $ readFileTxBody txbodyFile case unwitnessed of @@ -1393,7 +1403,8 @@ runTxSignWitness -> [WitnessFile] -> OutputFile -> ExceptT ShelleyTxCmdError IO () -runTxSignWitness (TxBodyFile txbodyFile) witnessFiles (OutputFile oFp) = do +runTxSignWitness (TxBodyFile txbodyFilePath) witnessFiles (OutputFile oFp) = do + txbodyFile <- liftIO $ fileOrPipe txbodyFilePath unwitnessed <- firstExceptT ShelleyTxCmdCddlError . newExceptT $ readFileTxBody txbodyFile case unwitnessed of diff --git a/cardano-cli/test/Test/OptParse.hs b/cardano-cli/test/Test/OptParse.hs index 4a875e1e59b..9e18d8e7ae7 100644 --- a/cardano-cli/test/Test/OptParse.hs +++ b/cardano-cli/test/Test/OptParse.hs @@ -72,7 +72,9 @@ checkTxCddlFormat => FilePath -- ^ Reference/golden file -> FilePath -- ^ Newly created file -> m () -checkTxCddlFormat reference created = do +checkTxCddlFormat referencePath createdPath = do + reference <- liftIO $ fileOrPipe referencePath + created <- liftIO $ fileOrPipe createdPath r <- liftIO $ readCddlTx reference c <- liftIO $ readCddlTx created r H.=== c diff --git a/cardano-testnet/cardano-testnet.cabal b/cardano-testnet/cardano-testnet.cabal index 22f2ef14304..7c228e04a8a 100644 --- a/cardano-testnet/cardano-testnet.cabal +++ b/cardano-testnet/cardano-testnet.cabal @@ -12,6 +12,10 @@ build-type: Simple common base { build-depends: base >= 4.14 && < 4.15 } +common maybe-unix + if !os(windows) + build-depends: unix + common project-config default-language: Haskell2010 default-extensions: NoImplicitPrelude @@ -96,7 +100,7 @@ executable cardano-testnet ghc-options: -threaded -rtsopts -with-rtsopts=-N -with-rtsopts=-T test-suite cardano-testnet-tests - import: base, project-config + import: base, project-config, maybe-unix hs-source-dirs: test @@ -104,6 +108,7 @@ test-suite cardano-testnet-tests other-modules: Test.Cli.Alonzo.LeadershipSchedule Test.Cli.Babbage.LeadershipSchedule + Test.Cli.Babbage.Pipes Test.Cli.KesPeriodInfo Test.FoldBlocks Test.Misc diff --git a/cardano-testnet/test/Main.hs b/cardano-testnet/test/Main.hs index 25c4a377b51..f444e6c6edd 100644 --- a/cardano-testnet/test/Main.hs +++ b/cardano-testnet/test/Main.hs @@ -13,6 +13,7 @@ import qualified Test.Tasty.Ingredients as T --import qualified Test.Cli.Alonzo.LeadershipSchedule import qualified Test.Cli.Babbage.LeadershipSchedule +import qualified Test.Cli.Babbage.Pipes import qualified Test.Cli.KesPeriodInfo import qualified Test.FoldBlocks import qualified Test.Node.Shutdown @@ -30,6 +31,7 @@ tests = pure $ T.testGroup "test/Spec.hs" -- ] , T.testGroup "Babbage" [ H.ignoreOnMacAndWindows "leadership-schedule" Test.Cli.Babbage.LeadershipSchedule.hprop_leadershipSchedule + , H.ignoreOnMacAndWindows "pipes" Test.Cli.Babbage.Pipes.hprop_pipes ] -- Ignored on Windows due to : commitBuffer: invalid argument (invalid character) -- as a result of the kes-period-info output to stdout. diff --git a/cardano-testnet/test/Test/Cli/Babbage/Pipes.hs b/cardano-testnet/test/Test/Cli/Babbage/Pipes.hs new file mode 100644 index 00000000000..2d07b901200 --- /dev/null +++ b/cardano-testnet/test/Test/Cli/Babbage/Pipes.hs @@ -0,0 +1,102 @@ +{-# LANGUAGE CPP #-} +{-# LANGUAGE DisambiguateRecordFields #-} +{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE OverloadedStrings #-} + +#if !defined(mingw32_HOST_OS) +#define UNIX +#endif + +module Test.Cli.Babbage.Pipes + ( hprop_pipes + ) where + +import Control.Monad (void) +import Control.Monad.IO.Class (MonadIO, liftIO) +import Data.Monoid (Last (..)) +import Hedgehog (Property) +import Prelude +import System.Environment (getEnvironment) +import System.FilePath (()) +import System.IO (hClose, hFlush, hPutStr) + +import qualified Hedgehog as H +import qualified Hedgehog.Extras.Stock.IO.Network.Sprocket as IO +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.Directory as IO +import qualified System.Info as SYS +import Testnet ( TestnetOptions (BabbageOnlyTestnetOptions), testnet) +import Testnet.Babbage (BabbageTestnetOptions (..)) +import qualified Testnet.Babbage as TC +import qualified Testnet.Conf as H +import qualified Util.Base as H +import qualified Util.Process as H +import qualified Util.Runtime as TR +#ifdef UNIX +import System.Posix.IO (closeFd, createPipe, fdToHandle) +#endif + +hprop_pipes :: Property +#ifdef UNIX +hprop_pipes = H.integration . H.runFinallies . H.workspace "babbage" $ \tempAbsBasePath' -> do + H.note_ SYS.os + base <- H.note =<< H.noteIO . IO.canonicalizePath =<< H.getProjectBase + configurationTemplate <- H.noteShow $ base "configuration/defaults/byron-mainnet/configuration.yaml" + conf@H.Conf { H.tempBaseAbsPath, H.tempAbsPath } <- H.noteShowM $ + H.mkConf (H.ProjectBase base) (H.YamlFilePath configurationTemplate) tempAbsBasePath' Nothing + + work <- H.note $ tempAbsPath "work" + H.createDirectoryIfMissing work + let + testnetOptions = BabbageOnlyTestnetOptions $ TC.defaultTestnetOptions + { nodeLoggingFormat = TR.NodeLoggingFormatAsJson + } + TR.TestnetRuntime{ poolNodes } <- testnet testnetOptions conf + + poolNode1 <- H.headM poolNodes + + env <- H.evalIO getEnvironment + + poolSprocket1 <- H.noteShow $ TR.nodeSprocket $ TR.poolRuntime poolNode1 + + execConfig <- H.noteShow H.ExecConfig + { H.execConfigEnv = Last $ Just $ + [ ("CARDANO_NODE_SOCKET_PATH", IO.sprocketArgumentName poolSprocket1) + ] + -- The environment must be passed onto child process on Windows in order to + -- successfully start that process. + <> env + , H.execConfigCwd = Last $ Just tempBaseAbsPath + } + + let tx = unlines + [ "{" + , "\"type\": \"Unwitnessed Tx BabbageEra\"," + , "\"description\": \"Ledger Cddl Format\"," + , "\"cborHex\": \"84a300818258208a5a31ae52cdc140b1c532c47d5cb3c8dbc6c9b7a4aa537ed9e3543ea94cfad1000183a200581d60768d2bcd29a84ae015d96118e3285bc0cbd399ae16039ad471c8a806011a001e8480a200581d60768d2bcd29a84ae015d96118e3285bc0cbd399ae16039ad471c8a806011a002625a0a200581d600ac4d3db1163e7884959c0317aa19263a236db113fa0fbefbd49b335011a00958d31021a0003094fa0f5f6\"" + , "}" + ] + + withPipe tx $ \path -> do + void $ H.execCli' execConfig + [ "transaction", "txid" + , "--tx-file", path + ] + +withPipe :: (MonadIO m) => String -> (FilePath -> m a) -> m a +withPipe contents k = do + (readEnd, writeEnd) <- liftIO createPipe + liftIO $ do + writeHandle <- fdToHandle writeEnd + hPutStr writeHandle contents + hFlush writeHandle + hClose writeHandle + res <- k $ "/dev/fd/" ++ show readEnd + liftIO $ closeFd readEnd + pure res + +#else +hprop_pipes = H.property $ pure () +#endif