diff --git a/alonzo/impl/cardano-ledger-alonzo.cabal b/alonzo/impl/cardano-ledger-alonzo.cabal index eca3512219a..a4e0ee45371 100644 --- a/alonzo/impl/cardano-ledger-alonzo.cabal +++ b/alonzo/impl/cardano-ledger-alonzo.cabal @@ -38,7 +38,8 @@ library deepseq, nothunks, shelley-spec-ledger, - small-steps + small-steps, + text hs-source-dirs: src ghc-options: @@ -86,6 +87,7 @@ test-suite tests Test.Cardano.Ledger.Alonzo.Serialisation.Tripping build-depends: base >=4.14 && <4.15, + base16-bytestring, bytestring, cardano-binary, cardano-ledger-alonzo, diff --git a/alonzo/impl/src/Cardano/Ledger/Alonzo/Data.hs b/alonzo/impl/src/Cardano/Ledger/Alonzo/Data.hs index ad643c0b8b2..b7ceb7f4a97 100644 --- a/alonzo/impl/src/Cardano/Ledger/Alonzo/Data.hs +++ b/alonzo/impl/src/Cardano/Ledger/Alonzo/Data.hs @@ -1,37 +1,82 @@ {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE DerivingVia #-} {-# LANGUAGE EmptyDataDeriving #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TypeFamilies #-} module Cardano.Ledger.Alonzo.Data - ( Data (..), + ( PlutusData (..), + -- Figure 2 (partial list) + Data (Data, ..), EraIndependentData, DataHash (..), hashData, ) where -import Cardano.Binary (FromCBOR (..), ToCBOR (..)) +import Cardano.Binary (FromCBOR (..), ToCBOR (..), decodeInt, encodeInt) import qualified Cardano.Crypto.Hash as Hash import Cardano.Ledger.Crypto (HASH) import qualified Cardano.Ledger.Crypto as CC import Cardano.Ledger.Era (Crypto, Era) import Control.DeepSeq (NFData) import Data.Coders +import Data.MemoBytes (Mem, MemoBytes (..), memoBytes) import GHC.Generics (Generic) import NoThunks.Class (NoThunks) import Shelley.Spec.Ledger.Hashing (HashAnnotated (..)) --- | TODO this should be isomorphic to the plutus (alonzo version) type -data Data era = NotReallyData - deriving (Eq, Ord, Generic, Show) +-- ===================================================================== +-- PlutusData is a placeholder for the type that Plutus expects as data. + +data PlutusData = NotReallyData + deriving (Eq, Show, Ord, Generic) + +instance NoThunks PlutusData + +-- | TODO appropriate serialisation for the Real Plutus Data +instance ToCBOR (PlutusData) where + toCBOR _ = encodeInt 0 + +instance FromCBOR (PlutusData) where + fromCBOR = do + i <- decodeInt + case i of + 0 -> pure NotReallyData + _ -> fail "oh no" + +instance FromCBOR (Annotator PlutusData) where + fromCBOR = pure <$> fromCBOR + +-- ============================================================================ +-- the newtype Data is a wrapper around the type that Plutus expects as data. +-- The newtype will memoize the serialized bytes. The strategy is to replace +-- PlutusData with the correct type + +newtype Data era = DataConstr (MemoBytes (PlutusData)) + deriving (Eq, Ord, Generic, ToCBOR, Show) + +deriving via + (Mem PlutusData) + instance + (Era era) => + FromCBOR (Annotator (Data era)) instance NoThunks (Data era) +pattern Data :: PlutusData -> Data era +pattern Data p <- + DataConstr (Memo p _) + where + Data p = DataConstr (memoBytes (To p)) + +-- ============================================================================= + data EraIndependentData newtype DataHash crypto @@ -53,15 +98,3 @@ hashData = DataHash . hashAnnotated -------------------------------------------------------------------------------- -- Serialisation -------------------------------------------------------------------------------- - --- | TODO appropriate serialisation -instance Era era => ToCBOR (Data era) where - toCBOR = encode . encodeData - where - encodeData NotReallyData = Sum NotReallyData 0 - -instance Era era => FromCBOR (Data era) where - fromCBOR = decode $ Summands "Data" decodeData - where - decodeData 0 = SumD NotReallyData - decodeData n = Invalid n diff --git a/alonzo/impl/src/Cardano/Ledger/Alonzo/PParams.hs b/alonzo/impl/src/Cardano/Ledger/Alonzo/PParams.hs index 0370239ac9f..1bbcf97ca76 100644 --- a/alonzo/impl/src/Cardano/Ledger/Alonzo/PParams.hs +++ b/alonzo/impl/src/Cardano/Ledger/Alonzo/PParams.hs @@ -3,10 +3,14 @@ {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE DerivingVia #-} +{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE GADTs #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE LambdaCase #-} +{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeApplications #-} @@ -24,11 +28,14 @@ module Cardano.Ledger.Alonzo.PParams PParamsUpdate, emptyPParamsUpdate, updatePParams, + hashLanguagePP, ) where import Cardano.Binary - ( FromCBOR (..), + ( Annotator, + Decoder, + FromCBOR (..), ToCBOR (..), decodeWord, encodeListLen, @@ -36,18 +43,26 @@ import Cardano.Binary encodeWord, ) import qualified Cardano.Crypto.Hash as Hash -import Cardano.Ledger.Alonzo.Scripts (CostModel, ExUnits (..), Language, Prices (..)) +import Cardano.Ledger.Alonzo.Scripts + ( CostModel, + ExUnits (..), + Language, + Prices (..), + hashCostModel, + ) import Cardano.Ledger.Crypto (HASH) import qualified Cardano.Ledger.Crypto as CC import Cardano.Ledger.Era import Control.DeepSeq (NFData) import Control.Monad (unless) +import Data.Coders (Decode (..), decode, (<*!)) import Data.Foldable (fold) import Data.Functor.Identity (Identity) import Data.List (nub) import Data.Map.Strict (Map) import qualified Data.Map.Strict as Map import Data.Maybe (fromMaybe, mapMaybe) +import Data.Proxy import GHC.Generics (Generic) import NoThunks.Class (NoThunks (..)) import Numeric.Natural (Natural) @@ -67,7 +82,7 @@ import Shelley.Spec.Ledger.Serialization ( FromCBORGroup (..), ToCBORGroup (..), decodeMapContents, - decodeRecordNamed, + decodeMapTraverse, mapFromCBOR, mapToCBOR, ratioFromCBOR, @@ -75,6 +90,7 @@ import Shelley.Spec.Ledger.Serialization ) import Shelley.Spec.Ledger.Slot (EpochNo (..)) +-- ================================================================ -- TODO -- make type families for PParams and PParamsUpdate -- what is the encodeListLen ?? @@ -193,33 +209,42 @@ instance (Era era) => ToCBOR (PParams era) where <> toCBOR maxTxExUnits' <> toCBOR maxBlockExUnits' -instance (Era era) => FromCBOR (PParams era) where - fromCBOR = do - decodeRecordNamed "PParams" (const 22) $ - PParams - <$> fromCBOR -- _minfeeA :: Integer - <*> fromCBOR -- _minfeeB :: Natural - <*> fromCBOR -- _maxBBSize :: Natural - <*> fromCBOR -- _maxTxSize :: Natural - <*> fromCBOR -- _maxBHSize :: Natural - <*> fromCBOR -- _keyDeposit :: Coin - <*> fromCBOR -- _poolDeposit :: Coin - <*> fromCBOR -- _eMax :: EpochNo - <*> fromCBOR -- _nOpt :: Natural - <*> ratioFromCBOR -- _a0 :: Rational - <*> fromCBOR -- _rho :: UnitInterval - <*> fromCBOR -- _tau :: UnitInterval - <*> fromCBOR -- _d :: UnitInterval - <*> fromCBOR -- _extraEntropy :: Nonce - <*> fromCBORGroup -- _protocolVersion :: ProtVer - <*> fromCBOR -- _minPoolCost :: Natural +instance + (Era era) => + FromCBOR (Annotator (PParams era)) + where + fromCBOR = + decode $ + Ann (RecD PParams) + <*! Ann From -- _minfeeA :: Integer + <*! Ann From -- _minfeeB :: Natural + <*! Ann From -- _maxBBSize :: Natural + <*! Ann From -- _maxTxSize :: Natural + <*! Ann From -- _maxBHSize :: Natural + <*! Ann From -- _keyDeposit :: Coin + <*! Ann From -- _poolDeposit :: Coin + <*! Ann From -- _eMax :: EpochNo + <*! Ann From -- _nOpt :: Natural + <*! Ann (D ratioFromCBOR) -- _a0 :: Rational + <*! Ann From -- _rho :: UnitInterval + <*! Ann From -- _tau :: UnitInterval + <*! Ann From -- _d :: UnitInterval + <*! Ann From -- _extraEntropy :: Nonce + <*! Ann (D fromCBORGroup) -- _protocolVersion :: ProtVer + <*! Ann From -- _minPoolCost :: Natural -- new/updated for alonzo - -- TODO what should all these really be? - <*> fromCBOR -- _adaPerUTxOByte :: - <*> fromCBOR -- _costmdls = costmdls', - <*> fromCBOR -- _prices = prices', - <*> fromCBOR -- _maxTxExUnits = maxTxExUnits', - <*> fromCBOR -- _maxBlockExUnits = maxBlockExUnits' + <*! Ann From -- _adaPerUTxOByte :: + <*! D (splitMapFromCBOR fromCBOR fromCBOR) -- _costmdls = costmdls', + <*! Ann From -- _prices = prices', + <*! Ann From -- _maxTxExUnits = maxTxExUnits', + <*! Ann From -- _maxBlockExUnits = maxBlockExUnits' + +splitMapFromCBOR :: + Ord dom => + Decoder s dom -> + Decoder s (Annotator rng) -> + Decoder s (Annotator (Map dom rng)) +splitMapFromCBOR a b = decodeMapTraverse (pure <$> a) b -- | Returns a basic "empty" `PParams` structure with all zero values. emptyPParams :: PParams era @@ -342,7 +367,8 @@ instance (Era era) => FromCBOR (PParamsUpdate era) where 15 -> fromCBOR >>= \x -> pure (15, \up -> up {_minPoolCost = SJust x}) -- new/updated for alonzo 16 -> fromCBOR >>= \x -> pure (15, \up -> up {_adaPerUTxOByte = SJust x}) - 17 -> fromCBOR >>= \x -> pure (15, \up -> up {_costmdls = SJust x}) + -- THIS STUB WILL HAVE to be adjusted since CostModel only has FromCBOR(Annotator _) instance + -- 17 -> fromCBOR >>= \x -> pure (15, \up -> up {_costmdls = SJust x}) 18 -> fromCBOR >>= \x -> pure (15, \up -> up {_prices = SJust x}) 19 -> fromCBOR >>= \x -> pure (15, \up -> up {_maxTxExUnits = SJust x}) 20 -> fromCBOR >>= \x -> pure (15, \up -> up {_maxBlockExUnits = SJust x}) @@ -401,15 +427,26 @@ updatePParams pp ppup = fromMaybe' :: a -> StrictMaybe a -> a fromMaybe' x = fromMaybe x . strictMaybeToMaybe -data EraIndependentPP +-- =================================================== +-- Figure 1: "Definitions Used in Protocol Parameters" -- Hash of a subset of Protocol Parameters relevant to Plutus script evaluation newtype PPHash crypto = PPHash - (Hash.Hash (HASH crypto) EraIndependentPP) + (Hash.Hash (HASH crypto) CostModel) deriving (Show, Eq, Ord, Generic) deriving newtype (NFData, NoThunks) deriving newtype instance CC.Crypto crypto => FromCBOR (PPHash crypto) deriving newtype instance CC.Crypto crypto => ToCBOR (PPHash crypto) + +hashLanguagePP :: forall era. Era era => PParams era -> Language -> PPHash (Crypto era) +hashLanguagePP pp lang = PPHash (hashCostModel (Proxy @era) cm) + where + cm :: CostModel + cm = case Map.lookup lang (_costmdls pp) of + Just x -> x + Nothing -> error ("CostModel map does not have cost for language: " ++ show lang) + +-- ============================================================= diff --git a/alonzo/impl/src/Cardano/Ledger/Alonzo/Scripts.hs b/alonzo/impl/src/Cardano/Ledger/Alonzo/Scripts.hs index 4f4bc4f4829..8c0c15203b7 100644 --- a/alonzo/impl/src/Cardano/Ledger/Alonzo/Scripts.hs +++ b/alonzo/impl/src/Cardano/Ledger/Alonzo/Scripts.hs @@ -1,24 +1,34 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DerivingVia #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-} +-- Needed for FromCBOR(Annotator CostModel) +{-# OPTIONS_GHC -fno-warn-orphans #-} module Cardano.Ledger.Alonzo.Scripts ( Tag (..), Script (..), ExUnits (..), - CostModel, - Language, + CostModel (CostModel), + Language (..), Prices (..), + proxyhash, + hashCostModel, ) where import Cardano.Binary (FromCBOR (fromCBOR), ToCBOR (toCBOR)) +import qualified Cardano.Crypto.Hash as Hash +import Cardano.Ledger.Crypto (HASH) import qualified Cardano.Ledger.Crypto as CC (Crypto) import Cardano.Ledger.Era (Era (Crypto)) import Cardano.Ledger.ShelleyMA.Timelocks @@ -26,12 +36,18 @@ import Control.DeepSeq (NFData (..)) import Data.ByteString (ByteString) import Data.Coders import Data.Map (Map) +import Data.MemoBytes import Data.Typeable import Data.Word (Word64) import GHC.Generics (Generic) import NoThunks.Class (NoThunks) import Shelley.Spec.Ledger.Coin (Coin (..)) +type SafeHash e t = Hash.Hash (HASH (Crypto e)) t + +proxyhash :: forall e t. (ToCBOR t, Era e) => Proxy e -> t -> SafeHash e t +proxyhash Proxy x = Hash.castHash (Hash.hashWithSerialiser @(HASH (Crypto e)) toCBOR x) + -- | Marker indicating the part of a transaction for which this script is acting -- as a validator. data Tag @@ -75,7 +91,10 @@ instance Semigroup ExUnits where instance Monoid ExUnits where mempty = ExUnits 0 0 --- Script language +-- Non-Native Script language. +-- This is an open type. We will add values of this type +-- for each Non-Native scripting language as they are added. + newtype Language = Language ByteString deriving (Eq, Generic, Show, Ord) @@ -87,17 +106,38 @@ deriving instance ToCBOR Language deriving instance FromCBOR Language --- Cost Model -newtype CostModel = CostModel (Map ByteString Integer) +-- ===================================== +-- Cost Model needs to preserve its serialization bytes as +-- it is going to be hashed. Thus we make it a newtype around a MemoBytes + +newtype CostModel = CostModelConstr (MemoBytes (Map ByteString Integer)) deriving (Eq, Generic, Show, Ord) +pattern CostModel :: (Map ByteString Integer) -> CostModel +pattern CostModel m <- + CostModelConstr (Memo m _) + where + CostModel m = CostModelConstr (memoBytes (To m)) + instance NoThunks CostModel instance NFData CostModel deriving instance ToCBOR CostModel -deriving instance FromCBOR CostModel +-- This is needed to derive the FromCBOR (Annotator CostModel) instance +instance FromCBOR (Annotator (Map ByteString Integer)) where + fromCBOR = pure <$> fromCBOR + +deriving via + Mem (Map ByteString Integer) + instance + FromCBOR (Annotator CostModel) + +hashCostModel :: forall e. Era e => Proxy e -> CostModel -> SafeHash e CostModel +hashCostModel Proxy x = Hash.castHash (Hash.hashWithSerialiser @(HASH (Crypto e)) toCBOR x) + +-- ================================== -- | Prices per execution unit data Prices = Prices @@ -160,3 +200,7 @@ instance decodeScript 0 = Ann (SumD NativeScript) <*! From decodeScript 1 = Ann (SumD PlutusScript) decodeScript n = Invalid n + +-- ================================================= +-- Languages +-- ================================================= diff --git a/alonzo/impl/src/Cardano/Ledger/Alonzo/Tx.hs b/alonzo/impl/src/Cardano/Ledger/Alonzo/Tx.hs index 6f9c5b99900..68b26220d97 100644 --- a/alonzo/impl/src/Cardano/Ledger/Alonzo/Tx.hs +++ b/alonzo/impl/src/Cardano/Ledger/Alonzo/Tx.hs @@ -1,53 +1,138 @@ +{-# LANGUAGE BangPatterns #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE DerivingVia #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE GADTs #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternSynonyms #-} +{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-} +-- | This module exports implementations of many of the functions outlined in the Alonzo specification. +-- The link to source of the specification +-- https://github.com/input-output-hk/cardano-ledger-specs/tree/master/alonzo/formal-spec +-- The most recent version of the document can be found here: +-- https://hydra.iohk.io/job/Cardano/cardano-ledger-specs/specs.alonzo-ledger/latest/download-by-type/doc-pdf/alonzo-changes +-- The functions can be found in Figures in that document, and sections of this code refer to those figures. module Cardano.Ledger.Alonzo.Tx - ( IsValidating (..), + ( Indexable (..), + -- Figure 1 + CostModel, + PPHash, + hashLanguagePP, + -- Figure 2 + ScriptData, + ScriptDataHash, + Data, + DataHash, + IsValidating (..), + hashData, + language, + plutusLanguage, + timelockLanguage, + nonNativeLanguages, + hashScriptData, + getCoin, + -- Figure 3 Tx (Tx, body, wits, isValidating, auxiliaryData), + TxBody (..), + -- Figure 4 ScriptPurpose (..), - Indexable (..), + -- Figure 5 + getValidatorHash, + txscriptfee, + txbody, + minfee, + isNonNativeScriptAddress, + feesOK, + -- Figure 6 txrdmrs, rdptr, getMapFromValue, indexedRdmrs, + -- Figure 7 + valContext, + runPLCScript, + -- Figure 8 + getData, + collectNNScriptInputs, + evalScripts, + -- Figure 12 + scriptsNeeded, + checkScriptData, + hashSD, ) where import Cardano.Binary (FromCBOR (..), ToCBOR (..)) -import Cardano.Ledger.Alonzo.Data (Data) -import qualified Cardano.Ledger.Alonzo.Scripts as AlonzoScript (Tag (..)) -import Cardano.Ledger.Alonzo.TxBody (AlonzoBody, TxBody (..), TxIn) -import Cardano.Ledger.Alonzo.TxWitness (RdmrPtr (..), TxWitness (..)) +import Cardano.Ledger.Alonzo.Data (Data, DataHash, hashData) +import Cardano.Ledger.Alonzo.PParams (PPHash, PParams, PParams' (..), hashLanguagePP) +import Cardano.Ledger.Alonzo.Scripts (CostModel, ExUnits (..), Language (..), Prices (..)) +import qualified Cardano.Ledger.Alonzo.Scripts as AlonzoScript (Script (..), Tag (..)) +import Cardano.Ledger.Alonzo.TxBody + ( AlonzoBody, + TxBody (..), + TxOut (..), + ) +import Cardano.Ledger.Alonzo.TxWitness + ( RdmrPtr (..), + ScriptData (..), + ScriptDataHash (..), + TxWitness (..), + hashSD, + witsData, + witsRdmr, + witsScript, + ) import Cardano.Ledger.Compactible import qualified Cardano.Ledger.Core as Core import Cardano.Ledger.Era (Crypto, Era) -import Cardano.Ledger.Mary.Value (AssetName, PolicyID, Value (..)) -import Cardano.Ledger.Val (DecodeMint, DecodeNonNegative, Val) +import Cardano.Ledger.Mary.Value (AssetName, PolicyID (..), Value (..)) +import Cardano.Ledger.Shelley.Constraints +import Cardano.Ledger.Val (DecodeMint, DecodeNonNegative, Val (coin, (<+>), (<×>))) +import Control.SetAlgebra (eval, (◁)) +import qualified Data.ByteString.Short as SBS (length) import Data.Coders +import Data.List (foldl') import qualified Data.Map as Map +import Data.Maybe (isJust, maybeToList) import Data.MemoBytes (Mem, MemoBytes (Memo), memoBytes) import Data.Sequence.Strict (StrictSeq) import qualified Data.Sequence.Strict as StrictSeq +import Data.Set (Set) import qualified Data.Set as Set + ( elemAt, + empty, + findIndex, + insert, + map, + null, + union, + ) +import Data.Text.Encoding (encodeUtf8) import Data.Typeable (Typeable) import Data.Word (Word64) import GHC.Generics (Generic) +import GHC.Records (HasField (..)) import NoThunks.Class (NoThunks) -import Shelley.Spec.Ledger.Address (RewardAcnt) +import Shelley.Spec.Ledger.Address (Addr (..), RewardAcnt, getRwdCred) import Shelley.Spec.Ledger.BaseTypes (StrictMaybe, maybeToStrictMaybe, strictMaybeToMaybe) -import Shelley.Spec.Ledger.Delegation.Certificates (DCert) -import Shelley.Spec.Ledger.TxBody (Wdrl (..), unWdrl) +import Shelley.Spec.Ledger.Coin (Coin (..)) +import Shelley.Spec.Ledger.Credential (Credential (ScriptHashObj)) +import Shelley.Spec.Ledger.Delegation.Certificates (DCert (..)) +import Shelley.Spec.Ledger.Scripts (ScriptHash) +import Shelley.Spec.Ledger.Tx (ValidateScript (isNativeScript)) +import Shelley.Spec.Ledger.TxBody (DelegCert (..), Delegation (..), TxIn (..), Wdrl (..), unWdrl) +import Shelley.Spec.Ledger.UTxO (UTxO (..), balance) -- =================================================== @@ -204,21 +289,150 @@ deriving via ) => FromCBOR (Annotator (Tx era)) --- =========================================== +-- ========================================================= +-- Figure 2: Definitions for Transactions + +-- For now, the only Non-Native Scriting language is Plutus +-- We might add new languages in the futures. + +nonNativeLanguages :: Set Language +nonNativeLanguages = Set.insert plutusLanguage Set.empty + +plutusLanguage :: Language +plutusLanguage = Language (encodeUtf8 "Plutus") + +timelockLanguage :: Language +timelockLanguage = Language (encodeUtf8 "Timelock") + +language :: AlonzoScript.Script era -> Language +language (AlonzoScript.NativeScript _) = timelockLanguage +language (AlonzoScript.PlutusScript) = plutusLanguage + +getCoin :: UsesValue era => TxOut era -> Coin +getCoin (TxOut _ v _) = coin v + +{- +-- TODO fix this. A ScriptDataHash is a hash of a Virtual triple. +-- See the selector function sdHash in TxWitness +-- In figure 14 we use this selector in the precondition +-- sdHash txb == hashScriptData pp ( languages txw ) ( txrdmrs txw ) +-- We have two functions that produce a ScriptDataHash +-- 1) hashSD :: TxWitness -> Maybe(ScriptDataHash) (Figure 12) +-- 2) hashScriptData :: PParams era -> Set Language -> +-- Map.Map RdmrPtr (Data era) -> Maybe (ScriptDataHash era) (Figure 2) +-- These two functions are not computing the same thing at all. + +hashSD :: + (Era era, ToCBOR (Core.Script era)) => + TxWitness era -> + Maybe (ScriptDataHash (Crypto era)) +hashSD (w@(TxWitnessConstr (Memo (TxWitnessRaw _ _ scriptdata) _))) = + if (Map.null (witsScript w) && Map.null (witsData w) && Map.null (witsRdmr w)) + then Nothing + else Just (ScriptDataHash (hashAnnotated scriptdata)) +-} + +hashScriptData :: + Era era => + PParams era -> + Set Language -> + Map.Map RdmrPtr (Data era) -> + Maybe (ScriptDataHash era) +hashScriptData pp langs rdmrs = + if Map.null rdmrs && Set.null langs + then Nothing + else + let _newset = Set.map (hashLanguagePP pp) langs + in undefined -- hash(rdmrs,_newset) + +-- =============================================================== +-- From the specification, Figure 5 "Functions related to fees" +-- =============================================================== + +isNonNativeScriptAddress :: + forall era. + ValidateScript era => + Tx era -> + Addr (Crypto era) -> + Bool +isNonNativeScriptAddress (TxConstr (Memo (TxRaw {_wits = w}) _)) addr = + case getValidatorHash addr of + Nothing -> False + Just hash -> + case Map.lookup hash (txscripts w) of + Nothing -> False + Just scr -> not (isNativeScript @era scr) + +feesOK :: + forall era. + ( UsesValue era, + AlonzoBody era, + UsesTxOut era, + ValidateScript era + ) => + PParams era -> + Tx era -> + UTxO era -> + Bool +feesOK pp tx (UTxO m) = + (bal >= txfee txb) + && (all (\txout -> not (isNonNativeScriptAddress tx (getField @"address" txout))) utxoFees) + && (minfee pp tx <= txfee txb) + where + txb = txbody tx + fees = txinputs_fee txb + utxoFees = eval (fees ◁ m) -- compute the domain restriction to those inputs where fees are paid + bal = coin (balance @era (UTxO utxoFees)) + +-- | The keys of all the inputs of the TxBody (both the inputs for fees, and the normal inputs). +txins :: AlonzoBody era => TxBody era -> Set (TxIn (Crypto era)) +txins (TxBody {txinputs = is, txinputs_fee = fs}) = Set.union is fs + +txscriptfee :: Prices -> ExUnits -> Coin +txscriptfee (Prices pr_mem pr_steps) (ExUnits mem steps) = + (mem <×> pr_mem) <+> (steps <×> pr_steps) + +-- | txsize computes the length of the serialised bytes +txsize :: Tx era -> Integer +txsize (TxConstr (Memo _ bytes)) = fromIntegral (SBS.length bytes) + +minfee :: AlonzoBody era => PParams era -> Tx era -> Coin +minfee pp tx = + ((txsize tx) <×> (a pp)) + <+> (b pp) + <+> (txscriptfee (_prices pp) (exunits (txbody tx))) + where + a protparam = Coin (fromIntegral (_minfeeA protparam)) + b protparam = Coin (fromIntegral (_minfeeB protparam)) + +-- The specification uses "validatorHash" to extract ScriptHash from +-- an Addr. But not every Addr has a ScriptHash. In particular KeyHashObj +-- do not. So we use getValidatorHash which returns a Maybe type. + +getValidatorHash :: Addr crypto -> Maybe (ScriptHash crypto) +getValidatorHash (Addr _network (ScriptHashObj hash) _ref) = Just hash +getValidatorHash _ = Nothing + +txbody :: Tx era -> TxBody era +txbody (TxConstr (Memo (TxRaw {_body = b}) _)) = b + +-- =============================================================== -- Operations on scripts from specification -- Figure 6:Indexing script and data objects +-- =============================================================== data ScriptPurpose crypto = Minting !(PolicyID crypto) | Spending !(TxIn crypto) | Rewarding !(RewardAcnt crypto) -- Not sure if this is the right type. | Certifying !(DCert crypto) + deriving (Eq) class Indexable elem container where indexOf :: elem -> container -> Word64 atIndex :: Word64 -> container -> elem -instance Ord k => Indexable k (Set.Set k) where +instance Ord k => Indexable k (Set k) where indexOf n set = fromIntegral $ Set.findIndex n set atIndex i set = Set.elemAt (fromIntegral i) set @@ -239,10 +453,10 @@ rdptr :: TxBody era -> ScriptPurpose (Crypto era) -> RdmrPtr -rdptr txbody (Minting pid) = RdmrPtr AlonzoScript.Mint (indexOf pid (getMapFromValue (mint txbody))) -rdptr txbody (Spending txin) = RdmrPtr AlonzoScript.Spend (indexOf txin (inputs txbody)) -rdptr txbody (Rewarding racnt) = RdmrPtr AlonzoScript.Rewrd (indexOf racnt (unWdrl (wdrls txbody))) -rdptr txbody (Certifying d) = RdmrPtr AlonzoScript.Cert (indexOf d (certs txbody)) +rdptr txb (Minting pid) = RdmrPtr AlonzoScript.Mint (indexOf pid (getMapFromValue (mint txb))) +rdptr txb (Spending txin) = RdmrPtr AlonzoScript.Spend (indexOf txin (txinputs txb)) +rdptr txb (Rewarding racnt) = RdmrPtr AlonzoScript.Rewrd (indexOf racnt (unWdrl (txwdrls txb))) +rdptr txb (Certifying d) = RdmrPtr AlonzoScript.Cert (indexOf d (txcerts txb)) getMapFromValue :: Value crypto -> Map.Map (PolicyID crypto) (Map.Map AssetName Integer) getMapFromValue (Value _ m) = m @@ -250,8 +464,8 @@ getMapFromValue (Value _ m) = m txrdmrs :: (Era era, ToCBOR (Core.Script era)) => TxWitness era -> - Map.Map RdmrPtr (Data era) -txrdmrs (TxWitness {witsRdmr = m}) = m + Map.Map RdmrPtr (Data era, ExUnits) +txrdmrs txw = witsRdmr txw indexedRdmrs :: ( Era era, @@ -261,7 +475,173 @@ indexedRdmrs :: ) => Tx era -> ScriptPurpose (Crypto era) -> - Maybe (Data era) -indexedRdmrs tx sp = Map.lookup policyid (txrdmrs . wits $ tx) + Maybe (Data era, ExUnits) +indexedRdmrs tx sp = Map.lookup policyid (txrdmrs . txwits $ tx) where policyid = rdptr (body tx) sp + +-- =============================================================== +-- From the specification, Figure 7 "Script Validation, cont." +-- =============================================================== + +-- | valContext collects info from the Tx and the UTxO and translates it into +-- a 'Data', which the Plutus language knows how to interpret. +valContext :: UTxO era -> Tx era -> ScriptPurpose (Crypto era) -> [Data era] +valContext _utxo _tx _sp = [] + +--TODO FIX THIS, when defined will always return singleton list +-- see also: collectNNScriptInputs where it is called + +-- TODO Specification says CostMod, not CostModel +runPLCScript :: + CostModel -> + AlonzoScript.Script era -> + [Data era] -> + ExUnits -> + (IsValidating, ExUnits) +runPLCScript _cost _script _data _exunits = (IsValidating True, ExUnits 0 0) -- TODO FIX THIS + +-- =============================================================== +-- From the specification, Figure 8 "Scripts and their Arguments" +-- =============================================================== + +getData :: + forall era. + ( ToCBOR (Core.AuxiliaryData era), + ToCBOR (Core.Script era), + UsesTxOut era, + HasField "datahash" (Core.TxOut era) (Maybe (DataHash (Crypto era))) + ) => + Tx era -> + UTxO era -> + ScriptPurpose (Crypto era) -> + [Data era] +getData tx (UTxO m) sp = case sp of + Minting _policyid -> [] + Rewarding _rewaccnt -> [] + Certifying _dcert -> [] + Spending txin -> + -- Only the Spending ScriptPurpose contains Data + case Map.lookup txin m of + Nothing -> [] + Just txout -> + case getField @"datahash" txout of + Nothing -> [] + Just hash -> + case Map.lookup hash (witsData (wits tx)) of + Nothing -> [] + Just d -> [d] + +collectNNScriptInputs :: + ( UsesTxOut era, + ToCBOR (Core.Script era), + ToCBOR (CompactForm (Core.Value era)), + ToCBOR (Core.AuxiliaryData era), + Core.Script era ~ AlonzoScript.Script era, + HasField "datahash" (Core.TxOut era) (Maybe (DataHash (Crypto era))) + ) => + PParams era -> + Tx era -> + UTxO era -> + [(AlonzoScript.Script era, [Data era], ExUnits, CostModel)] +collectNNScriptInputs _pp tx utxo = + [ (script, (d : (valContext utxo tx sp ++ getData tx utxo sp)), eu, cost) + | (sp, scripthash) <- scriptsNeeded utxo tx, -- TODO, IN specification ORDER IS WRONG + (d, eu) <- maybeToList (indexedRdmrs tx sp), + script <- maybeToList (Map.lookup scripthash (txscripts (txwits tx))), + cost <- maybeToList (Map.lookup (language script) (_costmdls _pp)) + ] + +evalScripts :: (AlonzoScript.Script era, [Data era], ExUnits, CostModel) -> Bool +evalScripts (AlonzoScript.NativeScript _timelock, _, _, _) = True +evalScripts (AlonzoScript.PlutusScript, ds, units, cost) = b + where + (IsValidating b, _exunits) = runPLCScript cost AlonzoScript.PlutusScript ds units + +-- =================================================================== +-- From Specification, Figure 12 "UTXOW helper functions" + +-- THE SPEC CALLS FOR A SET, BUT THAT NEEDS A BUNCH OF ORD INSTANCES (DCert) +scriptsNeeded :: + forall era. + ( UsesTxOut era, + AlonzoBody era + ) => + UTxO era -> + Tx era -> + [(ScriptPurpose (Crypto era), ScriptHash (Crypto era))] +scriptsNeeded (UTxO utxomap) tx = spend ++ reward ++ cert ++ minted + where + txb = txbody tx + !spend = foldl' accum [] (txins txb) + where + accum !ans !i = + case Map.lookup i utxomap of + Nothing -> ans + Just txout -> + case getValidatorHash (getField @"address" txout) of + Nothing -> ans + Just hash -> (Spending i, hash) : ans + + !reward = foldl' accum [] (Map.keys m2) + where + (Wdrl m2) = txwdrls txb + accum !ans !accnt = case getRwdCred accnt of -- TODO IS THIS RIGHT? + (ScriptHashObj hash) -> (Rewarding accnt, hash) : ans + _ -> ans + + !cert = foldl addOnlyCwitness [] (txcerts txb) + + !minted = map (\(pid@(PolicyID hash)) -> (Minting pid, hash)) (Map.keys m3) + where + m3 = getMapFromValue (mint txb) + +-- We only find certificate witnesses in Delegating and Deregistration DCerts +-- that have ScriptHashObj credentials. +addOnlyCwitness :: + [(ScriptPurpose crypto, ScriptHash crypto)] -> + DCert crypto -> + [(ScriptPurpose crypto, ScriptHash crypto)] +addOnlyCwitness !ans !(DCertDeleg (c@(DeRegKey (ScriptHashObj hk)))) = + (Certifying $ DCertDeleg c, hk) : ans +addOnlyCwitness !ans !(DCertDeleg (c@(Delegate (Delegation (ScriptHashObj hk) _dpool)))) = + (Certifying $ DCertDeleg c, hk) : ans +addOnlyCwitness !ans _ = ans + +checkScriptData :: + forall era. + ( ToCBOR (Core.AuxiliaryData era), + ValidateScript era, + ToCBOR (CompactForm (Core.Value era)), + UsesTxOut era, + HasField "datahash" (Core.TxOut era) (Maybe (DataHash (Crypto era))) + ) => + Tx era -> + UTxO era -> + (ScriptPurpose (Crypto era), ScriptHash (Crypto era)) -> + Bool +checkScriptData tx utxo (sp, _h) = any ok scripts + where + scripts = txscripts (txwits tx) + isSpending (Spending _) = True + isSpending _ = False + ok s = + (isNativeScript @era s) + || ( isJust (indexedRdmrs tx sp) + && (not (isSpending sp) || not (null (getData tx utxo sp))) + ) + +-- The function hashSD, specified in Figure 12 +-- hashSD :: TxWitness era -> Maybe (ScriptDataHash (Crypto era)) +-- is defined in Cardano.Ledger.Alonzo.TxWitness + +-- languages:: TxWitness era -> Set Language -- TODO + +txscripts :: + (Era era, ToCBOR (Core.Script era)) => + TxWitness era -> + Map.Map (ScriptHash (Crypto era)) (Core.Script era) +txscripts x = witsScript x + +txwits :: (Era era, ToCBOR (Core.AuxiliaryData era)) => Tx era -> TxWitness era +txwits x = wits x diff --git a/alonzo/impl/src/Cardano/Ledger/Alonzo/TxBody.hs b/alonzo/impl/src/Cardano/Ledger/Alonzo/TxBody.hs index 5f7725b7f90..09cbcd8429f 100644 --- a/alonzo/impl/src/Cardano/Ledger/Alonzo/TxBody.hs +++ b/alonzo/impl/src/Cardano/Ledger/Alonzo/TxBody.hs @@ -18,26 +18,24 @@ module Cardano.Ledger.Alonzo.TxBody ( IsFee (..), - TxIn (..), TxOut (TxOut, TxOutCompact), TxBody ( TxBody, - inputs, - outputs, - certs, - wdrls, + txinputs, + txinputs_fee, + txouts, + txcerts, + txwdrls, txfee, - vldt, - update, - adHash, + txvldt, + txUpdates, + txADhash, mint, exunits, ppHash, - scriptHash + sdHash ), AlonzoBody, - txins, - txinputs_fee, ) where @@ -69,8 +67,8 @@ import qualified Data.Sequence.Strict as StrictSeq import Data.Set (Set) import qualified Data.Set as Set import Data.Typeable (Typeable) -import Data.Word (Word64) import GHC.Generics (Generic) +import GHC.Records (HasField (..)) import GHC.Stack (HasCallStack) import NoThunks.Class (InspectHeapNamed (..), NoThunks) import Shelley.Spec.Ledger.Address (Addr) @@ -80,7 +78,7 @@ import Shelley.Spec.Ledger.CompactAddr (CompactAddr, compactAddr, decompactAddr) import Shelley.Spec.Ledger.Delegation.Certificates (DCert) import Shelley.Spec.Ledger.Hashing import Shelley.Spec.Ledger.PParams (Update) -import Shelley.Spec.Ledger.TxBody (TxId, Wdrl (Wdrl), unWdrl) +import Shelley.Spec.Ledger.TxBody (TxIn (..), Wdrl (Wdrl), unWdrl) import Prelude hiding (lookup) -- | Tag indicating whether an input should be used to pay transaction fees. @@ -98,26 +96,6 @@ newtype IsFee = IsFee Bool FromCBOR ) --- | Input of a UTxO. This references the transaction being spent from and the --- index of the output being spent, as well as a tag indicating whether this --- input should be used as a fee. -data TxIn crypto - = TxInCompact - {-# UNPACK #-} !(TxId crypto) - {-# UNPACK #-} !Word64 - !IsFee - deriving (Generic) - -deriving instance Ord (TxIn crypto) - -deriving instance Eq (TxIn crypto) - -deriving instance Show (TxIn crypto) - -instance CC.Crypto crypto => NFData (TxIn crypto) - -instance NoThunks (TxIn crypto) - data TxOut era = TxOutCompact {-# UNPACK #-} !(CompactAddr (Crypto era)) @@ -166,6 +144,7 @@ pattern TxOut addr vl dh <- data TxBodyRaw era = TxBodyRaw { _inputs :: !(Set (TxIn (Crypto era))), + _inputs_fee :: !(Set (TxIn (Crypto era))), _outputs :: !(StrictSeq (TxOut era)), _certs :: !(StrictSeq (DCert (Crypto era))), _wdrls :: !(Wdrl (Crypto era)), @@ -243,6 +222,7 @@ type AlonzoBody era = pattern TxBody :: AlonzoBody era => Set (TxIn (Crypto era)) -> + Set (TxIn (Crypto era)) -> StrictSeq (TxOut era) -> StrictSeq (DCert (Crypto era)) -> Wdrl (Crypto era) -> @@ -256,40 +236,43 @@ pattern TxBody :: StrictMaybe (ScriptDataHash (Crypto era)) -> TxBody era pattern TxBody - { inputs, - outputs, - certs, - wdrls, + { txinputs, + txinputs_fee, + txouts, + txcerts, + txwdrls, txfee, - vldt, - update, - adHash, + txvldt, + txUpdates, + txADhash, mint, exunits, ppHash, - scriptHash + sdHash } <- TxBodyConstr ( Memo TxBodyRaw - { _inputs = inputs, - _outputs = outputs, - _certs = certs, - _wdrls = wdrls, + { _inputs = txinputs, + _inputs_fee = txinputs_fee, + _outputs = txouts, + _certs = txcerts, + _wdrls = txwdrls, _txfee = txfee, - _vldt = vldt, - _update = update, - _adHash = adHash, + _vldt = txvldt, + _update = txUpdates, + _adHash = txADhash, _mint = mint, _exunits = exunits, _ppHash = ppHash, - _scriptHash = scriptHash + _scriptHash = sdHash } _ ) where TxBody inputs' + inputs_fee' outputs' certs' wdrls' @@ -306,6 +289,7 @@ pattern TxBody ( encodeTxBodyRaw $ TxBodyRaw inputs' + inputs_fee' outputs' certs' wdrls' @@ -328,17 +312,6 @@ instance Era era => HashAnnotated (TxBody era) era where -- Serialisation -------------------------------------------------------------------------------- -instance CC.Crypto crypto => ToCBOR (TxIn crypto) where - toCBOR (TxInCompact txId idx isFee) = - encode $ - Rec TxInCompact - !> To txId - !> To idx - !> To isFee - -instance CC.Crypto crypto => FromCBOR (TxIn crypto) where - fromCBOR = decode $ RecD TxInCompact - TxBodyRaw i o c w f (ValidityInterval b t) u mh mi e s + ( \i ifee o f t c w u mh b mi e s -> + TxBodyRaw i ifee o c w f (ValidityInterval b t) u mh mi e s ) !> Key 0 (E encodeFoldable _inputs) + !> Key 13 (E encodeFoldable _inputs_fee) !> Key 1 (E encodeFoldable _outputs) !> Key 2 (To _txfee) !> encodeKeyedStrictMaybe 3 top @@ -434,6 +409,7 @@ instance initial :: TxBodyRaw era initial = TxBodyRaw + mempty mempty StrictSeq.empty StrictSeq.empty @@ -450,6 +426,10 @@ instance field (\x tx -> tx {_inputs = x}) (D (decodeSet fromCBOR)) + bodyFields 13 = + field + (\x tx -> tx {_inputs_fee = x}) + (D (decodeSet fromCBOR)) bodyFields 1 = field (\x tx -> tx {_outputs = x}) @@ -497,16 +477,32 @@ instance where fromCBOR = pure <$> fromCBOR --- ============================================================ --- From the specification, Figure 5 "Functions related to fees" +-- ==================================================== +-- HasField instances to be consistent with earlier Era's -txins :: AlonzoBody era => TxBody era -> Set (TxId (Crypto era), Word64) -txins (TxBody {inputs = is}) = Set.foldl' accum Set.empty is - where - accum ans (TxInCompact idx index _) = Set.insert (idx, index) ans +instance (Crypto era ~ c) => HasField "inputs" (TxBody era) (Set (TxIn c)) where + getField (TxBodyConstr (Memo m _)) = Set.union (_inputs m) (_inputs_fee m) -txinputs_fee :: AlonzoBody era => TxBody era -> Set (TxId (Crypto era), Word64) -txinputs_fee (TxBody {inputs = is}) = Set.foldl' accum Set.empty is - where - accum ans (TxInCompact idx index (IsFee True)) = Set.insert (idx, index) ans - accum ans _ = ans +instance HasField "outputs" (TxBody era) (StrictSeq (TxOut era)) where + getField (TxBodyConstr (Memo m _)) = _outputs m + +instance Crypto era ~ crypto => HasField "certs" (TxBody era) (StrictSeq (DCert crypto)) where + getField (TxBodyConstr (Memo m _)) = _certs m + +instance Crypto era ~ crypto => HasField "wdrls" (TxBody era) (Wdrl crypto) where + getField (TxBodyConstr (Memo m _)) = _wdrls m + +instance HasField "txfee" (TxBody era) Coin where + getField (TxBodyConstr (Memo m _)) = _txfee m + +instance HasField "update" (TxBody era) (StrictMaybe (Update era)) where + getField (TxBodyConstr (Memo m _)) = _update m + +instance (Crypto era ~ c) => HasField "compactAddress" (TxOut era) (CompactAddr c) where + getField (TxOutCompact a _ _) = a + +instance (CC.Crypto c, Crypto era ~ c) => HasField "address" (TxOut era) (Addr c) where + getField (TxOutCompact a _ _) = decompactAddr a + +instance (Core.Value era ~ val, Compactible val) => HasField "value" (TxOut era) val where + getField (TxOutCompact _ v _) = fromCompact v diff --git a/alonzo/impl/src/Cardano/Ledger/Alonzo/TxWitness.hs b/alonzo/impl/src/Cardano/Ledger/Alonzo/TxWitness.hs index 6aebe996cc2..044a745e2e8 100644 --- a/alonzo/impl/src/Cardano/Ledger/Alonzo/TxWitness.hs +++ b/alonzo/impl/src/Cardano/Ledger/Alonzo/TxWitness.hs @@ -7,6 +7,7 @@ {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE LambdaCase #-} +{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE ScopedTypeVariables #-} @@ -18,20 +19,26 @@ module Cardano.Ledger.Alonzo.TxWitness ( RdmrPtr (..), - TxWitness (TxWitness, witsVKey, witsBoot, witsScript, witsData, witsRdmr), + TxWitness (FlatWitness, TxWitness, witsVKey, witsBoot, witsScriptData), + witsScript, + witsData, + witsRdmr, + ScriptData (ScriptData), EraIndependentScriptData, ScriptDataHash (..), + hashSD, ) where import Cardano.Binary (FromCBOR (..), ToCBOR (..)) import qualified Cardano.Crypto.Hash as Hash import Cardano.Ledger.Alonzo.Data (Data, DataHash) -import Cardano.Ledger.Alonzo.Scripts (Tag) +import Cardano.Ledger.Alonzo.Scripts (ExUnits (..), Tag) import qualified Cardano.Ledger.Core as Core import Cardano.Ledger.Crypto (HASH) import qualified Cardano.Ledger.Crypto as CC import Cardano.Ledger.Era (Era (Crypto)) +import Control.Applicative (liftA2) import Control.DeepSeq (NFData) import Data.Coders import Data.Map.Strict (Map) @@ -45,11 +52,18 @@ import GHC.Generics import GHC.Records import NoThunks.Class (NoThunks) import Shelley.Spec.Ledger.Address.Bootstrap (BootstrapWitness) +import Shelley.Spec.Ledger.Hashing (HashAnnotated (..)) import Shelley.Spec.Ledger.Keys import Shelley.Spec.Ledger.Scripts (ScriptHash) -import Shelley.Spec.Ledger.Serialization (mapFromCBOR, mapToCBOR) +import Shelley.Spec.Ledger.Serialization + ( decodeMapTraverse, + mapFromCBOR, + mapToCBOR, + ) import Shelley.Spec.Ledger.TxBody (WitVKey) +-- ========================================== + data RdmrPtr = RdmrPtr !Tag @@ -58,6 +72,43 @@ data RdmrPtr instance NoThunks RdmrPtr +-- ================================================================================ +-- 'TxWitness' has 3 parts +-- +-- data TxWitness era = TxWitness +-- (Set (WitVKey 'Witness (Crypto era))) +-- (Set (BootstrapWitness (Crypto era))) +-- (ScriptData era) +-- +-- 'ScriptData' has 3 parts +-- +-- data ScriptData = ScriptData +-- (Map (ScriptHash (Crypto era)) (Core.Script era)) +-- (Map (DataHash (Crypto era)) (Data era)) +-- (Map RdmrPtr (Data era, ExUnits)) +-- +-- There is also a VIEW 'FlatWitness' of 'TxWitness' with 5 parts +-- +-- data FlatWitness = FlatWitness +-- (Set (WitVKey 'Witness (Crypto era))) +-- (Set (BootstrapWitness (Crypto era))) +-- (Map (ScriptHash (Crypto era)) (Core.Script era)) +-- (Map (DataHash (Crypto era)) (Data era)) +-- (Map RdmrPtr (Data era, ExUnits)) +-- +-- 'ScriptData' is a projection of the parts of 'TxWitness' which may be +-- hashed to include in the transaction body. Note that this cannot be the hash +-- of the entire witness set, since the VKey witnesses themselves contain a hash +-- of the transaction body, creating a circular dependency. +-- +-- Because we have to hash both the ScriptData and the TxWitness separately, and +-- hashing is based upon the serialized bytes, so we have to be able to extract +-- the bytes for the ScriptData part from the bytes for TxWitness whole. +-- +-- The strategy is for both ScriptData and TxWitness to memoize their bytes. +-- Decoding either over the wire will preserve the exact bytes used in the encoding +-- ================================================================================ + -- | Internal 'TxWitness' type, lacking serialised bytes. data TxWitnessRaw era = TxWitnessRaw { _witsVKey :: Set (WitVKey 'Witness (Crypto era)), @@ -66,6 +117,26 @@ data TxWitnessRaw era = TxWitnessRaw } deriving (Generic, Typeable) +newtype TxWitness era = TxWitnessConstr (MemoBytes (TxWitnessRaw era)) + deriving newtype (ToCBOR) + +-- | Internal 'SciptData' type, lacking serialised bytes. +data ScriptDataRaw era = ScriptDataRaw + { _scriptDataScripts :: Map (ScriptHash (Crypto era)) (Core.Script era), + _scriptDataData :: Map (DataHash (Crypto era)) (Data era), + _scriptDataRdmrs :: Map RdmrPtr (Data era, ExUnits) + } + deriving (Generic) + +newtype ScriptData era = ScriptDataConstr + { unScriptDataConstr :: + MemoBytes (ScriptDataRaw era) + } + deriving newtype (ToCBOR) + +-- ===================================================== +-- TxWitness instances + deriving stock instance ( Era era, Eq (Core.Script era) @@ -78,9 +149,6 @@ deriving stock instance instance (Era era, NoThunks (Core.Script era)) => NoThunks (TxWitnessRaw era) -newtype TxWitness era = TxWitnessConstr (MemoBytes (TxWitnessRaw era)) - deriving newtype (ToCBOR) - deriving newtype instance (Era era, Eq (Core.Script era)) => Eq (TxWitness era) deriving newtype instance @@ -91,32 +159,53 @@ deriving newtype instance (Era era, NoThunks (Core.Script era)) => NoThunks (TxWitness era) +-- ===================================================== +-- Pattern for TxWitness with 3 parts + pattern TxWitness :: (Era era, ToCBOR (Core.Script era)) => Set (WitVKey 'Witness (Crypto era)) -> Set (BootstrapWitness (Crypto era)) -> - Map (ScriptHash (Crypto era)) (Core.Script era) -> - Map (DataHash (Crypto era)) (Data era) -> - Map RdmrPtr (Data era) -> + ScriptData era -> TxWitness era pattern TxWitness { witsVKey, witsBoot, - witsScript, - witsData, - witsRdmr + witsScriptData } <- + TxWitnessConstr + (Memo (TxWitnessRaw witsVKey witsBoot witsScriptData) _) + where + TxWitness witsVKey' witsBoot' witsScriptData' = + TxWitnessConstr + . memoBytes + $ encodeWitnessRaw witsVKey' witsBoot' witsScriptData' + +{-# COMPLETE TxWitness #-} + +-- ===================================================== +-- Pattern for TxWitness with 5 parts + +pattern FlatWitness :: + (Era era, ToCBOR (Core.Script era)) => + Set (WitVKey 'Witness (Crypto era)) -> + Set (BootstrapWitness (Crypto era)) -> + Map (ScriptHash (Crypto era)) (Core.Script era) -> + Map (DataHash (Crypto era)) (Data era) -> + Map RdmrPtr (Data era, ExUnits) -> + TxWitness era +pattern FlatWitness vkey boot script dat rdmr <- TxWitnessConstr ( Memo ( TxWitnessRaw - witsVKey - witsBoot + vkey + boot ( ScriptDataConstr ( Memo ( ScriptDataRaw - witsScript - witsData - witsRdmr + script + dat + rdmr ) _ ) @@ -125,12 +214,12 @@ pattern TxWitness _ ) where - TxWitness witsVKey' witsBoot' witsScript' witsDat' witsRdmr' = + FlatWitness witsVKey' witsBoot' witsScript' witsDat' witsRdmr' = TxWitnessConstr . memoBytes - $ encodeWitnessRaw witsVKey' witsBoot' witsScript' witsDat' witsRdmr' + $ encodeWitnessRaw witsVKey' witsBoot' (ScriptData witsScript' witsDat' witsRdmr') -{-# COMPLETE TxWitness #-} +{-# COMPLETE FlatWitness #-} -- | Right-biased semigroup - if there are (somehow) multiple entries either for -- a given 'ScriptHash' or a given 'Data', this will bias to the entry on the @@ -139,8 +228,8 @@ instance (Era era, ToCBOR (Core.Script era)) => Semigroup (TxWitness era) where - TxWitness a b c d e <> TxWitness a' b' c' d' e' = - TxWitness + FlatWitness a b c d e <> FlatWitness a' b' c' d' e' = + FlatWitness (a `Set.union` a') (b `Set.union` b') (c <> c') @@ -151,11 +240,32 @@ instance (Era era, ToCBOR (Core.Script era)) => Monoid (TxWitness era) where - mempty = TxWitness mempty mempty mempty mempty mempty + mempty = FlatWitness mempty mempty mempty mempty mempty + +-- ======================================================= +-- Virtual accessors for the FlatWitness VIEW of TxWitness +-- ======================================================= + +witsScript :: + (ToCBOR (Core.Script era), Era era) => + TxWitness era -> + Map (ScriptHash (Crypto era)) (Core.Script era) +witsScript (TxWitnessConstr (Memo (TxWitnessRaw _ _ (ScriptData a _ _)) _)) = a + +witsData :: + (Era era, ToCBOR (Core.Script era)) => + TxWitness era -> + Map (DataHash (Crypto era)) (Data era) +witsData (TxWitnessConstr (Memo (TxWitnessRaw _ _ (ScriptData _ b _)) _)) = b + +witsRdmr :: + (Era era, ToCBOR (Core.Script era)) => + TxWitness era -> + Map RdmrPtr (Data era, ExUnits) +witsRdmr (TxWitnessConstr (Memo (TxWitnessRaw _ _ (ScriptData _ _ c)) _)) = c + +-- HasField instances for the virtual accessors --------------------------------------------------------------------------------- --- Accessors --------------------------------------------------------------------------------- instance (Core.Script era ~ script, Crypto era ~ crypto) => HasField "witsScript" (TxWitness era) (Map (ScriptHash crypto) script) @@ -176,7 +286,7 @@ instance . unScriptDataConstr $ _witsScriptData m -instance HasField "witsRdmrs" (TxWitness era) (Map RdmrPtr (Data era)) where +instance HasField "witsRdmrs" (TxWitness era) (Map RdmrPtr (Data era, ExUnits)) where getField (TxWitnessConstr (Memo m _)) = _scriptDataRdmrs . memotype @@ -184,15 +294,8 @@ instance HasField "witsRdmrs" (TxWitness era) (Map RdmrPtr (Data era)) where $ _witsScriptData m -------------------------------------------------------------------------------- --- ScriptData +-- ScriptData instances -------------------------------------------------------------------------------- --- TODO now that this has a hash-to-data map, make sure CBOR is still correct! -data ScriptDataRaw era = ScriptDataRaw - { _scriptDataScripts :: Map (ScriptHash (Crypto era)) (Core.Script era), - _scriptDataData :: Map (DataHash (Crypto era)) (Data era), - _scriptDataRdmrs :: Map RdmrPtr (Data era) - } - deriving (Generic) deriving stock instance (Eq (Core.Script era)) => Eq (ScriptDataRaw era) @@ -202,20 +305,6 @@ instance (NoThunks (Core.Script era), NoThunks (Data era)) => NoThunks (ScriptDataRaw era) --- | 'ScriptData' is a projection of the parts of 'TxWitness' which may be --- hashed to include in the transaction body. Note that this cannot be the hash --- of the entire witness set, since the VKey witnesses themselves contain a hash --- of the transaction body, creating a circular dependency. --- --- The 'ScriptData' type itself is internal to this module; it is never directly --- serialised or deserialised, but will automatically be populated upon --- deserialisation or creation of 'TxWitness' -newtype ScriptData era = ScriptDataConstr - { unScriptDataConstr :: - MemoBytes (ScriptDataRaw era) - } - deriving newtype (ToCBOR) - deriving newtype instance (Eq (Core.Script era)) => Eq (ScriptData era) deriving newtype instance (Show (Core.Script era)) => Show (ScriptData era) @@ -228,7 +317,7 @@ pattern ScriptData :: (Era era, ToCBOR (Core.Script era)) => Map (ScriptHash (Crypto era)) (Core.Script era) -> Map (DataHash (Crypto era)) (Data era) -> - Map RdmrPtr (Data era) -> + Map RdmrPtr (Data era, ExUnits) -> ScriptData era pattern ScriptData s d r <- ScriptDataConstr (Memo (ScriptDataRaw s d r) _) @@ -238,6 +327,8 @@ pattern ScriptData s d r <- memoBytes $ encodeScriptDataRaw (ScriptDataRaw s d r) +{-# COMPLETE ScriptData #-} + instance (Era era, ToCBOR (Core.Script era)) => Semigroup (ScriptData era) @@ -246,8 +337,6 @@ instance <> (ScriptData s' d' r') = ScriptData (s <> s') (d `Map.union` d') (r <> r') -{-# COMPLETE ScriptData #-} - instance (Era era, ToCBOR (Core.Script era)) => Monoid (ScriptData era) @@ -297,33 +386,47 @@ instance decode $ Ann (RecD ScriptDataRaw) <*! D (sequence <$> mapFromCBOR) - <*! Ann (D mapFromCBOR) - <*! Ann (D mapFromCBOR) + <*! D (sequence <$> mapFromCBOR) + <*! D (splitMapFromCBOR fromCBOR fromCBOR fromCBOR) + +-- ScriptData includes a field with type: (Map RdmrPtr (Data era, ExUnits)) +-- We only have a (ToCBOR (Annotator (Data era))) instance, so we need a special +-- way to decode a Map where one half of its range has only a (FromCBOR (Annotator _)) +-- instance. We have to be careful since the map is encodedwith 'mapToCBOR' and the +-- decoder needs to be consistent with that encoding. + +splitMapFromCBOR :: + Ord dom => + Decoder s dom -> + Decoder s (Annotator rngLeft) -> + Decoder s rngRight -> + Decoder s (Annotator (Map dom (rngLeft, rngRight))) +splitMapFromCBOR a b c = decodeMapTraverse (pure <$> a) (liftPair <$> decodePair b c) + where + liftPair :: (Annotator a, b) -> Annotator (a, b) + liftPair (x, y) = liftA2 (,) x (pure y) deriving via (Mem (ScriptDataRaw era)) instance ( Era era, FromCBOR (Annotator (Core.Script era)), - FromCBOR (Data era), Typeable (Core.Script era) ) => FromCBOR (Annotator (ScriptData era)) -- | Encode witness information. encodeWitnessRaw :: - (Era era, ToCBOR (Core.Script era)) => + (Era era) => Set (WitVKey 'Witness (Crypto era)) -> Set (BootstrapWitness (Crypto era)) -> - Map (ScriptHash (Crypto era)) (Core.Script era) -> - Map (DataHash (Crypto era)) (Data era) -> - Map RdmrPtr (Data era) -> + ScriptData era -> Encode ('Closed 'Dense) (TxWitnessRaw era) -encodeWitnessRaw a b s d r = +encodeWitnessRaw a b sd = Rec TxWitnessRaw !> E encodeFoldable a !> E encodeFoldable b - !> To (ScriptData s d r) + !> To sd instance ( Era era, @@ -349,3 +452,26 @@ deriving via ToCBOR (Core.Script era) ) => FromCBOR (Annotator (TxWitness era)) + +-- ======================================================= +-- To compute a ScriptDataHash from a TxWitness we have to hash +-- the embedded (ScriptData era) value in the TxWitness. +-- See function hashSD in Figure 12 "UTXOW helper functions" +-- This virtual triple is stored inside of the TxWitness as a concrete value +-- (ScriptData era) is a newtype around a MemoBytes, so hashing +-- it is just hashing the memoized bytestring inside the MemoBytes. +-- Furtunately, applying toCBOR to (ScriptData era) is just that memoized +-- bytstring, so we can make a HashAnnotated instance for (ScriptData era) +-- to compute the hash, since (hashAnnotated x) is (hash (toCBOR x)) + +instance (Era era, Typeable era) => HashAnnotated (ScriptData era) era where + type HashIndex (ScriptData era) = EraIndependentScriptData + +hashSD :: + (Era era, ToCBOR (Core.Script era)) => + TxWitness era -> + Maybe (ScriptDataHash (Crypto era)) +hashSD (w@(TxWitnessConstr (Memo (TxWitnessRaw _ _ scriptdata) _))) = + if (Map.null (witsScript w) && Map.null (witsData w) && Map.null (witsRdmr w)) + then Nothing + else Just (ScriptDataHash (hashAnnotated scriptdata)) diff --git a/alonzo/impl/test/lib/Test/Cardano/Ledger/Alonzo/Serialisation/Generators.hs b/alonzo/impl/test/lib/Test/Cardano/Ledger/Alonzo/Serialisation/Generators.hs index 9e270c7faef..da58464a894 100644 --- a/alonzo/impl/test/lib/Test/Cardano/Ledger/Alonzo/Serialisation/Generators.hs +++ b/alonzo/impl/test/lib/Test/Cardano/Ledger/Alonzo/Serialisation/Generators.hs @@ -13,14 +13,12 @@ module Test.Cardano.Ledger.Alonzo.Serialisation.Generators where import Cardano.Ledger.Alonzo (AlonzoEra) -import Cardano.Ledger.Alonzo.Data (Data (..), DataHash (..)) +import Cardano.Ledger.Alonzo.Data (Data (..), DataHash (..), PlutusData (..)) import Cardano.Ledger.Alonzo.PParams (PPHash (..)) import Cardano.Ledger.Alonzo.Scripts (ExUnits (..), Script (..), Tag (..)) import Cardano.Ledger.Alonzo.Tx import Cardano.Ledger.Alonzo.TxBody ( IsFee (..), - TxBody (TxBody), - TxIn (..), TxOut (..), ) import Cardano.Ledger.Alonzo.TxWitness @@ -35,7 +33,7 @@ import Test.Shelley.Spec.Ledger.Serialisation.EraIndepGenerators () -- TODO correct arbitrary generator for Data instance Arbitrary (Data era) where - arbitrary = pure NotReallyData + arbitrary = pure (Data NotReallyData) instance Arbitrary Tag where arbitrary = elements [Spend, Mint, Cert, Rewrd] @@ -56,13 +54,24 @@ instance Arbitrary (TxWitness era) where arbitrary = - TxWitness + FlatWitness <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary +instance + ( Era era, + UsesValue era, + Mock (Crypto era), + Arbitrary (Core.Script era), + UsesScript era + ) => + Arbitrary (ScriptData era) + where + arbitrary = ScriptData <$> arbitrary <*> arbitrary <*> arbitrary + deriving newtype instance CC.Crypto c => Arbitrary (ScriptDataHash c) deriving newtype instance CC.Crypto c => Arbitrary (DataHash c) @@ -71,17 +80,6 @@ deriving newtype instance CC.Crypto c => Arbitrary (PPHash c) deriving newtype instance Arbitrary IsFee -instance - ( CC.Crypto c - ) => - Arbitrary (TxIn c) - where - arbitrary = - TxInCompact - <$> arbitrary - <*> arbitrary - <*> arbitrary - instance ( Era era, UsesValue era, @@ -111,6 +109,7 @@ instance <*> arbitrary <*> arbitrary <*> arbitrary + <*> arbitrary <*> genMintValues @c <*> arbitrary <*> arbitrary diff --git a/alonzo/impl/test/test/Test/Cardano/Ledger/Alonzo/Serialisation/Tripping.hs b/alonzo/impl/test/test/Test/Cardano/Ledger/Alonzo/Serialisation/Tripping.hs index 5b972da91e0..9ab690196a3 100644 --- a/alonzo/impl/test/test/Test/Cardano/Ledger/Alonzo/Serialisation/Tripping.hs +++ b/alonzo/impl/test/test/Test/Cardano/Ledger/Alonzo/Serialisation/Tripping.hs @@ -5,9 +5,11 @@ module Test.Cardano.Ledger.Alonzo.Serialisation.Tripping where import Cardano.Binary import Cardano.Ledger.Alonzo +import Cardano.Ledger.Alonzo.Data import Cardano.Ledger.Alonzo.Tx (Tx) import Cardano.Ledger.Alonzo.TxBody (TxBody) -import Cardano.Ledger.Alonzo.TxWitness (TxWitness) +import Cardano.Ledger.Alonzo.TxWitness +import qualified Data.ByteString.Base16.Lazy as Base16 import qualified Data.ByteString.Lazy.Char8 as BSL import Test.Cardano.Ledger.Alonzo.Serialisation.Generators () import Test.Cardano.Ledger.ShelleyMA.Serialisation.Coders @@ -32,7 +34,13 @@ trippingAnn x = case roundTripAnn x of False Left stuff -> counterexample - ("Failed to decode: " <> show stuff) + ( concat + [ "Failed to decode: ", + show stuff, + "\nbytes: ", + show (Base16.encode (serialize x)) + ] + ) False tests :: TestTree @@ -41,6 +49,10 @@ tests = "Alonzo CBOR round-trip" [ testProperty "alonzo/TxWitness" $ trippingAnn @(TxWitness (AlonzoEra C_Crypto)), + testProperty "alonzo/Data" $ + trippingAnn @(Data (AlonzoEra C_Crypto)), + testProperty "alonzo/ScriptDataRaw" $ + trippingAnn @(ScriptData (AlonzoEra C_Crypto)), testProperty "alonzo/TxBody" $ trippingAnn @(TxBody (AlonzoEra C_Crypto)), testProperty "alonzo/Tx" $ diff --git a/semantics/executable-spec/src/Data/Coders.hs b/semantics/executable-spec/src/Data/Coders.hs index 2c55ed8b3e1..668ac806e43 100644 --- a/semantics/executable-spec/src/Data/Coders.hs +++ b/semantics/executable-spec/src/Data/Coders.hs @@ -45,6 +45,7 @@ module Data.Coders decode, runE, -- Used in testing decodeList, + decodePair, decodeSeq, decodeStrictSeq, decodeSet, @@ -54,6 +55,7 @@ module Data.Coders unusedRequiredKeys, duplicateKey, wrapCBORArray, + encodePair, encodeFoldable, decodeCollectionWithLen, decodeCollection, @@ -147,6 +149,19 @@ decodeNullMaybe decoder = do pure Nothing _ -> Just <$> decoder + +decodePair :: Decoder s a -> Decoder s b -> Decoder s (a,b) +decodePair first second = decodeRecordNamed "pair" (const 2) $ do + a <- first + b <- second + pure (a,b) + +encodePair :: (a -> Encoding) -> (b -> Encoding) -> (a,b) -> Encoding +encodePair encodeFirst encodeSecond (x,y) = encodeListLen 2 + <> encodeFirst x + <> encodeSecond y + + invalidKey :: Word -> Decoder s a invalidKey k = cborError $ DecoderErrorCustom "not a valid key:" (Text.pack $ show k) diff --git a/semantics/small-steps-test/small-steps-test.cabal b/semantics/small-steps-test/small-steps-test.cabal index fcb7f9deb32..e8e2c27552a 100644 --- a/semantics/small-steps-test/small-steps-test.cabal +++ b/semantics/small-steps-test/small-steps-test.cabal @@ -33,6 +33,7 @@ library , microlens-th , mtl , nothunks + , prettyprinter , transformers >= 0.5 , QuickCheck -- IOHK deps @@ -55,6 +56,7 @@ test-suite doctests default-language: Haskell2010 build-depends: base , doctest + , prettyprinter build-tool-depends: doctest-discover:doctest-discover ghc-options: -Wall diff --git a/shelley/chain-and-ledger/executable-spec/src/Shelley/Spec/Ledger/Tx.hs b/shelley/chain-and-ledger/executable-spec/src/Shelley/Spec/Ledger/Tx.hs index a3599df1fce..c1ea7295510 100644 --- a/shelley/chain-and-ledger/executable-spec/src/Shelley/Spec/Ledger/Tx.hs +++ b/shelley/chain-and-ledger/executable-spec/src/Shelley/Spec/Ledger/Tx.hs @@ -382,6 +382,8 @@ class where validateScript :: Core.Script era -> Tx era -> Bool hashScript :: Core.Script era -> ScriptHash (Crypto era) + isNativeScript :: Core.Script era -> Bool + isNativeScript _ = True -- | Script evaluator for native multi-signature scheme. 'vhks' is the set of -- key hashes that signed the transaction to be validated.