Skip to content

Commit

Permalink
fix ex unit evaluation
Browse files Browse the repository at this point in the history
  - round trip test for translating exunits to/from plutus
  - test whether evaluateTransactionExecutionUnits provides enough exUnits
  - fixed ex unit translation
  • Loading branch information
redxaxder committed Jul 13, 2021
1 parent b49f598 commit 182f5b7
Show file tree
Hide file tree
Showing 6 changed files with 215 additions and 32 deletions.
12 changes: 2 additions & 10 deletions alonzo/impl/src/Cardano/Ledger/Alonzo/Tools.hs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import Cardano.Ledger.Alonzo.Scripts
)
import Cardano.Ledger.Alonzo.Tx (DataHash, ScriptPurpose (Spending), rdptr)
import Cardano.Ledger.Alonzo.TxBody (TxOut (..))
import Cardano.Ledger.Alonzo.TxInfo (txInfo, valContext)
import Cardano.Ledger.Alonzo.TxInfo (exBudgetToExUnits, txInfo, valContext)
import Cardano.Ledger.Alonzo.TxWitness (RdmrPtr (..), unRedeemers, unTxDats)
import Cardano.Ledger.BaseTypes (StrictMaybe (..), strictMaybeToMaybe)
import qualified Cardano.Ledger.Core as Core
Expand Down Expand Up @@ -57,20 +57,12 @@ data ScriptFailure c
| -- | The execution budget that was calculated by the Plutus
-- evaluator is out of bounds.
IncompatibleBudget P.ExBudget
deriving (Show)

note :: e -> Maybe a -> Either e a
note _ (Just x) = Right x
note e Nothing = Left e

safeFromInteger :: forall a. (Integral a, Bounded a) => Integer -> Maybe a
safeFromInteger i
| toInteger (minBound :: a) <= i && i <= toInteger (maxBound :: a) = Just $ fromInteger i
| otherwise = Nothing

exBudgetToExUnits :: P.ExBudget -> Maybe ExUnits
exBudgetToExUnits (P.ExBudget (P.ExCPU cpu) (P.ExMemory memory)) =
ExUnits <$> safeFromInteger (toInteger cpu) <*> safeFromInteger (toInteger memory)

