Skip to content

Commit 423aea4

Browse files
authored
Merge pull request #1288 from IntersectMBO/mgalazyn/fix/experimental-redeemer-pointer-indexing
Fix plutus script redeemer pointer indexing
2 parents d149224 + 842404d commit 423aea4

12 files changed

Lines changed: 1050 additions & 48 deletions

File tree

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
project: cardano-api
2+
pr: 1288
3+
kind:
4+
- bugfix
5+
- breaking
6+
- test
7+
description: |
8+
Fix plutus redeemer pointer indexing: proposal pointers now follow the transaction's insertion order and certificate pointers count unwitnessed certificates, in both the experimental and the deprecated transaction builders. Remove the unused StakeCredential field from the WitTxCert constructor. Add property tests checking every redeemer pointer against the ledger's own resolution.

cardano-api/cardano-api.cabal

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,7 @@ test-suite cardano-api-test
376376
cardano-crypto,
377377
cardano-crypto-class:{cardano-crypto-class, testlib} ^>=2.5,
378378
cardano-crypto-wrapper:testlib,
379+
cardano-data >=1.0,
379380
cardano-ledger-alonzo,
380381
cardano-ledger-api ^>=1.14,
381382
cardano-ledger-babbage,
@@ -432,6 +433,7 @@ test-suite cardano-api-test
432433
Test.Cardano.Api.Orphans
433434
Test.Cardano.Api.RawBytes
434435
Test.Cardano.Api.Transaction.Autobalance
436+
Test.Cardano.Api.Transaction.Body.Plutus.RedeemerIndex
435437
Test.Cardano.Api.Transaction.Body.Plutus.Scripts
436438
Test.Cardano.Api.Transaction.Collateral
437439
Test.Cardano.Api.Transaction.Fixtures

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

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ module Cardano.Api.Compatible.Tx
1515
)
1616
where
1717

18-
import Cardano.Api.Address (StakeCredential)
1918
import Cardano.Api.Era
2019
import Cardano.Api.Experimental.AnyScriptWitness
2120
import Cardano.Api.Experimental.Era (obtainCommonConstraints)
@@ -134,7 +133,7 @@ createCompatibleTx sbe ins outs extraDatums txFee' anyProtocolUpdate anyVote txC
134133

135134
apiScriptWitnesses =
136135
[ (ix, witness)
137-
| (ix, _, Just (_, witness)) <- indexedTxCerts
136+
| (ix, _, Just witness) <- indexedTxCerts
138137
]
139138

140139
pure
@@ -157,7 +156,7 @@ createCompatibleTx sbe ins outs extraDatums txFee' anyProtocolUpdate anyVote txC
157156
setRefInputs = do
158157
let refInputs =
159158
[ toShelleyTxIn refInput
160-
| (_, _, Just (_, wit)) <- indexedTxCerts
159+
| (_, _, Just wit) <- indexedTxCerts
161160
, refInput <- maybeToList $ getAnyWitnessReferenceInput wit
162161
]
163162

@@ -178,7 +177,7 @@ createCompatibleTx sbe ins outs extraDatums txFee' anyProtocolUpdate anyVote txC
178177
indexedTxCerts
179178
:: [ ( ScriptWitnessIndex
180179
, Exp.Certificate (ShelleyLedgerEra era)
181-
, Maybe (StakeCredential, Exp.AnyWitness (ShelleyLedgerEra era))
180+
, Maybe (Exp.AnyWitness (ShelleyLedgerEra era))
182181
)
183182
]
184183
indexedTxCerts = indexTxCertificates txCertificates'
@@ -334,7 +333,7 @@ indexTxCertificates
334333
:: Exp.TxCertificates (ShelleyLedgerEra era)
335334
-> [ ( ScriptWitnessIndex
336335
, Exp.Certificate (ShelleyLedgerEra era)
337-
, Maybe (StakeCredential, AnyWitness (ShelleyLedgerEra era))
336+
, Maybe (AnyWitness (ShelleyLedgerEra era))
338337
)
339338
]
340339
indexTxCertificates (Exp.TxCertificates certsWits) =

