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
2 changes: 2 additions & 0 deletions cardano-api/cardano-api.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,9 @@ library
Cardano.Api.Experimental.Tx.Internal.Certificate.Type
Cardano.Api.Experimental.Tx.Internal.Compatible
Cardano.Api.Experimental.Tx.Internal.Fee
Cardano.Api.Experimental.Tx.Internal.Serialise
Cardano.Api.Experimental.Tx.Internal.TxScriptWitnessRequirements
Cardano.Api.Experimental.Tx.Internal.Type
Cardano.Api.Genesis.Internal
Cardano.Api.Genesis.Internal.Parameters
Cardano.Api.Governance.Internal.Action.ProposalProcedure
Expand Down
85 changes: 6 additions & 79 deletions cardano-api/src/Cardano/Api/Experimental/Tx.hs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,8 @@
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}

Expand Down Expand Up @@ -140,6 +137,10 @@ module Cardano.Api.Experimental.Tx
, getTxScriptWitnessesRequirements
, obtainMonoidConstraint

-- ** Serialisation
, writeTxFileTextEnvelope
, writeTxFileTextEnvelopeCanonical

-- ** Internal functions
, extractExecutionUnits
, getTxScriptWitnessRequirements
Expand All @@ -153,67 +154,26 @@ import Cardano.Api.Era.Internal.Feature
import Cardano.Api.Experimental.Era
import Cardano.Api.Experimental.Tx.Internal.AnyWitness
import Cardano.Api.Experimental.Tx.Internal.Body
import Cardano.Api.Experimental.Tx.Internal.Serialise
import Cardano.Api.Experimental.Tx.Internal.TxScriptWitnessRequirements
import Cardano.Api.HasTypeProxy (HasTypeProxy (..), Proxy, asType)
import Cardano.Api.Experimental.Tx.Internal.Type
import Cardano.Api.Ledger.Internal.Reexport (StrictMaybe (..), maybeToStrictMaybe)
import Cardano.Api.Ledger.Internal.Reexport qualified as L
import Cardano.Api.Pretty (docToString, pretty)
import Cardano.Api.Serialise.Raw
( SerialiseAsRawBytes (..)
, SerialiseAsRawBytesError (SerialiseAsRawBytesError)
)
import Cardano.Api.Tx.Internal.Body
import Cardano.Api.Tx.Internal.Sign

import Cardano.Crypto.Hash qualified as Hash
import Cardano.Ledger.Alonzo.TxBody qualified as L
import Cardano.Ledger.Api qualified as L
import Cardano.Ledger.Binary qualified as Ledger
import Cardano.Ledger.Core qualified as Ledger
import Cardano.Ledger.Hashes qualified as L hiding (Hash)

import Control.Exception (displayException)
import Data.Bifunctor (bimap)
import Data.ByteString.Lazy (fromStrict)
import Data.Set qualified as Set
import GHC.Exts (IsList (..))
import GHC.Stack
import Lens.Micro

-- | A transaction that can contain everything
-- except key witnesses.
data UnsignedTx era
= L.EraTx (LedgerEra era) => UnsignedTx (Ledger.Tx (LedgerEra era))

instance HasTypeProxy era => HasTypeProxy (UnsignedTx era) where
data AsType (UnsignedTx era) = AsUnsignedTx (AsType era)
proxyToAsType :: Proxy (UnsignedTx era) -> AsType (UnsignedTx era)
proxyToAsType _ = AsUnsignedTx (asType @era)

instance
( HasTypeProxy era
, L.EraTx (LedgerEra era)
)
=> SerialiseAsRawBytes (UnsignedTx era)
where
serialiseToRawBytes (UnsignedTx tx) =
Ledger.serialize' (Ledger.eraProtVerHigh @(LedgerEra era)) tx
deserialiseFromRawBytes _ =
bimap wrapError UnsignedTx
. Ledger.decodeFullAnnotator
(Ledger.eraProtVerHigh @(LedgerEra era))
"UnsignedTx"
Ledger.decCBOR
. fromStrict
where
wrapError
:: Ledger.DecoderError -> SerialiseAsRawBytesError
wrapError = SerialiseAsRawBytesError . displayException

deriving instance Eq (UnsignedTx era)

deriving instance Show (UnsignedTx era)

