diff --git a/cardano-api/cardano-api.cabal b/cardano-api/cardano-api.cabal index 776691e9c..423dd4ff2 100644 --- a/cardano-api/cardano-api.cabal +++ b/cardano-api/cardano-api.cabal @@ -123,6 +123,7 @@ library internal Cardano.Api.NetworkId Cardano.Api.OperationalCertificate Cardano.Api.Orphans + Cardano.Api.Plutus Cardano.Api.Pretty Cardano.Api.Protocol Cardano.Api.ProtocolParameters @@ -161,6 +162,7 @@ library internal attoparsec, base16-bytestring >=1.0, base58-bytestring, + base64-bytestring, bech32 >=1.1.0, bytestring, cardano-binary, @@ -403,7 +405,6 @@ test-suite cardano-api-golden microlens, parsec, plutus-core ^>=1.36, - plutus-ledger-api ^>=1.36, tasty, tasty-hedgehog, text, diff --git a/cardano-api/internal/Cardano/Api/Fees.hs b/cardano-api/internal/Cardano/Api/Fees.hs index eb0b91771..fac558c5f 100644 --- a/cardano-api/internal/Cardano/Api/Fees.hs +++ b/cardano-api/internal/Cardano/Api/Fees.hs @@ -63,6 +63,7 @@ import qualified Cardano.Api.Experimental.Eras as Exp import Cardano.Api.Experimental.Tx import Cardano.Api.Feature import qualified Cardano.Api.Ledger.Lens as A +import Cardano.Api.Plutus import Cardano.Api.Pretty import Cardano.Api.ProtocolParameters import Cardano.Api.Query @@ -84,7 +85,6 @@ import qualified Cardano.Ledger.Keys as Ledger import qualified Cardano.Ledger.Plutus.Language as Plutus import qualified Cardano.Ledger.Val as L import qualified Ouroboros.Consensus.HardFork.History as Consensus -import qualified PlutusLedgerApi.V1 as Plutus import Control.Monad import Data.Bifunctor (bimap, first, second) @@ -99,7 +99,6 @@ import Data.Ratio import Data.Set (Set) import qualified Data.Set as Set import Data.Text (Text) -import qualified Data.Text as Text import GHC.Exts (IsList (..)) import Lens.Micro ((.~), (^.)) @@ -540,7 +539,7 @@ data ScriptExecutionError -- (which is not possible for 'evaluateTransactionExecutionUnits' since -- the whole point of it is to discover how many execution units are -- needed). - ScriptErrorEvaluationFailed Plutus.EvaluationError [Text.Text] + ScriptErrorEvaluationFailed DebugPlutusFailure | -- | The execution units overflowed a 64bit word. Congratulations if -- you encounter this error. With the current style of cost model this -- would need a script to run for over 7 months, which is somewhat more @@ -581,11 +580,8 @@ instance Error ScriptExecutionError where [ "The Plutus script witness has the wrong datum (according to the UTxO). " , "The expected datum value has hash " <> pshow dh ] - ScriptErrorEvaluationFailed evalErr logs -> - mconcat - [ "The Plutus script evaluation failed: " <> pretty evalErr - , "\nScript debugging logs: " <> mconcat (map (\t -> pretty $ t `Text.append` "\n") logs) - ] + ScriptErrorEvaluationFailed plutusDebugFailure -> + pretty $ renderDebugPlutusFailure plutusDebugFailure ScriptErrorExecutionUnitsOverflow -> mconcat [ "The execution units required by this Plutus script overflows a 64bit " @@ -740,9 +736,8 @@ evaluateTransactionExecutionUnitsShelley sbe systemstart epochInfo (LedgerProtoc where txin' = fromShelleyTxIn txin L.MissingDatum dh -> ScriptErrorWrongDatum (ScriptDataHash dh) - L.ValidationFailure _ evalErr logs _ -> - -- TODO: Include additional information from ValidationFailure - ScriptErrorEvaluationFailed evalErr logs + L.ValidationFailure execUnits evalErr logs scriptWithContext -> + ScriptErrorEvaluationFailed $ DebugPlutusFailure evalErr scriptWithContext execUnits logs L.IncompatibleBudget _ -> ScriptErrorExecutionUnitsOverflow L.RedeemerPointsToUnknownScriptHash rdmrPtr -> ScriptErrorRedeemerPointsToUnknownScriptHash $ toScriptIndex aOnwards rdmrPtr diff --git a/cardano-api/internal/Cardano/Api/Plutus.hs b/cardano-api/internal/Cardano/Api/Plutus.hs new file mode 100644 index 000000000..55afae835 --- /dev/null +++ b/cardano-api/internal/Cardano/Api/Plutus.hs @@ -0,0 +1,88 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE KindSignatures #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE UndecidableInstances #-} + +-- | This module provides an error to conveniently render plutus related failures. +module Cardano.Api.Plutus + ( DebugPlutusFailure (..) + , renderDebugPlutusFailure + ) +where + +import Cardano.Api.Pretty + +import qualified Cardano.Ledger.Api as L +import Cardano.Ledger.Binary.Encoding (serialize') +import Cardano.Ledger.Binary.Plain (serializeAsHexText) +import qualified Cardano.Ledger.Plutus.Evaluate as Plutus +import qualified Cardano.Ledger.Plutus.ExUnits as Plutus +import qualified Cardano.Ledger.Plutus.Language as Plutus +import qualified PlutusLedgerApi.V1 as Plutus + +import qualified Data.ByteString.Base64 as B64 +import Data.ByteString.Short as BSS +import Data.Text (Text) +import qualified Data.Text as Text +import qualified Data.Text.Encoding as Text +import Prettyprinter + +-- | A structured representation of Plutus script validation failures, +-- providing detailed information about the failed execution for debugging purposes. +-- This type contains the same information as the data constructor +-- 'Cardano.Ledger.Alonzo.Plutus.Evaluate.TransactionScriptFailure.ValidationFailure' +-- but with named fields and fixed crypto parameters for easier debugging and +-- error reporting. +data DebugPlutusFailure + = DebugPlutusFailure + { dpfEvaluationError :: Plutus.EvaluationError + , dpfScriptWithContext :: Plutus.PlutusWithContext L.StandardCrypto + , dpfExecutionUnits :: Plutus.ExUnits + , dpfExecutionLogs :: [Text] + } + deriving (Eq, Show) + +renderDebugPlutusFailure :: DebugPlutusFailure -> Text +renderDebugPlutusFailure dpf = + let pwc = dpfScriptWithContext dpf + lang = case pwc of + Plutus.PlutusWithContext{Plutus.pwcScript = script} -> + either Plutus.plutusLanguage Plutus.plutusLanguage script + + scriptArgs = case pwc of + Plutus.PlutusWithContext{Plutus.pwcArgs = args} -> + line <> indent 3 (pretty args) + protocolVersion = Plutus.pwcProtocolVersion pwc + scriptArgsBase64 = case pwc of + Plutus.PlutusWithContext{Plutus.pwcArgs = args} -> + Text.decodeUtf8 $ B64.encode $ serialize' protocolVersion args + evalError = dpfEvaluationError dpf + binaryScript = case pwc of + Plutus.PlutusWithContext{Plutus.pwcScript = scr} -> + let Plutus.Plutus bytes = either id Plutus.plutusFromRunnable scr + in Text.decodeUtf8 . B64.encode . BSS.fromShort $ Plutus.unPlutusBinary bytes + in Text.unlines + [ "Script hash: " <> serializeAsHexText (Plutus.pwcScriptHash pwc) + , "Script language: " <> Text.pack (show lang) + , "Protocol version: " <> Text.pack (show protocolVersion) + , "Script arguments: " <> docToText scriptArgs + , "Script evaluation error: " <> docToText (pretty evalError) + , "Script execution logs: " <> Text.unlines (dpfExecutionLogs dpf) + , "Script base64 encoded arguments: " <> scriptArgsBase64 + , "Script base64 encoded bytes: " <> binaryScript + ] + +{- +-- Should be used on `dpfExecutionLogs dpf`. Disabled until next plutus release. +See: https://github.com/IntersectMBO/cardano-api/pull/672#issuecomment-2455909946 + +PlutusTx.ErrorCodes.plutusPreludeErrorCodes + +lookupPlutusErrorCode :: Text -> Text +lookupPlutusErrorCode code = + let codeString = PlutusTx.stringToBuiltinString $ Text.unpack code + in case Map.lookup codeString plutusPreludeErrorCodes of + Just err -> Text.pack err + Nothing -> "Unknown error code: " <> code +-} diff --git a/cardano-api/test/cardano-api-golden/Test/Golden/ErrorsSpec.hs b/cardano-api/test/cardano-api-golden/Test/Golden/ErrorsSpec.hs index 19cc09359..dbc4490f2 100644 --- a/cardano-api/test/cardano-api-golden/Test/Golden/ErrorsSpec.hs +++ b/cardano-api/test/cardano-api-golden/Test/Golden/ErrorsSpec.hs @@ -40,7 +40,6 @@ import qualified Cardano.Ledger.Coin as L import Cardano.Ledger.Crypto (StandardCrypto) import qualified Cardano.Ledger.Plutus.Language as Plutus import qualified PlutusCore.Evaluation.Machine.CostModelInterface as Plutus -import qualified PlutusLedgerApi.Common as Plutus hiding (PlutusV2) import qualified Codec.Binary.Bech32 as Bech32 import Control.Error.Util (hush) @@ -270,10 +269,6 @@ test_ScriptExecutionError = [ ("ScriptErrorMissingTxIn", ScriptErrorMissingTxIn txin1) , ("ScriptErrorTxInWithoutDatum", ScriptErrorTxInWithoutDatum txin1) , ("ScriptErrorWrongDatum", ScriptErrorWrongDatum hashScriptData1) - , - ( "ScriptErrorEvaluationFailed" - , ScriptErrorEvaluationFailed Plutus.CostModelParameterMismatch (replicate 5 text) - ) , ("ScriptErrorExecutionUnitsOverflow", ScriptErrorExecutionUnitsOverflow) , ( "ScriptErrorNotPlutusWitnessedTxIn"