Skip to content

Commit 42eeaeb

Browse files
williamdemeoCopilotcarlostome
authored
[Dijkstra] resolve ambiguity: visibility of reference inputs (#1014)
* Fix prose explaining CIP 118. + Remove contradiction between "ref inputs may refer to earlier tx outputs in the batch" vs "all inputs must exist before applying any tx in the batch." The new text punts the exact constraint to the UTxO rules (where it belongs). + Fix Plutus bullet (old "nor earlier versions" reads like "no Plutus at all"). + Align fees with current Agda (`txFee : InTopLevel …`), but leave room for later CIP-driven updates. * update CHANGELOG * Adapt `txOutToP2Script` to two utxo views. Also highlight that `getDatum` gets the datum of the spent output (look up `txin` in the UTxO; if the output stores a datum hash, look it up in `DataOf tx`). It's a spending-input datum lookup (not a reference-input datum lookup). * Update src/Ledger/Dijkstra/Specification/Transaction.lagda.md * Add preliminary check that txIns exist in the UTxO prior to any changes --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Carlos Tomé Cortiñas <carlos.tome-cortinas@iohk.io>
1 parent 457901c commit 42eeaeb

File tree

4 files changed

+101
-77
lines changed

4 files changed

+101
-77
lines changed

src/Ledger/Dijkstra/Specification/Ledger.lagda.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ private variable
181181
enactState : EnactState
182182
treasury : Treasury
183183
isTopLevelValid : Bool
184+
utxo₀ : UTxO
184185
```
185186
-->
186187

@@ -197,15 +198,15 @@ data _⊢_⇀⦇_,SUBLEDGER⦈_ : SubLedgerEnv → LState → SubLevelTx → LSt
197198
```agda
198199
in
199200
∙ isTopLevelValid ≡ true
200-
∙ ⟦ slot , pp , treasury ⟧ ⊢ utxoState ⇀⦇ stx ,SUBUTXOW⦈ utxoState'
201+
∙ ⟦ slot , pp , treasury , utxo₀ ⟧ ⊢ utxoState ⇀⦇ stx ,SUBUTXOW⦈ utxoState'
201202
∙ ⟦ epoch slot , pp , txGovVotes , txWithdrawals , allColdCreds govState enactState ⟧ ⊢ certState ⇀⦇ txCerts ,CERTS⦈ certState'
202203
∙ ⟦ txId , epoch slot , pp , ppolicy , enactState , certState' , dom (RewardsOf certState) ⟧ ⊢ {- rmOrphanDRepVotes certState' -} govState ⇀⦇ txgov txb ,GOVS⦈ govState'
203204
────────────────────────────────
204205
⟦ slot , ppolicy , pp , enactState , treasury , isTopLevelValid ⟧ ⊢ ⟦ utxoState , govState , certState ⟧ ⇀⦇ stx ,SUBLEDGER⦈ ⟦ utxoState' , govState' , certState' ⟧
205206
206207
SUBLEDGER-I :
207208
∙ isTopLevelValid ≡ false
208-
∙ ⟦ slot , pp , treasury ⟧ ⊢ utxoState ⇀⦇ stx ,SUBUTXOW⦈ utxoState
209+
∙ ⟦ slot , pp , treasury , utxo₀ ⟧ ⊢ utxoState ⇀⦇ stx ,SUBUTXOW⦈ utxoState
209210
────────────────────────────────
210211
⟦ slot , ppolicy , pp , enactState , treasury , isTopLevelValid ⟧ ⊢ ⟦ utxoState , govState , certState ⟧ ⇀⦇ stx ,SUBLEDGER⦈ ⟦ utxoState , govState , certState ⟧
211212
@@ -237,7 +238,7 @@ data _⊢_⇀⦇_,LEDGER⦈_ : LedgerEnv → LState → TopLevelTx → LState
237238
in
238239
∙ isValid tx ≡ true
239240
∙ ⟦ slot , ppolicy , pp , enactState , treasury , isValid tx ⟧ ⊢ ⟦ utxoState , govState , certState ⟧ ⇀⦇ txSubTransactions ,SUBLEDGERS⦈ ⟦ utxoState' , govState' , certState' ⟧
240-
∙ ⟦ slot , pp , treasury ⟧ ⊢ utxoState' ⇀⦇ tx ,UTXOW⦈ utxoState''
241+
∙ ⟦ slot , pp , treasury , utxo₀ ⟧ ⊢ utxoState' ⇀⦇ tx ,UTXOW⦈ utxoState''
241242
∙ ⟦ epoch slot , pp , txGovVotes , txWithdrawals , allColdCreds govState enactState ⟧ ⊢ certState' ⇀⦇ txCerts ,CERTS⦈ certState''
242243
∙ ⟦ txId , epoch slot , pp , ppolicy , enactState , certState' , dom (RewardsOf certState) ⟧ ⊢ {- rmOrphanDRepVotes certState' -} govState ⇀⦇ txgov txb ,GOVS⦈ govState'
243244
────────────────────────────────
@@ -255,7 +256,7 @@ data _⊢_⇀⦇_,LEDGER⦈_ : LedgerEnv → LState → TopLevelTx → LState
255256
in
256257
∙ isValid tx ≡ false
257258
∙ ⟦ slot , ppolicy , pp , enactState , treasury , isValid tx ⟧ ⊢ ⟦ utxoState , govState , certState ⟧ ⇀⦇ txSubTransactions ,SUBLEDGERS⦈ ⟦ utxoState , govState , certState ⟧
258-
∙ ⟦ slot , pp , treasury ⟧ ⊢ utxoState ⇀⦇ tx ,UTXOW⦈ utxoState'
259+
∙ ⟦ slot , pp , treasury , utxo₀ ⟧ ⊢ utxoState ⇀⦇ tx ,UTXOW⦈ utxoState'
259260
────────────────────────────────
260261
⟦ slot , ppolicy , pp , enactState , treasury ⟧ ⊢ ⟦ utxoState , govState , certState ⟧ ⇀⦇ tx ,LEDGER⦈ ⟦ utxoState' , govState , certState ⟧
261262
```

src/Ledger/Dijkstra/Specification/Script/Validation.lagda.md

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,10 @@ indexedRdmrs : (Tx ℓ) → ScriptPurpose → Maybe (Redeemer × ExUnits)
5858
indexedRdmrs tx sp = maybe (λ x → lookupᵐ? txRedeemers x) nothing (rdptr tx sp)
5959
where open Tx tx; open TxWitnesses txWitnesses
6060
61+
-- Datum lookup for spent outputs (Spend txin). Uses initial UTxO snapshot, utxoSpend₀.
6162
getDatum : Tx ℓ → UTxO → ScriptPurpose → Maybe Datum
62-
getDatum tx utxo (Spend txin) =
63-
do (_ , _ , just d , _) ← lookupᵐ? utxo txin where
63+
getDatum tx utxoSpend₀ (Spend txin) =
64+
do (_ , _ , just d , _) ← lookupᵐ? utxoSpend₀ txin where
6465
(_ , _ , nothing , _) → nothing
6566
case d of λ where
6667
(inj₁ d) → just d
@@ -125,20 +126,19 @@ txInfo TxLevelSub utxo tx =
125126
126127
txInfoForPurpose : (ℓ : TxLevel) → UTxO → Tx ℓ → ScriptPurpose → TxInfo
127128
129+
-- subtransactions: never get subTx infos (even if the ScriptPurpose is Guard).
130+
txInfoForPurpose TxLevelSub utxo tx _ = txInfo TxLevelSub utxo tx
128131
129-
txInfoForPurpose TxLevelSub utxo tx sp = txInfo TxLevelSub utxo tx
130-
-- SubTx scripts never get subTx infos (even if their ScriptPurpose is Guard).
131-
132+
-- top-level transactions:
132133
txInfoForPurpose TxLevelTop utxo tx sp with sp
133-
-- Top-level scripts:
134-
-- · guard scripts see subTx infos
134+
-- · guard scripts see subTx infos
135135
... | Guard _ = record base { txInfoSubTxs = just subInfos }
136136
where
137137
base : TxInfo
138138
base = txInfo TxLevelTop utxo tx
139139
subInfos : List SubTxInfo
140140
subInfos = map (txInfo TxLevelSub utxo) (SubTransactionsOf tx)
141-
-- · other top-level scripts see no subTx infos
141+
-- · other top-level scripts see no subTx infos
142142
... | _ = txInfo TxLevelTop utxo tx
143143
```
144144