-- | Evaluate the execution budgets needed for all the redeemers in
-- a given transaction. If a redeemer is invalid, a failure is returned instead.
--
Expand Down
10 changes: 10 additions & 0 deletions alonzo/impl/src/Cardano/Ledger/Alonzo/TxInfo.hs
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,16 @@ transDataPair (x, y) = (transDataHash' x, P.Datum (getPlutusData y))
transExUnits :: ExUnits -> P.ExBudget
transExUnits (ExUnits mem steps) = P.ExBudget (P.ExCPU (fromIntegral steps)) (P.ExMemory (fromIntegral mem))

exBudgetToExUnits :: P.ExBudget -> Maybe ExUnits
exBudgetToExUnits (P.ExBudget (P.ExCPU steps) (P.ExMemory memory)) =
ExUnits <$> safeFromInteger (toInteger memory)
<*> safeFromInteger (toInteger steps)
where
safeFromInteger :: forall a. (Integral a, Bounded a) => Integer -> Maybe a
safeFromInteger i
| toInteger (minBound :: a) <= i && i <= toInteger (maxBound :: a) = Just $ fromInteger i
| otherwise = Nothing

-- ===================================
-- translate Script Purpose

Expand Down
3 changes: 3 additions & 0 deletions alonzo/test/cardano-ledger-alonzo-test.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -73,19 +73,22 @@ test-suite cardano-ledger-alonzo-test
test
other-modules:
Test.Cardano.Ledger.Alonzo.Trials
Test.Cardano.Ledger.Alonzo.Tools
Test.Cardano.Ledger.Alonzo.Golden
Test.Cardano.Ledger.Alonzo.Serialisation.Tripping
Test.Cardano.Ledger.Alonzo.Examples
Test.Cardano.Ledger.Alonzo.Translation
Test.Cardano.Ledger.Alonzo.Serialisation.CDDL
build-depends:
array,
base16-bytestring,
bytestring,
cardano-binary,
cardano-ledger-alonzo,
cardano-ledger-alonzo-test,
cardano-ledger-shelley-ma,
cardano-ledger-core,
cardano-ledger-test,
cardano-ledger-shelley-ma-test,
cardano-slotting,
containers,
Expand Down
32 changes: 10 additions & 22 deletions alonzo/test/test/Test/Cardano/Ledger/Alonzo/Examples.hs
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,10 @@ import Cardano.Ledger.Alonzo.Scripts (Script (..))
import Data.ByteString.Short (ShortByteString)
import Data.Maybe (fromMaybe)
import qualified Plutus.V1.Ledger.Api as P
( EvaluationError (..),
ExBudget (..),
ExCPU (..),
ExMemory (..),
VerboseMode (..),
defaultCostModelParams,
evaluateScriptRestricting,
)
import Plutus.V1.Ledger.Examples
( alwaysFailingNAryFunction,
alwaysSucceedingNAryFunction,
)
import qualified PlutusTx as P
import qualified Test.Cardano.Ledger.Alonzo.PlutusScripts as Generated
( evendata3,
guessTheNumber2,
Expand All @@ -39,27 +30,24 @@ data ShouldSucceed = ShouldSucceed | ShouldFail

directPlutusTest :: ShouldSucceed -> ShortByteString -> [P.Data] -> Assertion
directPlutusTest expectation script ds =
case (expectation, evalWithHugeBudget script ds) of
(ShouldSucceed, (_, Left e)) ->
case (expectation, evalWithTightBudget script ds) of
(ShouldSucceed, Left e) ->
assertBool ("This script should have succeeded, but: " <> show e) False
(ShouldSucceed, (_, Right _)) ->
(ShouldSucceed, Right _) ->
assertBool "" True
(ShouldFail, (_, Left ((P.CekError _)))) ->
(ShouldFail, Left ((P.CekError _))) ->
assertBool "" True -- TODO rule out cost model failure
(ShouldFail, (_, Left e)) ->
(ShouldFail, Left e) ->
assertBool ("Not the script failure we expected: " <> show e) False
(ShouldFail, (_, Right _)) ->
(ShouldFail, Right _) ->
assertBool "This script should have failed" False
where
costModel = fromMaybe (error "corrupt default cost model") P.defaultCostModelParams
-- Evaluate a script with sufficient budget to run it.
evalWithHugeBudget scr datums =
P.evaluateScriptRestricting
P.Verbose
costModel
(P.ExBudget (P.ExCPU 100000000) (P.ExMemory 10000000))
scr
datums
evalWithTightBudget :: ShortByteString -> [P.Data] -> Either P.EvaluationError ()
evalWithTightBudget scr datums = do
budget <- snd $ P.evaluateScriptCounting P.Quiet costModel scr datums
snd $ P.evaluateScriptRestricting P.Verbose costModel budget scr datums

-- | Expects 3 args (data, redeemer, context)
guessTheNumber3 :: ShortByteString
Expand Down
187 changes: 187 additions & 0 deletions alonzo/test/test/Test/Cardano/Ledger/Alonzo/Tools.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeApplications #-}

module Test.Cardano.Ledger.Alonzo.Tools (tests, testExUnitCalculation) where

import Cardano.Ledger.Alonzo.Language (Language (..))
import Cardano.Ledger.Alonzo.PParams (PParams, PParams' (..), ProtVer (..))
import Cardano.Ledger.Alonzo.Rules.Utxos (UTXOS)
import Cardano.Ledger.Alonzo.Scripts (CostModel, ExUnits (..), defaultCostModel)
import Cardano.Ledger.Alonzo.Tools (evaluateTransactionExecutionUnits)
import Cardano.Ledger.Alonzo.Tx
( IsValidating (..),
ValidatedTx (..),
)
import Cardano.Ledger.Alonzo.TxInfo (exBudgetToExUnits, transExUnits)
import Cardano.Ledger.Alonzo.TxWitness (RdmrPtr, Redeemers (..), txrdmrs)
import Cardano.Ledger.Coin (Coin (..))
import Cardano.Ledger.Keys (GenDelegs (..))
import Cardano.Ledger.SafeHash (hashAnnotated)
import qualified Cardano.Ledger.Tx as Core
import Cardano.Slotting.EpochInfo (EpochInfo, fixedEpochInfo)
import Cardano.Slotting.Slot (EpochSize (..), SlotNo (..))
import Cardano.Slotting.Time (SystemStart, mkSlotLength)
import Control.State.Transition.Extended (TRC (..))
import Data.Array (Array, array)
import Data.Default.Class (Default (..))
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Maybe (fromJust)
import Data.Word (Word64)
import GHC.Records (getField)
import Shelley.Spec.Ledger.LedgerState (UTxOState (..))
import Shelley.Spec.Ledger.STS.Utxo (UtxoEnv (..))
import Shelley.Spec.Ledger.UTxO (UTxO, makeWitnessVKey)
import Test.Cardano.Ledger.Examples.TwoPhaseValidation (A, datumExample1, initUTxO, someKeys, testSystemStart, validatingBody, validatingRedeemersEx1)
import Test.Cardano.Ledger.Generic.Proof (Evidence (Mock), Proof (Alonzo))
import Test.Cardano.Ledger.Generic.Updaters
import Test.Shelley.Spec.Ledger.Utils (applySTSTest, runShelleyBase)
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit (testCase)
import Test.Tasty.QuickCheck (Gen, Property, chooseBoundedIntegral, counterexample, testProperty)

tests :: TestTree
tests =
testGroup "ExUnit tools" $
[ testProperty "Plutus ExUnit translation round-trip" exUnitsTranslationRoundTrip,
testCase "calculate ExUnits" exampleExUnitCalc
]

genExUnits :: Gen ExUnits
genExUnits = ExUnits <$> genUnit <*> genUnit
where
genUnit :: Gen Word64
genUnit = chooseBoundedIntegral (0, 2 ^ (63 :: Word64) - 1)

-- ExUnits should remain intact when translating to and from the plutus type
exUnitsTranslationRoundTrip :: Gen Property
exUnitsTranslationRoundTrip = do
e <- genExUnits
let result = (exBudgetToExUnits . transExUnits) e
pure $
counterexample
( "Before: " <> show (Just e)
<> "\n After: "
<> show result
)
$ result == Just e

-- checks plutus script validation against a tx which has had
-- its ex units replaced by the output of evaluateTransactionExecutionUnits
testExUnitCalculation ::
MonadFail m =>
Core.Tx A ->
UTxOState A ->
UtxoEnv A ->
EpochInfo m ->
SystemStart ->
Array Language CostModel ->
m ()
testExUnitCalculation tx utxoState ue ei ss costmdls = do
tx' <- updateTxExUnits tx utxo ei ss costmdls
_ <-
failLeft $
runShelleyBase $
applySTSTest @(UTXOS A) (TRC (ue, utxoState, vtx tx'))
pure ()
where
utxo = _utxo utxoState

-- nonFreeCostModel = CostModel $ 1000 <$ fromJust defaultCostModelParams

exampleExUnitCalc :: IO ()
exampleExUnitCalc =
testExUnitCalculation
exampleTx
ustate
uenv
exampleEpochInfo
testSystemStart
costmodels

exampleTx :: Core.Tx A
exampleTx =
let pf = Alonzo Mock
in newTx
Override
pf
[ Body (validatingBody pf),
Witnesses'
[ AddrWits [makeWitnessVKey (hashAnnotated (validatingBody pf)) (someKeys pf)],
ScriptWits [always 3 pf],
DataWits [datumExample1],
RdmrWits validatingRedeemersEx1
]
]

exampleEpochInfo :: Monad m => EpochInfo m
exampleEpochInfo = fixedEpochInfo (EpochSize 100) (mkSlotLength 1)

uenv :: UtxoEnv A
uenv = UtxoEnv (SlotNo 0) pparams mempty (GenDelegs mempty)

costmodels :: Array Language CostModel
costmodels = array (PlutusV1, PlutusV1) [(PlutusV1, fromJust defaultCostModel)]

ustate :: UTxOState A
ustate =
UTxOState
{ _utxo = initUTxO (Alonzo Mock),
_deposited = Coin 0,
_fees = Coin 0,
_ppups = def
}

-- Requires ex units, but there are no fees
pparams :: PParams A
pparams =
newPParams
(Alonzo Mock)
[ Costmdls $ Map.singleton PlutusV1 $ fromJust defaultCostModel,
MaxValSize 1000000000,
MaxTxExUnits $ ExUnits 100000000 100000000,
MaxBlockExUnits $ ExUnits 100000000 100000000,
ProtocolVersion $ ProtVer 5 0
]

updateTxExUnits ::
MonadFail m =>
Core.Tx A ->
UTxO A ->
EpochInfo m ->
SystemStart ->
Array Language CostModel ->
m (Core.Tx A)
updateTxExUnits tx utxo ei ss costmdls = do
-- rdmrs :: Map RdmrPtr ExUnits
rdmrs <-
traverse failLeft
=<< evaluateTransactionExecutionUnits tx utxo ei ss costmdls
pure (replaceRdmrs tx rdmrs)

replaceRdmrs :: Core.Tx A -> Map RdmrPtr ExUnits -> Core.Tx A
replaceRdmrs tx rdmrs = tx {Core.wits = wits'}
where
wits' = (Core.wits tx) {txrdmrs = newrdmrs}
newrdmrs = foldr replaceRdmr (txrdmrs (Core.wits tx)) (Map.assocs rdmrs)

replaceRdmr :: (RdmrPtr, ExUnits) -> Redeemers A -> Redeemers A
replaceRdmr (ptr, ex) x@(Redeemers r) =
case Map.lookup ptr r of
Just (dat, _ex) -> Redeemers $ Map.insert ptr (dat, ex) r
Nothing -> x

failLeft :: MonadFail m => Show e => Either e a -> m a
failLeft (Right a) = pure a
failLeft (Left e) = fail (show e)

vtx :: Core.Tx A -> ValidatedTx A
vtx tx =
ValidatedTx
{ body = getField @"body" tx,
wits = getField @"wits" tx,
isValidating = IsValidating True,
auxiliaryData = getField @"auxiliaryData" tx
}
3 changes: 3 additions & 0 deletions alonzo/test/test/Tests.hs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import Test.Cardano.Ledger.Alonzo.Examples (plutusScriptExamples)
import Test.Cardano.Ledger.Alonzo.Golden as Golden
import qualified Test.Cardano.Ledger.Alonzo.Serialisation.CDDL as CDDL
import qualified Test.Cardano.Ledger.Alonzo.Serialisation.Tripping as Tripping
import qualified Test.Cardano.Ledger.Alonzo.Tools as TOOLS
import qualified Test.Cardano.Ledger.Alonzo.Translation as Translation
import Test.Cardano.Ledger.Alonzo.Trials (alonzoPropertyTests, fastPropertyTests)
import Test.Tasty
Expand All @@ -33,6 +34,7 @@ mainTests =
Tripping.tests,
Translation.tests,
CDDL.tests 5,
TOOLS.tests,
Golden.goldenUTxOEntryMinAda,
plutusScriptExamples
]
Expand All @@ -44,6 +46,7 @@ fastTests =
[ Tripping.tests,
Translation.tests,
CDDL.tests 1,
TOOLS.tests,
Golden.goldenUTxOEntryMinAda,
plutusScriptExamples
]
Expand Down

0 comments on commit 182f5b7

Please sign in to comment.