{-# LANGUAGE AllowAmbiguousTypes #-}

module Hydra.Node.Util where

import Hydra.Prelude

import Cardano.Api.UTxO (totalValue)
import Hydra.Cardano.Api (
  AsType (AsPaymentExtendedKey, AsPaymentKey, AsSigningKey, AsVerificationKey),
  AssetId (..),
  CardanoSigningKey (..),
  File (..),
  FileError,
  FromSomeType (..),
  HasTextEnvelope,
  PaymentKey,
  TextEnvelopeError,
  UTxO,
  Value,
  VerificationKey,
  castVerificationKey,
  filterValue,
  getCardanoPaymentVerificationKey,
  readFileTextEnvelope,
  readFileTextEnvelopeAnyOf,
 )
import Hydra.Tx.Secret (Secret, mkSecret)

-- | Thrown when a cardano key file could not be read as any of the accepted
-- text envelope formats.
newtype KeyFileError = KeyFileError (FileError TextEnvelopeError)
  deriving stock (Int -> KeyFileError -> ShowS
[KeyFileError] -> ShowS
KeyFileError -> String
(Int -> KeyFileError -> ShowS)
-> (KeyFileError -> String)
-> ([KeyFileError] -> ShowS)
-> Show KeyFileError
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> KeyFileError -> ShowS
showsPrec :: Int -> KeyFileError -> ShowS
$cshow :: KeyFileError -> String
show :: KeyFileError -> String
$cshowList :: [KeyFileError] -> ShowS
showList :: [KeyFileError] -> ShowS
Show)
  deriving anyclass (Show KeyFileError
Typeable KeyFileError
(Typeable KeyFileError, Show KeyFileError) =>
(KeyFileError -> SomeException)
-> (SomeException -> Maybe KeyFileError)
-> (KeyFileError -> String)
-> Exception KeyFileError
SomeException -> Maybe KeyFileError
KeyFileError -> String
KeyFileError -> SomeException
forall e.
(Typeable e, Show e) =>
(e -> SomeException)
-> (SomeException -> Maybe e) -> (e -> String) -> Exception e
$ctoException :: KeyFileError -> SomeException
toException :: KeyFileError -> SomeException
$cfromException :: SomeException -> Maybe KeyFileError
fromException :: SomeException -> Maybe KeyFileError
$cdisplayException :: KeyFileError -> String
displayException :: KeyFileError -> String
Exception)

-- | Read a cardano signing key pair from a text envelope file, accepting both
-- normal ('PaymentKey') and extended ('PaymentExtendedKey') key formats.
-- Returns the verification key (always as 'VerificationKey PaymentKey') and
-- the signing key wrapped in 'CardanoSigningKey' inside 'Secret'.
readKeyPair :: FilePath -> IO (VerificationKey PaymentKey, Secret CardanoSigningKey)
readKeyPair :: String -> IO (VerificationKey PaymentKey, Secret CardanoSigningKey)
readKeyPair String
keyPath = do
  CardanoSigningKey
sk <- String -> IO CardanoSigningKey
readSigningKey String
keyPath
  (VerificationKey PaymentKey, Secret CardanoSigningKey)
-> IO (VerificationKey PaymentKey, Secret CardanoSigningKey)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (CardanoSigningKey -> VerificationKey PaymentKey
getCardanoPaymentVerificationKey CardanoSigningKey
sk, CardanoSigningKey -> Secret CardanoSigningKey
forall a. a -> Secret a
mkSecret CardanoSigningKey
sk)

-- | Read a 'CardanoSigningKey' from a text envelope file, accepting both
-- normal ('PaymentKey') and extended ('PaymentExtendedKey') key formats.
-- Extended keys are kept native (not converted) to preserve correct signing.
-- Throws 'KeyFileError' listing both accepted formats if neither matches.
readSigningKey :: FilePath -> IO CardanoSigningKey
readSigningKey :: String -> IO CardanoSigningKey
readSigningKey String
path =
  [FromSomeType HasTextEnvelope CardanoSigningKey]
-> File Any 'In
-> IO (Either (FileError TextEnvelopeError) CardanoSigningKey)
forall b content.
[FromSomeType HasTextEnvelope b]
-> File content 'In -> IO (Either (FileError TextEnvelopeError) b)
readFileTextEnvelopeAnyOf
    [ AsType (SigningKey PaymentKey)
-> (SigningKey PaymentKey -> CardanoSigningKey)
-> FromSomeType HasTextEnvelope CardanoSigningKey
forall (c :: * -> Constraint) a b.
c a =>
AsType a -> (a -> b) -> FromSomeType c b
FromSomeType (AsType PaymentKey -> AsType (SigningKey PaymentKey)
forall a. AsType a -> AsType (SigningKey a)
AsSigningKey AsType PaymentKey
AsPaymentKey) SigningKey PaymentKey -> CardanoSigningKey
CardanoSigningKey
    , AsType (SigningKey PaymentExtendedKey)
-> (SigningKey PaymentExtendedKey -> CardanoSigningKey)
-> FromSomeType HasTextEnvelope CardanoSigningKey
forall (c :: * -> Constraint) a b.
c a =>
AsType a -> (a -> b) -> FromSomeType c b
FromSomeType (AsType PaymentExtendedKey -> AsType (SigningKey PaymentExtendedKey)
forall a. AsType a -> AsType (SigningKey a)
AsSigningKey AsType PaymentExtendedKey
AsPaymentExtendedKey) SigningKey PaymentExtendedKey -> CardanoSigningKey
CardanoExtendedSigningKey
    ]
    (String -> File Any 'In
forall content (direction :: FileDirection).
String -> File content direction
File String
path)
    IO (Either (FileError TextEnvelopeError) CardanoSigningKey)
-> (Either (FileError TextEnvelopeError) CardanoSigningKey
    -> IO CardanoSigningKey)
-> IO CardanoSigningKey
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= (FileError TextEnvelopeError -> IO CardanoSigningKey)
-> (CardanoSigningKey -> IO CardanoSigningKey)
-> Either (FileError TextEnvelopeError) CardanoSigningKey
-> IO CardanoSigningKey
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either (KeyFileError -> IO CardanoSigningKey
forall e a. Exception e => e -> IO a
forall (m :: * -> *) e a. (MonadThrow m, Exception e) => e -> m a
throwIO (KeyFileError -> IO CardanoSigningKey)
-> (FileError TextEnvelopeError -> KeyFileError)
-> FileError TextEnvelopeError
-> IO CardanoSigningKey
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FileError TextEnvelopeError -> KeyFileError
KeyFileError) CardanoSigningKey -> IO CardanoSigningKey
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure

-- | Read a 'VerificationKey PaymentKey' from a text envelope file, accepting
-- both normal ('PaymentKey') and extended ('PaymentExtendedKey') key formats.
-- Extended keys are converted using 'castVerificationKey'.
-- Throws 'KeyFileError' listing both accepted formats if neither matches.
readVerificationKey :: FilePath -> IO (VerificationKey PaymentKey)
readVerificationKey :: String -> IO (VerificationKey PaymentKey)
readVerificationKey String
path =
  [FromSomeType HasTextEnvelope (VerificationKey PaymentKey)]
-> File Any 'In
-> IO
     (Either (FileError TextEnvelopeError) (VerificationKey PaymentKey))
forall b content.
[FromSomeType HasTextEnvelope b]
-> File content 'In -> IO (Either (FileError TextEnvelopeError) b)
readFileTextEnvelopeAnyOf
    [ AsType (VerificationKey PaymentKey)
-> (VerificationKey PaymentKey -> VerificationKey PaymentKey)
-> FromSomeType HasTextEnvelope (VerificationKey PaymentKey)
forall (c :: * -> Constraint) a b.
c a =>
AsType a -> (a -> b) -> FromSomeType c b
FromSomeType (AsType PaymentKey -> AsType (VerificationKey PaymentKey)
forall a. AsType a -> AsType (VerificationKey a)
AsVerificationKey AsType PaymentKey
AsPaymentKey) VerificationKey PaymentKey -> VerificationKey PaymentKey
forall a. a -> a
id
    , AsType (VerificationKey PaymentExtendedKey)
-> (VerificationKey PaymentExtendedKey
    -> VerificationKey PaymentKey)
-> FromSomeType HasTextEnvelope (VerificationKey PaymentKey)
forall (c :: * -> Constraint) a b.
c a =>
AsType a -> (a -> b) -> FromSomeType c b
FromSomeType (AsType PaymentExtendedKey
-> AsType (VerificationKey PaymentExtendedKey)
forall a. AsType a -> AsType (VerificationKey a)
AsVerificationKey AsType PaymentExtendedKey
AsPaymentExtendedKey) VerificationKey PaymentExtendedKey -> VerificationKey PaymentKey
forall keyroleA keyroleB.
CastVerificationKeyRole keyroleA keyroleB =>
VerificationKey keyroleA -> VerificationKey keyroleB
castVerificationKey
    ]
    (String -> File Any 'In
forall content (direction :: FileDirection).
String -> File content direction
File String
path)
    IO
  (Either (FileError TextEnvelopeError) (VerificationKey PaymentKey))
-> (Either
      (FileError TextEnvelopeError) (VerificationKey PaymentKey)
    -> IO (VerificationKey PaymentKey))
-> IO (VerificationKey PaymentKey)
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= (FileError TextEnvelopeError -> IO (VerificationKey PaymentKey))
-> (VerificationKey PaymentKey -> IO (VerificationKey PaymentKey))
-> Either
     (FileError TextEnvelopeError) (VerificationKey PaymentKey)
-> IO (VerificationKey PaymentKey)
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either (KeyFileError -> IO (VerificationKey PaymentKey)
forall e a. Exception e => e -> IO a
forall (m :: * -> *) e a. (MonadThrow m, Exception e) => e -> m a
throwIO (KeyFileError -> IO (VerificationKey PaymentKey))
-> (FileError TextEnvelopeError -> KeyFileError)
-> FileError TextEnvelopeError
-> IO (VerificationKey PaymentKey)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FileError TextEnvelopeError -> KeyFileError
KeyFileError) VerificationKey PaymentKey -> IO (VerificationKey PaymentKey)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure

-- XXX: Should accept a 'File' path
readFileTextEnvelopeThrow ::
  HasTextEnvelope a =>
  FilePath ->
  IO a
readFileTextEnvelopeThrow :: forall a. HasTextEnvelope a => String -> IO a
readFileTextEnvelopeThrow String
fileContents =
  (FileError TextEnvelopeError -> IO a)
-> (a -> IO a) -> Either (FileError TextEnvelopeError) a -> IO a
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either (String -> IO a
forall a. String -> IO a
forall (m :: * -> *) a. MonadFail m => String -> m a
fail (String -> IO a)
-> (FileError TextEnvelopeError -> String)
-> FileError TextEnvelopeError
-> IO a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FileError TextEnvelopeError -> String
forall b a. (Show a, IsString b) => a -> b
show) a -> IO a
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Either (FileError TextEnvelopeError) a -> IO a)
-> IO (Either (FileError TextEnvelopeError) a) -> IO a
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< File Any 'In -> IO (Either (FileError TextEnvelopeError) a)
forall a content.
HasTextEnvelope a =>
File content 'In -> IO (Either (FileError TextEnvelopeError) a)
readFileTextEnvelope (String -> File Any 'In
forall content (direction :: FileDirection).
String -> File content direction
File String
fileContents)

-- | Filter and return any non-ADA assets as 'Left' if they are present in the 'UTxO' value.
checkNonADAAssetsUTxO :: UTxO -> Either Value ()
checkNonADAAssetsUTxO :: UTxO -> Either Value ()
checkNonADAAssetsUTxO UTxO
utxo =
  let nonADA :: Value
nonADA = (AssetId -> Bool) -> Value -> Value
filterValue (AssetId -> AssetId -> Bool
forall a. Eq a => a -> a -> Bool
/= AssetId
AdaAssetId) (Value -> Value) -> Value -> Value
forall a b. (a -> b) -> a -> b
$ UTxO -> Value
forall era. UTxO era -> Value
totalValue UTxO
utxo
   in if Value
nonADA Value -> Value -> Bool
forall a. Eq a => a -> a -> Bool
== Value
forall a. Monoid a => a
mempty
        then () -> Either Value ()
forall a b. b -> Either a b
Right ()
        else Value -> Either Value ()
forall a b. a -> Either a b
Left Value
nonADA