Skip to content

Commit fe9df43

Browse files
committed
ADR-10 Script Witness API
1 parent 1ff0dd8 commit fe9df43

File tree

1 file changed

+263
-0
lines changed

1 file changed

+263
-0
lines changed
Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
# Status
2+
3+
-[] Accepted ??-??-???
4+
5+
# Context
6+
7+
The current script witness api (and by extention the witness api) has been long overdue for a refactor. The main objectives for the introduction of the new witness api are as follows:
8+
- Use as much as possible (within reason) `cardano-ledger` types directly. This reduces the maintenance burden and allows us to leverage `cardano-ledger` type classes.
9+
- Treat simple script witnesses separately from plutus script witnesses. This is a QOL improvement that makes the code more easily understandable and nicer to work with.
10+
- Use `cardano-ledger`'s `PlutusRunnable` type in order to leverage it's serialization instances that check the validity of the plutus script at the point of disk read.
11+
- The new api needs to be compatible with the old api
12+
13+
# Decision
14+
15+
## Summary
16+
17+
Currently we have several functions that generate indicies in order to construct the redeemer pointer map. For example:
18+
19+
```haskell
20+
21+
indexTxWithdrawals
22+
:: TxWithdrawals BuildTx era
23+
-> [(ScriptWitnessIndex, StakeAddress, L.Coin, Witness WitCtxStake era)]
24+
indexTxWithdrawals TxWithdrawalsNone = []
25+
indexTxWithdrawals (TxWithdrawals _ withdrawals) =
26+
[ (ScriptWitnessIndexWithdrawal ix, addr, coin, witness)
27+
| (ix, (addr, coin, BuildTxWith witness)) <- zip [0 ..] (orderStakeAddrs withdrawals)
28+
]
29+
30+
...
31+
32+
indexTxMintValue
33+
:: TxMintValue build era
34+
-> [ ( ScriptWitnessIndex
35+
, PolicyId
36+
, AssetName
37+
, Quantity
38+
, BuildTxWith build (ScriptWitness WitCtxMint era)
39+
)
40+
]
41+
indexTxMintValue TxMintNone = []
42+
indexTxMintValue (TxMintValue _ policiesWithAssets) =
43+
[ (ScriptWitnessIndexMint ix, policyId', assetName', quantity, witness)
44+
| (ix, (policyId', assets)) <- zip [0 ..] $ toList policiesWithAssets
45+
, (assetName', quantity, witness) <- assets
46+
]
47+
48+
...
49+
50+
indexTxIns
51+
:: TxIns BuildTx era
52+
-> [(ScriptWitnessIndex, TxIn, Witness WitCtxTxIn era)]
53+
indexTxIns txins =
54+
[ (ScriptWitnessIndexTxIn ix, txIn, witness)
55+
| (ix, (txIn, BuildTxWith witness)) <- zip [0 ..] $ orderTxIns txins
56+
]
57+
58+
...etc
59+
60+
```
61+
62+
There is a repeating pattern here that basically has something that is _witnessable_ (tx input, mint value, certificate, etc) accompanied by its index and the corresponding witness. Therefore the following types are introduced to capture this pattern:
63+
64+
```haskell
65+
data Witnessable thing era where
66+
WitTxIn :: L.AlonzoEraScript era => TxIn -> Witnessable TxIn era
67+
WitTxCert :: L.AlonzoEraScript era => Cert -> Witnessable Cert era
68+
WitMint :: L.AlonzoEraScript era => Mint -> Witnessable Mint era
69+
WitWithdrawal
70+
:: L.AlonzoEraScript era => Withdrawal -> Witnessable Withdrawal era
71+
WitVote
72+
:: L.ConwayEraScript era
73+
=> Voter -> Witnessable Voter era
74+
WitProposal :: L.ConwayEraScript era => Proposal -> Witnessable Proposal era
75+
76+
77+
type Mint = (PolicyId, AssetName, Quantity)
78+
79+
type Withdrawal = (StakeAddress, L.Coin)
80+
81+
type Cert = (AnyCertificate, StakeCredential)
82+
83+
type Voter = Api.AnyVoter
84+
85+
type Proposal = Api.AnyProposal
86+
87+
88+
data AnyWitness era where
89+
AnyKeyWitness :: AnyWitness era
90+
AnySimpleScriptWitness :: SimpleScriptOrReferenceInput era -> AnyWitness era
91+
AnyPlutusScriptWitness :: PlutusScriptWitness lang purpose era -> AnyWitness era
92+
```
93+
94+
NB: See `class GetPlutusScriptPurpose` to understand the purpose of the ledger constraints in `Witnessable thing era` constructor.
95+
96+
And the upshot is once we have `[(Witnessable thing era, AnyWitness era)]` these can be uniformly treated to construct the redeemer pointer map (`Redeemers era`):
97+
98+
```haskell
99+
100+
data TxScriptWitnessRequirements era
101+
= TxScriptWitnessRequirements
102+
(Set L.Language)
103+
[L.Script era]
104+
(L.TxDats era)
105+
(L.Redeemers era) -- Redeemer pointer map
106+
107+
getTxScriptWitnessesRequirements
108+
:: AlonzoEraOnwards era
109+
-> [(Witnessable witnessable (ShelleyLedgerEra era), AnyWitness (ShelleyLedgerEra era))]
110+
-> TxScriptWitnessRequirements (ShelleyLedgerEra era)
111+
getTxScriptWitnessesRequirements eon wits =
112+
mconcat $ map (getTxScriptWitnessRequirements eon) wits
113+
```
114+
115+
## New `IndexedPlutusScriptWitness` types
116+
117+
We are only concerned with the index of something that is plutus script witnessed. Therefore we introduce `IndexedPlutusScriptWitness`:
118+
119+
```haskell
120+
data IndexedPlutusScriptWitness witnessable (lang :: L.Language) (purpose :: PlutusScriptPurpose) era where
121+
IndexedPlutusScriptWitness
122+
:: Witnessable witnessable era
123+
-> (L.PlutusPurpose L.AsIx era)
124+
-> (PlutusScriptWitness lang purpose era)
125+
-> IndexedPlutusScriptWitness witnessable lang purpose era
126+
```
127+
128+
We do away with `cardano-api`'s `ScriptWitnessIndex` and directly use `cardano-ledger`'s `PlutusPurpose AsIx era` which is essentially the same thing.
129+
The benefit here is we get to use ledger type classes to produce the index of the plutus script witness thing we are interested in:
130+
131+
```haskell
132+
class GetPlutusScriptPurpose era where
133+
toPlutusScriptPurpose
134+
:: Word32
135+
-> Witnessable thing era
136+
-> L.PlutusPurpose L.AsIx era
137+
138+
instance GetPlutusScriptPurpose era where
139+
toPlutusScriptPurpose index WitTxIn{} = L.mkSpendingPurpose (L.AsIx index)
140+
toPlutusScriptPurpose index WitWithdrawal{} = L.mkRewardingPurpose (L.AsIx index)
141+
toPlutusScriptPurpose index WitMint{} = L.mkMintingPurpose (L.AsIx index)
142+
toPlutusScriptPurpose index WitTxCert{} = L.mkCertifyingPurpose (L.AsIx index)
143+
toPlutusScriptPurpose index WitVote{} = L.mkVotingPurpose (L.AsIx index)
144+
toPlutusScriptPurpose index WitProposal{} = L.mkProposingPurpose (L.AsIx index)
145+
```
146+
147+
The ledger constraints in the `Witnessable thing era` data constructors allow us to directly construct the script witness index (`PlutusPurpose AsIx era`) with the `toPlutusScriptPurpose` class method. We no longer have to use the old api's `ScriptWitnessIndex` which is eventually converted into a `PlutusPurpose AsIx era`. This approach is more direct.
148+
149+
This ultimately allows us to collapse all of the different index calculation functions into a single function:
150+
151+
```haskell
152+
createIndexedPlutusScriptWitness
153+
:: Word32
154+
-> Witnessable witnessable era
155+
-> PlutusScriptWitness lang purpose era
156+
-> IndexedPlutusScriptWitness witnessable lang purpose era
157+
createIndexedPlutusScriptWitness index witnessable =
158+
IndexedPlutusScriptWitness witnessable (toPlutusScriptPurpose index witnessable)
159+
160+
createIndexedPlutusScriptWitnesses
161+
:: [(Witnessable witnessable era, AnyWitness era)]
162+
-> [AnyIndexedPlutusScriptWitness era]
163+
createIndexedPlutusScriptWitnesses witnessableThings =
164+
[ AnyIndexedPlutusScriptWitness $ createIndexedPlutusScriptWitness index thing sWit
165+
| (index, (thing, AnyPlutusScriptWitness sWit)) <- zip [0 ..] $ enforceOrdering witnessableThings
166+
]
167+
where
168+
enforceOrdering = List.sortBy (compareWitnesses `on` fst)
169+
170+
compareWitnesses :: Witnessable thing era -> Witnessable thing era -> Ordering
171+
compareWitnesses a b =
172+
case (a, b) of
173+
(WitTxIn txinA, WitTxIn txinB) -> compare txinA txinB
174+
(WitTxCert{}, WitTxCert{}) -> LT -- Certificates in the ledger are in an `OSet` therefore we preserve the order.
175+
(WitMint mintA, WitMint mintB) -> compare mintA mintB
176+
(WitWithdrawal (stakeAddrA, _), WitWithdrawal (stakeAddrB, _)) -> compare stakeAddrA stakeAddrB
177+
(WitVote voterA, WitVote voterB) -> compare voterA voterB
178+
(WitProposal propA, WitProposal propB) -> compare propA propB
179+
180+
```
181+
182+
## New `PlutusScriptInEra` type
183+
184+
185+
```haskell
186+
data PlutusScriptInEra (lang :: L.Language) era where
187+
PlutusScriptInEra :: PlutusRunnable lang -> PlutusScriptInEra lang era
188+
```
189+
190+
Why PlutusRunnable? Mainly for deserialization benefits. The deserialization of this type looks at the major protocol version and the script language to determine if indeed the script is runnable. This is the decode CBOR instance in `cardano-ledger`:
191+
192+
```haskell
193+
instance PlutusLanguage l => DecCBOR (PlutusRunnable l) where
194+
decCBOR = do
195+
plutus <- decCBOR
196+
pv <- getDecoderVersion
197+
either (fail . show) pure $ decodePlutusRunnable pv plutus
198+
```
199+
`decodePlutusRunnable` eventually calls a deserialization function in the `plutus` repo which will fail if
200+
the plutus script version you are using is not available in a given era.
201+
202+
A ficticious example: The plutus team introduces PlutusV4. This plutus script version can only be used in the era after Conway (Conway + 1).
203+
However a user tries to use a PlutusV4 script in Conway. Because we use `PlutusRunnable` to represent our plutus scripts we would get a deserialization error.
204+
If we stuck with `ShortByteString` to represent plutus scripts, the error would only occur at transaction submission or when using `transaction build`.
205+
This is a small QOL improvement as now if your script deserializes at least you know it's valid for a particular era.
206+
207+
208+
## New `PlutusScriptDatum` type
209+
210+
```haskell
211+
data PlutusScriptDatum (lang :: L.Language) (purpose :: PlutusScriptPurpose) where
212+
SpendingScriptDatum
213+
:: PlutusScriptDatumF lang SpendingScript -> PlutusScriptDatum lang SpendingScript
214+
InlineDatum :: PlutusScriptDatum lang purpose
215+
NoScriptDatum
216+
:: PlutusScriptDatum lang purpose
217+
218+
data NoScriptDatum = NoScriptDatumAllowed deriving Show
219+
220+
-- | The PlutusScriptDatum type family is used to determine if a script datum is allowed
221+
-- for a given plutus script purpose and version. This change was proposed in CIP-69
222+
-- https://github.com/cardano-foundation/CIPs/tree/master/CIP-0069
223+
type family PlutusScriptDatumF (lang :: L.Language) (purpose :: PlutusScriptPurpose) where
224+
PlutusScriptDatumF L.PlutusV1 SpendingScript = HashableScriptData
225+
PlutusScriptDatumF L.PlutusV2 SpendingScript = HashableScriptData
226+
PlutusScriptDatumF L.PlutusV3 SpendingScript = Maybe HashableScriptData -- CIP-69
227+
PlutusScriptDatumF L.PlutusV1 MintingScript = NoScriptDatum
228+
PlutusScriptDatumF L.PlutusV2 MintingScript = NoScriptDatum
229+
PlutusScriptDatumF L.PlutusV3 MintingScript = NoScriptDatum
230+
PlutusScriptDatumF L.PlutusV1 WithdrawingScript = NoScriptDatum
231+
PlutusScriptDatumF L.PlutusV2 WithdrawingScript = NoScriptDatum
232+
PlutusScriptDatumF L.PlutusV3 WithdrawingScript = NoScriptDatum
233+
PlutusScriptDatumF L.PlutusV1 CertifyingScript = NoScriptDatum
234+
PlutusScriptDatumF L.PlutusV2 CertifyingScript = NoScriptDatum
235+
PlutusScriptDatumF L.PlutusV3 CertifyingScript = NoScriptDatum
236+
PlutusScriptDatumF L.PlutusV1 ProposingScript = NoScriptDatum
237+
PlutusScriptDatumF L.PlutusV2 ProposingScript = NoScriptDatum
238+
PlutusScriptDatumF L.PlutusV3 ProposingScript = NoScriptDatum
239+
PlutusScriptDatumF L.PlutusV1 VotingScript = NoScriptDatum
240+
PlutusScriptDatumF L.PlutusV2 VotingScript = NoScriptDatum
241+
PlutusScriptDatumF L.PlutusV3 VotingScript = NoScriptDatum
242+
243+
```
244+
The `PlutusScriptDatum` GADT and `PlutusScriptDatumF` type family are defined to simply capture CIP-69 i.e spending script datums are optional in PlutusV3.
245+
246+
## New `SimpleScript` type
247+
248+
```haskell
249+
data SimpleScript era where
250+
SimpleScript :: Ledger.NativeScript era -> SimpleScript era
251+
```
252+
A wrapper around ledger's `NativeScript` type. We opt for this type because it restricts the allowable scripts to simple scripts.
253+
254+
## Misc
255+
256+
Future work would include creating a `TxBodyContents` successor that will use `[(Witnessable witnessable era, AnyWitness era)]` directly.
257+
258+
# Consequences
259+
260+
Acceptance of this ADR will:
261+
- Improve the clarity of the witness and script apis
262+
- Make clearer the relationship between the redeemer pointers and the script witnesses
263+
- Allow us to use the new api "under the hood" of the old api reducing the maintenance burden of maintaining the old vs new api.

0 commit comments

Comments
 (0)