Skip to content

Commit

Permalink
Merge #4446
Browse files Browse the repository at this point in the history
4446: Refactor transaction build command r=Jimbo4350 a=Jimbo4350



Co-authored-by: Jordan Millar <jordan.millar@iohk.io>
  • Loading branch information
iohk-bors[bot] and Jimbo4350 authored Sep 23, 2022
2 parents 420c4eb + 0ec700f commit 46fe867
Show file tree
Hide file tree
Showing 42 changed files with 631 additions and 377 deletions.
4 changes: 4 additions & 0 deletions cardano-api/cardano-api.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ library
Cardano.Api.Address
Cardano.Api.Block
Cardano.Api.Certificate
Cardano.Api.Convenience.Constraints
Cardano.Api.Convenience.Construction
Cardano.Api.Convenience.Query
Cardano.Api.Environment
Cardano.Api.Eras
Cardano.Api.Error
Cardano.Api.Fees
Expand Down
34 changes: 34 additions & 0 deletions cardano-api/src/Cardano/Api.hs
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,7 @@ module Cardano.Api (
QueryUTxOFilter(..),
UTxO(..),
queryNodeLocalState,
executeQueryCardanoMode,

-- *** Local tx monitoring
LocalTxMonitorClient(..),
Expand All @@ -597,6 +598,7 @@ module Cardano.Api (
getProgress,

-- *** Common queries
determineEra,
getLocalChainTip,

-- * Node operation
Expand Down Expand Up @@ -654,6 +656,12 @@ module Cardano.Api (
SlotsToEpochEnd(..),
slotToEpoch,

-- * Node socket related
EnvSocketError(..),
SocketPath(..),
readEnvSocketPath,
renderEnvSocketError,

NodeToClientVersion(..),

-- ** Monadic queries
Expand All @@ -671,11 +679,37 @@ module Cardano.Api (
-- ** Cast functions
EraCast (..),
EraCastError (..),

-- * Convenience functions

-- ** Transaction construction
constructBalancedTx,

-- ** Queries
QueryConvenienceError(..),
queryStateForBalancedTx,
renderQueryConvenienceError,

-- ** Constraint satisfaction functions
getIsCardanoEraConstraint,

-- ** Misc
NotScriptLockedTxInsError(..),
TxInsExistError(..),
renderNotScriptLockedTxInsError,
renderTxInsExistError,
txInsExistInUTxO,
notScriptLockedTxIns,
textShow,
) where

import Cardano.Api.Address
import Cardano.Api.Block
import Cardano.Api.Certificate
import Cardano.Api.Convenience.Constraints
import Cardano.Api.Convenience.Construction
import Cardano.Api.Convenience.Query
import Cardano.Api.Environment
import Cardano.Api.EraCast
import Cardano.Api.Eras
import Cardano.Api.Error
Expand Down
2 changes: 1 addition & 1 deletion cardano-api/src/Cardano/Api/Block.hs
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,8 @@ import qualified Cardano.Ledger.Block as Ledger
import qualified Cardano.Ledger.Era as Ledger

import Cardano.Api.Eras
import Cardano.Api.HasTypeProxy
import Cardano.Api.Hash
import Cardano.Api.HasTypeProxy
import Cardano.Api.KeysShelley
import Cardano.Api.Modes
import Cardano.Api.SerialiseRaw
Expand Down
19 changes: 19 additions & 0 deletions cardano-api/src/Cardano/Api/Convenience/Constraints.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{-# LANGUAGE GADTs #-}
{-# LANGUAGE RankNTypes #-}

-- | Constraint satisfaction functions. These are used to avoid propagating constraints.
--
module Cardano.Api.Convenience.Constraints (
getIsCardanoEraConstraint
) where


import Cardano.Api.Eras

getIsCardanoEraConstraint :: CardanoEra era -> (IsCardanoEra era => a) -> a
getIsCardanoEraConstraint ByronEra f = f
getIsCardanoEraConstraint ShelleyEra f = f
getIsCardanoEraConstraint AllegraEra f = f
getIsCardanoEraConstraint MaryEra f = f
getIsCardanoEraConstraint AlonzoEra f = f
getIsCardanoEraConstraint BabbageEra f = f
102 changes: 102 additions & 0 deletions cardano-api/src/Cardano/Api/Convenience/Construction.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
-- | Convenience transaction construction functions
--
module Cardano.Api.Convenience.Construction (
constructBalancedTx,

-- * Misc
TxInsExistError(..),
NotScriptLockedTxInsError(..),
notScriptLockedTxIns,
renderNotScriptLockedTxInsError,
renderTxInsExistError,
txInsExistInUTxO,

) where

import Prelude

import qualified Data.List as List
import qualified Data.Map.Strict as Map
import Data.Set (Set)
import qualified Data.Set as Set
import Data.Text (Text)
import qualified Data.Text as Text

import Cardano.Api.Address
import Cardano.Api.Certificate
import Cardano.Api.Eras
import Cardano.Api.Fees
import Cardano.Api.IPC
import Cardano.Api.Modes
import Cardano.Api.ProtocolParameters
import Cardano.Api.Query
import Cardano.Api.Tx
import Cardano.Api.TxBody
import Cardano.Api.Utils

-- | Construct a balanced transaction.
-- See Cardano.Api.Convenience.Query.queryStateForBalancedTx for a
-- convenient way of querying the node to get the required arguements
-- for constructBalancedTx.
constructBalancedTx
:: IsShelleyBasedEra era
=> EraInMode era CardanoMode
-> TxBodyContent BuildTx era
-> AddressInEra era -- ^ Change address
-> Maybe Word -- ^ Override key witnesses
-> UTxO era -- ^ Just the transaction inputs, not the entire 'UTxO'.
-> ProtocolParameters
-> EraHistory CardanoMode
-> SystemStart
-> Set PoolId -- ^ The set of registered stake pools
-> [ShelleyWitnessSigningKey]
-> Either TxBodyErrorAutoBalance (Tx era)
constructBalancedTx eInMode txbodcontent changeAddr mOverrideWits utxo pparams
eraHistory systemStart stakePools shelleyWitSigningKeys = do
BalancedTxBody txbody _txBalanceOutput _fee
<- makeTransactionBodyAutoBalance
eInMode systemStart eraHistory
pparams stakePools utxo txbodcontent
changeAddr mOverrideWits

let keyWits = map (makeShelleyKeyWitness txbody) shelleyWitSigningKeys
return $ makeSignedTransaction keyWits txbody

data TxInsExistError
= TxInsDoNotExist [TxIn]
| EmptyUTxO

renderTxInsExistError :: TxInsExistError -> Text
renderTxInsExistError EmptyUTxO =
"The UTxO is empty"
renderTxInsExistError (TxInsDoNotExist txins) =
"The following tx input(s) were not present in the UTxO: " <>
Text.singleton '\n' <>
Text.intercalate (Text.singleton '\n') (map renderTxIn txins)

txInsExistInUTxO :: [TxIn] -> UTxO era -> Either TxInsExistError ()
txInsExistInUTxO ins (UTxO utxo)
| null utxo = Left EmptyUTxO
| otherwise = do
let utxoIns = Map.keys utxo
occursInUtxo = [ txin | txin <- ins, txin `elem` utxoIns ]
if length occursInUtxo == length ins
then return ()
else Left . TxInsDoNotExist $ ins List.\\ occursInUtxo

newtype NotScriptLockedTxInsError = NotScriptLockedTxIns [TxIn]

renderNotScriptLockedTxInsError :: NotScriptLockedTxInsError -> Text
renderNotScriptLockedTxInsError (NotScriptLockedTxIns txins) =
"The followings tx inputs are not script locked: " <> textShow (map renderTxIn txins)

notScriptLockedTxIns :: [TxIn] -> UTxO era -> Either NotScriptLockedTxInsError ()
notScriptLockedTxIns collTxIns (UTxO utxo) = do
let onlyCollateralUTxOs = Map.restrictKeys utxo $ Set.fromList collTxIns
scriptLockedTxIns =
filter (\(_, TxOut aInEra _ _ _) -> not $ isKeyAddress aInEra ) $ Map.assocs onlyCollateralUTxOs
if null scriptLockedTxIns
then return ()
else Left . NotScriptLockedTxIns $ map fst scriptLockedTxIns


172 changes: 172 additions & 0 deletions cardano-api/src/Cardano/Api/Convenience/Query.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}

-- | Convenience query functions
--
module Cardano.Api.Convenience.Query (
QueryConvenienceError(..),
determineEra,
-- * Simplest query related
executeQueryCardanoMode,

queryStateForBalancedTx,
renderQueryConvenienceError,
) where

import Prelude

import Data.Bifunctor (first)
import Data.Set (Set)
import qualified Data.Set as Set
import Data.Text (Text)

import Ouroboros.Consensus.HardFork.Combinator.AcrossEras (EraMismatch (..))

import Cardano.Api.Certificate
import Cardano.Api.Convenience.Constraints
import Cardano.Api.Environment
import Cardano.Api.Eras
import Cardano.Api.IPC
import Cardano.Api.Modes
import Cardano.Api.NetworkId
import Cardano.Api.ProtocolParameters
import Cardano.Api.Query
import Cardano.Api.TxBody
import Cardano.Api.Utils

data QueryConvenienceError
= AcqFailure AcquiringFailure
| SockErr EnvSocketError
| QueryEraMismatch EraMismatch
| ByronEraNotSupported
| EraConsensusModeMismatch !AnyConsensusMode !AnyCardanoEra

renderQueryConvenienceError :: QueryConvenienceError -> Text
renderQueryConvenienceError (AcqFailure e) =
"Acquiring failure: " <> textShow e
renderQueryConvenienceError (SockErr e) =
renderEnvSocketError e
renderQueryConvenienceError (QueryEraMismatch (EraMismatch ledgerEraName' otherEraName')) =
"The era of the node and the tx do not match. " <>
"The node is running in the " <> ledgerEraName' <>
" era, but the transaction is for the " <> otherEraName' <> " era."
renderQueryConvenienceError ByronEraNotSupported =
"Byron era not supported"
renderQueryConvenienceError (EraConsensusModeMismatch cMode anyCEra) =
"Consensus mode and era mismatch. Consensus mode: " <> textShow cMode <>
" Era: " <> textShow anyCEra

-- | A convenience function to query the relevant information, from
-- the local node, for Cardano.Api.Convenience.Construction.constructBalancedTx
queryStateForBalancedTx
:: CardanoEra era
-> NetworkId
-> [TxIn]
-> IO (Either QueryConvenienceError (UTxO era, ProtocolParameters, EraHistory CardanoMode, SystemStart, Set PoolId))
queryStateForBalancedTx era networkId allTxIns = do
eSocketPath <- first SockErr <$> readEnvSocketPath
case eSocketPath of
Left e -> return $ Left e
Right (SocketPath sockPath) -> do
let cModeParams = CardanoModeParams $ EpochSlots 21600
localNodeConnInfo = LocalNodeConnectInfo
cModeParams
networkId
sockPath
eSbe <- return . getSbe $ cardanoEraStyle era
case eSbe of
Left e -> return $ Left e
Right qSbe -> do
case toEraInMode era CardanoMode of
Just qeInMode -> do

-- Queries
let utxoQuery = QueryInEra qeInMode $ QueryInShelleyBasedEra qSbe
$ QueryUTxO (QueryUTxOByTxIn (Set.fromList allTxIns))
pparamsQuery = QueryInEra qeInMode
$ QueryInShelleyBasedEra qSbe QueryProtocolParameters
eraHistoryQuery = QueryEraHistory CardanoModeIsMultiEra
systemStartQuery = QuerySystemStart
stakePoolsQuery = QueryInEra qeInMode . QueryInShelleyBasedEra qSbe $ QueryStakePools

-- Query execution
eUtxo <- executeQueryCardanoMode era networkId utxoQuery
ePparams <- executeQueryCardanoMode era networkId pparamsQuery
eEraHistory <- queryNodeLocalState localNodeConnInfo Nothing eraHistoryQuery
eSystemStart <- queryNodeLocalState localNodeConnInfo Nothing systemStartQuery
eStakePools <- executeQueryCardanoMode era networkId stakePoolsQuery
return $ do
utxo <- eUtxo
pparams <- ePparams
eraHistory <- first AcqFailure eEraHistory
systemStart <- first AcqFailure eSystemStart
stakePools <- eStakePools
Right (utxo, pparams, eraHistory, systemStart, stakePools)
Nothing -> return $ Left $ EraConsensusModeMismatch
(AnyConsensusMode CardanoMode)
(getIsCardanoEraConstraint era $ AnyCardanoEra era)

-- | Query the node to determine which era it is in.
determineEra
:: ConsensusModeParams mode
-> LocalNodeConnectInfo mode
-> IO (Either AcquiringFailure AnyCardanoEra)
determineEra cModeParams localNodeConnInfo =
case consensusModeOnly cModeParams of
ByronMode -> return . Right $ AnyCardanoEra ByronEra
ShelleyMode -> return . Right $ AnyCardanoEra ShelleyEra
CardanoMode ->
queryNodeLocalState localNodeConnInfo Nothing
$ QueryCurrentEra CardanoModeIsMultiEra

getSbe :: CardanoEraStyle era -> Either QueryConvenienceError (ShelleyBasedEra era)
getSbe LegacyByronEra = Left ByronEraNotSupported
getSbe (ShelleyBasedEra sbe) = return sbe

-- | Execute a query against the local node. The local
-- node must be in CardanoMode.
executeQueryCardanoMode
:: CardanoEra era
-> NetworkId
-> QueryInMode CardanoMode (Either EraMismatch result)
-> IO (Either QueryConvenienceError result)
executeQueryCardanoMode era nid q = do
eSocketPath <- first SockErr <$> readEnvSocketPath
case eSocketPath of
Left e -> return $ Left e
Right (SocketPath sockPath) -> do
let localConnectInfo =
LocalNodeConnectInfo
{ localConsensusModeParams = CardanoModeParams (EpochSlots 21600)
, localNodeNetworkId = nid
, localNodeSocketPath = sockPath
}
executeQueryAnyMode era localConnectInfo q

-- | Execute a query against the local node in any mode.
executeQueryAnyMode
:: forall result era mode. CardanoEra era
-> LocalNodeConnectInfo mode
-> QueryInMode mode (Either EraMismatch result)
-> IO (Either QueryConvenienceError result)
executeQueryAnyMode era localNodeConnInfo q = do
let cMode = consensusModeOnly $ localConsensusModeParams localNodeConnInfo
case toEraInMode era cMode of
Just eraInMode ->
case eraInMode of
ByronEraInByronMode -> return $ Left ByronEraNotSupported
_ -> execQuery
Nothing -> return $ Left $ EraConsensusModeMismatch
(AnyConsensusMode CardanoMode)
(getIsCardanoEraConstraint era $ AnyCardanoEra era)
where
execQuery :: IO (Either QueryConvenienceError result)
execQuery = collapse <$> queryNodeLocalState localNodeConnInfo Nothing q

collapse
:: Either AcquiringFailure (Either EraMismatch a)
-> Either QueryConvenienceError a
collapse res = do
innerRes <- first AcqFailure res
first QueryEraMismatch innerRes

Loading

0 comments on commit 46fe867

Please sign in to comment.