newtype UnsignedTxError
= UnsignedTxError TxBodyError

Expand Down Expand Up @@ -341,39 +301,6 @@ makeKeyWitness era (UnsignedTx unsignedTx) wsk =
signature = makeShelleySignature txhash sk
in L.WitVKey vk signature

-- | A transaction that has been witnesssed
data SignedTx era
= L.EraTx (LedgerEra era) => SignedTx (Ledger.Tx (LedgerEra era))

deriving instance Eq (SignedTx era)

deriving instance Show (SignedTx era)

instance HasTypeProxy era => HasTypeProxy (SignedTx era) where
data AsType (SignedTx era) = AsSignedTx (AsType era)
proxyToAsType :: Proxy (SignedTx era) -> AsType (SignedTx era)
proxyToAsType _ = AsSignedTx (asType @era)

instance
( HasTypeProxy era
, L.EraTx (LedgerEra era)
)
=> SerialiseAsRawBytes (SignedTx era)
where
serialiseToRawBytes (SignedTx tx) =
Ledger.serialize' (Ledger.eraProtVerHigh @(LedgerEra era)) tx
deserialiseFromRawBytes _ =
bimap wrapError SignedTx
. Ledger.decodeFullAnnotator
(Ledger.eraProtVerHigh @(LedgerEra era))
"SignedTx"
Ledger.decCBOR
. fromStrict
where
wrapError
:: Ledger.DecoderError -> SerialiseAsRawBytesError
wrapError = SerialiseAsRawBytesError . displayException

signTx
:: Era era
-> [L.BootstrapWitness]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}

module Cardano.Api.Experimental.Tx.Internal.Serialise
( writeTxFileTextEnvelope
, writeTxFileTextEnvelopeCanonical
)
where

import Cardano.Api.Error
import Cardano.Api.Experimental.Era
import Cardano.Api.Experimental.Tx.Internal.Type
import Cardano.Api.IO
import Cardano.Api.Serialise.Cbor.Canonical
import Cardano.Api.Serialise.TextEnvelope.Internal

writeTxFileTextEnvelope
:: IsEra era
=> File content Out
-> SignedTx era
-> IO (Either (FileError ()) ())
writeTxFileTextEnvelope path =
writeLazyByteStringFile path
. serialiseTextEnvelope
. serialiseTxToTextEnvelope

serialiseTxToTextEnvelope :: forall era. IsEra era => SignedTx era -> TextEnvelope
serialiseTxToTextEnvelope tx' =
obtainCommonConstraints (useEra @era) $
serialiseToTextEnvelope (Just "Ledger Cddl Format") tx'

-- | Write transaction in the text envelope format. The CBOR will be in canonical format according
-- to RFC 7049. It is also a requirement of CIP-21, which is not fully implemented.
--
-- 1. RFC 7049: https://datatracker.ietf.org/doc/html/rfc7049#section-3.9
-- 2. CIP-21: https://github.com/cardano-foundation/CIPs/blob/master/CIP-0021/README.md#canonical-cbor-serialization-format
writeTxFileTextEnvelopeCanonical
:: IsEra era
=> File content Out
-> SignedTx era
-> IO (Either (FileError ()) ())
writeTxFileTextEnvelopeCanonical path =
writeLazyByteStringFile path
. serialiseTextEnvelope
. canonicaliseTextEnvelopeCbor
. serialiseTxToTextEnvelope
where
canonicaliseTextEnvelopeCbor :: TextEnvelope -> TextEnvelope
canonicaliseTextEnvelopeCbor te = do
let canonicalisedTxBs =
either
( \err ->
error $
"writeTxFileTextEnvelopeCanonical: Impossible - deserialisation of just serialised bytes failed "
<> show err
)
id
. canonicaliseCborBs
$ teRawCBOR te
Comment on lines +53 to +61
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use: first or mapLeft.

te{teRawCBOR = canonicalisedTxBs}
121 changes: 121 additions & 0 deletions cardano-api/src/Cardano/Api/Experimental/Tx/Internal/Type.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}

module Cardano.Api.Experimental.Tx.Internal.Type
( SignedTx (..)
, UnsignedTx (..)
)
where

