Skip to content

Commit fee5f11

Browse files
committed
makeUnsignedTx: error when Plutus scripts present without protocol params
1 parent 1c39fba commit fee5f11

7 files changed

Lines changed: 156 additions & 91 deletions

File tree

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ module Cardano.Api.Experimental
1212
-- ** Transaction-related
1313
UnsignedTx (..)
1414
, SignedTx (..)
15+
, MakeUnsignedTxError (..)
1516
, makeUnsignedTx
1617
, makeKeyWitness
1718
, signTx

cardano-api/src/Cardano/Api/Experimental/Tx.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ module Cardano.Api.Experimental.Tx
117117
-- * Contents
118118
UnsignedTx (..)
119119
, SignedTx (..)
120+
, MakeUnsignedTxError (..)
120121
, makeUnsignedTx
121122
, makeKeyWitness
122123
, signTx

cardano-api/src/Cardano/Api/Experimental/Tx/Internal/BodyContent/New.hs

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ module Cardano.Api.Experimental.Tx.Internal.BodyContent.New
2222
, TxWithdrawals (..)
2323
, TxBodyContent (..)
2424
, Datum (..)
25+
, MakeUnsignedTxError (..)
2526
, defaultTxBodyContent
2627
, extractDatumsAndHashes
2728
, getDatums
@@ -145,11 +146,27 @@ import Data.Set qualified as Set
145146
import GHC.Exts (IsList (..))
146147
import Lens.Micro
147148

149+
-- | Error that can occur when constructing an unsigned transaction.
150+
data MakeUnsignedTxError
151+
= -- | Plutus scripts are present in the transaction but no protocol
152+
-- parameters were provided. Protocol parameters are required to
153+
-- compute the script integrity hash (script_data_hash).
154+
MakeUnsignedTxMissingProtocolParams
155+
deriving (Eq, Show)
156+
157+
instance Error MakeUnsignedTxError where
158+
prettyError MakeUnsignedTxMissingProtocolParams =
159+
mconcat
160+
[ "Transaction uses Plutus scripts but no protocol parameters were provided. "
161+
, "Protocol parameters are required to compute the script integrity hash "
162+
, "(script_data_hash) from the cost models."
163+
]
164+
148165
makeUnsignedTx
149166
:: forall era
150167
. Era era
151168
-> TxBodyContent (LedgerEra era)
152-
-> UnsignedTx (LedgerEra era)
169+
-> Either MakeUnsignedTxError (UnsignedTx (LedgerEra era))
153170
makeUnsignedTx DijkstraEra _ = error "makeUnsignedTx: Dijkstra era not supported yet"
154171
makeUnsignedTx era@ConwayEra bc = obtainCommonConstraints era $ do
155172
let TxScriptWitnessRequirements languages scripts datums redeemers = collectTxBodyScriptWitnessRequirements bc
@@ -172,12 +189,13 @@ makeUnsignedTx era@ConwayEra bc = obtainCommonConstraints era $ do
172189
totCollateral = unTxTotalCollateral <$> txTotalCollateral bc
173190
txAuxData = toAuxiliaryData (txMetadata bc) (txAuxScripts bc)
174191
scriptValidity = scriptValidityToIsValid $ txScriptValidity bc
175-
scriptIntegrityHash =
176-
convPParamsToScriptIntegrityHash
177-
protocolParameters
178-
redeemers
179-
datums
180-
languages
192+
193+
scriptIntegrityHash <-
194+
convPParamsToScriptIntegrityHash
195+
protocolParameters
196+
redeemers
197+
datums
198+
languages
181199

182200
let setMint = convMintValue apiMintValue
183201
setReqSignerHashes = convExtraKeyWitnesses apiExtraKeyWitnesses
@@ -210,11 +228,12 @@ makeUnsignedTx era@ConwayEra bc = obtainCommonConstraints era $ do
210228
& L.rdmrsTxWitsL .~ redeemers
211229

