Skip to content

Commit f36a224

Browse files
iohk-bors[bot]Robert 'Probie' Offnereyeinsky
authored
Merge #4625 #4682
4625: Handle pipes r=Jimbo4350 a=LudvikGalois Fixes #4235 4682: Export `fromShelleyBasedScript` from Cardano.Api r=Jimbo4350 a=eyeinsky New PR based off of a branch in this repo. Old PR here #4386 Co-authored-by: Robert 'Probie' Offner <[email protected]> Co-authored-by: Markus Läll <[email protected]>
3 parents 101e247 + ac73752 + 1f8344d commit f36a224

File tree

11 files changed

+272
-39
lines changed

11 files changed

+272
-39
lines changed

cardano-api/src/Cardano/Api.hs

+1
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,7 @@ module Cardano.Api (
510510
-- single API.
511511
FromSomeTypeCDDL(..),
512512
readFileTextEnvelopeCddlAnyOf,
513+
deserialiseFromTextEnvelopeCddlAnyOf,
513514
writeTxFileTextEnvelopeCddl,
514515
writeTxWitnessFileTextEnvelopeCddl,
515516
serialiseTxLedgerCddl,

cardano-api/src/Cardano/Api/SerialiseLedgerCddl.hs

+3-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ module Cardano.Api.SerialiseLedgerCddl
1515
-- * Reading one of several transaction or
1616
-- key witness types
1717
, readFileTextEnvelopeCddlAnyOf
18+
, deserialiseFromTextEnvelopeCddlAnyOf
1819

1920
, writeTxFileTextEnvelopeCddl
2021
, writeTxWitnessFileTextEnvelopeCddl
@@ -34,7 +35,6 @@ import qualified Data.Aeson as Aeson
3435
import Data.Aeson.Encode.Pretty (Config (..), defConfig, encodePretty', keyOrder)
3536
import Data.Bifunctor (first)
3637
import Data.ByteString (ByteString)
37-
import qualified Data.ByteString as BS
3838
import qualified Data.ByteString.Base16 as Base16
3939
import qualified Data.ByteString.Lazy as LBS
4040
import qualified Data.List as List
@@ -50,6 +50,7 @@ import Cardano.Api.Error
5050
import Cardano.Api.HasTypeProxy
5151
import Cardano.Api.SerialiseCBOR
5252
import Cardano.Api.Tx
53+
import Cardano.Api.Utils
5354

5455

5556
-- Why have we gone this route? The serialization format of `TxBody era`
@@ -317,6 +318,6 @@ readTextEnvelopeCddlFromFile
317318
readTextEnvelopeCddlFromFile path =
318319
runExceptT $ do
319320
bs <- handleIOExceptT (FileIOError path) $
320-
BS.readFile path
321+
readFileBlocking path
321322
firstExceptT (FileError path . TextEnvelopeCddlAesonDecodeError path)
322323
. hoistEither $ Aeson.eitherDecodeStrict' bs

cardano-api/src/Cardano/Api/Shelley.hs

+1
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ module Cardano.Api.Shelley
111111
ProtocolParametersError(..),
112112

113113
-- * Scripts
114+
fromShelleyBasedScript,
114115
toShelleyScript,
115116
toShelleyMultiSig,
116117
fromShelleyMultiSig,

cardano-api/src/Cardano/Api/Utils.hs

-1
Original file line numberDiff line numberDiff line change
@@ -131,4 +131,3 @@ renderEra (AnyCardanoEra AllegraEra) = "Allegra"
131131
renderEra (AnyCardanoEra MaryEra) = "Mary"
132132
renderEra (AnyCardanoEra AlonzoEra) = "Alonzo"
133133
renderEra (AnyCardanoEra BabbageEra) = "Babbage"
134-

cardano-cli/cardano-cli.cabal

+7-2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ common maybe-unix
3737
if !os(windows)
3838
build-depends: unix
3939

40+
common maybe-bytestring
41+
if !os(windows)
42+
build-depends: bytestring
43+
4044
library
4145
import: project-config
4246

@@ -162,7 +166,7 @@ executable cardano-cli
162166
, transformers-except
163167

164168
test-suite cardano-cli-test
165-
import: project-config
169+
import: project-config, maybe-unix, maybe-bytestring
166170

167171
hs-source-dirs: test
168172
main-is: cardano-cli-test.hs
@@ -201,6 +205,7 @@ test-suite cardano-cli-test
201205
Test.Cli.Pioneers.Exercise4
202206
Test.Cli.Pioneers.Exercise5
203207
Test.Cli.Pioneers.Exercise6
208+
Test.Cli.Pipes
204209
Test.Cli.Shelley.Run.Query
205210
Test.OptParse
206211

@@ -227,7 +232,7 @@ test-suite cardano-cli-golden
227232
, directory
228233
, exceptions
229234
, filepath
230-
, hedgehog
235+
, hedgehog ^>= 1.2
231236
, hedgehog-extras
232237
, text
233238
, time

cardano-cli/src/Cardano/CLI/Shelley/Run/Read.hs

+124-22
Original file line numberDiff line numberDiff line change
@@ -53,19 +53,34 @@ module Cardano.CLI.Shelley.Run.Read
5353
, RequiredSignerError(..)
5454
, categoriseSomeWitness
5555
, readRequiredSigner
56+
57+
-- * FileOrPipe
58+
, FileOrPipe
59+
, fileOrPipe
60+
, fileOrPipePath
61+
, fileOrPipeCache
62+
, readFileOrPipe
5663
) where
5764

5865
import Prelude
5966

67+
import Control.Exception (bracket)
68+
import Control.Monad (unless)
6069
import Control.Monad.Trans.Except (ExceptT (..), runExceptT)
61-
import Control.Monad.Trans.Except.Extra (firstExceptT, handleIOExceptT, hoistEither, left)
70+
import Control.Monad.Trans.Except.Extra (firstExceptT, handleIOExceptT, hoistEither, left,
71+
newExceptT)
6272
import qualified Data.Aeson as Aeson
6373
import Data.Bifunctor (first)
74+
import qualified Data.ByteString.Builder as Builder
6475
import qualified Data.ByteString.Char8 as BS
6576
import qualified Data.ByteString.Lazy.Char8 as LBS
77+
import Data.IORef (IORef, newIORef, readIORef, writeIORef)
6678
import qualified Data.List as List
6779
import qualified Data.Text as Text
6880
import Data.Word
81+
import GHC.IO.Handle (hClose, hIsSeekable)
82+
import GHC.IO.Handle.FD (openFileBlocking)
83+
import System.IO (IOMode (ReadMode))
6984

7085

7186
import Cardano.Api
@@ -447,11 +462,11 @@ deserialiseScriptInAnyLang bs =
447462

448463
newtype CddlTx = CddlTx {unCddlTx :: InAnyCardanoEra Tx} deriving (Show, Eq)
449464

450-
readFileTx :: FilePath -> IO (Either CddlError (InAnyCardanoEra Tx))
451-
readFileTx fp = do
452-
eAnyTx <- readFileInAnyCardanoEra AsTx fp
465+
readFileTx :: FileOrPipe -> IO (Either CddlError (InAnyCardanoEra Tx))
466+
readFileTx file = do
467+
eAnyTx <- readFileInAnyCardanoEra AsTx file
453468
case eAnyTx of
454-
Left e -> fmap unCddlTx <$> acceptTxCDDLSerialisation e
469+
Left e -> fmap unCddlTx <$> acceptTxCDDLSerialisation file e
455470
Right tx -> return $ Right tx
456471

457472
-- IncompleteCddlFormattedTx is an CDDL formatted tx or partial tx
@@ -463,11 +478,11 @@ data IncompleteTx
463478
= UnwitnessedCliFormattedTxBody (InAnyCardanoEra TxBody)
464479
| IncompleteCddlFormattedTx (InAnyCardanoEra Tx)
465480

466-
readFileTxBody :: FilePath -> IO (Either CddlError IncompleteTx)
467-
readFileTxBody fp = do
468-
eTxBody <- readFileInAnyCardanoEra AsTxBody fp
481+
readFileTxBody :: FileOrPipe -> IO (Either CddlError IncompleteTx)
482+
readFileTxBody file = do
483+
eTxBody <- readFileInAnyCardanoEra AsTxBody file
469484
case eTxBody of
470-
Left e -> fmap (IncompleteCddlFormattedTx . unCddlTx) <$> acceptTxCDDLSerialisation e
485+
Left e -> fmap (IncompleteCddlFormattedTx . unCddlTx) <$> acceptTxCDDLSerialisation file e
471486
Right txBody -> return $ Right $ UnwitnessedCliFormattedTxBody txBody
472487

473488
data CddlError = CddlErrorTextEnv
@@ -484,21 +499,22 @@ renderCddlError (CddlErrorTextEnv textEnvErr cddlErr) = mconcat
484499
renderCddlError (CddlIOError e) = Text.pack $ displayError e
485500

486501
acceptTxCDDLSerialisation
487-
:: FileError TextEnvelopeError
502+
:: FileOrPipe
503+
-> FileError TextEnvelopeError
488504
-> IO (Either CddlError CddlTx)
489-
acceptTxCDDLSerialisation err =
505+
acceptTxCDDLSerialisation file err =
490506
case err of
491-
e@(FileError fp (TextEnvelopeDecodeError _)) ->
492-
first (CddlErrorTextEnv e) <$> readCddlTx fp
493-
e@(FileError fp (TextEnvelopeAesonDecodeError _)) ->
494-
first (CddlErrorTextEnv e) <$> readCddlTx fp
495-
e@(FileError fp (TextEnvelopeTypeError _ _)) ->
496-
first (CddlErrorTextEnv e) <$> readCddlTx fp
507+
e@(FileError _ (TextEnvelopeDecodeError _)) ->
508+
first (CddlErrorTextEnv e) <$> readCddlTx file
509+
e@(FileError _ (TextEnvelopeAesonDecodeError _)) ->
510+
first (CddlErrorTextEnv e) <$> readCddlTx file
511+
e@(FileError _ (TextEnvelopeTypeError _ _)) ->
512+
first (CddlErrorTextEnv e) <$> readCddlTx file
497513
e@FileErrorTempFile{} -> return . Left $ CddlIOError e
498514
e@FileIOError{} -> return . Left $ CddlIOError e
499515

500-
readCddlTx :: FilePath -> IO (Either (FileError TextEnvelopeCddlError) CddlTx)
501-
readCddlTx = readFileTextEnvelopeCddlAnyOf teTypes
516+
readCddlTx :: FileOrPipe -> IO (Either (FileError TextEnvelopeCddlError) CddlTx)
517+
readCddlTx = readFileOrPipeTextEnvelopeCddlAnyOf teTypes
502518
where
503519
teTypes = [ FromCDDLTx "Witnessed Tx ByronEra" CddlTx
504520
, FromCDDLTx "Witnessed Tx ShelleyEra" CddlTx
@@ -521,7 +537,8 @@ newtype CddlWitness = CddlWitness { unCddlWitness :: InAnyCardanoEra KeyWitness}
521537
readFileTxKeyWitness :: FilePath
522538
-> IO (Either CddlWitnessError (InAnyCardanoEra KeyWitness))
523539
readFileTxKeyWitness fp = do
524-
eWitness <- readFileInAnyCardanoEra AsKeyWitness fp
540+
file <- fileOrPipe fp
541+
eWitness <- readFileInAnyCardanoEra AsKeyWitness file
525542
case eWitness of
526543
Left e -> fmap unCddlWitness <$> acceptKeyWitnessCDDLSerialisation e
527544
Right keyWit -> return $ Right keyWit
@@ -727,14 +744,99 @@ readFileInAnyCardanoEra
727744
, HasTextEnvelope (thing BabbageEra)
728745
)
729746
=> (forall era. AsType era -> AsType (thing era))
730-
-> FilePath
747+
-> FileOrPipe
731748
-> IO (Either (FileError TextEnvelopeError) (InAnyCardanoEra thing))
732749
readFileInAnyCardanoEra asThing =
733-
readFileTextEnvelopeAnyOf
750+
readFileOrPipeTextEnvelopeAnyOf
734751
[ FromSomeType (asThing AsByronEra) (InAnyCardanoEra ByronEra)
735752
, FromSomeType (asThing AsShelleyEra) (InAnyCardanoEra ShelleyEra)
736753
, FromSomeType (asThing AsAllegraEra) (InAnyCardanoEra AllegraEra)
737754
, FromSomeType (asThing AsMaryEra) (InAnyCardanoEra MaryEra)
738755
, FromSomeType (asThing AsAlonzoEra) (InAnyCardanoEra AlonzoEra)
739756
, FromSomeType (asThing AsBabbageEra) (InAnyCardanoEra BabbageEra)
740757
]
758+
759+
-- | We need a type for handling files that may be actually be things like
760+
-- pipes. Currently the CLI makes no guarantee that a "file" will only
761+
-- be read once. This is a problem for a user who who expects to be able to pass
762+
-- a pipe. To handle this, we have a type for representing either files or pipes
763+
-- where the contents will be saved in memory if what we're reading is a pipe (so
764+
-- it can be re-read later). Unfortunately this means we can't easily stream data
765+
-- from pipes, but at present that's not an issue.
766+
data FileOrPipe = FileOrPipe FilePath (IORef (Maybe LBS.ByteString))
767+
768+
769+
instance Show FileOrPipe where
770+
show (FileOrPipe fp _) = show fp
771+
772+
fileOrPipe :: FilePath -> IO FileOrPipe
773+
fileOrPipe fp = FileOrPipe fp <$> newIORef Nothing
774+
775+
-- | Get the path backing a FileOrPipe. This should primarily be used when
776+
-- generating error messages for a user. A user should not call directly
777+
-- call a function like readFile on the result of this function
778+
fileOrPipePath :: FileOrPipe -> FilePath
779+
fileOrPipePath (FileOrPipe fp _) = fp
780+
781+
fileOrPipeCache :: FileOrPipe -> IO (Maybe LBS.ByteString)
782+
fileOrPipeCache (FileOrPipe _ c) = readIORef c
783+
784+
-- | Get the contents of a file or pipe. This function reads the entire
785+
-- contents of the file or pipe, and is blocking.
786+
readFileOrPipe :: FileOrPipe -> IO LBS.ByteString
787+
readFileOrPipe (FileOrPipe fp cacheRef) = do
788+
cached <- readIORef cacheRef
789+
case cached of
790+
Just dat -> pure dat
791+
Nothing -> bracket
792+
(openFileBlocking fp ReadMode)
793+
hClose
794+
(\handle -> do
795+
-- An arbitrary block size.
796+
let blockSize = 4096
797+
let go acc = do
798+
next <- BS.hGet handle blockSize
799+
if BS.null next
800+
then pure acc
801+
else go (acc <> Builder.byteString next)
802+
contents <- go mempty
803+
let dat = Builder.toLazyByteString contents
804+
-- If our file is not seekable, it's likely a pipe, so we need to
805+
-- save the result for subsequent calls
806+
seekable <- hIsSeekable handle
807+
unless seekable (writeIORef cacheRef (Just dat))
808+
pure dat)
809+
810+
readFileOrPipeTextEnvelopeAnyOf
811+
:: [FromSomeType HasTextEnvelope b]
812+
-> FileOrPipe
813+
-> IO (Either (FileError TextEnvelopeError) b)
814+
readFileOrPipeTextEnvelopeAnyOf types file = do
815+
let path = fileOrPipePath file
816+
runExceptT $ do
817+
content <- handleIOExceptT (FileIOError path) $ readFileOrPipe file
818+
firstExceptT (FileError path) $ hoistEither $ do
819+
te <- first TextEnvelopeAesonDecodeError $ Aeson.eitherDecode' content
820+
deserialiseFromTextEnvelopeAnyOf types te
821+
822+
readFileOrPipeTextEnvelopeCddlAnyOf
823+
:: [FromSomeTypeCDDL TextEnvelopeCddl b]
824+
-> FileOrPipe
825+
-> IO (Either (FileError TextEnvelopeCddlError) b)
826+
readFileOrPipeTextEnvelopeCddlAnyOf types file = do
827+
let path = fileOrPipePath file
828+
runExceptT $ do
829+
te <- newExceptT $ readTextEnvelopeCddlFromFileOrPipe file
830+
firstExceptT (FileError path) $ hoistEither $ do
831+
deserialiseFromTextEnvelopeCddlAnyOf types te
832+
833+
readTextEnvelopeCddlFromFileOrPipe
834+
:: FileOrPipe
835+
-> IO (Either (FileError TextEnvelopeCddlError) TextEnvelopeCddl)
836+
readTextEnvelopeCddlFromFileOrPipe file = do
837+
let path = fileOrPipePath file
838+
runExceptT $ do
839+
bs <- handleIOExceptT (FileIOError path) $
840+
readFileOrPipe file
841+
firstExceptT (FileError path . TextEnvelopeCddlAesonDecodeError path)
842+
. hoistEither $ Aeson.eitherDecode' bs

0 commit comments

Comments
 (0)