Skip to content

Commit 8f19613

Browse files
committed
makeUnsignedTx: error when Plutus scripts present without protocol params
1 parent 62293e4 commit 8f19613

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
@@ -24,6 +24,7 @@ module Cardano.Api.Experimental.Tx.Internal.BodyContent.New
2424
, TxWithdrawals (..)
2525
, TxBodyContent (..)
2626
, Datum (..)
27+
, MakeUnsignedTxError (..)
2728
, defaultTxBodyContent
2829
, extractDatumsAndHashes
2930
, getDatums
@@ -170,11 +171,27 @@ import Data.Typeable (cast)
170171
import GHC.Exts (IsList (..))
171172
import Lens.Micro
172173

174+
-- | Error that can occur when constructing an unsigned transaction.
175+
data MakeUnsignedTxError
176+
= -- | Plutus scripts are present in the transaction but no protocol
177+
-- parameters were provided. Protocol parameters are required to
178+
-- compute the script integrity hash (script_data_hash).
179+
MakeUnsignedTxMissingProtocolParams
180+
deriving (Eq, Show)
181+
182+
instance Error MakeUnsignedTxError where
183+
prettyError MakeUnsignedTxMissingProtocolParams =
184+
mconcat
185+
[ "Transaction uses Plutus scripts but no protocol parameters were provided. "
186+
, "Protocol parameters are required to compute the script integrity hash "
187+
, "(script_data_hash) from the cost models."
188+
]
189+
173190
makeUnsignedTx
174191
:: forall era
175192
. Era era
176193
-> TxBodyContent (LedgerEra era)
177-
-> UnsignedTx (LedgerEra era)
194+
-> Either MakeUnsignedTxError (UnsignedTx (LedgerEra era))
178195
makeUnsignedTx DijkstraEra _ = error "makeUnsignedTx: Dijkstra era not supported yet"
179196
makeUnsignedTx era@ConwayEra bc = obtainCommonConstraints era $ do
180197
let TxScriptWitnessRequirements languages scripts datums redeemers = collectTxBodyScriptWitnessRequirements bc
@@ -197,12 +214,13 @@ makeUnsignedTx era@ConwayEra bc = obtainCommonConstraints era $ do
197214
totCollateral = unTxTotalCollateral <$> txTotalCollateral bc
198215
txAuxData = toAuxiliaryData (txMetadata bc) (txAuxScripts bc)
199216
scriptValidity = scriptValidityToIsValid $ txScriptValidity bc
200-
scriptIntegrityHash =
201-
convPParamsToScriptIntegrityHash
202-
protocolParameters
203-
redeemers
204-
datums
205-
languages
217+
218+
scriptIntegrityHash <-
219+
convPParamsToScriptIntegrityHash
220+
protocolParameters
221+
redeemers
222+
datums
223+
languages
206224

207225
let setMint = convMintValue apiMintValue
208226
setReqSignerHashes = convExtraKeyWitnesses apiExtraKeyWitnesses
@@ -235,11 +253,12 @@ makeUnsignedTx era@ConwayEra bc = obtainCommonConstraints era $ do
235253
& L.rdmrsTxWitsL .~ redeemers
236254

237255
let eraSpecificTxBody = eraSpecificLedgerTxBody era ledgerTxBody bc
238-
UnsignedTx $
239-
L.mkBasicTx eraSpecificTxBody
240-
& L.witsTxL .~ scriptWitnesses
241-
& L.auxDataTxL .~ L.maybeToStrictMaybe (toAuxiliaryData (txMetadata bc) (txAuxScripts bc))
242-
& L.isValidTxL .~ scriptValidity
256+
Right $
257+
UnsignedTx $
258+
L.mkBasicTx eraSpecificTxBody
259+
& L.witsTxL .~ scriptWitnesses
260+
& L.auxDataTxL .~ L.maybeToStrictMaybe (toAuxiliaryData (txMetadata bc) (txAuxScripts bc))
261+
& L.isValidTxL .~ scriptValidity
243262

244263
convTxIns :: [(TxIn, AnyWitness era)] -> Set L.TxIn
245264
convTxIns inputs =
@@ -283,19 +302,23 @@ convPParamsToScriptIntegrityHash
283302
-> L.Redeemers (LedgerEra era)
284303
-> L.TxDats (LedgerEra era)
285304
-> Set Plutus.Language
286-
-> StrictMaybe L.ScriptIntegrityHash
305+
-> Either MakeUnsignedTxError (StrictMaybe L.ScriptIntegrityHash)
287306
convPParamsToScriptIntegrityHash mTxProtocolParams redeemers datums languages = obtainCommonConstraints (useEra @era) $ do
288-
pp <- L.maybeToStrictMaybe mTxProtocolParams
289307
-- This logic is copied from ledger, because their code is not reusable
290308
-- c.f. https://github.com/IntersectMBO/cardano-ledger/commit/5a975d9af507c9ee835a86d3bb77f3e2670ad228#diff-8236dfec9688f22550b91fc9a87af9915523ab9c5bd817218ecceec8ca7a789bR282
291309
let shouldCalculateHash =
292310
not $
293311
null (redeemers ^. L.unRedeemersL)
294312
&& null (datums ^. L.unTxDatsL)
295313
&& null languages
296-
guard shouldCalculateHash
297-
let scriptIntegrity = L.ScriptIntegrity redeemers datums (Set.map (L.getLanguageView pp) languages)
298-
pure $ L.hashScriptIntegrity scriptIntegrity
314+
if shouldCalculateHash
315+
then do
316+
pp <- maybe (Left MakeUnsignedTxMissingProtocolParams) Right mTxProtocolParams
317+
pure $
318+
SJust $
319+
L.hashScriptIntegrity $
320+
L.ScriptIntegrity redeemers datums (Set.map (L.getLanguageView pp) languages)
321+
else pure SNothing
299322

300323
convProposalProcedures
301324
:: 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)