212230
let eraSpecificTxBody = eraSpecificLedgerTxBody era ledgerTxBody bc
213-
UnsignedTx $
214-
L.mkBasicTx eraSpecificTxBody
215-
& L.witsTxL .~ scriptWitnesses
216-
& L.auxDataTxL .~ L.maybeToStrictMaybe (toAuxiliaryData (txMetadata bc) (txAuxScripts bc))
217-
& L.isValidTxL .~ scriptValidity
231+
Right $
232+
UnsignedTx $
233+
L.mkBasicTx eraSpecificTxBody
234+
& L.witsTxL .~ scriptWitnesses
235+
& L.auxDataTxL .~ L.maybeToStrictMaybe (toAuxiliaryData (txMetadata bc) (txAuxScripts bc))
236+
& L.isValidTxL .~ scriptValidity
218237

219238
convTxIns :: [(TxIn, AnyWitness era)] -> Set L.TxIn
220239
convTxIns inputs =
@@ -258,19 +277,23 @@ convPParamsToScriptIntegrityHash
258277
-> L.Redeemers (LedgerEra era)
259278
-> L.TxDats (LedgerEra era)
260279
-> Set Plutus.Language
261-
-> StrictMaybe L.ScriptIntegrityHash
280+
-> Either MakeUnsignedTxError (StrictMaybe L.ScriptIntegrityHash)
262281
convPParamsToScriptIntegrityHash mTxProtocolParams redeemers datums languages = obtainCommonConstraints (useEra @era) $ do
263-
pp <- L.maybeToStrictMaybe mTxProtocolParams
264282
-- This logic is copied from ledger, because their code is not reusable
265283
-- c.f. https://github.com/IntersectMBO/cardano-ledger/commit/5a975d9af507c9ee835a86d3bb77f3e2670ad228#diff-8236dfec9688f22550b91fc9a87af9915523ab9c5bd817218ecceec8ca7a789bR282
266284
let shouldCalculateHash =
267285
not $
268286
null (redeemers ^. L.unRedeemersL)
269287
&& null (datums ^. L.unTxDatsL)
270288
&& null languages
271-
guard shouldCalculateHash
272-
let scriptIntegrity = L.ScriptIntegrity redeemers datums (Set.map (L.getLanguageView pp) languages)
273-
pure $ L.hashScriptIntegrity scriptIntegrity
289+
if shouldCalculateHash
290+
then do
291+
pp <- maybe (Left MakeUnsignedTxMissingProtocolParams) Right mTxProtocolParams
292+
pure $
293+
SJust $
294+
L.hashScriptIntegrity $
295+
L.ScriptIntegrity redeemers datums (Set.map (L.getLanguageView pp) languages)
296+
else pure SNothing
274297

275298
convProposalProcedures
276299
:: forall era

cardano-api/src/Cardano/Api/Experimental/Tx/Internal/Fee.hs

Lines changed: 58 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ data TxBodyErrorAutoBalance era
138138
-- ^ Total deposits
139139
L.MaryValue
140140
-- ^ Balance
141+
| TxBodyErrorMakeUnsignedTx MakeUnsignedTxError
141142

142143
deriving instance Show (TxBodyErrorAutoBalance era)
143144

@@ -203,6 +204,8 @@ instance Error (TxBodyErrorAutoBalance era) where
203204
, "\nBalance (UTxO value - deposits): "
204205
, pshow balance
205206
]
207+
TxBodyErrorMakeUnsignedTx err ->
208+
prettyError err
206209

207210
-- | Use when you do not have access to the UTxOs you intend to spend
208211
estimateBalancedTxBody
@@ -254,12 +257,14 @@ estimateBalancedTxBody
254257
data TxFeeEstimationError era
255258
= TxFeeEstimationScriptExecutionError (TxBodyErrorAutoBalance (LedgerEra era))
256259
| TxFeeEstimationBalanceError (TxBodyErrorAutoBalance (LedgerEra era))
260+
| TxFeeEstimationMakeUnsignedTxError MakeUnsignedTxError
257261
deriving Show
258262

