Skip to content

Commit

Permalink
Make coverFee_ polymorphic on era
Browse files Browse the repository at this point in the history
This will not work as the plutus purpose does not unify Babbage and Conway.

We should be looking for a better, more generic way to update ex units
in a transaction.
  • Loading branch information
ch1bo committed Aug 6, 2024
1 parent 5098f51 commit ec5e666
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 78 deletions.
4 changes: 2 additions & 2 deletions hydra-cardano-api/src/Hydra/Cardano/Api/TxOut.hs
Original file line number Diff line number Diff line change
Expand Up @@ -144,12 +144,12 @@ isScriptTxOut script txOut =
-- * Type Conversions

-- | Convert a cardano-ledger 'TxOut' into a cardano-api 'TxOut'
fromLedgerTxOut :: Ledger.TxOut (ShelleyLedgerEra Era) -> TxOut ctx Era
fromLedgerTxOut :: IsShelleyBasedEra era => Ledger.TxOut (ShelleyLedgerEra era) -> TxOut ctx era
fromLedgerTxOut =
fromShelleyTxOut shelleyBasedEra

-- | Convert a cardano-api 'TxOut' into a cardano-ledger 'TxOut'
toLedgerTxOut :: TxOut CtxUTxO Era -> Ledger.TxOut (ShelleyLedgerEra Era)
toLedgerTxOut :: IsShelleyBasedEra era => TxOut CtxUTxO era -> Ledger.TxOut (ShelleyLedgerEra era)
toLedgerTxOut =
toShelleyTxOut shelleyBasedEra

Expand Down
139 changes: 63 additions & 76 deletions hydra-node/src/Hydra/Chain/Direct/Wallet.hs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@ module Hydra.Chain.Direct.Wallet where
import Hydra.Prelude