cardano-api/src/Cardano/Api/Experimental/Plutus/Internal/IndexedPlutusScriptWitness.hs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,6 @@ data Witnessable (thing :: WitnessableItem) era where
7979
WitTxCert
8080
:: (L.EraTxCert era, L.AlonzoEraScript era)
8181
=> L.TxCert era
82-
-> StakeCredential
8382
-> Witnessable CertItem era
8483
WitMint
8584
:: L.AlonzoEraScript era
@@ -111,11 +110,16 @@ compareWitnesses :: Witnessable thing era -> Witnessable thing era -> Ordering
111110
compareWitnesses a b =
112111
case (a, b) of
113112
(WitTxIn txinA, WitTxIn txinB) -> compare txinA txinB
114-
(WitTxCert{}, WitTxCert{}) -> LT -- Certificates in the ledger are in an `OSet` therefore we preserve the order.
113+
-- Certificates are stored in an `OSet` but resolved positionally, via
114+
-- `certsTxBodyL`'s `findIndexL`. `EQ` lets the stable sort in
115+
-- `createIndexedPlutusScriptWitnesses` preserve insertion order.
116+
(WitTxCert{}, WitTxCert{}) -> EQ
115117
(WitMint polIdA _, WitMint polIdB _) -> compare polIdA polIdB
116118
(WitWithdrawal stakeAddrA _, WitWithdrawal stakeAddrB _) -> compare stakeAddrA stakeAddrB
117119
(WitVote voterA, WitVote voterB) -> compare voterA voterB
118-
(WitProposal propA, WitProposal propB) -> compare propA propB
120+
-- Proposals are also stored in an `OSet` and resolved positionally
121+
-- (`StrictSeq.findIndexL`), same as `WitTxCert` above.
122+
(WitProposal{}, WitProposal{}) -> EQ
119123

