From 579ecbe791e14918184de6801e1ed97dc094f763 Mon Sep 17 00:00:00 2001 From: mmontin Date: Thu, 4 Jul 2024 13:27:57 +0200 Subject: [PATCH 1/9] tests pass with new mockchain return type --- src/Cooked/MockChain.hs | 10 +-- src/Cooked/MockChain/BlockChain.hs | 12 ++++ src/Cooked/MockChain/Direct.hs | 52 +++++++++----- src/Cooked/MockChain/Staged.hs | 69 ++++--------------- src/Cooked/MockChain/Testing.hs | 4 +- src/Cooked/Pretty/Cooked.hs | 83 +++++++++++----------- src/Cooked/Pretty/Options.hs | 21 +++++- tests/Cooked/Attack/DatumHijackingSpec.hs | 8 +-- tests/Cooked/Attack/DoubleSatSpec.hs | 84 ++++++++++++----------- tests/Cooked/Attack/DupTokenSpec.hs | 8 +-- tests/Cooked/InlineDatumsSpec.hs | 4 +- tests/Cooked/MockChain/BlockChainSpec.hs | 6 +- tests/Cooked/Tweak/CommonSpec.hs | 83 +++++++++++----------- tests/Cooked/Tweak/OutPermutationsSpec.hs | 10 +-- tests/Cooked/Tweak/TamperDatumSpec.hs | 66 +++++++++--------- tests/Cooked/Tweak/ValidityRangeSpec.hs | 6 +- 16 files changed, 268 insertions(+), 258 deletions(-) diff --git a/src/Cooked/MockChain.hs b/src/Cooked/MockChain.hs index 78ba59c79..b5a7b3193 100644 --- a/src/Cooked/MockChain.hs +++ b/src/Cooked/MockChain.hs @@ -3,14 +3,10 @@ module Cooked.MockChain (module X) where import Cooked.MockChain.Balancing as X -import Cooked.MockChain.BlockChain as X -import Cooked.MockChain.Direct as X +import Cooked.MockChain.BlockChain as X hiding (BlockChainLogEntry) +import Cooked.MockChain.Direct as X hiding (MockChainReturn) import Cooked.MockChain.MinAda as X -import Cooked.MockChain.Staged as X hiding - ( MockChainLog, - MockChainLogEntry, - StagedMockChain, - ) +import Cooked.MockChain.Staged as X hiding (StagedMockChain) import Cooked.MockChain.Testing as X import Cooked.MockChain.UtxoSearch as X import Cooked.MockChain.UtxoState as X (UtxoState) diff --git a/src/Cooked/MockChain/BlockChain.hs b/src/Cooked/MockChain/BlockChain.hs index fcdf38e79..5003e9a1c 100644 --- a/src/Cooked/MockChain/BlockChain.hs +++ b/src/Cooked/MockChain/BlockChain.hs @@ -15,6 +15,7 @@ -- from the core definition of our blockchain. module Cooked.MockChain.BlockChain ( MockChainError (..), + BlockChainLogEntry (..), MonadBlockChainBalancing (..), MonadBlockChainWithoutValidation (..), MonadBlockChain (..), @@ -110,6 +111,12 @@ data MockChainError where FailWith :: String -> MockChainError deriving (Show, Eq) +data BlockChainLogEntry where + BCLogSubmittedTxSkel :: SkelContext -> TxSkel -> BlockChainLogEntry + BCLogNewTx :: Api.TxId -> BlockChainLogEntry + BCLogInfo :: String -> BlockChainLogEntry + BCLogFail :: String -> BlockChainLogEntry + -- | Contains methods needed for balancing. class (MonadFail m, MonadError MockChainError m) => MonadBlockChainBalancing m where -- | Returns the parameters of the chain. @@ -128,6 +135,9 @@ class (MonadFail m, MonadError MockChainError m) => MonadBlockChainBalancing m w -- | Returns an output given a reference to it txOutByRefLedger :: Api.TxOutRef -> m (Maybe Ledger.TxOut) + -- | Logs an event that occured during a BlockChain run + blockChainLog :: BlockChainLogEntry -> m () + class (MonadBlockChainBalancing m) => MonadBlockChainWithoutValidation m where -- | Returns a list of all currently known outputs. allUtxosLedger :: m [(Api.TxOutRef, Ledger.TxOut)] @@ -493,6 +503,7 @@ instance (MonadTrans t, MonadBlockChainBalancing m, Monad (t m), MonadError Mock utxosAtLedger = lift . utxosAtLedger txOutByRefLedger = lift . txOutByRefLedger datumFromHash = lift . datumFromHash + blockChainLog = lift . blockChainLog instance (MonadTrans t, MonadBlockChainWithoutValidation m, Monad (t m), MonadError MockChainError (AsTrans t m)) => MonadBlockChainWithoutValidation (AsTrans t m) where allUtxosLedger = lift allUtxosLedger @@ -536,6 +547,7 @@ instance (MonadBlockChainBalancing m) => MonadBlockChainBalancing (ListT m) wher utxosAtLedger = lift . utxosAtLedger txOutByRefLedger = lift . txOutByRefLedger datumFromHash = lift . datumFromHash + blockChainLog = lift . blockChainLog instance (MonadBlockChainWithoutValidation m) => MonadBlockChainWithoutValidation (ListT m) where allUtxosLedger = lift allUtxosLedger diff --git a/src/Cooked/MockChain/Direct.hs b/src/Cooked/MockChain/Direct.hs index c2a977eca..a92ad8f05 100644 --- a/src/Cooked/MockChain/Direct.hs +++ b/src/Cooked/MockChain/Direct.hs @@ -16,6 +16,7 @@ import Control.Monad.Except import Control.Monad.Identity import Control.Monad.Reader import Control.Monad.State.Strict +import Control.Monad.Writer import Cooked.Conversion.ToScript import Cooked.Conversion.ToScriptHash import Cooked.InitialDistribution @@ -97,6 +98,12 @@ data MockChainSt = MockChainSt } deriving (Show) +mcstToSkelContext :: MockChainSt -> SkelContext +mcstToSkelContext MockChainSt {..} = + SkelContext + (txOutV2FromLedger <$> getIndex mcstIndex) + (Map.map fst mcstDatums) + -- | Generating an emulated state for the emulator from a mockchain state and -- some parameters, based on a standard initial state mcstToEmulatedLedgerState :: MockChainSt -> Emulator.EmulatedLedgerState @@ -129,8 +136,8 @@ instance Eq MockChainSt where ] newtype MockChainT m a = MockChainT - {unMockChain :: StateT MockChainSt (ExceptT MockChainError m) a} - deriving newtype (Functor, Applicative, MonadState MockChainSt, MonadError MockChainError) + {unMockChain :: (StateT MockChainSt (ExceptT MockChainError (WriterT [BlockChainLogEntry] m))) a} + deriving newtype (Functor, Applicative, MonadState MockChainSt, MonadError MockChainError, MonadWriter [BlockChainLogEntry]) type MockChain = MockChainT Identity @@ -140,13 +147,15 @@ instance (Monad m) => Monad (MockChainT m) where MockChainT x >>= f = MockChainT $ x >>= unMockChain . f instance (Monad m) => MonadFail (MockChainT m) where - fail = throwError . FailWith + fail s = do + blockChainLog $ BCLogFail s + throwError $ FailWith s instance MonadTrans MockChainT where - lift = MockChainT . lift . lift + lift = MockChainT . lift . lift . lift instance (Monad m, Alternative m) => Alternative (MockChainT m) where - empty = MockChainT $ StateT $ const $ ExceptT empty + empty = MockChainT $ StateT $ const $ ExceptT $ WriterT empty (<|>) = combineMockChainT (<|>) combineMockChainT :: @@ -157,15 +166,17 @@ combineMockChainT :: MockChainT m x combineMockChainT f ma mb = MockChainT $ StateT $ \s -> - let resA = runExceptT $ runStateT (unMockChain ma) s - resB = runExceptT $ runStateT (unMockChain mb) s - in ExceptT $ f resA resB + let resA = runWriterT $ runExceptT $ runStateT (unMockChain ma) s + resB = runWriterT $ runExceptT $ runStateT (unMockChain mb) s + in ExceptT $ WriterT $ f resA resB + +type MockChainReturn a b = (Either MockChainError (a, b), [BlockChainLogEntry]) mapMockChainT :: - (m (Either MockChainError (a, MockChainSt)) -> n (Either MockChainError (b, MockChainSt))) -> + (m (MockChainReturn a MockChainSt) -> n (MockChainReturn b MockChainSt)) -> MockChainT m a -> MockChainT n b -mapMockChainT f = MockChainT . mapStateT (mapExceptT f) . unMockChain +mapMockChainT f = MockChainT . mapStateT (mapExceptT (mapWriterT f)) . unMockChain -- | Executes a 'MockChainT' from some initial state; does /not/ convert the -- 'MockChainSt' into a 'UtxoState'. @@ -173,8 +184,8 @@ runMockChainTRaw :: (Monad m) => MockChainSt -> MockChainT m a -> - m (Either MockChainError (a, MockChainSt)) -runMockChainTRaw i0 = runExceptT . flip runStateT i0 . unMockChain + m (MockChainReturn a MockChainSt) +runMockChainTRaw i0 = runWriterT . runExceptT . flip runStateT i0 . unMockChain -- | Executes a 'MockChainT' from an initial state set up with the given initial -- value distribution. Similar to 'runMockChainT', uses the default @@ -184,25 +195,25 @@ runMockChainTFrom :: (Monad m) => InitialDistribution -> MockChainT m a -> - m (Either MockChainError (a, UtxoState)) -runMockChainTFrom i0 = fmap (fmap $ second mcstToUtxoState) . runMockChainTRaw (mockChainSt0From i0) + m (MockChainReturn a UtxoState) +runMockChainTFrom i0 s = first (right (second mcstToUtxoState)) <$> runMockChainTRaw (mockChainSt0From i0) s -- | Executes a 'MockChainT' from the canonical initial state and environment. -- The canonical environment uses the default 'SlotConfig' and -- @Cooked.Wallet.wallet 1@ as the sole wallet signing transactions. -runMockChainT :: (Monad m) => MockChainT m a -> m (Either MockChainError (a, UtxoState)) +runMockChainT :: (Monad m) => MockChainT m a -> m (MockChainReturn a UtxoState) runMockChainT = runMockChainTFrom def -- | See 'runMockChainTRaw' -runMockChainRaw :: MockChain a -> Either MockChainError (a, MockChainSt) +runMockChainRaw :: MockChain a -> MockChainReturn a MockChainSt runMockChainRaw = runIdentity . runMockChainTRaw def -- | See 'runMockChainTFrom' -runMockChainFrom :: InitialDistribution -> MockChain a -> Either MockChainError (a, UtxoState) +runMockChainFrom :: InitialDistribution -> MockChain a -> MockChainReturn a UtxoState runMockChainFrom i0 = runIdentity . runMockChainTFrom i0 -- | See 'runMockChainT' -runMockChain :: MockChain a -> Either MockChainError (a, UtxoState) +runMockChain :: MockChain a -> MockChainReturn a UtxoState runMockChain = runIdentity . runMockChainT -- * Canonical initial values @@ -316,6 +327,7 @@ instance (Monad m) => MonadBlockChainBalancing (MockChainT m) where txOutByRefLedger outref = gets $ Map.lookup outref . getIndex . mcstIndex datumFromHash datumHash = (txSkelOutUntypedDatum <=< Just . fst <=< Map.lookup datumHash) <$> gets mcstDatums utxosAtLedger addr = filter ((addr ==) . outputAddress . txOutV2FromLedger . snd) <$> allUtxosLedger + blockChainLog l = tell [l] instance (Monad m) => MonadBlockChainWithoutValidation (MockChainT m) where allUtxosLedger = gets $ Map.toList . getIndex . mcstIndex @@ -325,6 +337,8 @@ instance (Monad m) => MonadBlockChainWithoutValidation (MockChainT m) where instance (Monad m) => MonadBlockChain (MockChainT m) where validateTxSkel skelUnbal = do + -- We log the submitted skeleton + gets mcstToSkelContext >>= blockChainLog . (`BCLogSubmittedTxSkel` skelUnbal) -- We retrieve the current parameters oldParams <- getParams -- We compute the optionally modified parameters @@ -386,6 +400,8 @@ instance (Monad m) => MonadBlockChain (MockChainT m) where modify' (\st -> st {mcstCurrentSlot = mcstCurrentSlot st + 1}) -- We return the parameters to their original state setParams oldParams + -- We log the validated transaction + blockChainLog $ BCLogNewTx (Ledger.fromCardanoTxId $ Ledger.getCardanoTxId cardanoTx) -- We return the validated transaction return cardanoTx where diff --git a/src/Cooked/MockChain/Staged.hs b/src/Cooked/MockChain/Staged.hs index a61a6cff9..9f14917f7 100644 --- a/src/Cooked/MockChain/Staged.hs +++ b/src/Cooked/MockChain/Staged.hs @@ -6,8 +6,6 @@ module Cooked.MockChain.Staged ( interpretAndRunWith, interpretAndRun, - MockChainLogEntry (..), - MockChainLog (..), StagedMockChain, runTweakFrom, MonadModalBlockChain, @@ -26,7 +24,6 @@ import Control.Monad (MonadPlus (..), msum) import Control.Monad.Except import Control.Monad.Reader import Control.Monad.State -import Control.Monad.Trans.Writer (WriterT (runWriterT), tell) import Cooked.Ltl import Cooked.MockChain.BlockChain import Cooked.MockChain.Direct @@ -34,10 +31,8 @@ import Cooked.MockChain.UtxoState import Cooked.Skeleton import Cooked.Tweak.Common import Data.Default -import Data.Map qualified as Map import Ledger.Slot qualified as Ledger import Ledger.Tx qualified as Ledger -import Ledger.Tx.CardanoAPI qualified as Ledger import Plutus.Script.Utils.Scripts qualified as Script import PlutusLedgerApi.V3 qualified as Api @@ -49,30 +44,15 @@ import PlutusLedgerApi.V3 qualified as Api interpretAndRunWith :: (forall m. (Monad m) => MockChainT m a -> m res) -> StagedMockChain a -> - [(res, MockChainLog)] -interpretAndRunWith f smc = runWriterT $ f $ interpret smc + [res] +interpretAndRunWith f smc = f $ interpret smc -interpretAndRun :: - StagedMockChain a -> - [(Either MockChainError (a, UtxoState), MockChainLog)] +interpretAndRun :: StagedMockChain a -> [MockChainReturn a UtxoState] interpretAndRun = interpretAndRunWith runMockChainT -data MockChainLogEntry - = MCLogSubmittedTxSkel SkelContext TxSkel - | MCLogNewTx Api.TxId - | MCLogFail String - -newtype MockChainLog = MockChainLog {unMockChainLog :: [MockChainLogEntry]} - -instance Semigroup MockChainLog where - MockChainLog x <> MockChainLog y = MockChainLog $ x <> y - -instance Monoid MockChainLog where - mempty = MockChainLog [] - -- | The semantic domain in which 'StagedMockChain' gets interpreted; see the -- 'interpret' function for more. -type InterpMockChain = MockChainT (WriterT MockChainLog []) +type InterpMockChain = MockChainT [] -- | The 'interpret' function gives semantics to our traces. One -- 'StagedMockChain' computation yields a potential list of 'MockChainT' @@ -99,6 +79,7 @@ data MockChainBuiltin a where AllUtxosLedger :: MockChainBuiltin [(Api.TxOutRef, Ledger.TxOut)] UtxosAtLedger :: Api.Address -> MockChainBuiltin [(Api.TxOutRef, Ledger.TxOut)] ValidatorFromHash :: Script.ValidatorHash -> MockChainBuiltin (Maybe (Script.Versioned Script.Validator)) + BlockChainLog :: BlockChainLogEntry -> MockChainBuiltin () -- | The empty set of traces Empty :: MockChainBuiltin a -- | The union of two sets of traces @@ -132,33 +113,17 @@ instance InterpLtl (UntypedTweak InterpMockChain) MockChainBuiltin InterpMockCha interpBuiltin (ValidateTxSkel skel) = get >>= msum - . map (uncurry interpretAndTell) + . map (uncurry interpretNow) . nowLaterList where - interpretAndTell :: + interpretNow :: UntypedTweak InterpMockChain -> [Ltl (UntypedTweak InterpMockChain)] -> StateT [Ltl (UntypedTweak InterpMockChain)] InterpMockChain Ledger.CardanoTx - interpretAndTell (UntypedTweak now) later = do - mcst <- lift get - let managedTxOuts = getIndex . mcstIndex $ mcst - managedDatums = mcstDatums mcst + interpretNow (UntypedTweak now) later = do (_, skel') <- lift $ runTweakInChain now skel - lift $ - lift $ - tell $ - MockChainLog - [ MCLogSubmittedTxSkel - (SkelContext (txOutV2FromLedger <$> managedTxOuts) $ Map.map fst managedDatums) - skel' - ] - tx <- validateTxSkel skel' - lift $ - lift $ - tell $ - MockChainLog [MCLogNewTx (Ledger.fromCardanoTxId $ Ledger.getCardanoTxId tx)] put later - return tx + validateTxSkel skel' interpBuiltin (TxOutByRefLedger o) = txOutByRefLedger o interpBuiltin GetCurrentSlot = currentSlot interpBuiltin (AwaitSlot s) = awaitSlot s @@ -168,23 +133,18 @@ instance InterpLtl (UntypedTweak InterpMockChain) MockChainBuiltin InterpMockCha interpBuiltin (UtxosAtLedger address) = utxosAtLedger address interpBuiltin Empty = mzero interpBuiltin (Alt l r) = interpLtl l `mplus` interpLtl r - interpBuiltin (Fail msg) = do - lift $ lift $ tell $ MockChainLog [MCLogFail msg] - fail msg + interpBuiltin (Fail msg) = fail msg interpBuiltin (ThrowError err) = throwError err interpBuiltin (CatchError act handler) = catchError (interpLtl act) (interpLtl . handler) + interpBuiltin (BlockChainLog entry) = blockChainLog entry -- ** Helpers to run tweaks for use in tests for tweaks -runTweak :: Tweak InterpMockChain a -> TxSkel -> [Either MockChainError (a, TxSkel)] +runTweak :: Tweak InterpMockChain a -> TxSkel -> [MockChainReturn a TxSkel] runTweak = runTweakFrom def -runTweakFrom :: MockChainSt -> Tweak InterpMockChain a -> TxSkel -> [Either MockChainError (a, TxSkel)] -runTweakFrom mcst tweak skel = - map (right fst . fst) - . runWriterT - . runMockChainTRaw mcst - $ runTweakInChain tweak skel +runTweakFrom :: MockChainSt -> Tweak InterpMockChain a -> TxSkel -> [MockChainReturn a TxSkel] +runTweakFrom mcst tweak = map (first (right fst)) . runMockChainTRaw mcst . runTweakInChain tweak -- ** Modalities @@ -241,6 +201,7 @@ instance MonadBlockChainBalancing StagedMockChain where txOutByRefLedger = singletonBuiltin . TxOutByRefLedger utxosAtLedger = singletonBuiltin . UtxosAtLedger validatorFromHash = singletonBuiltin . ValidatorFromHash + blockChainLog = singletonBuiltin . BlockChainLog instance MonadBlockChainWithoutValidation StagedMockChain where allUtxosLedger = singletonBuiltin AllUtxosLedger diff --git a/src/Cooked/MockChain/Testing.hs b/src/Cooked/MockChain/Testing.hs index afa3f39bd..89d4735b6 100644 --- a/src/Cooked/MockChain/Testing.hs +++ b/src/Cooked/MockChain/Testing.hs @@ -148,7 +148,7 @@ testAllSatisfiesFrom :: prop testAllSatisfiesFrom pcOpts f = testSatisfiesFrom' (testAll go) where - go :: (Either MockChainError (a, UtxoState), MockChainLog) -> prop + go :: MockChainReturn a UtxoState -> prop go (prop, mcLog) = testCounterexample (renderString (prettyCookedOpt pcOpts) mcLog) (f prop) -- | Asserts that the given 'StagedMockChain' produces exactly two outcomes, @@ -227,7 +227,7 @@ testOneEquivClass pcOpts rel = testSatisfiesFrom' $ \case -- predicates. Check 'testAllSatisfiesFrom' or 'testBinaryRelatedBy' for -- examples on using this. testSatisfiesFrom' :: - ([(Either MockChainError (a, UtxoState), MockChainLog)] -> prop) -> + ([MockChainReturn a UtxoState] -> prop) -> InitialDistribution -> StagedMockChain a -> prop diff --git a/src/Cooked/Pretty/Cooked.hs b/src/Cooked/Pretty/Cooked.hs index 6ef15fd3a..75d5a39ce 100644 --- a/src/Cooked/Pretty/Cooked.hs +++ b/src/Cooked/Pretty/Cooked.hs @@ -31,8 +31,8 @@ where import Cooked.Conversion import Cooked.MockChain.BlockChain +import Cooked.MockChain.Direct import Cooked.MockChain.GenerateTx -import Cooked.MockChain.Staged import Cooked.MockChain.UtxoState import Cooked.Output import Cooked.Pretty.Class @@ -124,51 +124,50 @@ instance (Show a) => PrettyCooked (a, UtxoState) where "-" ["Returns:" <+> PP.viaShow res, prettyCookedOpt opts state] -instance (Show a) => PrettyCooked (Either MockChainError (a, UtxoState)) where - prettyCookedOpt opts (Left err) = "🔴" <+> prettyCookedOpt opts err - prettyCookedOpt opts (Right endState) = "🟢" <+> prettyCookedOpt opts endState +prettyBlockChainEntries :: PrettyCookedOpts -> [BlockChainLogEntry] -> DocCooked +prettyBlockChainEntries opts entries = + prettyItemize + "MockChain run:" + "-" + (prettyCookedOpt opts <$> entries) + +instance (Show a) => PrettyCooked (MockChainReturn a UtxoState) where + prettyCookedOpt opts (res, entries) = prettyLogWith $ + case res of + Left err -> "🔴" <+> prettyCookedOpt opts err + Right (a, s) -> "🟢" <+> prettyCookedOpt opts (a, s) + where + prettyLogWith :: DocCooked -> DocCooked + prettyLogWith inner = + case pcOptLog opts of + PCOptLogNone -> inner + PCOptLogNoInfo -> + prettyItemize + "End result:" + "-" + [prettyBlockChainEntries opts (filter (\case BCLogInfo _ -> False; _ -> True) entries), inner] + PCOptLogAll -> + prettyItemize + "End result:" + "-" + [prettyBlockChainEntries opts entries, inner] -- | This pretty prints a 'MockChainLog' that usually consists of the list of -- validated or submitted transactions. In the log, we know a transaction has -- been validated if the 'MCLogSubmittedTxSkel' is followed by a 'MCLogNewTx'. -instance PrettyCooked MockChainLog where - prettyCookedOpt opts = - prettyEnumerate "MockChain run:" "." - . go [] - . unMockChainLog - where - -- In order to avoid printing 'MockChainLogValidateTxSkel' then - -- 'MockChainLogNewTx' as two different items, we combine them into one - -- single 'DocCooked' - go :: [DocCooked] -> [MockChainLogEntry] -> [DocCooked] - go - acc - ( MCLogSubmittedTxSkel skelContext skel - : MCLogNewTx txId - : entries - ) - | pcOptPrintTxHashes opts = - go - ( "Validated" - <+> PP.parens ("TxId:" <+> prettyCookedOpt opts txId) - <+> prettyTxSkel opts skelContext skel - : acc - ) - entries - | otherwise = go ("Validated" <+> prettyTxSkel opts skelContext skel : acc) entries - go - acc - ( MCLogSubmittedTxSkel skelContext skel - : entries - ) = - go ("Submitted" <+> prettyTxSkel opts skelContext skel : acc) entries - go acc (MCLogFail msg : entries) = - go ("Fail:" <+> PP.pretty msg : acc) entries - -- This case is not supposed to occur because it should follow a - -- 'MCLogSubmittedTxSkel' - go acc (MCLogNewTx txId : entries) = - go ("New transaction:" <+> prettyCookedOpt opts txId : acc) entries - go acc [] = reverse acc +instance PrettyCooked BlockChainLogEntry where + prettyCookedOpt opts (BCLogSubmittedTxSkel skelContext skel) = "Submitted:" <+> prettyTxSkel opts skelContext skel + prettyCookedOpt _ (BCLogFail msg) = "Fail:" <+> PP.pretty msg + prettyCookedOpt opts (BCLogNewTx txId) = "New transaction:" <+> prettyCookedOpt opts txId + prettyCookedOpt _ (BCLogInfo info) = "Info:" <+> PP.pretty info + +-- go acc (MCLogFail msg : entries) = +-- go ("Fail:" <+> PP.pretty msg : acc) entries +-- -- This case is not supposed to occur because it should follow a +-- -- 'MCLogSubmittedTxSkel' +-- go acc (MCLogNewTx txId : entries) = +-- go ("New transaction:" <+> prettyCookedOpt opts txId : acc) entries +-- go acc [] = reverse acc prettyTxSkel :: PrettyCookedOpts -> SkelContext -> TxSkel -> DocCooked prettyTxSkel opts skelContext (TxSkel lbl txopts mints signers validityRange ins insReference outs proposals) = diff --git a/src/Cooked/Pretty/Options.hs b/src/Cooked/Pretty/Options.hs index 602a65009..dfc6db987 100644 --- a/src/Cooked/Pretty/Options.hs +++ b/src/Cooked/Pretty/Options.hs @@ -4,6 +4,7 @@ module Cooked.Pretty.Options ( PrettyCookedOpts (..), PrettyCookedHashOpts (..), PCOptTxOutRefs (..), + PCOptLog (..), hashNamesFromList, defaultHashNames, ) @@ -31,7 +32,9 @@ data PrettyCookedOpts = PrettyCookedOpts -- @53_000_000@ instead of @53000000@. By default: True pcOptNumericUnderscores :: Bool, -- | Options related to printing hashes - pcOptHashes :: PrettyCookedHashOpts + pcOptHashes :: PrettyCookedHashOpts, + -- | What kind of log to print + pcOptLog :: PCOptLog } deriving (Eq, Show) @@ -42,9 +45,23 @@ instance Default PrettyCookedOpts where pcOptPrintTxOutRefs = PCOptTxOutRefsHidden, pcOptPrintDefaultTxOpts = False, pcOptNumericUnderscores = True, - pcOptHashes = def + pcOptHashes = def, + pcOptLog = def } +-- | What log to display +data PCOptLog + = -- | No logging at all + PCOptLogNone + | -- | All logging except for infos, default option + PCOptLogNoInfo + | -- | All logging, for debugging purpose + PCOptLogAll + deriving (Eq, Show) + +instance Default PCOptLog where + def = PCOptLogNoInfo + -- | Whether to print transaction outputs references. data PCOptTxOutRefs = -- | Hide them diff --git a/tests/Cooked/Attack/DatumHijackingSpec.hs b/tests/Cooked/Attack/DatumHijackingSpec.hs index 89dd0cfbb..9f86b29b4 100644 --- a/tests/Cooked/Attack/DatumHijackingSpec.hs +++ b/tests/Cooked/Attack/DatumHijackingSpec.hs @@ -201,14 +201,14 @@ tests = ], txSkelSigners = [wallet 1] } - in [ testCase "no modified transactions if no interesting outputs to steal" $ [] @=? skelOut mempty (const True), + in [ testCase "no modified transactions if no interesting outputs to steal" $ [] @=? fst <$> skelOut mempty (const True), testCase "one modified transaction for one interesting output" $ [ Right ( [ConcreteOutput val1 Nothing (TxSkelOutInlineDatum SecondLock) x3 Nothing], skelExpected thief val1 ) ] - @=? skelOut x2 (0 ==), + @=? fst <$> skelOut x2 (0 ==), testCase "two modified transactions for two interesting outputs" $ [ Right ( [ ConcreteOutput val1 Nothing (TxSkelOutInlineDatum SecondLock) x3 Nothing, @@ -217,14 +217,14 @@ tests = skelExpected thief thief ) ] - @=? skelOut x2 (const True), + @=? fst <$> skelOut x2 (const True), testCase "select second interesting output to get one modified transaction" $ [ Right ( [ConcreteOutput val1 Nothing (TxSkelOutInlineDatum SecondLock) x2 Nothing], skelExpected val1 thief ) ] - @=? skelOut x2 (1 ==) + @=? fst <$> skelOut x2 (1 ==) ], testCase "careful validator" $ testFails diff --git a/tests/Cooked/Attack/DoubleSatSpec.hs b/tests/Cooked/Attack/DoubleSatSpec.hs index 68a641a72..64384aa07 100644 --- a/tests/Cooked/Attack/DoubleSatSpec.hs +++ b/tests/Cooked/Attack/DoubleSatSpec.hs @@ -121,7 +121,7 @@ customInitDist = -- | Utxos generated from the initial distribution aUtxo1, aUtxo2, aUtxo3, aUtxo4, bUtxo1, bUtxo2 :: (Api.TxOutRef, Api.TxOut) (aUtxo1, aUtxo2, aUtxo3, aUtxo4, bUtxo1, bUtxo2) = - case runMockChainFrom customInitDist $ do + case fst $ runMockChainFrom customInitDist $ do [a1, a2, a3, a4] <- runUtxoSearch $ utxosAtSearch aValidator [b1, b2] <- runUtxoSearch $ utxosAtSearch bValidator return (a1, a2, a3, a4, b1, b2) of @@ -160,46 +160,48 @@ tests = -- on the focused input 'aValidator' UTxO. skelsOut :: ([Api.TxOutRef] -> [[Api.TxOutRef]]) -> [(ARedeemer, Api.TxOutRef)] -> [TxSkel] skelsOut splitMode aInputs = - mapMaybe (\case Right (_, skel') -> Just skel'; _ -> Nothing) $ - runTweakFrom - (mockChainSt0From customInitDist) - ( doubleSatAttack - splitMode - (txSkelInsL % itraversed) -- we know that every 'TxOutRef' in the inputs points to a UTxO that the 'aValidator' owns - ( \aOref _aRedeemer -> do - bUtxos <- runUtxoSearch $ scriptOutputsSearch bValidator - if - | aOref == fst aUtxo1 -> - return - [ (txSkelSomeRedeemer ARedeemer2, toDelta bOref $ txSkelSomeRedeemer BRedeemer1) - | (bOref, bOut) <- bUtxos, - outputValue bOut == Script.lovelaceValueOf 123 -- not satisfied by any UTxO in 'dsTestMockChain' - ] - | aOref == fst aUtxo2 -> - return - [ (txSkelSomeRedeemer ARedeemer2, toDelta bOref $ txSkelSomeRedeemer BRedeemer1) - | (bOref, _) <- bUtxos, - bOref == fst bUtxo1 - ] - | aOref == fst aUtxo3 -> - return $ - concatMap - ( \(bOref, _) -> - if - | bOref == fst bUtxo1 -> - [(txSkelSomeRedeemer ARedeemer2, toDelta bOref $ txSkelSomeRedeemer BRedeemer1)] - | bOref == fst bUtxo2 -> - [ (txSkelSomeRedeemer ARedeemer2, toDelta bOref $ txSkelSomeRedeemer BRedeemer1), - (txSkelSomeRedeemer ARedeemer3, toDelta bOref $ txSkelSomeRedeemer BRedeemer2) - ] - | otherwise -> [] - ) - bUtxos - | otherwise -> return [] - ) - (wallet 6) - ) - (skelIn aInputs) + mapMaybe + ((\case Right (_, skel') -> Just skel'; _ -> Nothing) . fst) + ( runTweakFrom + (mockChainSt0From customInitDist) + ( doubleSatAttack + splitMode + (txSkelInsL % itraversed) -- we know that every 'TxOutRef' in the inputs points to a UTxO that the 'aValidator' owns + ( \aOref _aRedeemer -> do + bUtxos <- runUtxoSearch $ scriptOutputsSearch bValidator + if + | aOref == fst aUtxo1 -> + return + [ (txSkelSomeRedeemer ARedeemer2, toDelta bOref $ txSkelSomeRedeemer BRedeemer1) + | (bOref, bOut) <- bUtxos, + outputValue bOut == Script.lovelaceValueOf 123 -- not satisfied by any UTxO in 'dsTestMockChain' + ] + | aOref == fst aUtxo2 -> + return + [ (txSkelSomeRedeemer ARedeemer2, toDelta bOref $ txSkelSomeRedeemer BRedeemer1) + | (bOref, _) <- bUtxos, + bOref == fst bUtxo1 + ] + | aOref == fst aUtxo3 -> + return $ + concatMap + ( \(bOref, _) -> + if + | bOref == fst bUtxo1 -> + [(txSkelSomeRedeemer ARedeemer2, toDelta bOref $ txSkelSomeRedeemer BRedeemer1)] + | bOref == fst bUtxo2 -> + [ (txSkelSomeRedeemer ARedeemer2, toDelta bOref $ txSkelSomeRedeemer BRedeemer1), + (txSkelSomeRedeemer ARedeemer3, toDelta bOref $ txSkelSomeRedeemer BRedeemer2) + ] + | otherwise -> [] + ) + bUtxos + | otherwise -> return [] + ) + (wallet 6) + ) + (skelIn aInputs) + ) where toDelta :: Api.TxOutRef -> TxSkelRedeemer -> DoubleSatDelta toDelta oref howSpent = (Map.singleton oref howSpent, [], mempty) diff --git a/tests/Cooked/Attack/DupTokenSpec.hs b/tests/Cooked/Attack/DupTokenSpec.hs index b15dd49c5..2f6866eb3 100644 --- a/tests/Cooked/Attack/DupTokenSpec.hs +++ b/tests/Cooked/Attack/DupTokenSpec.hs @@ -105,11 +105,11 @@ tests = ) ] in [ testCase "add one token in every asset class" $ - skelExpected 6 8 @=? skelOut (\_ n -> n + 1), + skelExpected 6 8 @=? fst <$> skelOut (\_ n -> n + 1), testCase "no modified transaction if no increase in value specified" $ - [] @=? skelOut (\_ n -> n), + [] @=? fst <$> skelOut (\_ n -> n), testCase "add tokens depending on the asset class" $ - skelExpected 10 7 @=? skelOut (\ac n -> if ac == ac1 then n + 5 else n) + skelExpected 10 7 @=? fst <$> skelOut (\ac n -> if ac == ac1 then n + 5 else n) ], testCase "careful minting policy" $ let tName = Script.tokenName "MockToken" @@ -155,5 +155,5 @@ tests = ) ] skelOut = runTweak (dupTokenAttack (\_ i -> i + 1) attacker) skelIn - in skelExpected @=? skelOut + in skelExpected @=? fst <$> skelOut ] diff --git a/tests/Cooked/InlineDatumsSpec.hs b/tests/Cooked/InlineDatumsSpec.hs index df39f0e80..52ae2433a 100644 --- a/tests/Cooked/InlineDatumsSpec.hs +++ b/tests/Cooked/InlineDatumsSpec.hs @@ -177,7 +177,7 @@ tests = let theValidator = inputDatumValidator True in [ testCase "the datum is retrieved correctly" $ assertBool "... it's not" $ - case runMockChain (listUtxosTestTrace True theValidator >> allUtxos) of + case fst $ runMockChain (listUtxosTestTrace True theValidator >> allUtxos) of Right (utxos, _endState) -> case mapMaybe ((outputOutputDatum <$>) . isScriptOutputFrom theValidator . snd) utxos of [Api.OutputDatum _] -> True @@ -185,7 +185,7 @@ tests = _ -> False, testCase "the datum hash is retrieved correctly" $ assertBool "... it's not" $ - case runMockChain (listUtxosTestTrace False theValidator >> allUtxos) of + case fst $ runMockChain (listUtxosTestTrace False theValidator >> allUtxos) of Right (utxos, _endState) -> case mapMaybe ((outputOutputDatum <$>) . isScriptOutputFrom theValidator . snd) utxos of [Api.OutputDatumHash _] -> True diff --git a/tests/Cooked/MockChain/BlockChainSpec.hs b/tests/Cooked/MockChain/BlockChainSpec.hs index faacacdf2..0bd506cf3 100644 --- a/tests/Cooked/MockChain/BlockChainSpec.hs +++ b/tests/Cooked/MockChain/BlockChainSpec.hs @@ -15,7 +15,7 @@ tests = "time handling" [ testProperty "bounds computed by slotToTimeInterval are included in slot" $ \n -> - case runMockChain $ do + case fst $ runMockChain $ do (l, r) <- slotToTimeInterval $ Ledger.Slot n Ledger.Slot nl <- getEnclosingSlot l Ledger.Slot nr <- getEnclosingSlot r @@ -24,7 +24,7 @@ tests = Right ((nl, nr), _) -> nl == n && nr == n, testProperty "bounds computed by slotToTimeInterval are maximal" $ \n -> - case runMockChain $ do + case fst $ runMockChain $ do (l, r) <- slotToTimeInterval $ Ledger.Slot n Ledger.Slot nl <- getEnclosingSlot (l - 1) Ledger.Slot nr <- getEnclosingSlot (r + 1) @@ -32,7 +32,7 @@ tests = Left _err -> False Right ((nl, nr), _) -> nl == n - 1 && nr == n + 1, testProperty "time is always included in enclosing slot" $ - \t -> case runMockChain $ slotToTimeInterval =<< getEnclosingSlot (Api.POSIXTime t) of + \t -> case fst $ runMockChain $ slotToTimeInterval =<< getEnclosingSlot (Api.POSIXTime t) of Left _err -> False Right ((Api.POSIXTime a, Api.POSIXTime b), _) -> a <= t && a <= b ] diff --git a/tests/Cooked/Tweak/CommonSpec.hs b/tests/Cooked/Tweak/CommonSpec.hs index 0be654572..43f0ded04 100644 --- a/tests/Cooked/Tweak/CommonSpec.hs +++ b/tests/Cooked/Tweak/CommonSpec.hs @@ -22,35 +22,38 @@ tests = let skel = mkSkel [123, 234, 345] in [ testCase "return empty list and don't change anything if no applicable modifications" $ -- this one is a regression test [Right ([], skel)] - @=? runTweak - ( overMaybeSelectingTweak - (txSkelOutsL % traversed % txSkelOutValueL) - (const Nothing) - (const True) - ) - skel, + @=? fst + <$> runTweak + ( overMaybeSelectingTweak + (txSkelOutsL % traversed % txSkelOutValueL) + (const Nothing) + (const True) + ) + skel, testCase "select applied modification by index" $ [Right ([Script.lovelaceValueOf 345], mkSkel [123, 234, 789])] - @=? runTweak - ( overMaybeSelectingTweak - (txSkelOutsL % traversed % txSkelOutValueL) - ( \value -> - if value `Script.geq` Script.lovelaceValueOf 200 - then Just $ Script.lovelaceValueOf 789 - else Nothing - ) - (== 1) - ) - skel, + @=? fst + <$> runTweak + ( overMaybeSelectingTweak + (txSkelOutsL % traversed % txSkelOutValueL) + ( \value -> + if value `Script.geq` Script.lovelaceValueOf 200 + then Just $ Script.lovelaceValueOf 789 + else Nothing + ) + (== 1) + ) + skel, testCase "return unmodified foci in the right order" $ [Right ([Script.lovelaceValueOf 123, Script.lovelaceValueOf 345], mkSkel [789, 234, 789])] - @=? runTweak - ( overMaybeSelectingTweak - (txSkelOutsL % traversed % txSkelOutValueL) - (const $ Just $ Script.lovelaceValueOf 789) - (`elem` [0, 2]) - ) - skel + @=? fst + <$> runTweak + ( overMaybeSelectingTweak + (txSkelOutsL % traversed % txSkelOutValueL) + (const $ Just $ Script.lovelaceValueOf 789) + (`elem` [0, 2]) + ) + skel ], testGroup "combineModsTweak" $ let skelIn = mkSkel [0, 0, 0] @@ -87,13 +90,14 @@ tests = skelOut 2 2 1, skelOut 2 2 2 ] - ( runTweak - ( combineModsTweak - (tail . subsequences) - (txSkelOutsL % itraversed % txSkelOutValueL % adaL) - (\i x -> return [(x + 1, i), (x + 2, i)]) - ) - skelIn + ( fst + <$> runTweak + ( combineModsTweak + (tail . subsequences) + (txSkelOutsL % itraversed % txSkelOutValueL % adaL) + (\i x -> return [(x + 1, i), (x + 2, i)]) + ) + skelIn ), testCase "separate modifications" $ assertSameSets @@ -105,13 +109,14 @@ tests = skelOut 0 0 1, skelOut 0 0 2 ] - ( runTweak - ( combineModsTweak - (map (: [])) - (txSkelOutsL % itraversed % txSkelOutValueL % adaL) - (\i x -> return [(x + 1, i), (x + 2, i)]) - ) - skelIn + ( fst + <$> runTweak + ( combineModsTweak + (map (: [])) + (txSkelOutsL % itraversed % txSkelOutValueL % adaL) + (\i x -> return [(x + 1, i), (x + 2, i)]) + ) + skelIn ) ] ] diff --git a/tests/Cooked/Tweak/OutPermutationsSpec.hs b/tests/Cooked/Tweak/OutPermutationsSpec.hs index f8a4e6c41..897a2a88e 100644 --- a/tests/Cooked/Tweak/OutPermutationsSpec.hs +++ b/tests/Cooked/Tweak/OutPermutationsSpec.hs @@ -67,23 +67,23 @@ tests = in [ testCase "KeepIdentity (Just 2)" $ assertSameSets (map (Right . ((),)) [skel a b c, skel b a c]) - (runTweak (allOutPermutsTweak $ KeepIdentity $ Just 2) $ skel a b c), + (fst <$> runTweak (allOutPermutsTweak $ KeepIdentity $ Just 2) (skel a b c)), testCase "KeepIdentity Nothing" $ assertSameSets (map (Right . ((),)) [skel a b c, skel a c b, skel b a c, skel b c a, skel c a b, skel c b a]) - (runTweak (allOutPermutsTweak $ KeepIdentity Nothing) $ skel a b c), + (fst <$> runTweak (allOutPermutsTweak $ KeepIdentity Nothing) (skel a b c)), testCase "OmitIdentity (Just 2)" $ assertSameSets [Right ((), skel b a c)] - (runTweak (allOutPermutsTweak $ OmitIdentity $ Just 2) $ skel a b c), + (fst <$> runTweak (allOutPermutsTweak $ OmitIdentity $ Just 2) (skel a b c)), testCase "OmitIdentity Nothing" $ assertSameSets (map (Right . ((),)) [skel a c b, skel b a c, skel b c a, skel c a b, skel c b a]) - (runTweak (allOutPermutsTweak $ OmitIdentity Nothing) $ skel a b c) + (fst <$> runTweak (allOutPermutsTweak $ OmitIdentity Nothing) (skel a b c)) ], testGroup "tests for a single random outputs permutation:" $ let l = (\i -> paysPK (wallet i) $ Script.lovelaceValueOf 123) <$> [1 .. 5] - runs = txSkelOuts . snd <$> rights ((\i -> runTweak (singleOutPermutTweak i) txSkelTemplate {txSkelOuts = l}) =<< [1 .. 5]) + runs = txSkelOuts . snd <$> rights (fst <$> ((\i -> runTweak (singleOutPermutTweak i) txSkelTemplate {txSkelOuts = l}) =<< [1 .. 5])) in [ testCase "All permutations contain the correct elements" $ mapM_ (assertSameSets l) runs, testCase "All permutations are different from the initial distribution" $ diff --git a/tests/Cooked/Tweak/TamperDatumSpec.hs b/tests/Cooked/Tweak/TamperDatumSpec.hs index b1cd22980..212279468 100644 --- a/tests/Cooked/Tweak/TamperDatumSpec.hs +++ b/tests/Cooked/Tweak/TamperDatumSpec.hs @@ -32,18 +32,19 @@ tamperDatumTweakTest = } ) ] - @=? runTweak - ( tamperDatumTweak @(Integer, Integer) - (\(x, y) -> if y == 77 then Nothing else Just (x, y + 1)) - ) - ( txSkelTemplate - { txSkelOuts = - [ paysPK alice (Script.lovelaceValueOf 789) `withDatum` (52 :: Integer, 53 :: Integer), - paysPK alice (Script.lovelaceValueOf 234) `withDatum` (), - paysPK alice (Script.lovelaceValueOf 567) `withDatum` (76 :: Integer, 77 :: Integer) - ] - } - ) + @=? fst + <$> runTweak + ( tamperDatumTweak @(Integer, Integer) + (\(x, y) -> if y == 77 then Nothing else Just (x, y + 1)) + ) + ( txSkelTemplate + { txSkelOuts = + [ paysPK alice (Script.lovelaceValueOf 789) `withDatum` (52 :: Integer, 53 :: Integer), + paysPK alice (Script.lovelaceValueOf 234) `withDatum` (), + paysPK alice (Script.lovelaceValueOf 567) `withDatum` (76 :: Integer, 77 :: Integer) + ] + } + ) malformDatumTweakTest :: TestTree malformDatumTweakTest = @@ -72,26 +73,27 @@ malformDatumTweakTest = txSkelWithDatums1And4 (52 :: Integer, 53 :: Integer) (84 :: Integer, ()), -- datum1 untouched, datum4 changed txSkelWithDatums1And4 (52 :: Integer, 53 :: Integer) False -- datum1 untouched, datum4 changed ] - ( runTweak - ( malformDatumTweak @(Integer, Integer) - ( \(x, y) -> - if y == 77 - then [] - else - [ PlutusTx.toBuiltinData (x, ()), - PlutusTx.toBuiltinData False - ] - ) - ) - ( txSkelTemplate - { txSkelOuts = - [ paysPK alice (Script.lovelaceValueOf 789) `withDatum` (52 :: Integer, 53 :: Integer), - paysPK alice (Script.lovelaceValueOf 234) `withDatum` (), - paysPK alice (Script.lovelaceValueOf 567) `withDatum` (76 :: Integer, 77 :: Integer), - paysPK alice (Script.lovelaceValueOf 567) `withDatum` (84 :: Integer, 85 :: Integer) - ] - } - ) + ( fst + <$> runTweak + ( malformDatumTweak @(Integer, Integer) + ( \(x, y) -> + if y == 77 + then [] + else + [ PlutusTx.toBuiltinData (x, ()), + PlutusTx.toBuiltinData False + ] + ) + ) + ( txSkelTemplate + { txSkelOuts = + [ paysPK alice (Script.lovelaceValueOf 789) `withDatum` (52 :: Integer, 53 :: Integer), + paysPK alice (Script.lovelaceValueOf 234) `withDatum` (), + paysPK alice (Script.lovelaceValueOf 567) `withDatum` (76 :: Integer, 77 :: Integer), + paysPK alice (Script.lovelaceValueOf 567) `withDatum` (84 :: Integer, 85 :: Integer) + ] + } + ) ) tests :: TestTree diff --git a/tests/Cooked/Tweak/ValidityRangeSpec.hs b/tests/Cooked/Tweak/ValidityRangeSpec.hs index e9ecf0e8d..b94d3c9c7 100644 --- a/tests/Cooked/Tweak/ValidityRangeSpec.hs +++ b/tests/Cooked/Tweak/ValidityRangeSpec.hs @@ -62,7 +62,7 @@ tests :: TestTree tests = testGroup "Validity range tweaks" - [ testCase "Validity inclusion" $ fst . head . rights $ runTweak checkIsValidDuring txSkelTemplate, - testCase "Validity intersection" $ fst . head . rights $ runTweak checkAddToValidityRange txSkelTemplate, - testCase "Time shifting in validity range" $ fst . head . rights $ runTweak checkMoveCurrentSlot txSkelTemplate + [ testCase "Validity inclusion" $ fst . head . rights $ fst <$> runTweak checkIsValidDuring txSkelTemplate, + testCase "Validity intersection" $ fst . head . rights $ fst <$> runTweak checkAddToValidityRange txSkelTemplate, + testCase "Time shifting in validity range" $ fst . head . rights $ fst <$> runTweak checkMoveCurrentSlot txSkelTemplate ] From fc36439a1dde19b24423d840a4370490ae130daf Mon Sep 17 00:00:00 2001 From: mmontin Date: Fri, 5 Jul 2024 00:13:21 +0200 Subject: [PATCH 2/9] more log levels --- src/Cooked/MockChain/BlockChain.hs | 15 +++++++++++- src/Cooked/MockChain/Direct.hs | 4 ++- src/Cooked/Pretty/Cooked.hs | 39 +++++++++++++++--------------- src/Cooked/Pretty/Options.hs | 17 ++++++++++--- 4 files changed, 49 insertions(+), 26 deletions(-) diff --git a/src/Cooked/MockChain/BlockChain.hs b/src/Cooked/MockChain/BlockChain.hs index 5003e9a1c..5aaeddc43 100644 --- a/src/Cooked/MockChain/BlockChain.hs +++ b/src/Cooked/MockChain/BlockChain.hs @@ -52,6 +52,7 @@ module Cooked.MockChain.BlockChain validateTxSkel', txSkelProposalsDeposit, govActionDeposit, + blockChainLogEntryToInt, ) where @@ -78,6 +79,7 @@ import Data.Kind import Data.Map (Map) import Data.Map qualified as Map import Data.Maybe +import Data.Set (Set) import Ledger.Index qualified as Ledger import Ledger.Slot qualified as Ledger import Ledger.Tx qualified as Ledger @@ -99,6 +101,7 @@ data MockChainError where -- | Thrown when not enough collateral are provided. Built upon the fee, the -- percentage and the expected minimal collateral value. MCENoSuitableCollateral :: Integer -> Integer -> Api.Value -> MockChainError + -- | Thrown when an error occured during transaction generation MCEGenerationError :: GenerateTxError -> MockChainError -- | Thrown when an output reference should be in the state of the mockchain, -- but isn't. @@ -113,9 +116,19 @@ data MockChainError where data BlockChainLogEntry where BCLogSubmittedTxSkel :: SkelContext -> TxSkel -> BlockChainLogEntry + BCLogAdjustedTxSkel :: SkelContext -> TxSkel -> Integer -> Set Api.TxOutRef -> Wallet -> BlockChainLogEntry BCLogNewTx :: Api.TxId -> BlockChainLogEntry + BCLogError :: String -> BlockChainLogEntry + BCLogWarning :: String -> BlockChainLogEntry BCLogInfo :: String -> BlockChainLogEntry - BCLogFail :: String -> BlockChainLogEntry + +blockChainLogEntryToInt :: BlockChainLogEntry -> Integer +blockChainLogEntryToInt BCLogSubmittedTxSkel {} = 3 +blockChainLogEntryToInt BCLogAdjustedTxSkel {} = 3 +blockChainLogEntryToInt BCLogNewTx {} = 3 +blockChainLogEntryToInt BCLogError {} = 3 +blockChainLogEntryToInt BCLogWarning {} = 2 +blockChainLogEntryToInt BCLogInfo {} = 1 -- | Contains methods needed for balancing. class (MonadFail m, MonadError MockChainError m) => MonadBlockChainBalancing m where diff --git a/src/Cooked/MockChain/Direct.hs b/src/Cooked/MockChain/Direct.hs index a92ad8f05..a3cf340ca 100644 --- a/src/Cooked/MockChain/Direct.hs +++ b/src/Cooked/MockChain/Direct.hs @@ -148,7 +148,7 @@ instance (Monad m) => Monad (MockChainT m) where instance (Monad m) => MonadFail (MockChainT m) where fail s = do - blockChainLog $ BCLogFail s + blockChainLog $ BCLogError s throwError $ FailWith s instance MonadTrans MockChainT where @@ -351,6 +351,8 @@ instance (Monad m) => MonadBlockChain (MockChainT m) where -- We balance the skeleton when requested in the skeleton option, and get -- the associated fee, collateral inputs and return collateral wallet (skel, fee, collateralIns, returnCollateralWallet) <- balanceTxSkel minAdaSkelUnbal + -- We log the adjusted skeleton + gets mcstToSkelContext >>= \ctx -> blockChainLog $ BCLogAdjustedTxSkel ctx skel fee collateralIns returnCollateralWallet -- We retrieve data that will be used in the transaction generation process: -- datums, validators and various kinds of inputs. This idea is to provide a -- rich-enough context for the transaction generation to succeed. diff --git a/src/Cooked/Pretty/Cooked.hs b/src/Cooked/Pretty/Cooked.hs index 75d5a39ce..7d9af66dd 100644 --- a/src/Cooked/Pretty/Cooked.hs +++ b/src/Cooked/Pretty/Cooked.hs @@ -132,33 +132,32 @@ prettyBlockChainEntries opts entries = (prettyCookedOpt opts <$> entries) instance (Show a) => PrettyCooked (MockChainReturn a UtxoState) where - prettyCookedOpt opts (res, entries) = prettyLogWith $ - case res of - Left err -> "🔴" <+> prettyCookedOpt opts err - Right (a, s) -> "🟢" <+> prettyCookedOpt opts (a, s) - where - prettyLogWith :: DocCooked -> DocCooked - prettyLogWith inner = - case pcOptLog opts of - PCOptLogNone -> inner - PCOptLogNoInfo -> - prettyItemize - "End result:" - "-" - [prettyBlockChainEntries opts (filter (\case BCLogInfo _ -> False; _ -> True) entries), inner] - PCOptLogAll -> - prettyItemize - "End result:" - "-" - [prettyBlockChainEntries opts entries, inner] + prettyCookedOpt opts (res, entries) = + let inner = case res of + Left err -> "🔴" <+> prettyCookedOpt opts err + Right (a, s) -> "🟢" <+> prettyCookedOpt opts (a, s) + in -- We only keep log entries above the required level + case filter ((pcOptLogToInt (pcOptLog opts) <=) . blockChainLogEntryToInt) entries of + [] -> inner + subEntries -> prettyItemize "End result:" "-" [prettyBlockChainEntries opts subEntries, inner] -- | This pretty prints a 'MockChainLog' that usually consists of the list of -- validated or submitted transactions. In the log, we know a transaction has -- been validated if the 'MCLogSubmittedTxSkel' is followed by a 'MCLogNewTx'. instance PrettyCooked BlockChainLogEntry where prettyCookedOpt opts (BCLogSubmittedTxSkel skelContext skel) = "Submitted:" <+> prettyTxSkel opts skelContext skel - prettyCookedOpt _ (BCLogFail msg) = "Fail:" <+> PP.pretty msg + prettyCookedOpt opts (BCLogAdjustedTxSkel skelContext skel fee collaterals returnWallet) = + prettyItemize + "Adjusted:" + "-" + [ "Fee:" <+> prettyCookedOpt opts fee, + "Collaterals:" <+> prettyCookedOpt opts (Set.toList collaterals), + "Return collateral wallet:" <+> prettyCookedOpt opts (walletPKHash returnWallet), + prettyTxSkel opts skelContext skel + ] prettyCookedOpt opts (BCLogNewTx txId) = "New transaction:" <+> prettyCookedOpt opts txId + prettyCookedOpt _ (BCLogError err) = "Fail:" <+> PP.pretty err + prettyCookedOpt _ (BCLogWarning warn) = "Warning:" <+> PP.pretty warn prettyCookedOpt _ (BCLogInfo info) = "Info:" <+> PP.pretty info -- go acc (MCLogFail msg : entries) = diff --git a/src/Cooked/Pretty/Options.hs b/src/Cooked/Pretty/Options.hs index dfc6db987..37b12d9a5 100644 --- a/src/Cooked/Pretty/Options.hs +++ b/src/Cooked/Pretty/Options.hs @@ -7,6 +7,7 @@ module Cooked.Pretty.Options PCOptLog (..), hashNamesFromList, defaultHashNames, + pcOptLogToInt, ) where @@ -53,14 +54,22 @@ instance Default PrettyCookedOpts where data PCOptLog = -- | No logging at all PCOptLogNone - | -- | All logging except for infos, default option - PCOptLogNoInfo - | -- | All logging, for debugging purpose + | -- | Log errors and special log entries + PCOptLogSpecial + | -- | Same as `PCOptLogSpecial`, but with warnings + PCOptLogWarning + | -- | Log everything PCOptLogAll deriving (Eq, Show) instance Default PCOptLog where - def = PCOptLogNoInfo + def = PCOptLogAll + +pcOptLogToInt :: PCOptLog -> Integer +pcOptLogToInt PCOptLogNone = 4 +pcOptLogToInt PCOptLogSpecial = 3 +pcOptLogToInt PCOptLogWarning = 2 +pcOptLogToInt PCOptLogAll = 1 -- | Whether to print transaction outputs references. data PCOptTxOutRefs From 38c0718a0733cb4e58f171bb473d26e35a618636 Mon Sep 17 00:00:00 2001 From: mmontin Date: Fri, 5 Jul 2024 00:57:09 +0200 Subject: [PATCH 3/9] logging removal of unusable balancing utxos --- src/Cooked/MockChain/Balancing.hs | 22 ++++++++++++++++++---- src/Cooked/Pretty/Cooked.hs | 8 ++++---- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/Cooked/MockChain/Balancing.hs b/src/Cooked/MockChain/Balancing.hs index 910422baa..61034bb80 100644 --- a/src/Cooked/MockChain/Balancing.hs +++ b/src/Cooked/MockChain/Balancing.hs @@ -7,6 +7,7 @@ import Cardano.Api.Ledger qualified as Cardano import Cardano.Api.Shelley qualified as Cardano import Cardano.Node.Emulator.Internal.Node.Params qualified as Emulator import Cardano.Node.Emulator.Internal.Node.Validation qualified as Emulator +import Control.Monad import Control.Monad.Except import Cooked.Conversion import Cooked.MockChain.BlockChain @@ -83,10 +84,23 @@ balanceTxSkel skelUnbal@TxSkel {..} = do Just bWallet -> do -- The balancing should be performed. We collect the balancing utxos and -- filter out those already used in the unbalanced skeleton. - (filter ((`notElem` txSkelKnownTxOutRefs skelUnbal) . fst) -> balancingUtxos) <- - runUtxoSearch $ case txOptBalancingUtxos txSkelOpts of - BalancingUtxosFromBalancingWallet -> onlyValueOutputsAtSearch bWallet `filterWithAlways` outputTxOut - BalancingUtxosFromSet utxos -> txOutByRefSearch (Set.toList utxos) `filterWithPure` isPKOutput `filterWithAlways` outputTxOut + candidateBalancingUtxos <- + case txOptBalancingUtxos txSkelOpts of + BalancingUtxosFromBalancingWallet -> runUtxoSearch $ onlyValueOutputsAtSearch bWallet `filterWithAlways` outputTxOut + BalancingUtxosFromSet utxos -> do + bUtxos <- runUtxoSearch $ txOutByRefSearch (Set.toList utxos) + let (pkUtxos, length -> scriptUtxosNb) = partition (isJust . isPKOutput . snd) bUtxos + unless (scriptUtxosNb == 0) $ + blockChainLog $ + BCLogWarning $ + show scriptUtxosNb <> " utxos belonging to scripts have been provided for balancing and are ignored." + return pkUtxos + let (balancingUtxos, length -> knownUtxosNb) = partition ((`notElem` txSkelKnownTxOutRefs skelUnbal) . fst) candidateBalancingUtxos + unless (knownUtxosNb == 0) $ + blockChainLog $ + BCLogWarning $ + show knownUtxosNb <> " utxos already used in the skeleton have been provided for balancing and are ignored." + case txOptFeePolicy txSkelOpts of -- If fees are left for us to compute, we run a dichotomic search. This -- is full auto mode, the most powerful but time-consuming. diff --git a/src/Cooked/Pretty/Cooked.hs b/src/Cooked/Pretty/Cooked.hs index 7d9af66dd..0e1bf244d 100644 --- a/src/Cooked/Pretty/Cooked.hs +++ b/src/Cooked/Pretty/Cooked.hs @@ -150,10 +150,10 @@ instance PrettyCooked BlockChainLogEntry where prettyItemize "Adjusted:" "-" - [ "Fee:" <+> prettyCookedOpt opts fee, - "Collaterals:" <+> prettyCookedOpt opts (Set.toList collaterals), - "Return collateral wallet:" <+> prettyCookedOpt opts (walletPKHash returnWallet), - prettyTxSkel opts skelContext skel + [ prettyTxSkel opts skelContext skel, + "Fee: Lovelace" <+> prettyCookedOpt opts fee, + "Collateral inputs:" <+> prettyCookedOpt opts (Set.toList collaterals), + "Return collateral wallet:" <+> prettyCookedOpt opts (walletPKHash returnWallet) ] prettyCookedOpt opts (BCLogNewTx txId) = "New transaction:" <+> prettyCookedOpt opts txId prettyCookedOpt _ (BCLogError err) = "Fail:" <+> PP.pretty err From b525aeb7f36707e972f50918bcfecc0dfbeb8f71 Mon Sep 17 00:00:00 2001 From: mmontin Date: Mon, 8 Jul 2024 19:37:29 +0200 Subject: [PATCH 4/9] improving logging in balancing --- src/Cooked/MockChain/Balancing.hs | 39 +++++++++++++++++-------------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/src/Cooked/MockChain/Balancing.hs b/src/Cooked/MockChain/Balancing.hs index 61034bb80..abed19761 100644 --- a/src/Cooked/MockChain/Balancing.hs +++ b/src/Cooked/MockChain/Balancing.hs @@ -43,8 +43,8 @@ type BalancingOutputs = [(Api.TxOutRef, Api.TxOut)] -- | This is the main entry point of our balancing mechanism. This function -- takes a skeleton and returns a (possibly) balanced skeleton alongside the -- associated fee, collateral inputs and return collateral wallet. The options --- from the skeleton control whether it should be balanced, and how to compute its --- associated elements. +-- from the skeleton control whether it should be balanced, and how to compute +-- its associated elements. balanceTxSkel :: (MonadBlockChainBalancing m) => TxSkel -> m (TxSkel, Fee, Collaterals, Wallet) balanceTxSkel skelUnbal@TxSkel {..} = do -- We retrieve the possible balancing wallet. Any extra payment will be @@ -82,24 +82,23 @@ balanceTxSkel skelUnbal@TxSkel {..} = do ManualFee fee' -> fee' in (skelUnbal,fee,) <$> collateralInsFromFees fee collateralIns returnCollateralWallet Just bWallet -> do - -- The balancing should be performed. We collect the balancing utxos and - -- filter out those already used in the unbalanced skeleton. - candidateBalancingUtxos <- + -- The balancing should be performed. We collect the candidates balancing + -- utxos based on the associated policy + balancingUtxos <- case txOptBalancingUtxos txSkelOpts of BalancingUtxosFromBalancingWallet -> runUtxoSearch $ onlyValueOutputsAtSearch bWallet `filterWithAlways` outputTxOut - BalancingUtxosFromSet utxos -> do - bUtxos <- runUtxoSearch $ txOutByRefSearch (Set.toList utxos) - let (pkUtxos, length -> scriptUtxosNb) = partition (isJust . isPKOutput . snd) bUtxos - unless (scriptUtxosNb == 0) $ - blockChainLog $ - BCLogWarning $ - show scriptUtxosNb <> " utxos belonging to scripts have been provided for balancing and are ignored." - return pkUtxos - let (balancingUtxos, length -> knownUtxosNb) = partition ((`notElem` txSkelKnownTxOutRefs skelUnbal) . fst) candidateBalancingUtxos - unless (knownUtxosNb == 0) $ - blockChainLog $ - BCLogWarning $ - show knownUtxosNb <> " utxos already used in the skeleton have been provided for balancing and are ignored." + BalancingUtxosFromSet utxos -> + -- We resolve the given set of utxos + runUtxoSearch (txOutByRefSearch (Set.toList utxos)) + >>= + -- We filter out those belonging to scripts, while throwing a + -- warning if any was actually discarded. + filterAndWarn (isJust . isPKOutput . snd) "utxos belonging to scripts have been provided for balancing and are discarded." + -- We filter the candidate utxos by removing those already present in the + -- skeleton, throwing a warning if any was actually discarder + >>= filterAndWarn + ((`notElem` txSkelKnownTxOutRefs skelUnbal) . fst) + "utxos already used in the skeleton have been provided for balancing and are discarded." case txOptFeePolicy txSkelOpts of -- If fees are left for us to compute, we run a dichotomic search. This @@ -114,6 +113,10 @@ balanceTxSkel skelUnbal@TxSkel {..} = do return (attemptedSkel, fee, adjustedCollateralIns) return (txSkelBal, fee, adjustedCollateralIns, returnCollateralWallet) + where + filterAndWarn f s l + | (ok, length -> koLength) <- partition f l = + unless (koLength == 0) (blockChainLog $ BCLogWarning $ show koLength <> " " <> s) >> return ok -- | This computes the minimum and maximum possible fee a transaction can cost -- based on the current protocol parameters From 98b5e810ee5c50f145e995f7d4f1f7104e612fab Mon Sep 17 00:00:00 2001 From: mmontin Date: Wed, 10 Jul 2024 11:55:38 +0200 Subject: [PATCH 5/9] new log version with dedicated constructors --- src/Cooked/MockChain.hs | 2 +- src/Cooked/MockChain/Balancing.hs | 11 +++------ src/Cooked/MockChain/BlockChain.hs | 39 ++++++++++-------------------- src/Cooked/MockChain/Direct.hs | 18 ++++++-------- src/Cooked/MockChain/Staged.hs | 6 ++--- src/Cooked/Pretty/Cooked.hs | 27 ++++++--------------- src/Cooked/Pretty/Options.hs | 29 +++------------------- src/Cooked/Skeleton.hs | 1 + 8 files changed, 41 insertions(+), 92 deletions(-) diff --git a/src/Cooked/MockChain.hs b/src/Cooked/MockChain.hs index b5a7b3193..65903ff85 100644 --- a/src/Cooked/MockChain.hs +++ b/src/Cooked/MockChain.hs @@ -3,7 +3,7 @@ module Cooked.MockChain (module X) where import Cooked.MockChain.Balancing as X -import Cooked.MockChain.BlockChain as X hiding (BlockChainLogEntry) +import Cooked.MockChain.BlockChain as X hiding (MockChainLogEntry) import Cooked.MockChain.Direct as X hiding (MockChainReturn) import Cooked.MockChain.MinAda as X import Cooked.MockChain.Staged as X hiding (StagedMockChain) diff --git a/src/Cooked/MockChain/Balancing.hs b/src/Cooked/MockChain/Balancing.hs index abed19761..9765fa442 100644 --- a/src/Cooked/MockChain/Balancing.hs +++ b/src/Cooked/MockChain/Balancing.hs @@ -90,15 +90,12 @@ balanceTxSkel skelUnbal@TxSkel {..} = do BalancingUtxosFromSet utxos -> -- We resolve the given set of utxos runUtxoSearch (txOutByRefSearch (Set.toList utxos)) - >>= -- We filter out those belonging to scripts, while throwing a -- warning if any was actually discarded. - filterAndWarn (isJust . isPKOutput . snd) "utxos belonging to scripts have been provided for balancing and are discarded." + >>= filterAndWarn (isJust . isPKOutput . snd) "They belong to scripts." -- We filter the candidate utxos by removing those already present in the -- skeleton, throwing a warning if any was actually discarder - >>= filterAndWarn - ((`notElem` txSkelKnownTxOutRefs skelUnbal) . fst) - "utxos already used in the skeleton have been provided for balancing and are discarded." + >>= filterAndWarn ((`notElem` txSkelKnownTxOutRefs skelUnbal) . fst) "They are already used in the skeleton." case txOptFeePolicy txSkelOpts of -- If fees are left for us to compute, we run a dichotomic search. This @@ -115,8 +112,8 @@ balanceTxSkel skelUnbal@TxSkel {..} = do return (txSkelBal, fee, adjustedCollateralIns, returnCollateralWallet) where filterAndWarn f s l - | (ok, length -> koLength) <- partition f l = - unless (koLength == 0) (blockChainLog $ BCLogWarning $ show koLength <> " " <> s) >> return ok + | (ok, toInteger . length -> koLength) <- partition f l = + unless (koLength == 0) (publish $ MCLogDiscardedUtxos koLength s) >> return ok -- | This computes the minimum and maximum possible fee a transaction can cost -- based on the current protocol parameters diff --git a/src/Cooked/MockChain/BlockChain.hs b/src/Cooked/MockChain/BlockChain.hs index 5aaeddc43..2d8e7f02b 100644 --- a/src/Cooked/MockChain/BlockChain.hs +++ b/src/Cooked/MockChain/BlockChain.hs @@ -15,7 +15,7 @@ -- from the core definition of our blockchain. module Cooked.MockChain.BlockChain ( MockChainError (..), - BlockChainLogEntry (..), + MockChainLogEntry (..), MonadBlockChainBalancing (..), MonadBlockChainWithoutValidation (..), MonadBlockChain (..), @@ -52,7 +52,6 @@ module Cooked.MockChain.BlockChain validateTxSkel', txSkelProposalsDeposit, govActionDeposit, - blockChainLogEntryToInt, ) where @@ -89,12 +88,11 @@ import Optics.Core import Plutus.Script.Utils.Scripts qualified as Script import PlutusLedgerApi.V3 qualified as Api --- * BlockChain monad +-- * MockChain errors -- | The errors that can be produced by the 'MockChainT' monad data MockChainError where - -- FIXME, maybe the validation phase can be deduced from the nature of the - -- error + -- | Validation errors, either in Phase 1 or Phase 2 MCEValidationError :: Ledger.ValidationPhase -> Ledger.ValidationError -> MockChainError -- | Thrown when the balancing wallet does not have enough funds MCEUnbalanceable :: Wallet -> Api.Value -> TxSkel -> MockChainError @@ -103,8 +101,7 @@ data MockChainError where MCENoSuitableCollateral :: Integer -> Integer -> Api.Value -> MockChainError -- | Thrown when an error occured during transaction generation MCEGenerationError :: GenerateTxError -> MockChainError - -- | Thrown when an output reference should be in the state of the mockchain, - -- but isn't. + -- | Thrown when an output reference is missing from the mockchain state MCEUnknownOutRefError :: String -> Api.TxOutRef -> MockChainError -- | Same as 'MCEUnknownOutRefError' for validators. MCEUnknownValidator :: String -> Script.ValidatorHash -> MockChainError @@ -114,25 +111,15 @@ data MockChainError where FailWith :: String -> MockChainError deriving (Show, Eq) -data BlockChainLogEntry where - BCLogSubmittedTxSkel :: SkelContext -> TxSkel -> BlockChainLogEntry - BCLogAdjustedTxSkel :: SkelContext -> TxSkel -> Integer -> Set Api.TxOutRef -> Wallet -> BlockChainLogEntry - BCLogNewTx :: Api.TxId -> BlockChainLogEntry - BCLogError :: String -> BlockChainLogEntry - BCLogWarning :: String -> BlockChainLogEntry - BCLogInfo :: String -> BlockChainLogEntry - -blockChainLogEntryToInt :: BlockChainLogEntry -> Integer -blockChainLogEntryToInt BCLogSubmittedTxSkel {} = 3 -blockChainLogEntryToInt BCLogAdjustedTxSkel {} = 3 -blockChainLogEntryToInt BCLogNewTx {} = 3 -blockChainLogEntryToInt BCLogError {} = 3 -blockChainLogEntryToInt BCLogWarning {} = 2 -blockChainLogEntryToInt BCLogInfo {} = 1 +data MockChainLogEntry where + MCLogSubmittedTxSkel :: SkelContext -> TxSkel -> MockChainLogEntry + MCLogAdjustedTxSkel :: SkelContext -> TxSkel -> Integer -> Set Api.TxOutRef -> Wallet -> MockChainLogEntry + MCLogNewTx :: Api.TxId -> MockChainLogEntry + MCLogDiscardedUtxos :: Integer -> String -> MockChainLogEntry -- | Contains methods needed for balancing. class (MonadFail m, MonadError MockChainError m) => MonadBlockChainBalancing m where - -- | Returns the parameters of the chain. + -- | Returns the emulator parameters, including protocol parameters getParams :: m Emulator.Params -- | Returns a list of all UTxOs at a certain address. @@ -149,7 +136,7 @@ class (MonadFail m, MonadError MockChainError m) => MonadBlockChainBalancing m w txOutByRefLedger :: Api.TxOutRef -> m (Maybe Ledger.TxOut) -- | Logs an event that occured during a BlockChain run - blockChainLog :: BlockChainLogEntry -> m () + publish :: MockChainLogEntry -> m () class (MonadBlockChainBalancing m) => MonadBlockChainWithoutValidation m where -- | Returns a list of all currently known outputs. @@ -516,7 +503,7 @@ instance (MonadTrans t, MonadBlockChainBalancing m, Monad (t m), MonadError Mock utxosAtLedger = lift . utxosAtLedger txOutByRefLedger = lift . txOutByRefLedger datumFromHash = lift . datumFromHash - blockChainLog = lift . blockChainLog + publish = lift . publish instance (MonadTrans t, MonadBlockChainWithoutValidation m, Monad (t m), MonadError MockChainError (AsTrans t m)) => MonadBlockChainWithoutValidation (AsTrans t m) where allUtxosLedger = lift allUtxosLedger @@ -560,7 +547,7 @@ instance (MonadBlockChainBalancing m) => MonadBlockChainBalancing (ListT m) wher utxosAtLedger = lift . utxosAtLedger txOutByRefLedger = lift . txOutByRefLedger datumFromHash = lift . datumFromHash - blockChainLog = lift . blockChainLog + publish = lift . publish instance (MonadBlockChainWithoutValidation m) => MonadBlockChainWithoutValidation (ListT m) where allUtxosLedger = lift allUtxosLedger diff --git a/src/Cooked/MockChain/Direct.hs b/src/Cooked/MockChain/Direct.hs index a3cf340ca..01c08fef6 100644 --- a/src/Cooked/MockChain/Direct.hs +++ b/src/Cooked/MockChain/Direct.hs @@ -136,8 +136,8 @@ instance Eq MockChainSt where ] newtype MockChainT m a = MockChainT - {unMockChain :: (StateT MockChainSt (ExceptT MockChainError (WriterT [BlockChainLogEntry] m))) a} - deriving newtype (Functor, Applicative, MonadState MockChainSt, MonadError MockChainError, MonadWriter [BlockChainLogEntry]) + {unMockChain :: (StateT MockChainSt (ExceptT MockChainError (WriterT [MockChainLogEntry] m))) a} + deriving newtype (Functor, Applicative, MonadState MockChainSt, MonadError MockChainError, MonadWriter [MockChainLogEntry]) type MockChain = MockChainT Identity @@ -147,9 +147,7 @@ instance (Monad m) => Monad (MockChainT m) where MockChainT x >>= f = MockChainT $ x >>= unMockChain . f instance (Monad m) => MonadFail (MockChainT m) where - fail s = do - blockChainLog $ BCLogError s - throwError $ FailWith s + fail = throwError . FailWith instance MonadTrans MockChainT where lift = MockChainT . lift . lift . lift @@ -170,7 +168,7 @@ combineMockChainT f ma mb = MockChainT $ resB = runWriterT $ runExceptT $ runStateT (unMockChain mb) s in ExceptT $ WriterT $ f resA resB -type MockChainReturn a b = (Either MockChainError (a, b), [BlockChainLogEntry]) +type MockChainReturn a b = (Either MockChainError (a, b), [MockChainLogEntry]) mapMockChainT :: (m (MockChainReturn a MockChainSt) -> n (MockChainReturn b MockChainSt)) -> @@ -327,7 +325,7 @@ instance (Monad m) => MonadBlockChainBalancing (MockChainT m) where txOutByRefLedger outref = gets $ Map.lookup outref . getIndex . mcstIndex datumFromHash datumHash = (txSkelOutUntypedDatum <=< Just . fst <=< Map.lookup datumHash) <$> gets mcstDatums utxosAtLedger addr = filter ((addr ==) . outputAddress . txOutV2FromLedger . snd) <$> allUtxosLedger - blockChainLog l = tell [l] + publish l = tell [l] instance (Monad m) => MonadBlockChainWithoutValidation (MockChainT m) where allUtxosLedger = gets $ Map.toList . getIndex . mcstIndex @@ -338,7 +336,7 @@ instance (Monad m) => MonadBlockChainWithoutValidation (MockChainT m) where instance (Monad m) => MonadBlockChain (MockChainT m) where validateTxSkel skelUnbal = do -- We log the submitted skeleton - gets mcstToSkelContext >>= blockChainLog . (`BCLogSubmittedTxSkel` skelUnbal) + gets mcstToSkelContext >>= publish . (`MCLogSubmittedTxSkel` skelUnbal) -- We retrieve the current parameters oldParams <- getParams -- We compute the optionally modified parameters @@ -352,7 +350,7 @@ instance (Monad m) => MonadBlockChain (MockChainT m) where -- the associated fee, collateral inputs and return collateral wallet (skel, fee, collateralIns, returnCollateralWallet) <- balanceTxSkel minAdaSkelUnbal -- We log the adjusted skeleton - gets mcstToSkelContext >>= \ctx -> blockChainLog $ BCLogAdjustedTxSkel ctx skel fee collateralIns returnCollateralWallet + gets mcstToSkelContext >>= \ctx -> publish $ MCLogAdjustedTxSkel ctx skel fee collateralIns returnCollateralWallet -- We retrieve data that will be used in the transaction generation process: -- datums, validators and various kinds of inputs. This idea is to provide a -- rich-enough context for the transaction generation to succeed. @@ -403,7 +401,7 @@ instance (Monad m) => MonadBlockChain (MockChainT m) where -- We return the parameters to their original state setParams oldParams -- We log the validated transaction - blockChainLog $ BCLogNewTx (Ledger.fromCardanoTxId $ Ledger.getCardanoTxId cardanoTx) + publish $ MCLogNewTx (Ledger.fromCardanoTxId $ Ledger.getCardanoTxId cardanoTx) -- We return the validated transaction return cardanoTx where diff --git a/src/Cooked/MockChain/Staged.hs b/src/Cooked/MockChain/Staged.hs index 9f14917f7..17016929f 100644 --- a/src/Cooked/MockChain/Staged.hs +++ b/src/Cooked/MockChain/Staged.hs @@ -79,7 +79,7 @@ data MockChainBuiltin a where AllUtxosLedger :: MockChainBuiltin [(Api.TxOutRef, Ledger.TxOut)] UtxosAtLedger :: Api.Address -> MockChainBuiltin [(Api.TxOutRef, Ledger.TxOut)] ValidatorFromHash :: Script.ValidatorHash -> MockChainBuiltin (Maybe (Script.Versioned Script.Validator)) - BlockChainLog :: BlockChainLogEntry -> MockChainBuiltin () + Publish :: MockChainLogEntry -> MockChainBuiltin () -- | The empty set of traces Empty :: MockChainBuiltin a -- | The union of two sets of traces @@ -136,7 +136,7 @@ instance InterpLtl (UntypedTweak InterpMockChain) MockChainBuiltin InterpMockCha interpBuiltin (Fail msg) = fail msg interpBuiltin (ThrowError err) = throwError err interpBuiltin (CatchError act handler) = catchError (interpLtl act) (interpLtl . handler) - interpBuiltin (BlockChainLog entry) = blockChainLog entry + interpBuiltin (Publish entry) = publish entry -- ** Helpers to run tweaks for use in tests for tweaks @@ -201,7 +201,7 @@ instance MonadBlockChainBalancing StagedMockChain where txOutByRefLedger = singletonBuiltin . TxOutByRefLedger utxosAtLedger = singletonBuiltin . UtxosAtLedger validatorFromHash = singletonBuiltin . ValidatorFromHash - blockChainLog = singletonBuiltin . BlockChainLog + publish = singletonBuiltin . Publish instance MonadBlockChainWithoutValidation StagedMockChain where allUtxosLedger = singletonBuiltin AllUtxosLedger diff --git a/src/Cooked/Pretty/Cooked.hs b/src/Cooked/Pretty/Cooked.hs index 0e1bf244d..6cee78e6c 100644 --- a/src/Cooked/Pretty/Cooked.hs +++ b/src/Cooked/Pretty/Cooked.hs @@ -124,29 +124,20 @@ instance (Show a) => PrettyCooked (a, UtxoState) where "-" ["Returns:" <+> PP.viaShow res, prettyCookedOpt opts state] -prettyBlockChainEntries :: PrettyCookedOpts -> [BlockChainLogEntry] -> DocCooked -prettyBlockChainEntries opts entries = - prettyItemize - "MockChain run:" - "-" - (prettyCookedOpt opts <$> entries) - instance (Show a) => PrettyCooked (MockChainReturn a UtxoState) where prettyCookedOpt opts (res, entries) = - let inner = case res of + let mcLog = prettyItemize "MockChain run log:" "-" (prettyCookedOpt opts <$> entries) + mcEndResult = case res of Left err -> "🔴" <+> prettyCookedOpt opts err Right (a, s) -> "🟢" <+> prettyCookedOpt opts (a, s) - in -- We only keep log entries above the required level - case filter ((pcOptLogToInt (pcOptLog opts) <=) . blockChainLogEntryToInt) entries of - [] -> inner - subEntries -> prettyItemize "End result:" "-" [prettyBlockChainEntries opts subEntries, inner] + in prettyItemizeNoTitle "-" $ if pcOptLog opts then [mcLog, mcEndResult] else [mcEndResult] -- | This pretty prints a 'MockChainLog' that usually consists of the list of -- validated or submitted transactions. In the log, we know a transaction has -- been validated if the 'MCLogSubmittedTxSkel' is followed by a 'MCLogNewTx'. -instance PrettyCooked BlockChainLogEntry where - prettyCookedOpt opts (BCLogSubmittedTxSkel skelContext skel) = "Submitted:" <+> prettyTxSkel opts skelContext skel - prettyCookedOpt opts (BCLogAdjustedTxSkel skelContext skel fee collaterals returnWallet) = +instance PrettyCooked MockChainLogEntry where + prettyCookedOpt opts (MCLogSubmittedTxSkel skelContext skel) = "Submitted:" <+> prettyTxSkel opts skelContext skel + prettyCookedOpt opts (MCLogAdjustedTxSkel skelContext skel fee collaterals returnWallet) = prettyItemize "Adjusted:" "-" @@ -155,10 +146,8 @@ instance PrettyCooked BlockChainLogEntry where "Collateral inputs:" <+> prettyCookedOpt opts (Set.toList collaterals), "Return collateral wallet:" <+> prettyCookedOpt opts (walletPKHash returnWallet) ] - prettyCookedOpt opts (BCLogNewTx txId) = "New transaction:" <+> prettyCookedOpt opts txId - prettyCookedOpt _ (BCLogError err) = "Fail:" <+> PP.pretty err - prettyCookedOpt _ (BCLogWarning warn) = "Warning:" <+> PP.pretty warn - prettyCookedOpt _ (BCLogInfo info) = "Info:" <+> PP.pretty info + prettyCookedOpt opts (MCLogNewTx txId) = "New transaction:" <+> prettyCookedOpt opts txId + prettyCookedOpt opts (MCLogDiscardedUtxos n s) = prettyCookedOpt opts n <+> "balancing utxos were discarded:" <+> PP.pretty s -- go acc (MCLogFail msg : entries) = -- go ("Fail:" <+> PP.pretty msg : acc) entries diff --git a/src/Cooked/Pretty/Options.hs b/src/Cooked/Pretty/Options.hs index 37b12d9a5..8e630ed92 100644 --- a/src/Cooked/Pretty/Options.hs +++ b/src/Cooked/Pretty/Options.hs @@ -4,10 +4,8 @@ module Cooked.Pretty.Options ( PrettyCookedOpts (..), PrettyCookedHashOpts (..), PCOptTxOutRefs (..), - PCOptLog (..), hashNamesFromList, defaultHashNames, - pcOptLogToInt, ) where @@ -34,8 +32,8 @@ data PrettyCookedOpts = PrettyCookedOpts pcOptNumericUnderscores :: Bool, -- | Options related to printing hashes pcOptHashes :: PrettyCookedHashOpts, - -- | What kind of log to print - pcOptLog :: PCOptLog + -- | Whether to display the log + pcOptLog :: Bool } deriving (Eq, Show) @@ -47,30 +45,9 @@ instance Default PrettyCookedOpts where pcOptPrintDefaultTxOpts = False, pcOptNumericUnderscores = True, pcOptHashes = def, - pcOptLog = def + pcOptLog = True } --- | What log to display -data PCOptLog - = -- | No logging at all - PCOptLogNone - | -- | Log errors and special log entries - PCOptLogSpecial - | -- | Same as `PCOptLogSpecial`, but with warnings - PCOptLogWarning - | -- | Log everything - PCOptLogAll - deriving (Eq, Show) - -instance Default PCOptLog where - def = PCOptLogAll - -pcOptLogToInt :: PCOptLog -> Integer -pcOptLogToInt PCOptLogNone = 4 -pcOptLogToInt PCOptLogSpecial = 3 -pcOptLogToInt PCOptLogWarning = 2 -pcOptLogToInt PCOptLogAll = 1 - -- | Whether to print transaction outputs references. data PCOptTxOutRefs = -- | Hide them diff --git a/src/Cooked/Skeleton.hs b/src/Cooked/Skeleton.hs index 23e415a40..b504a7ab6 100644 --- a/src/Cooked/Skeleton.hs +++ b/src/Cooked/Skeleton.hs @@ -1051,6 +1051,7 @@ data SkelContext = SkelContext { skelContextTxOuts :: Map Api.TxOutRef Api.TxOut, skelContextTxSkelOutDatums :: Map Api.DatumHash TxSkelOutDatum } + deriving (Show, Eq) -- | Returns the full value contained in the skeleton outputs txSkelValueInOutputs :: TxSkel -> Api.Value From 47f54a506b5daee738d6706364b0f0878df6463e Mon Sep 17 00:00:00 2001 From: mmontin Date: Wed, 10 Jul 2024 12:02:43 +0200 Subject: [PATCH 6/9] changing item --- src/Cooked/Pretty/Cooked.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Cooked/Pretty/Cooked.hs b/src/Cooked/Pretty/Cooked.hs index 6cee78e6c..4ca784258 100644 --- a/src/Cooked/Pretty/Cooked.hs +++ b/src/Cooked/Pretty/Cooked.hs @@ -130,7 +130,7 @@ instance (Show a) => PrettyCooked (MockChainReturn a UtxoState) where mcEndResult = case res of Left err -> "🔴" <+> prettyCookedOpt opts err Right (a, s) -> "🟢" <+> prettyCookedOpt opts (a, s) - in prettyItemizeNoTitle "-" $ if pcOptLog opts then [mcLog, mcEndResult] else [mcEndResult] + in prettyItemizeNoTitle "*" $ if pcOptLog opts then [mcLog, mcEndResult] else [mcEndResult] -- | This pretty prints a 'MockChainLog' that usually consists of the list of -- validated or submitted transactions. In the log, we know a transaction has From 2ff0e461553a4bedd99d869bda4eb2dfcef066b1 Mon Sep 17 00:00:00 2001 From: mmontin Date: Wed, 10 Jul 2024 15:38:43 +0200 Subject: [PATCH 7/9] integrating comments, adding comments and more readable bullets --- src/Cooked/MockChain/BlockChain.hs | 12 ++++++++++++ src/Cooked/Pretty/Cooked.hs | 20 ++++++-------------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/Cooked/MockChain/BlockChain.hs b/src/Cooked/MockChain/BlockChain.hs index 2d8e7f02b..01828985d 100644 --- a/src/Cooked/MockChain/BlockChain.hs +++ b/src/Cooked/MockChain/BlockChain.hs @@ -111,10 +111,22 @@ data MockChainError where FailWith :: String -> MockChainError deriving (Show, Eq) +-- * MockChain logs + +-- | This represents the specific events that should be logged when processing +-- transactions. If a new kind of event arises, then a new constructor should be +-- provided here. data MockChainLogEntry where + -- | Logging a Skeleton as it is submitted by the user. MCLogSubmittedTxSkel :: SkelContext -> TxSkel -> MockChainLogEntry + -- | Logging a Skeleton as it has been adjusted by the balancing mechanism, + -- alongside fee, collateral utxos and return collateral wallet. MCLogAdjustedTxSkel :: SkelContext -> TxSkel -> Integer -> Set Api.TxOutRef -> Wallet -> MockChainLogEntry + -- | Logging the appearance of a new transaction, after a skeleton has been + -- successfully sent for validation. MCLogNewTx :: Api.TxId -> MockChainLogEntry + -- | Logging the fact that utxos provided by the user for balancing have to be + -- discarded for a specific reason. MCLogDiscardedUtxos :: Integer -> String -> MockChainLogEntry -- | Contains methods needed for balancing. diff --git a/src/Cooked/Pretty/Cooked.hs b/src/Cooked/Pretty/Cooked.hs index 4ca784258..b2d3d4e63 100644 --- a/src/Cooked/Pretty/Cooked.hs +++ b/src/Cooked/Pretty/Cooked.hs @@ -126,41 +126,33 @@ instance (Show a) => PrettyCooked (a, UtxoState) where instance (Show a) => PrettyCooked (MockChainReturn a UtxoState) where prettyCookedOpt opts (res, entries) = - let mcLog = prettyItemize "MockChain run log:" "-" (prettyCookedOpt opts <$> entries) + let mcLog = "📘" <+> prettyItemize "MockChain run log:" "⁍" (prettyCookedOpt opts <$> entries) mcEndResult = case res of Left err -> "🔴" <+> prettyCookedOpt opts err Right (a, s) -> "🟢" <+> prettyCookedOpt opts (a, s) - in prettyItemizeNoTitle "*" $ if pcOptLog opts then [mcLog, mcEndResult] else [mcEndResult] + in PP.vsep $ if pcOptLog opts then [mcLog, mcEndResult] else [mcEndResult] -- | This pretty prints a 'MockChainLog' that usually consists of the list of -- validated or submitted transactions. In the log, we know a transaction has -- been validated if the 'MCLogSubmittedTxSkel' is followed by a 'MCLogNewTx'. instance PrettyCooked MockChainLogEntry where - prettyCookedOpt opts (MCLogSubmittedTxSkel skelContext skel) = "Submitted:" <+> prettyTxSkel opts skelContext skel + prettyCookedOpt opts (MCLogSubmittedTxSkel skelContext skel) = prettyItemize "Submitted:" "-" [prettyTxSkel opts skelContext skel] prettyCookedOpt opts (MCLogAdjustedTxSkel skelContext skel fee collaterals returnWallet) = prettyItemize "Adjusted:" "-" [ prettyTxSkel opts skelContext skel, "Fee: Lovelace" <+> prettyCookedOpt opts fee, - "Collateral inputs:" <+> prettyCookedOpt opts (Set.toList collaterals), - "Return collateral wallet:" <+> prettyCookedOpt opts (walletPKHash returnWallet) + prettyItemize "Collateral inputs:" "-" (prettyCookedOpt opts <$> Set.toList collaterals), + "Return collateral target:" <+> prettyCookedOpt opts (walletPKHash returnWallet) ] prettyCookedOpt opts (MCLogNewTx txId) = "New transaction:" <+> prettyCookedOpt opts txId prettyCookedOpt opts (MCLogDiscardedUtxos n s) = prettyCookedOpt opts n <+> "balancing utxos were discarded:" <+> PP.pretty s --- go acc (MCLogFail msg : entries) = --- go ("Fail:" <+> PP.pretty msg : acc) entries --- -- This case is not supposed to occur because it should follow a --- -- 'MCLogSubmittedTxSkel' --- go acc (MCLogNewTx txId : entries) = --- go ("New transaction:" <+> prettyCookedOpt opts txId : acc) entries --- go acc [] = reverse acc - prettyTxSkel :: PrettyCookedOpts -> SkelContext -> TxSkel -> DocCooked prettyTxSkel opts skelContext (TxSkel lbl txopts mints signers validityRange ins insReference outs proposals) = prettyItemize - "transaction skeleton:" + "Transaction skeleton:" "-" ( catMaybes [ prettyItemizeNonEmpty "Labels:" "-" (prettyCookedOpt opts <$> Set.toList lbl), From 73d8ab22a08fc850eec6c45aafb21a65dcae00f2 Mon Sep 17 00:00:00 2001 From: mmontin Date: Wed, 10 Jul 2024 16:27:17 +0200 Subject: [PATCH 8/9] fixing the bug where collateral inputs were not resolved --- src/Cooked/Pretty/Cooked.hs | 46 +++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/src/Cooked/Pretty/Cooked.hs b/src/Cooked/Pretty/Cooked.hs index b2d3d4e63..2c5f43536 100644 --- a/src/Cooked/Pretty/Cooked.hs +++ b/src/Cooked/Pretty/Cooked.hs @@ -143,7 +143,7 @@ instance PrettyCooked MockChainLogEntry where "-" [ prettyTxSkel opts skelContext skel, "Fee: Lovelace" <+> prettyCookedOpt opts fee, - prettyItemize "Collateral inputs:" "-" (prettyCookedOpt opts <$> Set.toList collaterals), + prettyItemize "Collateral inputs:" "-" (prettyCollateralIn opts skelContext <$> Set.toList collaterals), "Return collateral target:" <+> prettyCookedOpt opts (walletPKHash returnWallet) ] prettyCookedOpt opts (MCLogNewTx txId) = "New transaction:" <+> prettyCookedOpt opts txId @@ -348,29 +348,35 @@ prettyTxSkelOut opts (Pays output) = prettyTxSkelOutDatumMaybe :: PrettyCookedOpts -> TxSkelOutDatum -> Maybe DocCooked prettyTxSkelOutDatumMaybe _ TxSkelOutNoDatum = Nothing prettyTxSkelOutDatumMaybe opts txSkelOutDatum@(TxSkelOutInlineDatum _) = - Just $ - "Datum (inlined):" - <+> PP.align (prettyCookedOpt opts txSkelOutDatum) + Just $ "Datum (inlined):" <+> PP.align (prettyCookedOpt opts txSkelOutDatum) prettyTxSkelOutDatumMaybe opts txSkelOutDatum = - Just $ - "Datum (hashed):" - <+> PP.align (prettyCookedOpt opts txSkelOutDatum) + Just $ "Datum (hashed):" <+> PP.align (prettyCookedOpt opts txSkelOutDatum) + +prettyTxOutRefM :: PrettyCookedOpts -> SkelContext -> Api.TxOutRef -> Maybe (DocCooked, DocCooked, [DocCooked]) +prettyTxOutRefM opts skelContext txOutRef = + ( \(output, txSkelOutDatum) -> + ( prettyCookedOpt opts (outputAddress output), + prettyCookedOpt opts (outputValue output), + catMaybes + [ prettyTxSkelOutDatumMaybe opts txSkelOutDatum, + getReferenceScriptDoc opts output + ] + ) + ) + <$> lookupOutput skelContext txOutRef + +prettyCollateralIn :: PrettyCookedOpts -> SkelContext -> Api.TxOutRef -> DocCooked +prettyCollateralIn opts skelContext txOutRef = + case prettyTxOutRefM opts skelContext txOutRef of + Nothing -> prettyCookedOpt opts txOutRef <+> "(non resolved)" + Just (addressDoc, valueDoc, otherDocs) -> prettyItemize ("Belonging to" <+> addressDoc) "-" (valueDoc : otherDocs) prettyTxSkelIn :: PrettyCookedOpts -> SkelContext -> (Api.TxOutRef, TxSkelRedeemer) -> DocCooked -prettyTxSkelIn opts skelContext (txOutRef, txSkelRedeemer) = do - case lookupOutput skelContext txOutRef of +prettyTxSkelIn opts skelContext (txOutRef, txSkelRedeemer) = + case prettyTxOutRefM opts skelContext txOutRef of Nothing -> "Spends" <+> prettyCookedOpt opts txOutRef <+> "(non resolved)" - Just (output, txSkelOutDatum) -> - prettyItemize - ("Spends from" <+> prettyCookedOpt opts (outputAddress output)) - "-" - ( prettyCookedOpt opts (outputValue output) - : prettyTxSkelRedeemer opts txSkelRedeemer - <> catMaybes - [ prettyTxSkelOutDatumMaybe opts txSkelOutDatum, - getReferenceScriptDoc opts output - ] - ) + Just (addressDoc, valueDoc, otherDocs) -> + prettyItemize ("Spends from" <+> addressDoc) "-" (valueDoc : prettyTxSkelRedeemer opts txSkelRedeemer <> otherDocs) prettyTxSkelInReference :: PrettyCookedOpts -> SkelContext -> Api.TxOutRef -> Maybe DocCooked prettyTxSkelInReference opts skelContext txOutRef = do From a94db0a8f72f2248abb7d2737c87d0bbf311f396 Mon Sep 17 00:00:00 2001 From: mmontin Date: Wed, 10 Jul 2024 16:32:39 +0200 Subject: [PATCH 9/9] CHANGELOG.md --- CHANGELOG.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97ad33523..f1a349d8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,10 +9,13 @@ - `toInitDistWithMinAda` and `unsafeToInitDistWithMinAda` to ensure the initial distribution only provides outputs with the required minimal ada based on default parameters. - +- PrettyCooked option `pcOptLog`, which is a boolean, to turn on or off the log + display in the pretty printer. The default value is `True`. + ### Removed - `positivePart` and `negativePart` in `ValueUtils.hs`. Replaced by `Api.split`. +- Redundant logging of errors in mockchain runs. ### Changed @@ -22,6 +25,11 @@ constructors: `txSkelSomeRedeemer`, `txSkelEmptyRedeemer`, `txSkelSomeRedeemerAndReferenceScript`, `txSkelEmptyRedeemerAndReferenceScript`. +- Logging has been reworked: + * it is no longer limited to `StagedMockChain` runs + * it is now a component of `MonadBlockChainBalancing` + * it can be turned on/off in skeleton options + * it now displays the discarding of utxos during balancing. ### Fixed