import Cardano.Api.Experimental.Era
import Cardano.Api.HasTypeProxy (HasTypeProxy (..), Proxy, asType)
import Cardano.Api.Ledger.Internal.Reexport qualified as L
import Cardano.Api.Serialise.Cbor
import Cardano.Api.Serialise.Raw
( SerialiseAsRawBytes (..)
, SerialiseAsRawBytesError (SerialiseAsRawBytesError)
)
import Cardano.Api.Serialise.TextEnvelope.Internal

import Cardano.Ledger.Binary qualified as Ledger
import Cardano.Ledger.Core qualified as Ledger

import Control.Exception (displayException)
import Data.Bifunctor (bimap)
import Data.ByteString.Lazy (fromStrict)

-- | A transaction that has been witnesssed
data SignedTx era
= L.EraTx (LedgerEra era) => SignedTx (Ledger.Tx (LedgerEra era))

deriving instance Eq (SignedTx era)

deriving instance Show (SignedTx era)

instance
( HasTypeProxy era
, L.EraTx (LedgerEra era)
)
=> SerialiseAsCBOR (SignedTx era)
where
serialiseToCBOR (SignedTx tx) =
Ledger.serialize' (Ledger.eraProtVerHigh @(LedgerEra era)) tx
deserialiseFromCBOR _ =
fmap SignedTx
. Ledger.decodeFullAnnotator
(Ledger.eraProtVerHigh @(LedgerEra era))
"UnsignedTx"
Ledger.decCBOR
. fromStrict

instance (L.EraTx (LedgerEra era), HasTypeProxy era) => HasTextEnvelope (SignedTx era) where
textEnvelopeType _ = "Tx"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may break compatibility with the old API text envelope type:

instance IsShelleyBasedEra era => HasTextEnvelope (Tx era) where

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, do you have in mind how will deserialising work without knowing the era? I guess because we have only two eras is not too problematic, but we may need two separate functions (one for each era).


instance HasTypeProxy era => HasTypeProxy (SignedTx era) where
data AsType (SignedTx era) = AsSignedTx (AsType era)
proxyToAsType :: Proxy (SignedTx era) -> AsType (SignedTx era)
proxyToAsType _ = AsSignedTx (asType @era)

instance
( HasTypeProxy era
, L.EraTx (LedgerEra era)
)
=> SerialiseAsRawBytes (SignedTx era)
where
serialiseToRawBytes (SignedTx tx) =
Ledger.serialize' (Ledger.eraProtVerHigh @(LedgerEra era)) tx
deserialiseFromRawBytes _ =
bimap wrapError SignedTx
. Ledger.decodeFullAnnotator
(Ledger.eraProtVerHigh @(LedgerEra era))
"SignedTx"
Ledger.decCBOR
. fromStrict
where
wrapError
:: Ledger.DecoderError -> SerialiseAsRawBytesError
wrapError = SerialiseAsRawBytesError . displayException
Comment on lines +75 to +87
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can probably be implemented in terms of SerialiseAsCBOR now, or the other way around, because they are almost identical code except for the error handling


-- | A transaction that can contain everything
-- except key witnesses.
data UnsignedTx era
= L.EraTx (LedgerEra era) => UnsignedTx (Ledger.Tx (LedgerEra era))

instance HasTypeProxy era => HasTypeProxy (UnsignedTx era) where
data AsType (UnsignedTx era) = AsUnsignedTx (AsType era)
proxyToAsType :: Proxy (UnsignedTx era) -> AsType (UnsignedTx era)
proxyToAsType _ = AsUnsignedTx (asType @era)

instance
( HasTypeProxy era
, L.EraTx (LedgerEra era)
)
=> SerialiseAsRawBytes (UnsignedTx era)
where
serialiseToRawBytes (UnsignedTx tx) =
Ledger.serialize' (Ledger.eraProtVerHigh @(LedgerEra era)) tx
deserialiseFromRawBytes _ =
bimap wrapError UnsignedTx
. Ledger.decodeFullAnnotator
(Ledger.eraProtVerHigh @(LedgerEra era))
"UnsignedTx"
Ledger.decCBOR
. fromStrict
where
wrapError
:: Ledger.DecoderError -> SerialiseAsRawBytesError
wrapError = SerialiseAsRawBytesError . displayException

deriving instance Eq (UnsignedTx era)

deriving instance Show (UnsignedTx era)
Loading