120124
data WitnessableItem
121125
= TxInItem

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

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
{-# LANGUAGE RankNTypes #-}
77
{-# LANGUAGE ScopedTypeVariables #-}
88
{-# LANGUAGE StandaloneDeriving #-}
9-
{-# LANGUAGE TupleSections #-}
109
{-# LANGUAGE TypeApplications #-}
1110
{-# LANGUAGE TypeFamilies #-}
1211
{-# LANGUAGE TypeOperators #-}
@@ -666,11 +665,12 @@ newtype TxWithdrawals era = TxWithdrawals {unTxWithdrawals :: [(StakeAddress, L.
666665

667666
newtype TxCertificates era
668667
= TxCertificates
669-
{unTxCertificates :: OMap (Exp.Certificate era) (Maybe (StakeCredential, AnyWitness era))}
668+
{unTxCertificates :: OMap (Exp.Certificate era) (Maybe (AnyWitness era))}
670669
deriving (Show, Eq)
671670

672-
-- | Create 'TxCertificates'. Note that 'Certificate era' will be deduplicated. Only Certificates with a
673-
-- stake credential will be in the result.
671+
-- | Create 'TxCertificates'. Note that 'Certificate era' will be deduplicated. Certificates that
672+
-- require a witness will be stored with 'Just' the caller-supplied witness; those that do not (e.g.
673+
-- deposit-less stake registration in Conway) will be stored with 'Nothing'.
674674
--
675675
-- Note that, when building a transaction in Conway era, a witness is not required for staking credential
676676
-- registration, but this is only the case during the transitional period of Conway era and only for staking
@@ -686,10 +686,10 @@ mkTxCertificates era certs = TxCertificates . OMap.fromList $ map getStakeCred c
686686
getStakeCred
687687
:: (Exp.Certificate (LedgerEra era), AnyWitness (LedgerEra era))
688688
-> ( Exp.Certificate (LedgerEra era)
689-
, Maybe (StakeCredential, AnyWitness (LedgerEra era))
689+
, Maybe (AnyWitness (LedgerEra era))
690690
)
691691
getStakeCred (c@(Exp.Certificate cert), wit) =
692-
(c, (,wit) <$> getTxCertWitness (convert era) (obtainCommonConstraints era cert))
692+
(c, wit <$ getTxCertWitness (convert era) (obtainCommonConstraints era cert))
693693

694694
newtype TxMintValue era
695695
= TxMintValue
@@ -866,21 +866,30 @@ extractWitnessableTxIns tIns =
866866
obtainCommonConstraints (useEra @era) $
867867
List.nub [(WitTxIn txin, wit) | (txin, wit) <- tIns]
868868

869+
-- | Wrap every certificate as a 'Witnessable', paired with its witness.
870+
--
871+
-- An unwitnessed certificate still occupies a redeemer index slot: the
872+
-- ledger indexes the 'Certifying' purpose by position in the full
873+
-- certificate sequence, not just the witnessed subset, so the result below
874+
-- keeps one entry per certificate in insertion order.
875+
--
876+
-- In the Conway era only, a certificate may legitimately have no witness
877+
-- (deposit-less stake registration), so a missing witness defaults to
878+
-- 'AnyKeyWitnessPlaceholder'. From Dijkstra onwards 'mkTxCertificates'
879+
-- guarantees every entry has a 'Just' witness, so the placeholder is dead
880+
-- code for those eras.
869881
extractWitnessableCertificates
870882
:: forall era
871883
. IsEra era
872884
=> TxCertificates (LedgerEra era)
873885
-> [(Witnessable CertItem (LedgerEra era), AnyWitness (LedgerEra era))]
874-
extractWitnessableCertificates txCerts =
886+
extractWitnessableCertificates (TxCertificates certs) =
875887
obtainCommonConstraints (useEra @era) $
876888
List.nub
877-
[ ( WitTxCert cert stakeCred
878-
, wit
879-
)
880-
| (Exp.Certificate cert, Just (stakeCred, wit)) <- getCertificates txCerts
889+
[ (WitTxCert cert, wit)
890+
| (Exp.Certificate cert, mWit) <- toList certs
891+
, let wit = fromMaybe AnyKeyWitnessPlaceholder mWit
881892
]
882-
where
883-
getCertificates (TxCertificates txcs) = toList txcs
884893

885894
extractWitnessableMints
886895
:: forall era
@@ -923,13 +932,20 @@ extractWitnessableVotes (Just txVoteProc) =
923932
| (vote, wit) <- getVotes txVoteProc
924933
]
925934
where
935+
-- Uses a total 'Map.findWithDefault' (placeholder witness on a miss),
936+
-- not a lookup that skips missing voters. A skipped voter would shrink
937+
-- this list and shift every later voter's redeemer index.
938+
--
939+
-- 'mkTxVotingProcedures' builds 'scriptWitnessedVotes' in lockstep with
940+
-- 'allVotingProcedures', assuming exactly one voter per merged
941+
-- 'L.VotingProcedures' value, so a miss should not normally happen.
926942
getVotes
927943
:: TxVotingProcedures (LedgerEra era)
928944
-> [(L.Voter, AnyWitness (LedgerEra era))]
929945
getVotes (TxVotingProcedures allVotingProcedures scriptWitnessedVotes) =
930946
[ (voter, wit)
931947
| (voter, _) <- toList $ L.unVotingProcedures allVotingProcedures
932-
, wit <- maybe [] return (Map.lookup voter scriptWitnessedVotes)
948+
, let wit = Map.findWithDefault AnyKeyWitnessPlaceholder voter scriptWitnessedVotes
933949
]
934950

935951
extractWitnessableProposals

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

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ import Cardano.Api.Plutus.Internal.Script
4646

4747
import Cardano.Ledger.Keys qualified as Ledger
4848

49+
import Control.Applicative
50+
4951
type family Delegatee era where
5052
Delegatee DijkstraEra = Ledger.Delegatee
5153
Delegatee ConwayEra = Ledger.Delegatee
@@ -201,9 +203,25 @@ getTxCertWitness
201203
:: ShelleyBasedEra era
202204
-> Ledger.TxCert (ShelleyLedgerEra era)
203205
-> Maybe StakeCredential
204-
getTxCertWitness sbe ledgerCert = shelleyBasedEraConstraints sbe $
205-
case Ledger.getVKeyWitnessTxCert ledgerCert of
206-
Just keyHash -> Just $ StakeCredentialByKey $ Api.StakeKeyHash $ Ledger.coerceKeyRole keyHash
207-
Nothing ->
208-
StakeCredentialByScript . fromShelleyScriptHash
209-
<$> Ledger.getScriptWitnessTxCert ledgerCert
206+
getTxCertWitness sbe ledgerCert = mStakeCredByKey <|> mStakeCredByScript <|> witnessOptionalUpToConway
207+
where
208+
mStakeCredByKey =
209+
shelleyBasedEraConstraints sbe $
210+
StakeCredentialByKey . Api.StakeKeyHash . Ledger.coerceKeyRole
211+
<$> Ledger.getVKeyWitnessTxCert ledgerCert
212+
mStakeCredByScript =
213+
shelleyBasedEraConstraints sbe $
214+
StakeCredentialByScript . fromShelleyScriptHash <$> Ledger.getScriptWitnessTxCert ledgerCert
215+
witnessOptionalUpToConway =
216+
case sbe of
217+
ShelleyBasedEraShelley -> Nothing
218+
ShelleyBasedEraAllegra -> Nothing
219+
ShelleyBasedEraMary -> Nothing
220+
ShelleyBasedEraAlonzo -> Nothing
221+
ShelleyBasedEraBabbage -> Nothing
222+
ShelleyBasedEraConway -> Nothing
223+
ShelleyBasedEraDijkstra ->
224+
error
225+
"getTxCertWitness: certificate has no witness in the Dijkstra era. \
226+
\From Dijkstra onwards every certificate requires a witness. \
227+
\This indicates a bug in the ledger's EraTxCert instance for this certificate type."

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

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1149,18 +1149,14 @@ substituteExecutionUnits
11491149
:: [ ( Exp.Certificate (LedgerEra era)
11501150
, Either
11511151
(TxBodyErrorAutoBalance (LedgerEra era))
1152-
( Maybe
1153-
( StakeCredential
1154-
, AnyWitness (LedgerEra era)
1155-
)
1156-
)
1152+
(Maybe (AnyWitness (LedgerEra era)))
11571153
)
11581154
]
11591155
mappedScriptWitnesses =
11601156
[ case mWit of
11611157
Nothing -> (cert, Right Nothing)
1162-
Just (stakeCred, wit) ->
1163-
(cert, Just . (stakeCred,) <$> substituteExecUnits ix wit)
1158+
Just wit ->
1159+
(cert, Just <$> substituteExecUnits ix wit)
11641160
| (ix, cert, mWit) <- indexTxCertificates txCerts
11651161
]
11661162
TxCertificates . fromList <$> traverseScriptWitnesses mappedScriptWitnesses
@@ -1272,14 +1268,16 @@ collectTxBodyScriptWitnesses
12721268
[ (ix, wit)
12731269
| (ix, _, _, Just wit@AnyScriptWitnessPlutus{}) <- fmap toAnyScriptWitness <$> indexTxWithdrawals txw
12741270
]
1275-
-- TODO: If this works you need to change the rest!
1271+
-- Unlike the other categories, this intentionally collects simple script
1272+
-- witnesses as well as Plutus ones, so that a script-witnessed certificate
1273+
-- is never reported as unwitnessed.
12761274
scriptWitnessesCertificates
12771275
:: TxCertificates (LedgerEra era)
12781276
-> [(ScriptWitnessIndex, Exp.AnyScriptWitness (LedgerEra era))]
12791277
scriptWitnessesCertificates txc =
12801278
List.nub
12811279
[ (ix, wit)
1282-
| (ix, _, Just (_, anyWit)) <- indexTxCertificates txc
1280+
| (ix, _, Just anyWit) <- indexTxCertificates txc
12831281
, Just wit <- [toAnyScriptWitness anyWit]
12841282
]
12851283

@@ -1366,7 +1364,7 @@ indexTxCertificates
13661364
:: TxCertificates (LedgerEra era)
13671365
-> [ ( ScriptWitnessIndex
13681366
, Exp.Certificate (LedgerEra era)
1369-
, Maybe (StakeCredential, AnyWitness (LedgerEra era))
1367+
, Maybe (AnyWitness (LedgerEra era))
13701368
)
13711369
]
13721370
indexTxCertificates (TxCertificates certsWits) =
@@ -1782,7 +1780,7 @@ estimateTransactionKeyWitnessCount
17821780
+ case txCertificates of
17831781
TxCertificates credWits ->
17841782
length
1785-
[() | (_, Just (_, AnyKeyWitnessPlaceholder)) <- toList credWits]
1783+
[() | (_, Just AnyKeyWitnessPlaceholder) <- toList credWits]
17861784
+ case txProposalProcedures of
17871785
Just (TxProposalProcedures m) ->
17881786
OMap.size m

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -846,13 +846,13 @@ module Cardano.Api.Tx
846846
, fromShelleyMetadata
847847
, toShelleyMetadatum
848848
, fromShelleyMetadatum
849-
-- Exported for testing
850-
, extractWitnessableCertificates
849+
-- Exported for testing and advanced use
851850
, extractWitnessableMints
852851
, extractWitnessableProposals
853852
, extractWitnessableTxIns
854853
, extractWitnessableVotes
855854
, extractWitnessableWithdrawals
855+
, extractWitnessableCertificates
856856
-- Exporting for testing. Deprecate in the future.
857857
, legacyKeyWitnessEncode
858858

cardano-api/src/Cardano/Api/Tx/Internal/Body.hs

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -830,8 +830,8 @@ mkTxProposalProcedures proposals = do
830830
fromList $
831831
map (second pure) proposals
832832

833-
-- | Index proposal procedures by their order ('Ord').
834-
-- | and filter out the ones that do not have a witness.
833+
-- | Index proposal procedures by the order they appear in the transaction,
834+
-- and filter out the ones that do not have a witness.
835835
indexTxProposalProcedures
836836
:: TxProposalProcedures BuildTx era
837837
-> [(ScriptWitnessIndex, L.ProposalProcedure (ShelleyLedgerEra era), ScriptWitness WitCtxStake era)]
@@ -840,7 +840,7 @@ indexTxProposalProcedures proposals =
840840
| (proposal, Just (ix, scriptWitness)) <- indexWitnessedTxProposalProcedures proposals
841841
]
842842

843-
-- | Index proposal procedures by their order ('Ord').
843+
-- | Index proposal procedures by the order they appear in the transaction.
844844
indexWitnessedTxProposalProcedures
845845
:: TxProposalProcedures BuildTx era
846846
-> [ ( L.ProposalProcedure (ShelleyLedgerEra era)
@@ -2465,17 +2465,28 @@ extractWitnessableWithdrawals aeon txWithdrawals =
24652465
getWithdrawals TxWithdrawalsNone = []
24662466
getWithdrawals (TxWithdrawals _ txws) = txws
24672467

2468+
-- | Convert every certificate to a 'Witnessable', paired with its witness.
2469+
--
2470+
-- Every certificate must stay in the result, witnessed or not: an
2471+
-- unwitnessed certificate still occupies a redeemer index slot, since the
2472+
-- ledger indexes the 'Certifying' purpose by position in the full
2473+
-- certificate sequence, not just the witnessed subset. See
2474+
-- 'indexCertificatesWith' for the same rule applied to the deprecated
2475+
-- indexing path. An unwitnessed certificate is paired with the old API's
2476+
-- inert stake witness, 'KeyWitness' 'KeyWitnessForStakeAddr' (the same
2477+
-- default 'mkTxCertificates' uses), from which
2478+
-- 'legacyWitnessToScriptRequirements' extracts no script requirement.
24682479
extractWitnessableCertificates
24692480
:: AlonzoEraOnwards era
24702481
-> TxCertificates BuildTx era
24712482
-> [(Witnessable CertItem (ShelleyLedgerEra era), BuildTxWith BuildTx (Witness WitCtxStake era))]
24722483
extractWitnessableCertificates aeon txCertificates =
24732484
alonzoEraOnwardsConstraints aeon $
24742485
List.nub
2475-
[ ( WitTxCert cert stakeCred
2476-
, BuildTxWith wit
2486+
[ ( WitTxCert cert
2487+
, BuildTxWith $ maybe (KeyWitness KeyWitnessForStakeAddr) snd mCredAndWit
24772488
)
2478-
| (Exp.Certificate cert, BuildTxWith (Just (stakeCred, wit))) <- getCertificates txCertificates
2489+
| (Exp.Certificate cert, BuildTxWith mCredAndWit) <- getCertificates txCertificates
24792490
]
24802491
where
24812492
getCertificates TxCertificatesNone = []

0 commit comments

Comments
 (0)