259263
instance Error (TxFeeEstimationError era) where
260264
prettyError = \case
261265
TxFeeEstimationScriptExecutionError e -> prettyError e
262266
TxFeeEstimationBalanceError e -> prettyError e
267+
TxFeeEstimationMakeUnsignedTxError e -> prettyError e
263268

264269
-- | Use when you do not have access to the UTxOs you intend to spend
265270
estimateBalancedTxBody'
@@ -365,17 +370,18 @@ estimateBalancedTxBody'
365370
-- Step 3. Create a tx body with out max lovelace fee. This is strictly for
366371
-- calculating our fee with evaluateTransactionFee.
367372
let maxLovelaceFee = L.Coin (2 ^ (32 :: Integer) - 1)
368-
let txbody1ForFeeEstimateOnly =
369-
makeUnsignedTx
370-
useEra
371-
txbodycontent1
372-
{ txFee = maxLovelaceFee
373-
, txOuts =
374-
obtainCommonConstraints (useEra @era) (TxOut changeTxOut)
375-
: txOuts txbodycontent
376-
, txReturnCollateral = mDummyReturnCollateral
377-
, txTotalCollateral = mDummyTotalCollateral
378-
}
373+
txbody1ForFeeEstimateOnly <-
374+
first TxFeeEstimationMakeUnsignedTxError $
375+
makeUnsignedTx
376+
useEra
377+
txbodycontent1
378+
{ txFee = maxLovelaceFee
379+
, txOuts =
380+
obtainCommonConstraints (useEra @era) (TxOut changeTxOut)
381+
: txOuts txbodycontent
382+
, txReturnCollateral = mDummyReturnCollateral
383+
, txTotalCollateral = mDummyTotalCollateral
384+
}
379385
let fee =
380386
evaluateTransactionFee
381387
pparams
@@ -400,8 +406,8 @@ estimateBalancedTxBody'
400406
-- 1. The original outputs
401407
-- 2. Tx fee
402408
-- 3. Return and total collateral
403-
let
404-
txbody2 =
409+
txbody2 <-
410+
first TxFeeEstimationMakeUnsignedTxError $
405411
makeUnsignedTx
406412
useEra
407413
txbodycontent1
@@ -1420,10 +1426,11 @@ makeTransactionBodyAutoBalance
14201426
-- 3. update tx with fees
14211427
-- 4. balance the transaction and update tx change output
14221428

1423-
let txbodyForChange =
1424-
makeUnsignedTx
1425-
useEra
1426-
txbodycontent
1429+
txbodyForChange <-
1430+
first TxBodyErrorMakeUnsignedTx $
1431+
makeUnsignedTx
1432+
useEra
1433+
txbodycontent
14271434

14281435
let initialChangeTxOutValue :: Ledger.Value (LedgerEra era) =
14291436
evaluateTransactionBalance pp poolids stakeDelegDeposits drepDelegDeposits utxo txbodyForChange
@@ -1441,13 +1448,14 @@ makeTransactionBodyAutoBalance
14411448
-- scripts execution costs.
14421449
-- TODO: The txbody is made (leder tx) so this
14431450
-- is where the execution units map is made
1444-
let UnsignedTx txbody =
1445-
makeUnsignedTx
1446-
useEra
1447-
( txbodycontent
1448-
& modTxOuts
1449-
(<> [initialChangeTxOut])
1450-
)
1451+
UnsignedTx txbody <-
1452+
first TxBodyErrorMakeUnsignedTx $
1453+
makeUnsignedTx
1454+
useEra
1455+
( txbodycontent
1456+
& modTxOuts
1457+
(<> [initialChangeTxOut])
1458+
)
14511459
let exUnitsMapWithLogs =
14521460
evaluateTransactionExecutionUnits
14531461
systemstart
@@ -1479,17 +1487,18 @@ makeTransactionBodyAutoBalance
14791487
let maxLovelaceFee = L.Coin (2 ^ (32 :: Integer) - 1)
14801488
-- Make a txbody that we will use for calculating the fees.
14811489
let (maybeDummyReturnTxCollateral, maybeDummyTotalTxCollateral) = maybeDummyTotalCollAndCollReturnOutput txbodycontent changeaddr
1482-
let txbody1 =
1483-
makeUnsignedTx
1484-
useEra
1485-
txbodycontent1
1486-
{ txFee = maxLovelaceFee
1487-
, txReturnCollateral = maybeDummyReturnTxCollateral
1488-
, txTotalCollateral = maybeDummyTotalTxCollateral
1489-
, txOuts =
1490-
txOuts txbodycontent
1491-
<> [initialChangeTxOut]
1492-
}
1490+
txbody1 <-
1491+
first TxBodyErrorMakeUnsignedTx $
1492+
makeUnsignedTx
1493+
useEra
1494+
txbodycontent1
1495+
{ txFee = maxLovelaceFee
1496+
, txReturnCollateral = maybeDummyReturnTxCollateral
1497+
, txTotalCollateral = maybeDummyTotalTxCollateral
1498+
, txOuts =
1499+
txOuts txbodycontent
1500+
<> [initialChangeTxOut]
1501+
}
14931502