@@ -171,12 +171,10 @@ credsNeeded TxLevelSub utxo txb = credsNeededMinusCollateral txb
171171
txOutToDataHash : TxOut → Maybe DataHash
172172
txOutToDataHash (_ , _ , d , _) = d >>= isInj₂
173173
174-
txOutToP2Script
175-
: UTxO → (Tx ℓ)
176-
→ TxOut → Maybe P2Script
177-
txOutToP2Script utxo tx (a , _) =
174+
txOutToP2Script : UTxO → UTxO → (Tx ℓ) → TxOut → Maybe P2Script
175+
txOutToP2Script utxoSpend₀ utxoRefView tx (a , _) =
178176
do sh ← isScriptObj (payCred a)
179-
s ← lookupScriptHash sh tx utxo
177+
s ← lookupScriptHash sh tx utxoSpend₀ utxoRefView
180178
toP2Script s
181179
-- opaque
182180
-- collectP2ScriptsWithContext

src/Ledger/Dijkstra/Specification/Transaction.lagda.md

Lines changed: 60 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ can include required top-level guards (the `txRequiredTopLevelGuards`{.AgdaField
6060

6161
To that end, we define two auxiliary functions that will aid in
6262
specifying which record fields of a transaction body are present at
63-
each `TxLevel`{.agdatype}:
63+
each `TxLevel`{.AgdaDatatype}:
6464

6565
```agda
6666
InTopLevel : TxLevel → Type → Type
@@ -73,7 +73,7 @@ InSubLevel TxLevelTop _ = ⊤
7373
```
7474

7575
These functions discriminate on an argument of type
76-
`TxLevel`{.agdatype} and either act as the identity function on types
76+
`TxLevel`{.AgdaDatatype} and either act as the identity function on types
7777
or as the constant function that returns the unit type.
7878

7979
<!--
@@ -291,6 +291,14 @@ could be either of them.
291291
field TxIdOf : A → TxId
292292
open HasTxId ⦃...⦄ public
293293
294+
record HasSpendInputs {a} (A : Type a) : Type a where
295+
field SpendInputsOf : A → ℙ TxIn
296+
open HasSpendInputs ⦃...⦄ public
297+
298+
record HasReferenceInputs {a} (A : Type a) : Type a where
299+
field ReferenceInputsOf : A → ℙ TxIn
300+
open HasReferenceInputs ⦃...⦄ public
301+
294302
record HasFees? {a} (A : Type a) : Type a where
295303
field FeesOf? : A → Maybe Fees
296304
open HasFees? ⦃...⦄ public
@@ -339,6 +347,18 @@ could be either of them.
339347
HasWithdrawals-Tx : HasWithdrawals (Tx txLevel)
340348
HasWithdrawals-Tx .WithdrawalsOf = WithdrawalsOf ∘ TxBodyOf
341349
350+
HasSpendInputs-TxBody : HasSpendInputs (TxBody txLevel)
351+
HasSpendInputs-TxBody .SpendInputsOf = TxBody.txIns
352+
353+
HasSpendInputs-Tx : HasSpendInputs (Tx txLevel)
354+
HasSpendInputs-Tx .SpendInputsOf = SpendInputsOf ∘ TxBodyOf
355+
356+
HasReferenceInputs-TxBody : HasReferenceInputs (TxBody txLevel)
357+
HasReferenceInputs-TxBody .ReferenceInputsOf = TxBody.refInputs
358+
359+
HasReferenceInputs-Tx : HasReferenceInputs (Tx txLevel)
360+
HasReferenceInputs-Tx .ReferenceInputsOf = ReferenceInputsOf ∘ TxBodyOf
361+
342362
HasValue-TxBody : HasValue (TxBody txLevel)
343363
HasValue-TxBody .ValueOf = TxBody.mint
344364
@@ -403,23 +423,36 @@ This section collects some unimportant but useful helper and accessor functions.
403423
404424
txinsScript : ℙ TxIn → UTxO → ℙ TxIn
405425
txinsScript txins utxo = txins ∩ dom (proj₁ (scriptOuts utxo))
426+
```
427+
428+
In the Dijkstra era, *spending* inputs must exist in the initial UTxO snapshot, while
429+
*reference* inputs may see earlier outputs, so we need two UTxO views:
406430

407-
refScripts : Tx txLevel → UTxO → List Script
408-
refScripts tx utxo =
409-
mapMaybe (proj₂ ∘ proj₂ ∘ proj₂) $ setToList (range (utxo ∣ (txIns ∪ refInputs)))
410-
where open Tx; open TxBody (TxBodyOf tx)
431+
+ `utxoSpend₀`{.AgdaBound}, the initial snapshot, used for `txIns`{.AgdaField},
432+
+ `utxoRefView`{.AgdaBound}, the evolving view, used for `refInputs`{.AgdaField}.
411433

412-
txscripts : Tx txLevel → UTxO → ℙ Script
413-
txscripts tx utxo = scripts (tx .txWitnesses) ∪ fromList (refScripts tx utxo)
434+
Thus functions like `refScripts`{.AgdaFunction}, `txscripts`{.AgdaFunction},
435+
and `lookupScriptHash`{.AgdaFunction} (defined below) will take *two* UTxO arguments.
436+
437+
```agda
438+
refScripts : Tx txLevel → UTxO → UTxO → List Script
439+
refScripts tx utxoSpend₀ utxoRefView =
440+
mapMaybe (proj₂ ∘ proj₂ ∘ proj₂) ( setToList (range (utxoSpend₀ ∣ txIns))
441+
++ setToList (range (utxoRefView ∣ refInputs))
442+
)
443+
where open Tx tx; open TxBody (TxBodyOf tx)
444+
445+
txscripts : Tx txLevel → UTxO → UTxO → ℙ Script
446+
txscripts tx utxoSpend₀ utxoRefView =
447+
scripts (tx .txWitnesses) ∪ fromList (refScripts tx utxoSpend₀ utxoRefView)
414448
where open Tx; open TxWitnesses
415449
416-
lookupScriptHash : ScriptHash → Tx txLevel → UTxO → Maybe Script
417-
lookupScriptHash sh tx utxo =
418-
if sh ∈ mapˢ proj₁ (m ˢ) then
419-
just (lookupᵐ m sh)
420-
else
421-
nothing
422-
where m = setToMap (mapˢ < hash , id > (txscripts tx utxo))
450+
lookupScriptHash : ScriptHash → Tx txLevel → UTxO → UTxO → Maybe Script
451+
lookupScriptHash sh tx utxoSpend₀ utxoRefView =
452+
if sh ∈ mapˢ proj₁ (m ˢ) then just (lookupᵐ m sh) else nothing
453+
where m = setToMap (mapˢ < hash , id > (txscripts tx utxoSpend₀ utxoRefView))
454+
455+
-- TODO: implement the actual evolving `utxoRefView` (issue #1006)
423456
424457
getSubTxScripts : SubLevelTx → ℙ (TxId × ScriptHash)
425458
getSubTxScripts subtx = mapˢ (λ hash → (TxIdOf subtx , hash)) (ScriptHashes subtx)
@@ -520,6 +553,14 @@ The Dijkstra era introduces *nested transactions* (a single top-level transactio
520553
that contains a list of sub-transactions) and *guards* (CIP-0112 / CIP-0118). As a
521554
result, several checks that were "per-transaction" in Conway become *batch-aware*.
522555

556+
**Design Note** (CIP-0118: spending inputs vs reference inputs).
557+
For Dijkstra batches, we distinguish *spending inputs* from *reference inputs*.
558+
All spending inputs across the whole batch must exist in the initial UTxO snapshot
559+
(mempool safety). Reference inputs may additionally point to outputs of **preceding
560+
subtransactions in the batch order**. Therefore reference-script/datum lookup is
561+
performed against an *evolving, tentative UTxO view* (prefix-applied), used only for
562+
lookup/validation, while *ledger state is committed only if the full batch succeeds*.
563+
523564
### Key points
524565

525566
1. **No further nesting**
@@ -537,17 +578,17 @@ result, several checks that were "per-transaction" in Conway become *batch-aware
537578

538579
3. **Batch-wide phase-2 evaluation**
539580

540-
Phase-2 (Plutus) validation is performed **once**
541-
for the whole batch (mempool safety), even though script inputs/contexts are
542-
constructed from both sub- and top-level components.
581+
Phase-2 (Plutus) validation is performed *once* for the whole batch (mempool
582+
safety), even though script inputs/contexts are constructed from both sub- and
583+
top-level components.
543584

544585
4. **Guards are credentials**
545586

546587
A transaction body may specify *guards* as a set of *credentials* (key or
547588
script). This is distinct from the legacy "required signer" key hashes used for
548589
phase-1 key-witness checking and native/timelock scripts.
549590

550-
5. **Required top-level guards are requests (list-like)**
591+
5. **Required top-level guards are requests**
551592

552593
Sub-transaction bodies may include a list of "required top-level guard" requests
553594
(credential plus optional datum). Ledger phase-1 checks must ensure that the
@@ -581,28 +622,6 @@ result, several checks that were "per-transaction" in Conway become *batch-aware
581622
referenced are defined by the UTxO rules.)
582623

583624

584-
<!--
585-
The following text previously appeared in item 4.
586-
587-
> All scripts are shared across all transactions within a single batch, so
588-
> attaching one script to either a sub- or a top-level-transaction allows other
589-
> transactions to run it without also including it in its own scripts. This
590-
> includes reference scripts that are sourced from the outputs to which reference
591-
> inputs point in the UTxO. These referenced UTxO entries could be outputs of
592-
> preceding transactions in the batch.
593-
594-
> Datums (from reference inputs and from other transactions) are also shared in
595-
> this way. As before, only the datums fixed by the executing transaction are
596-
> included in the `TxInfo`{.AgdaRecord} constructed for its scripts, however, now
597-
> they don't necessarily have to be attached to that transaction.
598-
599-
I've removed these two paragraphs for now because I'm not sure they're accurate.
600-
I've replaced them with an explanation that removes the contradiction between "ref
601-
inputs may refer to earlier tx outputs in the batch" vs "all inputs must exist before
602-
applying any tx in the batch," and punts the exact constraint to the UTxO rules
603-
(where I think it belongs).
604-
-->
605-
606625
[^1]: See CIP 0118; once finalized and merged, CIP 0118 will appear in the
607626
main branch of [the CIP repository][CIPs]; until then, it can be found
608627
at <https://github.com/polinavino/CIPs/tree/polina/CIP0118/CIP-0118>.

src/Ledger/Dijkstra/Specification/Utxo.lagda.md

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ record UTxOEnv : Type where
4141
slot : Slot
4242
pparams : PParams
4343
treasury : Treasury
44+
utxo₀ : UTxO
4445
4546
record UTxOState : Type where
4647
field
@@ -53,9 +54,14 @@ record UTxOState : Type where
5354
<!--
5455
```agda
5556
instance
57+
HasUTxO-UTxOEnv : HasUTxO UTxOEnv
58+
HasUTxO-UTxOEnv .UTxOOf = UTxOEnv.utxo₀
59+
60+
HasUTxO-UTxOState : HasUTxO UTxOState
61+
HasUTxO-UTxOState .UTxOOf = UTxOState.utxo
62+
5663
unquoteDecl HasCast-UTxOEnv HasCast-UTxOState = derive-HasCast
57-
( (quote UTxOEnv , HasCast-UTxOEnv ) ∷
58-
[ (quote UTxOState , HasCast-UTxOState) ])
64+
((quote UTxOEnv , HasCast-UTxOEnv ) ∷ (quote UTxOState , HasCast-UTxOState) ∷ [])
5965
```
6066
-->
6167

@@ -83,40 +89,40 @@ data _⊢_⇀⦇_,UTXOS⦈_ : UTxOEnv → UTxOState → TopLevelTx → UTxOState
8389
Γ ⊢ s ⇀⦇ tx ,UTXOS⦈ s
8490
```
8591

86-
## UTXO
92+
## The <span class="AgdaDatatype">UTXO</span> Transition System {#sec:the-utxo-transition-system}
93+
94+
### New in Dijkstra
8795

88-
(skeleton with the new phase-1 check)
96+
1. The set of spending inputs must exist in the UTxO _before_ applying the
97+
transaction (or partially applying any part of it). TODO: Add link to CIP once its finalized.
8998

90-
This is the intended home of Dijkstra "phase-1 structural checks."
9199

92100
```agda
93101
data _⊢_⇀⦇_,SUBUTXO⦈_ : UTxOEnv → UTxOState → SubLevelTx → UTxOState → Type where
94102
95103
SUBUTXO :
96-
let txb = Tx.txBody tx
97-
subTxs = TxBody.txSubTransactions txb
98-
in
104+
99105
────────────────────────────────
100106
Γ ⊢ s ⇀⦇ stx ,SUBUTXO⦈ s'
101107
102108
data _⊢_⇀⦇_,UTXO⦈_ : UTxOEnv → UTxOState → TopLevelTx → UTxOState → Type where
103109
104110
UTXO :
105-
let txb = Tx.txBody tx
106-
subTxs = TxBody.txSubTransactions txb
107-
in
108-
∙ requiredTopLevelGuardsSatisfied tx subTxs
111+
112+
∙ SpendInputsOf tx ≢ ∅
113+
∙ SpendInputsOf tx ⊆ dom (UTxOOf Γ) -- (1)
114+
∙ ReferenceInputsOf tx ⊆ dom (UTxOOf s)
115+
∙ requiredTopLevelGuardsSatisfied tx (SubTransactionsOf tx) -- (2)
109116
∙ Γ ⊢ s ⇀⦇ tx ,UTXOS⦈ s'
110117
────────────────────────────────
111118
Γ ⊢ s ⇀⦇ tx ,UTXO⦈ s'
112119
```
113120

114-
**Note.** The premise `requiredTopLevelGuardsSatisfied tx subTxs`
115-
is explicitly a **phase-1** condition (CIP-0118): every guard credential requested by
116-
subTx bodies must appear in the top-level `txGuards` set.
121+
**Notes**.
117122

118-
A couple of details to notice:
123+
1. The set of spending inputs must exist in the UTxO *before* applying the
124+
transaction (or partially applying any part of it).
119125

120-
- `txb` is computed via `Tx.txBody tx` (no reliance on `TxBodyOf` instances).
121-
- `UTXOS-stub` returns the **same** state (so we can typecheck
122-
everything without inventing semantics prematurely).
126+
2. The premise `requiredTopLevelGuardsSatisfied tx subTxs` is explicitly a
127+
phase-1 condition (CIP-0118): every guard credential requested by subtransaction
128+
bodies must appear in the top-level `txGuards` set.

0 commit comments

Comments
 (0)