import Cardano.Api.UTxO (UTxO)
import Cardano.Api.UTxO qualified as UTxO
import Cardano.Crypto.Hash.Class
import Cardano.Ledger.Address qualified as Ledger
import Cardano.Ledger.Alonzo.Plutus.Context (ContextError)
import Cardano.Ledger.Alonzo.Plutus.Context (ContextError, EraPlutusContext)
import Cardano.Ledger.Alonzo.Scripts (
AlonzoEraScript (..),
AlonzoPlutusPurpose (AlonzoSpending),
Expand All @@ -20,17 +19,21 @@ import Cardano.Ledger.Alonzo.Scripts (
unAsIx,
)
import Cardano.Ledger.Alonzo.TxWits (
AlonzoTxWits (..),
Redeemers (..),
txdats,
txscripts,
datsTxWitsL,
)
import Cardano.Ledger.Alonzo.UTxO (AlonzoScriptsNeeded)
import Cardano.Ledger.Api (
AlonzoEraTx,
Babbage,
BabbageEraTxBody,
Conway,
Data,
EraCrypto,
PParams,
TransactionScriptFailure,
Tx,
bodyTxL,
coinTxOutL,
collateralInputsTxBodyL,
ensureMinCoinTxOut,
estimateMinFeeTx,
Expand All @@ -43,23 +46,25 @@ import Cardano.Ledger.Api (
referenceInputsTxBodyL,
reqSignerHashesTxBodyL,
scriptIntegrityHashTxBodyL,
scriptTxWitsL,
witsTxL,
)
import Cardano.Ledger.Babbage.Tx (body, getLanguageView, hashScriptIntegrity, wits)
import Cardano.Ledger.Api.UTxO (EraUTxO, ScriptsNeeded)
import Cardano.Ledger.Babbage.Tx (body, getLanguageView, hashScriptIntegrity)
import Cardano.Ledger.Babbage.Tx qualified as Babbage
import Cardano.Ledger.Babbage.TxBody qualified as Babbage
import Cardano.Ledger.Babbage.UTxO (getReferenceScripts)
import Cardano.Ledger.BaseTypes qualified as Ledger
import Cardano.Ledger.Coin (Coin (..))
import Cardano.Ledger.Core (isNativeScript, upgradeTx)
import Cardano.Ledger.Core (isNativeScript)
import Cardano.Ledger.Core qualified as Core
import Cardano.Ledger.Core qualified as Ledger
import Cardano.Ledger.Crypto (HASH, StandardCrypto)
import Cardano.Ledger.Hashes (EraIndependentTxBody)
import Cardano.Ledger.SafeHash qualified as SafeHash
import Cardano.Ledger.Shelley.API (unUTxO)
import Cardano.Ledger.Shelley.API qualified as Ledger
import Cardano.Ledger.Val (Val (..), invert)
import Cardano.Ledger.Val (invert)
import Cardano.Slotting.EpochInfo (EpochInfo)
import Cardano.Slotting.Time (SystemStart (..))
import Control.Arrow (left)
Expand All @@ -68,7 +73,6 @@ import Control.Lens (view, (%~), (.~), (^.))
import Data.List qualified as List
import Data.Map.Strict ((!))
import Data.Map.Strict qualified as Map
import Data.Maybe.Strict (StrictMaybe (..))
import Data.Ratio ((%))
import Data.Sequence.Strict ((|>))
import Data.Set qualified as Set
Expand All @@ -85,16 +89,12 @@ import Hydra.Cardano.Api (
VerificationKey,
fromLedgerTx,
fromLedgerTxIn,
fromLedgerTxOut,
fromLedgerUTxO,
getChainPoint,
makeShelleyAddress,
selectLovelace,
shelleyAddressInEra,
toLedgerAddr,
toLedgerTx,
toLedgerTxIn,
toLedgerTxOut,
toLedgerUTxO,
verificationKeyHash,
)
Expand Down Expand Up @@ -242,7 +242,7 @@ data ErrCoverFee
= ErrNotEnoughFunds ChangeError
| ErrNoFuelUTxOFound
| ErrUnknownInput {input :: TxIn}
| ErrScriptExecutionFailed {scriptFailure :: (PlutusPurpose AsIx LedgerEra, TransactionScriptFailure LedgerEra)}
| ErrScriptExecutionFailed {scriptFailure :: Text} -- FIXME: try to avoid Text
| ErrTranslationError (ContextError LedgerEra)
deriving stock (Show)

Expand All @@ -259,17 +259,27 @@ data SomePParams
--
-- XXX: All call sites of this function use cardano-api types
coverFee_ ::
Core.PParams LedgerEra ->
( EraCrypto era ~ StandardCrypto
, EraPlutusContext era
, AlonzoEraTx era
, ScriptsNeeded era ~ AlonzoScriptsNeeded era
, EraUTxO era
, BabbageEraTxBody era
, PlutusPurpose AsIx era ~ AlonzoPlutusPurpose AsIx era -- FIXME this is a problem as conway has different purposes
) =>
PParams era ->
SystemStart ->
EpochInfo (Either Text) ->
Map TxIn TxOut ->
Map TxIn TxOut ->
Babbage.AlonzoTx LedgerEra ->
Either ErrCoverFee (Babbage.AlonzoTx LedgerEra)
coverFee_ pparams systemStart epochInfo lookupUTxO walletUTxO partialTx@Babbage.AlonzoTx{body, wits} = do
Map TxIn (Ledger.TxOut era) ->
Map TxIn (Ledger.TxOut era) ->
Tx era ->
Either ErrCoverFee (Tx era)
coverFee_ pparams systemStart epochInfo lookupUTxO walletUTxO partialTx = do
let body = partialTx ^. bodyTxL
let wits = partialTx ^. witsTxL
(feeTxIn, feeTxOut) <- findUTxOToPayFees walletUTxO

let newInputs = view inputsTxBodyL body <> Set.singleton feeTxIn
let newInputs = body ^. inputsTxBodyL <> Set.singleton feeTxIn
resolvedInputs <- traverse resolveInput (toList newInputs)

-- Ensure we have at least the minimum amount of ada. NOTE: setMinCoinTxOut
Expand All @@ -281,24 +291,24 @@ coverFee_ pparams systemStart epochInfo lookupUTxO walletUTxO partialTx@Babbage.
estimatedScriptCosts <- estimateScriptsCost pparams systemStart epochInfo utxo partialTx
let adjustedRedeemers =
adjustRedeemers
(view inputsTxBodyL body)
(body ^. inputsTxBodyL)
newInputs
estimatedScriptCosts
(txrdmrs wits)
(wits ^. rdmrsTxWitsL)

-- Compute script integrity hash from adjusted redeemers
let referenceScripts = getReferenceScripts @LedgerEra (Ledger.UTxO utxo) (view referenceInputsTxBodyL body)
let referenceScripts = getReferenceScripts (Ledger.UTxO utxo) (body ^. referenceInputsTxBodyL)
langs =
[ getLanguageView pparams l
| (_hash, script) <- Map.toList $ Map.union (txscripts wits) referenceScripts
, (not . isNativeScript @LedgerEra) script
| (_hash, script) <- Map.toList $ Map.union (wits ^. scriptTxWitsL) referenceScripts
, (not . isNativeScript) script
, l <- maybeToList $ plutusScriptLanguage <$> toPlutusScript script
]
scriptIntegrityHash =
hashScriptIntegrity
(Set.fromList langs)
adjustedRedeemers
(txdats wits)
(wits ^. datsTxWitsL)

let
unbalancedBody =
Expand All @@ -314,15 +324,7 @@ coverFee_ pparams systemStart epochInfo lookupUTxO walletUTxO partialTx@Babbage.

-- Compute fee using a body with selected txOut to pay fees (= full change)
-- and an aditional witness (we will sign this tx later)
let fee = case pparams of
(pp :: Ledger.PParams Babbage) -> estimateMinFeeTx pp costingTx additionalWitnesses 0 0
(conwayPParams :: Ledger.PParams Conway) ->
let newTx = case upgradeTx costingTx of
-- TODO: Proper error
Left e -> error $ show e
Right tx -> tx
in estimateMinFeeTx conwayPParams newTx additionalWitnesses 0 0

let fee = estimateMinFeeTx pparams costingTx additionalWitnesses 0 0
costingTx =
unbalancedTx
& bodyTxL . outputsTxBodyL %~ (|> feeTxOut)
Expand All @@ -349,23 +351,12 @@ coverFee_ pparams systemStart epochInfo lookupUTxO walletUTxO partialTx@Babbage.
Just (i, o) ->
Right (i, o)

getAdaValue :: TxOut -> Coin
getAdaValue (Babbage.BabbageTxOut _ value _ _) =
coin value

resolveInput :: TxIn -> Either ErrCoverFee TxOut
resolveInput i = do
case Map.lookup i (lookupUTxO <> walletUTxO) of
Nothing -> Left $ ErrUnknownInput i
Just o -> Right o

mkChange ::
TxOut ->
[TxOut] ->
[TxOut] ->
Coin ->
Either ChangeError TxOut
mkChange (Babbage.BabbageTxOut addr _ datum _) resolvedInputs otherOutputs fee
mkChange feeTxOut resolvedInputs otherOutputs fee
-- FIXME: The delta between in and out must be greater than the min utxo value!
| totalIn <= totalOut =
Left $
Expand All @@ -374,21 +365,28 @@ coverFee_ pparams systemStart epochInfo lookupUTxO walletUTxO partialTx@Babbage.
, outputBalance = totalOut
}
| otherwise =
Right $ Babbage.BabbageTxOut addr (Ledger.inject changeOut) datum refScript
Right $ feeTxOut & coinTxOutL .~ changeOut
where
totalOut = foldMap getAdaValue otherOutputs <> fee
totalIn = foldMap getAdaValue resolvedInputs
totalOut = foldMap (view coinTxOutL) otherOutputs <> fee
totalIn = foldMap (view coinTxOutL) resolvedInputs
changeOut = totalIn <> invert totalOut
refScript = SNothing

adjustRedeemers :: Set TxIn -> Set TxIn -> Map (PlutusPurpose AsIx LedgerEra) ExUnits -> Redeemers LedgerEra -> Redeemers LedgerEra
adjustRedeemers ::
forall era.
(AlonzoEraScript era, PlutusPurpose AsIx era ~ AlonzoPlutusPurpose AsIx era) =>
Set TxIn ->
Set TxIn ->
Map (PlutusPurpose AsIx era) ExUnits ->
Redeemers era ->
Redeemers era
adjustRedeemers initialInputs finalInputs estimatedCosts (Redeemers initialRedeemers) =
Redeemers $ Map.fromList $ map adjustOne $ Map.toList initialRedeemers
where
sortedInputs = sort $ toList initialInputs
sortedFinalInputs = sort $ toList finalInputs
differences = List.findIndices (not . uncurry (==)) $ zip sortedInputs sortedFinalInputs

adjustOne :: (PlutusPurpose AsIx era, (Data era, ExUnits)) -> (PlutusPurpose AsIx era, (Data era, ExUnits))
adjustOne (ptr, (d, _exUnits)) =
case ptr of
AlonzoSpending idx
Expand All @@ -397,7 +395,7 @@ coverFee_ pparams systemStart epochInfo lookupUTxO walletUTxO partialTx@Babbage.
_ ->
(ptr, (d, executionUnitsFor ptr))

executionUnitsFor :: PlutusPurpose AsIx LedgerEra -> ExUnits
executionUnitsFor :: PlutusPurpose AsIx era -> ExUnits
executionUnitsFor ptr =
let ExUnits maxMem maxCpu = pparams ^. ppMaxTxExUnitsL
ExUnits totalMem totalCpu = foldMap identity estimatedCosts
Expand All @@ -406,42 +404,31 @@ coverFee_ pparams systemStart epochInfo lookupUTxO walletUTxO partialTx@Babbage.
(floor (maxMem * approxMem % totalMem))
(floor (maxCpu * approxCpu % totalCpu))

findLargestUTxO :: Map TxIn TxOut -> Maybe (TxIn, TxOut)
findLargestUTxO :: Ledger.EraTxOut era => Map TxIn (Ledger.TxOut era) -> Maybe (TxIn, Ledger.TxOut era)
findLargestUTxO utxo =
maxLovelaceUTxO apiUtxo
where
apiUtxo = UTxO.fromPairs $ bimap fromLedgerTxIn fromLedgerTxOut <$> Map.toList utxo

maxLovelaceUTxO =
fmap (bimap toLedgerTxIn toLedgerTxOut)
. listToMaybe
. List.sortOn (Down . selectLovelace . Api.txOutValue . snd)
. UTxO.pairs
listToMaybe
. List.sortOn (Down . view coinTxOutL . snd)
$ Map.toList utxo

-- | Estimate cost of script executions on the transaction. This is only an
-- estimates because the transaction isn't sealed at this point and adding new
-- elements to it like change outputs or script integrity hash may increase that
-- cost a little.
estimateScriptsCost ::
(AlonzoEraTx era, EraPlutusContext era, ScriptsNeeded era ~ AlonzoScriptsNeeded era, EraCrypto era ~ StandardCrypto, EraUTxO era) =>
-- | Protocol parameters
Core.PParams LedgerEra ->
Core.PParams era ->
-- | Start of the blockchain, for converting slots to UTC times
SystemStart ->
-- | Information about epoch sizes, for converting slots to UTC times
EpochInfo (Either Text) ->
-- | A UTXO needed to resolve inputs
Map TxIn TxOut ->
Map TxIn (Ledger.TxOut era) ->
-- | The pre-constructed transaction
Babbage.AlonzoTx LedgerEra ->
Either ErrCoverFee (Map (PlutusPurpose AsIx LedgerEra) ExUnits)
Tx era ->
Either ErrCoverFee (Map (PlutusPurpose AsIx era) ExUnits)
estimateScriptsCost pparams systemStart epochInfo utxo tx = do
Map.traverseWithKey (\ptr -> left $ ErrScriptExecutionFailed . (ptr,)) result
where
result ::
Map
(AlonzoPlutusPurpose AsIx LedgerEra)
(Either (TransactionScriptFailure Babbage) ExUnits)
result =
Map.traverseWithKey (\ptr -> left $ ErrScriptExecutionFailed . show) $
evalTxExUnits
pparams
tx
Expand Down

0 comments on commit ec5e666

Please sign in to comment.