14941503
-- NB: This has the potential to over estimate the fees because estimateTransactionKeyWitnessCount
14951504
-- makes the conservative assumption that all inputs are from distinct
@@ -1521,14 +1530,15 @@ makeTransactionBodyAutoBalance
15211530
-- does not matter, instead it's just the values of the fee and outputs.
15221531
-- Here we do not want to start with any change output, since that's what
15231532
-- we need to calculate.
1524-
let txbody2 =
1525-
makeUnsignedTx
1526-
useEra
1527-
txbodycontent1
1528-
{ txFee = fee
1529-
, txReturnCollateral = maybeReturnTxCollateral
1530-
, txTotalCollateral = maybeTotalTxCollateral
1531-
}
1533+
txbody2 <-
1534+
first TxBodyErrorMakeUnsignedTx $
1535+
makeUnsignedTx
1536+
useEra
1537+
txbodycontent1
1538+
{ txFee = fee
1539+
, txReturnCollateral = maybeReturnTxCollateral
1540+
, txTotalCollateral = maybeTotalTxCollateral
1541+
}
15321542

15331543
case useEra @era of
15341544
DijkstraEra -> error "makeTransactionBodyAutoBalance: DijkstraEra not supported"
@@ -1566,10 +1576,11 @@ makeTransactionBodyAutoBalance
15661576
, txReturnCollateral = maybeReturnTxCollateral
15671577
, txTotalCollateral = maybeTotalTxCollateral
15681578
}
1569-
let txbody3 =
1570-
makeUnsignedTx
1571-
useEra
1572-
finalTxBodyContent
1579+
txbody3 <-
1580+
first TxBodyErrorMakeUnsignedTx $
1581+
makeUnsignedTx
1582+
useEra
1583+
finalTxBodyContent
15731584
return
15741585
(txbody3, finalTxBodyContent)
15751586

cardano-api/test/cardano-api-golden/Test/Golden/Cardano/Api/Tx.hs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,11 @@ tx_canonical = H.propertyOnce $ do
9999
& Exp.setTxTotalCollateral txTotalColl
100100
& Exp.setTxFee (L.Coin 0)
101101

102-
let unsignedTx = Exp.makeUnsignedTx era txBodyContent'
103-
tx = Exp.signTx era [] [] unsignedTx
104-
Exp.SignedTx ledgerTx = tx
105-
oldStyleTx = OldApi.ShelleyTx sbe ledgerTx
102+
unsignedTx <- H.evalEither $ Exp.makeUnsignedTx era txBodyContent'
103+
let
104+
tx = Exp.signTx era [] [] unsignedTx
105+
Exp.SignedTx ledgerTx = tx
106+
oldStyleTx = OldApi.ShelleyTx sbe ledgerTx
106107

107108
void . H.evalIO $ OldApi.writeTxFileTextEnvelope sbe (OldApi.File outFileNonCanonical) oldStyleTx
108109
void . H.evalIO $

0 commit comments

Comments
 (0)