Transaction¶
{-# OPTIONS --safe #-} module Ledger.Dijkstra.Specification.Transaction where import Data.Maybe.Base as M open import Ledger.Prelude renaming (filterᵐ to filter) open import Ledger.Core.Specification.Crypto open import Ledger.Core.Specification.Epoch open import Ledger.Dijkstra.Specification.Gov.Base import Ledger.Core.Specification.Address renaming (RwdAddr to RewardAddress) import Ledger.Dijkstra.Specification.Certs import Ledger.Dijkstra.Specification.Gov.Actions import Ledger.Dijkstra.Specification.PParams import Ledger.Dijkstra.Specification.Script import Ledger.Dijkstra.Specification.TokenAlgebra.Base open import Tactic.Derive.DecEq open import Relation.Nullary.Decidable using (⌊_⌋)
A transaction in Dijkstra is very similar to a transaction in Conway except that now, as described in CIP 01181, it may include
- other (sub)transactions as part of its body;
- guard scripts.
Before continuing, we remark that transactions cannot be arbitrarily nested. That is, a transaction (henceforth refered as top-level transaction) can include subtransactions, but these cannot include other subtransactions.
Transaction Levels¶
To differentiate between the two types of transactions (i.e. top-level and sub-level), we define the type of transaction level.
data TxLevel : Type where TxLevelTop TxLevelSub : TxLevel
This type will be used, among other purposes, to provide a concise definition of the types of top-level and sub transactions in the Transactions section below.
To that end, we define two auxiliary functions that will aid in
specifying which record fields of a transaction body are present at
each TxLevel
:
InTopLevel : TxLevel → Type → Type InTopLevel TxLevelTop X = X InTopLevel TxLevelSub _ = ⊤ InSubLevel : TxLevel → Type → Type InSubLevel TxLevelSub X = X InSubLevel TxLevelTop _ = ⊤
These functions discriminate on an argument of type
TxLevel
and either act as the identity function on types
or as the constant function that returns the unit type.
unquoteDecl DecEq-TxLevel = derive-DecEq ((quote TxLevel , DecEq-TxLevel) ∷ []) private variable txLevel : TxLevel data Tag : TxLevel → Type where Spend Mint Cert Reward Vote Propose Guard : Tag txLevel SubGuard : Tag TxLevelSub unquoteDecl DecEq-Tag = derive-DecEq ((quote Tag , DecEq-Tag) ∷ [])
Transactions¶
record TransactionStructure : Type₁ where field Ix TxId AuxiliaryData : Type ⦃ DecEq-Ix ⦄ : DecEq Ix ⦃ DecEq-TxId ⦄ : DecEq TxId adHashingScheme : isHashableSet AuxiliaryData open isHashableSet adHashingScheme renaming (THash to ADHash) public field globalConstants : _ open GlobalConstants globalConstants public field crypto : _ open CryptoStructure crypto public open Ledger.Dijkstra.Specification.TokenAlgebra.Base ScriptHash public open Ledger.Core.Specification.Address Network KeyHash ScriptHash ⦃ it ⦄ ⦃ it ⦄ ⦃ it ⦄ public field epochStructure : _ open EpochStructure epochStructure public open Ledger.Dijkstra.Specification.Script crypto epochStructure public field scriptStructure : _ open ScriptStructure scriptStructure public open Ledger.Dijkstra.Specification.PParams crypto epochStructure scriptStructure public field govParams : _ open GovParams govParams public field tokenAlgebra : TokenAlgebra open TokenAlgebra tokenAlgebra public field txidBytes : TxId → Ser govStructure : GovStructure govStructure = record -- TODO: figure out what to do with the hash { TxId = TxId; DocHash = ADHash ; cryptoStructure = crypto ; epochStructure = epochStructure ; scriptStructure = scriptStructure ; govParams = govParams ; globalConstants = globalConstants } module GovernanceActions = Ledger.Dijkstra.Specification.Gov.Actions govStructure open GovernanceActions hiding (Vote; yes; no; abstain) public open import Ledger.Dijkstra.Specification.Certs govStructure TxIn : Type TxIn = TxId × Ix TxOut : Type TxOut = Addr × Value × Maybe (Datum ⊎ DataHash) × Maybe Script UTxO : Type UTxO = TxIn ⇀ TxOut -- Datums : Type -- Datums = DataHash ⇀ Datum RedeemerPtr : TxLevel → Type RedeemerPtr txLevel = Tag txLevel × Ix ProposedPPUpdates : Type ProposedPPUpdates = KeyHash ⇀ PParamsUpdate Update : Type Update = ProposedPPUpdates × Epoch record HasUTxO {a} (A : Type a) : Type a where field UTxOOf : A → UTxO open HasUTxO ⦃...⦄ public
The type of transactions is defined as three mutually recursive
records parameterised by a value of type TxLevel
.
The fields that depend on the transaction level use the auxiliary functions
InTopLevel
and InSubLevel
defined in the section on Transaction Levels.
mutual record Tx (txLevel : TxLevel) : Type where inductive field txBody : TxBody txLevel txWitnesses : TxWitnesses txLevel isValid : InTopLevel txLevel Bool txAuxData : Maybe AuxiliaryData record TxBody (txLevel : TxLevel) : Type where inductive field txIns : ℙ TxIn refInputs : ℙ TxIn collateralInputs : InTopLevel txLevel (ℙ TxIn) -- only in top-level tx txOuts : Ix ⇀ TxOut txId : TxId txCerts : List DCert txFee : InTopLevel txLevel Fees -- only in top-level tx txWithdrawals : Withdrawals txVldt : Maybe Slot × Maybe Slot txADhash : Maybe ADHash txDonation : Donations txGovVotes : List GovVote txGovProposals : List GovProposal txNetworkId : Maybe Network currentTreasury : Maybe Coin mint : Value scriptIntegrityHash : Maybe ScriptHash -- New in Dijkstra -- -- txSubTransactions : InTopLevel txLevel (List (Tx TxLevelSub)) -- only in top-level tx -- ^^^^^^^^^^ should this be a set? i.e. InTopLevel txLevel (ℙ (Tx TxLevelSub)) -- (in getTxScripts function below we need it as a set) -- txRequiredGuards : ℙ KeyHash -- replaces reqSigHash : ℙ KeyHash txRequiredTopLevelGuards : InSubLevel txLevel (ScriptHash ⇀ Datum) -- only in sub-level tx --------------------- record TxWitnesses (txLevel : TxLevel) : Type where inductive field vKeySigs : VKey ⇀ Sig scripts : ℙ Script txData : DataHash ⇀ Datum txRedeemers : RedeemerPtr txLevel ⇀ Redeemer × ExUnits scriptsP1 : ℙ P1Script scriptsP1 = mapPartial isInj₁ scripts
Using these types, we define the types of top-level and sub transaction as follows:
TopLevelTx : Type TopLevelTx = Tx TxLevelTop SubLevelTx : Type SubLevelTx = Tx TxLevelSub
In addition, we define the type of transactions in which its level could be either of them.
AnyLevelTx : Type AnyLevelTx = TopLevelTx ⊎ SubLevelTx
Changes to Transaction Validity¶
As discussed in [Ledger.Conway.Specification.Properties][], transaction validity is tricky, and this is as true in the Dijkstra era as it was in Conway, if not moreso.
Here are some key points about transaction validity in the Dijkstra era.
-
Sub-transactions are not allowed to contain sub-transactions themselves.
-
Sub-transactions are not allowed to contain collateral inputs. Only a top-level transaction is allowed to (furthermore, obligated to) provide sufficient collateral for all scripts that are run as part of validating all transactions in that batch. If any script in a batch fails, none of the transactions in the batch is applied; only the collateral is collected.
-
Transactions using the new features of the Dijkstra era are not allowed to run PlutusV3 scripts (nor earlier Plutus version scripts).
-
All scripts are shared across all transactions within a single batch, so attaching one script to either a sub- or a top-level-transaction allows other transactions to run it without also including it in its own scripts. This includes references scripts that are sourced from the outputs to which reference inputs point in the UTxO. These referenced UTxO entries could be outputs of preceding transactions in the batch.
Datums (from reference inputs and from other transactions) are also shared in this way. As before, only the datums fixed by the executing transaction are included in the
TxInfo
constructed for its scripts, however, now they don't necessarily have to be attached to that transaction. -
All inputs of all transactions in a single batch must be contained in the UTxO set before any of the batch transactions are applied. This ensures that operation of scripts is not disrupted, for example, by temporarily duplicating thread tokens, or falsifying access to assets via flash loans. In the future, this may be up for reconsideration.
-
The batch must be balanced; i.e., preservation of value (POV) must hold. The updated
produced
andconsumed
calculations sum up the appropriate quantities not for individual transactions, but for the entire batch, which includes the top-level transaction and all its sub-transactions. -
All transactions (sub- and top-level) may specify a non-zero fee. The total fee summed up across all transactions in a batch is required to cover the minimum fees of all transactions. The fees specified in all transactions are always collected. Individual transactions in a batch do not need to meet the min-fee requirement.
-
The total size of the top-level transaction (including all its sub-transactions) must be less than the
maxTxSize
. This constraint is necessary to ensure efficient network operation since batches will be transmitted wholesale across the Cardano network.
-
See CIP 0118; once finalized and merged, CIP 0118 will appear in the main branch of the CIP repository; until then, it can be found at https://github.com/polinavino/CIPs/tree/polina/CIP0118/CIP-0118. ↩