From d952fef40aa3b5a3a54ad7ef660d01ad67eac043 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C4=B1z=C4=B1r=20Sefa=20=C4=B0rken?= Date: Tue, 19 Aug 2025 14:58:03 +0300 Subject: [PATCH 01/10] Implemented eval tx stream --- testgen-hs/CLI.hs | 15 +++- testgen-hs/Generators.hs | 2 +- testgen-hs/Main.hs | 160 +++++++++++++++++++++++++++++++++++- testgen-hs/SynthEvalTx.hs | 20 ++++- testgen-hs/testgen-hs.cabal | 3 +- 5 files changed, 190 insertions(+), 10 deletions(-) diff --git a/testgen-hs/CLI.hs b/testgen-hs/CLI.hs index 158f524..a4cf6f7 100644 --- a/testgen-hs/CLI.hs +++ b/testgen-hs/CLI.hs @@ -16,7 +16,7 @@ import qualified Data.Version as V import Options.Applicative as O import qualified Paths_testgen_hs as V -data Command = Generate GenerateOptions | Deserialize ByteString | DeserializeStream deriving (Show) +data Command = Generate GenerateOptions | Deserialize ByteString | DeserializeStream | EvaluateStream deriving (Show) data GenerateOptions = GenerateOptions (Maybe Seed) GenSize NumCases TypeCommand deriving (Show) @@ -30,7 +30,7 @@ data TypeCommand = GHCInteger | DataText | ExampleADT - | Tx'Conway + | Tx'ConwayDummy | ApplyTxErr'Byron | ApplyTxErr'Shelley | ApplyTxErr'Allegra @@ -90,6 +90,15 @@ commandParser = (progDesc "Deserialize an STDIN stream of multiple lines of base16-encoded CBOR of ‘HardForkApplyTxErr’") ) ) + <> ( command + "evaluate-stream" + ( info + ( pure EvaluateStream + <**> helper + ) + (progDesc "Evaluate an STDIN stream of Txs with Utxos") + ) + ) ) optionsParser :: Parser GenerateOptions @@ -138,7 +147,7 @@ typeCommandParser :: Parser TypeCommand typeCommandParser = subparser ( mempty - <> mkTypeCommand Tx'Conway + <> mkTypeCommand Tx'ConwayDummy <> mkTypeCommand ApplyTxErr'Byron <> mkTypeCommand ApplyTxErr'Shelley <> mkTypeCommand ApplyTxErr'Allegra diff --git a/testgen-hs/Generators.hs b/testgen-hs/Generators.hs index 36b42a3..01465e0 100644 --- a/testgen-hs/Generators.hs +++ b/testgen-hs/Generators.hs @@ -181,7 +181,7 @@ instance OurCBOR Tx'Conway where ourToCBOR (Tx'Conway (tx, _utxo)) = Cardano.Binary.toCBOR tx ourToJSON (Tx'Conway (tx, utxo)) = J.object - [ "executionUnits" J..= SynthEvalTx.eval'Conway tx utxo, + [ "executionUnits" J..= SynthEvalTx.eval'ConwayDummy tx utxo, "utxoSetCBOR" J..= ( T.decodeUtf8With T.lenientDecode . B16.encode diff --git a/testgen-hs/Main.hs b/testgen-hs/Main.hs index fd79be0..27352e0 100644 --- a/testgen-hs/Main.hs +++ b/testgen-hs/Main.hs @@ -1,13 +1,22 @@ +{-# LANGUAGE TypeApplications #-} + module Main where import CLI (GenSize (..), NumCases (..), Seed (..)) import qualified CLI +import Cardano.Binary (FromCBOR, decodeFull') +import Cardano.Ledger.Api (ConwayEra, PParams) +import qualified Cardano.Ledger.Core +import Cardano.Slotting.EpochInfo (EpochInfo, fixedEpochInfo) +import Cardano.Slotting.Slot (EpochSize (..)) +import Cardano.Slotting.Time (SystemStart (..), mkSlotLength) import qualified Codec.CBOR.Write as C import qualified Control.Concurrent.Async as Async import Control.Concurrent.MVar (modifyMVar_, newMVar) import Data.Aeson (FromJSON, ToJSON) import qualified Data.Aeson as J import qualified Data.Aeson.Encode.Pretty as J +import Data.Bifunctor (first) import Data.ByteString (ByteString) import qualified Data.ByteString.Base16 as B16 import qualified Data.ByteString.Char8 as B8 @@ -19,10 +28,13 @@ import Data.Text (Text) import qualified Data.Text as T import qualified Data.Text.Encoding as T import qualified Data.Text.Encoding.Error as T +import Data.Time (NominalDiffTime) import Data.Time.Clock.POSIX (getPOSIXTime) +import Data.Word (Word16, Word64) import qualified Deserialize as D import GHC.Generics (Generic) import qualified Generators as G +import qualified SynthEvalTx import System.Exit (exitFailure) import System.IO (hPutStrLn, stderr) import qualified System.IO as SIO @@ -32,6 +44,8 @@ import qualified Test.QuickCheck as QC import qualified Test.QuickCheck.Gen as QC import Test.QuickCheck.Instances.Text () import qualified Test.QuickCheck.Random as QC +import qualified Cardano.Ledger.Binary.Decoding as Binary +import qualified Cardano.Ledger.Core as Ledger main :: IO () main = @@ -39,6 +53,7 @@ main = CLI.Generate opts -> runGenerate opts CLI.Deserialize cbor -> runDeserialize cbor CLI.DeserializeStream -> runDeserializeStream + CLI.EvaluateStream -> runEvaluateStream data TestCase a = TestCase { cbor :: Text, @@ -48,6 +63,62 @@ data TestCase a = TestCase } deriving (Generic, Show, FromJSON, ToJSON) +-- | Represents initialization payload containing configuration parameters. +-- +-- The InitPayload type encapsulates essential system and protocol configuration for eval tx: +-- +-- - systemStart: Starting time of the system, CBOR +-- - protocolParams: Protocol parameters in text format, CBOR +-- - slotConfig: Configuration for slots +-- - era: Era identifier as a 16-bit word +-- +-- This data type implements Generic, Show and FromJSON type classes for serialization +-- and debugging purposes. +data InitPayload = InitPayload + { systemStart :: Text, --cbor + protocolParams :: Text, --cbor + slotConfig :: SlotConfig, + era :: Word16 + } + deriving (Generic, Show, FromJSON) + +data SlotConfig = SlotConfig + { slotLength :: Word64, -- in milliseconds + zeroSlot :: Word64, + zeroTime :: Word64, + epochLength :: Word64 + } + deriving (Generic, Show, FromJSON) + + -- This is used as a generic response to any incoming request. +data PayloadResponse = PayloadResponse + { rError :: Maybe Text, + rJson :: J.Value + } + deriving (Generic, Show) + +instance ToJSON PayloadResponse where + toJSON = J.genericToJSON J.defaultOptions {J.fieldLabelModifier = modifier} + where + modifier "rError" = "error" + modifier "rJson" = "json" + modifier s = s + + +-- | +-- EvalPayload represents the data structure for transaction evaluation payload. +-- +-- Contains transaction and UTxO information needed for evaluation. +-- +-- Fields: +-- * tx - Transaction data as CBOR +-- * utxos - Unspent Transaction Outputs (UTxOs) as CBOR +data EvalPayload = EvalPayload + { tx :: Text, + utxos :: Text -- TxIn & TxOut as key value pair CBOR (map) + } + deriving (Generic, Show, FromJSON) + runGenerate :: CLI.GenerateOptions -> IO () runGenerate (CLI.GenerateOptions maybeSeed genSize numCases command) = do SIO.hSetBuffering SIO.stdout SIO.LineBuffering @@ -59,7 +130,7 @@ runGenerate (CLI.GenerateOptions maybeSeed genSize numCases command) = do BL8.putStrLn $ J.encode (J.object [("seed", (\(Seed s) -> J.toJSON s) seed)]) ( case command of - CLI.Tx'Conway -> writeRandom @G.Tx'Conway Proxy + CLI.Tx'ConwayDummy -> writeRandom @G.Tx'Conway Proxy CLI.ApplyTxErr'Byron -> writeRandom @G.ApplyTxErr'Byron Proxy CLI.ApplyTxErr'Shelley -> writeRandom @G.ApplyTxErr'Shelley Proxy CLI.ApplyTxErr'Allegra -> writeRandom @G.ApplyTxErr'Allegra Proxy @@ -176,3 +247,90 @@ runDeserializeStream = do Left err -> J.object ["error" J..= err] BL8.putStrLn . J.encode $ result processLines + +runEvaluateStream :: IO () +runEvaluateStream = do + -- We need line buffering for this to work: + SIO.hSetBuffering SIO.stdin SIO.LineBuffering + SIO.hSetBuffering SIO.stdout SIO.LineBuffering + line <- B8.getLine + case J.eitherDecodeStrict line of + Left err -> do + hPutStrLn stderr $ "Expected InitPayload first, but failed to parse line: " ++ err + runEvaluateStream + Right initPayload -> do + let ei = convertEpochInfo (slotConfig initPayload) + let combinedResult = do + pp <- decodeFromHex (protocolParams initPayload) + ss <- decodeFromHex (systemStart initPayload) + return (pp, ss) + + case combinedResult of + Left err -> do + hPutStrLn stderr $ "Failed to decode initial payload: " ++ err + exitFailure + Right (pp, ss) -> do + BL8.putStrLn . J.encode $ PayloadResponse { rJson = J.object [], rError = Nothing } + processLines initPayload pp ss ei + where + processLines :: InitPayload -> PParams ConwayEra -> SystemStart -> EpochInfo (Either Text) -> IO () + processLines initPayload pp ss ei = do + eof <- SIO.isEOF + if eof + then pure () + else do + line <- B8.getLine + case J.eitherDecodeStrict line of + Left errEval -> do + hPutStrLn stderr $ + "Failed to parse line as EvalPayload (\"" + ++ errEval + ++ "\")" + processLines initPayload pp ss ei + Right (evalPayload :: EvalPayload) -> do + let evalResult = do + let txBytes = either (Left . show) Right $ B16.decode (T.encodeUtf8 (tx evalPayload)) + decodedTx <- either + Left + (decodeCborWith "Transaction" (Left . show) (Binary.decCBOR @(Cardano.Ledger.Core.Tx ConwayEra))) + txBytes + utxos <- decodeFromHex (utxos evalPayload) + return (decodedTx, utxos) + + case evalResult of + Left err -> do + hPutStrLn stderr $ "Failed to decode eval payload: " ++ err + processLines initPayload pp ss ei + Right (tx, utxos) -> do + let result = SynthEvalTx.eval'Conway tx utxos ei ss + BL8.putStrLn . J.encode $ PayloadResponse { rJson = result, rError = Nothing } + processLines initPayload pp ss ei + +-- | Creates an EpochInfo from the given SlotConfig +convertEpochInfo :: SlotConfig -> EpochInfo (Either Text) +convertEpochInfo sc = + let slotLengthInSeconds :: NominalDiffTime = fromIntegral (slotLength sc) / 1000 + in fixedEpochInfo (EpochSize $ epochLength sc) (mkSlotLength slotLengthInSeconds) + +-- | Generic CBOR hex decoder +decodeFromHex :: (FromCBOR a) => T.Text -> Either String a +decodeFromHex hexText = do + -- 1. Decode from hex + cborBytes <- first show $ B16.decode (T.encodeUtf8 hexText) + -- 2. Decode from CBOR. + first show $ decodeFull' cborBytes + + +-- Run a CBOR decoder for data in Conway era +decodeCborWith + :: Text -- ^ Label for error reporting + -> (Binary.DecoderError -> Either String a) -- ^ Error handler + -> (forall s. Binary.Decoder s a) -- ^ CBOR decoder + -> ByteString -- ^ Input bytes + -> Either String a +decodeCborWith lbl handleErr decoder bytes = + case Binary.decodeFullDecoder version lbl decoder (BL.fromStrict bytes) of + Left err -> handleErr err + Right val -> Right val + where + version = Ledger.eraProtVerLow @ConwayEra \ No newline at end of file diff --git a/testgen-hs/SynthEvalTx.hs b/testgen-hs/SynthEvalTx.hs index d5cddbb..e75d431 100644 --- a/testgen-hs/SynthEvalTx.hs +++ b/testgen-hs/SynthEvalTx.hs @@ -9,7 +9,7 @@ -- | Generates a fake minimal UTxO set for a given transaction, for the purposes -- of running `Cardano.Ledger.Alonzo.Plutus.Evaluate.evalTxExUnits` on the -- transaction. -module SynthEvalTx (eval'Conway, genTxUTxO, stubUTxO) where +module SynthEvalTx (eval'Conway, eval'ConwayDummy, genTxUTxO, stubUTxO) where import Cardano.Crypto.Hash.Class (hashFromBytes) import Cardano.Ledger.Address (Addr (..)) @@ -94,8 +94,20 @@ genTxUTxO = do proof :: Proof.Proof Cardano.Ledger.Api.Era.ConwayEra proof = Proof.Conway -eval'Conway :: (Cardano.Ledger.Core.Tx (Cardano.Ledger.Api.Era.ConwayEra)) -> UTxO Cardano.Ledger.Api.Era.ConwayEra -> J.Value -eval'Conway tx utxo = ogmiosSuccess redeemerReport +eval'Conway :: + (Cardano.Ledger.Core.Tx (Cardano.Ledger.Api.Era.ConwayEra)) -> + UTxO (Cardano.Ledger.Api.Era.ConwayEra) -> + EpochInfo (Either Text) -> + SystemStart -> + J.Value +eval'Conway tx utxo epochInfo systemStart = ogmiosSuccess redeemerReport + where + redeemerReport :: RedeemerReport (Cardano.Ledger.Api.Era.ConwayEra) + redeemerReport = evalTxExUnits protocolParams tx utxo epochInfo systemStart + +-- | Version of eval'Conway that uses dummy epoch info and system start +eval'ConwayDummy :: (Cardano.Ledger.Core.Tx (Cardano.Ledger.Api.Era.ConwayEra)) -> UTxO Cardano.Ledger.Api.Era.ConwayEra -> J.Value +eval'ConwayDummy tx utxo = ogmiosSuccess redeemerReport where redeemerReport :: RedeemerReport (Cardano.Ledger.Api.Era.ConwayEra) redeemerReport = evalTxExUnits protocolParams tx utxo dummyEpochInfo dummySystemStart @@ -184,4 +196,4 @@ ogmiosSuccess report = ["message" J..= show err] -- simple text trace ] | (ptr, res) <- Map.toList report - ] + ] \ No newline at end of file diff --git a/testgen-hs/testgen-hs.cabal b/testgen-hs/testgen-hs.cabal index 06ab9df..7d23b37 100644 --- a/testgen-hs/testgen-hs.cabal +++ b/testgen-hs/testgen-hs.cabal @@ -5,7 +5,7 @@ synopsis: CBOR test case generator and deserializer for cross-checking other implementations -- Version tracks cardano-node, adding a patch segment: -version: 10.4.1.1 +version: 10.4.1.2 executable testgen-hs main-is: Main.hs @@ -20,6 +20,7 @@ executable testgen-hs , cardano-binary , cardano-crypto-class , cardano-ledger-alonzo + , cardano-ledger-binary , cardano-ledger-api , cardano-ledger-byron , cardano-ledger-core From e0c2cc8a95852726be6f4f97116426de756770a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C4=B1z=C4=B1r=20Sefa=20=C4=B0rken?= Date: Thu, 2 Oct 2025 14:50:28 +0300 Subject: [PATCH 02/10] Better error handling for tx evaluate --- testgen-hs/Main.hs | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/testgen-hs/Main.hs b/testgen-hs/Main.hs index 27352e0..d43b0e0 100644 --- a/testgen-hs/Main.hs +++ b/testgen-hs/Main.hs @@ -256,7 +256,10 @@ runEvaluateStream = do line <- B8.getLine case J.eitherDecodeStrict line of Left err -> do - hPutStrLn stderr $ "Expected InitPayload first, but failed to parse line: " ++ err + BL8.putStrLn . J.encode $ PayloadResponse + { rJson = J.Null, + rError = Just . T.pack $ "Expected InitPayload first, but failed to parse line: " ++ err + } runEvaluateStream Right initPayload -> do let ei = convertEpochInfo (slotConfig initPayload) @@ -267,7 +270,10 @@ runEvaluateStream = do case combinedResult of Left err -> do - hPutStrLn stderr $ "Failed to decode initial payload: " ++ err + BL8.putStrLn . J.encode $ PayloadResponse + { rJson = J.Null, + rError = Just . T.pack $ "Failed to decode initial payload " ++ err + } exitFailure Right (pp, ss) -> do BL8.putStrLn . J.encode $ PayloadResponse { rJson = J.object [], rError = Nothing } @@ -281,11 +287,11 @@ runEvaluateStream = do else do line <- B8.getLine case J.eitherDecodeStrict line of - Left errEval -> do - hPutStrLn stderr $ - "Failed to parse line as EvalPayload (\"" - ++ errEval - ++ "\")" + Left err -> do + BL8.putStrLn . J.encode $ PayloadResponse + { rJson = J.Null, + rError = Just . T.pack $ "Failed to parse line as EvalPayload" ++ err + } processLines initPayload pp ss ei Right (evalPayload :: EvalPayload) -> do let evalResult = do @@ -299,7 +305,10 @@ runEvaluateStream = do case evalResult of Left err -> do - hPutStrLn stderr $ "Failed to decode eval payload: " ++ err + BL8.putStrLn . J.encode $ PayloadResponse + { rJson = J.Null, + rError = Just . T.pack $ err + } processLines initPayload pp ss ei Right (tx, utxos) -> do let result = SynthEvalTx.eval'Conway tx utxos ei ss From e28f1e5d78b96b8e41fc4c8f5c63a2a7bb6a2ad8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C4=B1z=C4=B1r=20Sefa=20=C4=B0rken?= Date: Tue, 7 Oct 2025 13:38:36 +0300 Subject: [PATCH 03/10] Made response json fields optional --- testgen-hs/Main.hs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/testgen-hs/Main.hs b/testgen-hs/Main.hs index d43b0e0..a24694c 100644 --- a/testgen-hs/Main.hs +++ b/testgen-hs/Main.hs @@ -93,12 +93,12 @@ data SlotConfig = SlotConfig -- This is used as a generic response to any incoming request. data PayloadResponse = PayloadResponse { rError :: Maybe Text, - rJson :: J.Value + rJson :: Maybe J.Value } deriving (Generic, Show) instance ToJSON PayloadResponse where - toJSON = J.genericToJSON J.defaultOptions {J.fieldLabelModifier = modifier} + toJSON = J.genericToJSON J.defaultOptions {J.fieldLabelModifier = modifier, J.omitNothingFields = True} where modifier "rError" = "error" modifier "rJson" = "json" @@ -257,7 +257,7 @@ runEvaluateStream = do case J.eitherDecodeStrict line of Left err -> do BL8.putStrLn . J.encode $ PayloadResponse - { rJson = J.Null, + { rJson = Nothing, rError = Just . T.pack $ "Expected InitPayload first, but failed to parse line: " ++ err } runEvaluateStream @@ -271,12 +271,12 @@ runEvaluateStream = do case combinedResult of Left err -> do BL8.putStrLn . J.encode $ PayloadResponse - { rJson = J.Null, + { rJson = Nothing, rError = Just . T.pack $ "Failed to decode initial payload " ++ err } exitFailure Right (pp, ss) -> do - BL8.putStrLn . J.encode $ PayloadResponse { rJson = J.object [], rError = Nothing } + BL8.putStrLn . J.encode $ PayloadResponse { rJson = Just (J.object []), rError = Nothing } processLines initPayload pp ss ei where processLines :: InitPayload -> PParams ConwayEra -> SystemStart -> EpochInfo (Either Text) -> IO () @@ -289,7 +289,7 @@ runEvaluateStream = do case J.eitherDecodeStrict line of Left err -> do BL8.putStrLn . J.encode $ PayloadResponse - { rJson = J.Null, + { rJson = Nothing, rError = Just . T.pack $ "Failed to parse line as EvalPayload" ++ err } processLines initPayload pp ss ei @@ -306,13 +306,13 @@ runEvaluateStream = do case evalResult of Left err -> do BL8.putStrLn . J.encode $ PayloadResponse - { rJson = J.Null, + { rJson = Nothing, rError = Just . T.pack $ err } processLines initPayload pp ss ei Right (tx, utxos) -> do let result = SynthEvalTx.eval'Conway tx utxos ei ss - BL8.putStrLn . J.encode $ PayloadResponse { rJson = result, rError = Nothing } + BL8.putStrLn . J.encode $ PayloadResponse { rJson = Just ( result ), rError = Nothing } processLines initPayload pp ss ei -- | Creates an EpochInfo from the given SlotConfig From 9bb1e9a60e9e5a26dc54308b5c66883573d69faf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C4=B1z=C4=B1r=20Sefa=20=C4=B0rken?= Date: Mon, 15 Dec 2025 11:53:00 +0300 Subject: [PATCH 04/10] Implement ogmious errors --- testgen-hs/CLI.hs | 2 + testgen-hs/Encoder.hs | 445 ++++++++++++++++++++++++++++++++++++ testgen-hs/Evaluation.hs | 182 +++++++++++++++ testgen-hs/Generators.hs | 6 + testgen-hs/Main.hs | 60 ++--- testgen-hs/Response.hs | 46 ++++ testgen-hs/testgen-hs.cabal | 12 +- 7 files changed, 725 insertions(+), 28 deletions(-) create mode 100644 testgen-hs/Encoder.hs create mode 100644 testgen-hs/Evaluation.hs create mode 100644 testgen-hs/Response.hs diff --git a/testgen-hs/CLI.hs b/testgen-hs/CLI.hs index a4cf6f7..f65345f 100644 --- a/testgen-hs/CLI.hs +++ b/testgen-hs/CLI.hs @@ -38,6 +38,7 @@ data TypeCommand | ApplyTxErr'Alonzo | ApplyTxErr'Babbage | ApplyTxErr'Conway + | TxScriptFailure'Conway deriving (Show) parse :: IO Command @@ -155,6 +156,7 @@ typeCommandParser = <> mkTypeCommand ApplyTxErr'Alonzo <> mkTypeCommand ApplyTxErr'Babbage <> mkTypeCommand ApplyTxErr'Conway + <> mkTypeCommand TxScriptFailure'Conway <> mkTypeCommand GHCInteger <> mkTypeCommand DataText <> mkTypeCommand ExampleADT diff --git a/testgen-hs/Encoder.hs b/testgen-hs/Encoder.hs new file mode 100644 index 0000000..bba4c4d --- /dev/null +++ b/testgen-hs/Encoder.hs @@ -0,0 +1,445 @@ +{-# OPTIONS_GHC -Wno-orphans #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE ViewPatterns #-} + +module Encoder + ( serializeScriptPurposeIndex + , serializeTransactionScriptFailure + , serializeTxIn + , serializeTxId + , serializeHash + , serializeFoldable + , serializeDecoderError + , ogmiosError + , ogmiosSuccess + ) where + +import qualified Cardano.Ledger.Conway.Scripts as C +import qualified Cardano.Ledger.Conway.TxInfo as C + +import qualified Cardano.Ledger.Babbage.TxInfo as Ba + + +import qualified Cardano.Ledger.Alonzo.Core as Al hiding + ( TranslationError + ) +import qualified Cardano.Ledger.Alonzo.Plutus.TxInfo as Al +import qualified Cardano.Ledger.Alonzo.Scripts as Al + +import Cardano.Ledger.Api + ( AsIx (..) + , PlutusPurpose + ) + +import Cardano.Ledger.Plutus + ( TxOutSource (..) + ) + +import qualified Cardano.Ledger.Api as Ledger +import qualified Cardano.Ledger.TxIn as Ledger +import qualified Cardano.Ledger.Core as Ledger +import qualified Cardano.Ledger.Plutus.Language as Ledger +import qualified Cardano.Ledger.BaseTypes as LedgerBase +import qualified Cardano.Ledger.Binary.Decoding as Binary + +import qualified Codec.CBOR.Read as Cbor + +import qualified Data.Map as Map + +import qualified Cardano.Ledger.Hashes as Hashes +import qualified Cardano.Crypto.Hash.Class as CC + +import qualified Data.Aeson as A +import qualified Data.Aeson.Encoding as J +import Data.Aeson.Encoding (Encoding) +import Data.Foldable (toList) +import Data.Int (Int64) +import Data.Text (Text) +import qualified Data.Text as T +import qualified Data.Text.Encoding as TE + +import qualified Data.ByteString.Base16 as B16 + +import qualified Data.ByteString.Short as SBS + +import Data.SatInt + ( fromSatInt + ) +import qualified Data.ByteString as BS + +import qualified PlutusLedgerApi.Common as P + + +import Prettyprinter (pretty) + +-------------------------------------------------------------------------------- +-- Generic helpers +-------------------------------------------------------------------------------- + +-- | Encode any Foldable as a JSON array using an element encoder. +serializeFoldable + :: Foldable f + => (a -> Encoding) + -> f a + -> Encoding +serializeFoldable enc = + J.list enc . toList + +-------------------------------------------------------------------------------- +-- Hash / TxId / TxIn +-------------------------------------------------------------------------------- + +-- | Serialize a SafeHash as lower-case hex JSON string. +serializeHash + :: Hashes.SafeHash crypto + -> Encoding +serializeHash safeH = + let CC.UnsafeHash short = Hashes.extractHash safeH + bs = SBS.fromShort short + hex = B16.encode bs + in J.text (TE.decodeUtf8 hex) + +serializDataHash + :: Al.DataHash + -> Encoding +serializDataHash = + serializeHash + + +serializeTxId + :: Ledger.TxId + -> Encoding +serializeTxId txId = + J.pairs $ + J.pair "id" (serializeHash (Ledger.unTxId txId)) + +serializeTxIn + :: Ledger.TxIn + -> Encoding +serializeTxIn (Ledger.TxIn txId ix) = + J.pairs $ + J.pair "transaction" (serializeTxId txId) + <> J.pair "index" (A.toEncoding ix) + +-------------------------------------------------------------------------------- +-- Script purpose index +-------------------------------------------------------------------------------- + +serializeScriptPurposeIndex + :: forall era. () + => C.ConwayPlutusPurpose AsIx era + -> Encoding +serializeScriptPurposeIndex = \case + C.ConwaySpending ix -> + mk ix "spend" + + C.ConwayMinting ix -> + mk ix "mint" + + C.ConwayCertifying (AsIx ix) -> + mk ix "publish" + + C.ConwayRewarding ix -> + mk ix "withdraw" + + C.ConwayVoting (AsIx ix) -> + mk ix "vote" + + C.ConwayProposing (AsIx ix) -> + mk ix "propose" + where + mk ix purpose = + J.pairs $ + J.pair "index" (A.toEncoding ix) + <> J.pair "purpose" (J.text purpose) + + + +serializeExBudget + :: P.ExBudget + -> Encoding +serializeExBudget budget = + J.pairs $ + J.pair "memory" (J.integer (fromSatInt mem)) + <> J.pair "cpu" (J.integer (fromSatInt cpu)) + where + P.ExMemory mem = P.exBudgetMemory budget + P.ExCPU cpu = P.exBudgetCPU budget + + +serializeLanguage + :: Ledger.Language + -> Encoding +serializeLanguage = \case + Ledger.PlutusV1 -> "plutus:v1" + Ledger.PlutusV2 -> "plutus:v2" + Ledger.PlutusV3 -> "plutus:v3" + +-- | Ogmios "budget" object from 'ExUnits'. +serializeExUnits :: Al.ExUnits -> Encoding +serializeExUnits (Al.ExUnits mem cpu) = + J.pairs $ + J.pair "memory" (J.integer (fromIntegral mem)) + <> J.pair "cpu" (J.integer (fromIntegral cpu)) + +txInToText + :: Ledger.TxIn + -> Text +txInToText (Ledger.TxIn txid (LedgerBase.TxIx ix)) = + txIdToText txid <> "#" <> T.pack (show ix) + +txIdToText :: Ledger.TxId -> Text +txIdToText (Ledger.TxId (Ledger.originalBytes -> bytes)) = + TE.decodeUtf8 (B16.encode bytes) + +redeemerPtrToText :: forall era. (Ledger.ConwayEraScript era) => PlutusPurpose AsIx era -> Text +redeemerPtrToText = \case + Ledger.SpendingPurpose (AsIx ix) -> "spend:" <> showT ix + Ledger.CertifyingPurpose (AsIx ix) -> "publish:" <> showT ix + Ledger.MintingPurpose (AsIx ix) -> "mint:" <> showT ix + Ledger.RewardingPurpose (AsIx ix) -> "withdraw:" <> showT ix + Ledger.ProposingPurpose (AsIx ix) -> "propose:" <> showT ix + Ledger.VotingPurpose (AsIx ix) -> "vote:" <> showT ix + _ -> error "unreachable: unknown PlutusPurpose" -- matches _are_ exhaustive, but it’s not provable statically + where + showT = T.pack . show -- Word32 → Text + + + +-------------------------------------------------------------------------------- +-- Ogmios-style error envelope +-------------------------------------------------------------------------------- + +ogmiosError + :: Int64 + -> Text + -> Encoding -- ^ payload / "data" + -> Encoding +ogmiosError code msg payload = + J.pairs $ + J.pair "code" (A.toEncoding code) + <> J.pair "message" (J.text msg) + <> J.pair "data" payload + +-- | Build the JSON-RPC success envelope that Ogmios returns when every +-- redeemer succeeds. +ogmiosSuccess :: + Map.Map + (PlutusPurpose AsIx Ledger.ConwayEra) + (Either (Ledger.TransactionScriptFailure Ledger.ConwayEra) Al.ExUnits) + -> J.Encoding +ogmiosSuccess report = + J.list + (\(ptr, res) -> + case res of + Right exu -> + J.pairs $ + J.pair "validator" (J.text $ redeemerPtrToText ptr) + <> J.pair "budget" (serializeExUnits exu) + + Left err -> + J.pairs $ + J.pair "validator" (J.text $ redeemerPtrToText ptr) + <> J.pair "error" ( + J.pairs $ + J.pair "message" (serializeTransactionScriptFailure err) + ) + ) + (Map.toList report) + +--makeScriptExecFailure + -- :: Encoding + -- -> Encoding +--makeScriptExecFailure = ogmiosError + -- 3010 + -- "Some scripts of the transactions terminated with error(s)." + + +-------------------------------------------------------------------------------- +-- ContextError +-------------------------------------------------------------------------------- + +serializeContextError + :: ( PlutusPurpose AsIx era ~ C.ConwayPlutusPurpose AsIx era + ) + => C.ConwayContextError era + -> Encoding +serializeContextError err = J.text $ case err of + C.CertificateNotSupported{} -> + "A certificate in the transaction isn't supported in neither plutus:v1 nor plutus:v2. Use plutus:v3 or higher." + C.PlutusPurposeNotSupported{} -> + "A script purpose in the transaction isn't supported in neither plutus:v1 nor plutus:v2. Use plutus:v3 or higher." + C.CurrentTreasuryFieldNotSupported{} -> + "Unsupported field in transaction: 'treasury'. Use plutus:v3 or higher, or remove this field." + C.VotingProceduresFieldNotSupported{} -> + "Unsupported field in transaction: 'votes'. Use plutus:v3 or higher, or remove this field." + C.ProposalProceduresFieldNotSupported{} -> + "Unsupported field in transaction: 'proposals'. Use plutus:v3 or higher, or remove this field." + C.TreasuryDonationFieldNotSupported{} -> + "Unsupported field in transaction: 'donation'. Use plutus:v3 or higher, or remove this field." + C.BabbageContextError (Ba.ByronTxOutInContext TxOutFromInput{}) -> + "Found inputs locked by a (legacy) Byron/Bootstrap address. Don't use those." + C.BabbageContextError (Ba.ByronTxOutInContext TxOutFromOutput{}) -> + "Found outputs to a (legacy) Byron/Bootstrap address. Don't use those." + C.BabbageContextError (Ba.InlineDatumsNotSupported{}) -> + "Inline datums not supported in plutus:v1. Use plutus:v2 or higher." + C.BabbageContextError (Ba.ReferenceScriptsNotSupported{}) -> + "Reference scripts not supported in plutus:v1. Use plutus:v2 or higher." + C.BabbageContextError (Ba.ReferenceInputsNotSupported{}) -> + "Reference inputs not supported in plutus:v1. Use plutus:v2 or higher." + C.BabbageContextError (Ba.RedeemerPointerPointsToNothing purpose) -> + let (title, ptr) = + case purpose of + C.ConwaySpending (AsIx ix) -> ("spending input", ix) + C.ConwayMinting (AsIx ix) -> ("minting policy", ix) + C.ConwayCertifying (AsIx ix) -> ("publishing certificate", ix) + C.ConwayRewarding (AsIx ix) -> ("withdrawing from account", ix) + C.ConwayVoting (AsIx ix) -> ("voting as voter", ix) + C.ConwayProposing (AsIx ix) -> ("proposing governance proposal", ix) + in "Couldn't find corresponding redeemer for " <> title <> " #" <> T.pack (show ptr) <> ". Verify your transaction's construction." + C.BabbageContextError (Ba.AlonzoContextError (Al.TimeTranslationPastHorizon e)) -> + "Uncomputable slot arithmetic; transaction's validity bounds go beyond the foreseeable end of the current era: " <> e + C.BabbageContextError (Ba.AlonzoContextError (Al.TranslationLogicMissingInput i)) -> + "Unknown transaction input (missing from UTxO set): " <> txInToText i +-------------------------------------------------------------------------------- +-- TransactionScriptFailure +-------------------------------------------------------------------------------- + +serializeTransactionScriptFailure :: + Ledger.TransactionScriptFailure Ledger.ConwayEra -> + Encoding +serializeTransactionScriptFailure = \case + Ledger.RedeemerPointsToUnknownScriptHash reedemers -> + ogmiosError + 3110 + "Extraneous (non-required) redeemers found in the transaction." + ( J.pairs $ + J.pair "extraneousRedeemers" + (serializeFoldable serializeScriptPurposeIndex [reedemers]) + ) + + Ledger.MissingScript purpose _ -> + ogmiosError + 3102 + "An associated script witness is missing." + ( J.pairs $ + J.pair "missingScripts" + (serializeFoldable serializeScriptPurposeIndex [purpose]) + ) + + Ledger.MissingDatum datum -> + ogmiosError + 3111 + "Transaction failed because some Plutus scripts are missing their associated datums." + ( J.pairs $ + J.pair "missingDatums" + (serializeFoldable serializDataHash [datum]) + ) + + Ledger.ValidationFailure _exUnits evalErr traces _ctx -> + ogmiosError + 3012 + "Some of the scripts failed to evaluate to a positive outcome." + ( J.pairs $ + J.pair "validationError" + (J.text (T.pack (show (pretty evalErr)))) + <> J.pair "traces" + (serializeFoldable A.toEncoding traces) + ) + Ledger.UnknownTxIn txIn -> + ogmiosError + 3117 + "The transaction contains unknown UTxO references as inputs." + (J.pairs $ + J.pair "unknownOutputReferences" (serializeFoldable serializeTxIn [txIn]) + ) + + Ledger.InvalidTxIn txIn -> + ogmiosError + 3013 + "A redeemer points to an input that isn't locked by a Plutus script." + ( J.pairs $ + J.pair "unsuitableOutputReference" (serializeTxIn txIn) + ) + + Ledger.IncompatibleBudget budget -> + ogmiosError + 3161 + "The transaction ran out of execution budget!" + (J.pairs $ + J.pair "budgetUsed" (serializeExBudget budget) + ) + + Ledger.NoCostModelInLedgerState lang -> + ogmiosError + 3115 + "It seems like the transaction is using a Plutus version for which there's no available cost model yet." + (J.pairs $ + J.pair "missingCostModels" (serializeFoldable serializeLanguage [lang]) + ) + + Ledger.ContextError ctxErr -> + ogmiosError + 3004 + "Unable to create the evaluation context from the given transaction." + (J.pairs $ + J.pair "reason" (serializeContextError ctxErr) + ) + + + +serializeDecoderError :: Int -> Binary.DecoderError -> Text +serializeDecoderError size = extractId . reduceNoise . \case + Binary.DecoderErrorCanonicityViolation lbl -> + "couldn't decode due to internal constraint violations on '" <> lbl <> "': \ + \ found CBOR that isn't canonical when I expected it to be." + Binary.DecoderErrorCustom lbl hint -> + "couldn't decode due to internal constraint violations on '" <> lbl <> "': " <> hint + Binary.DecoderErrorDeserialiseFailure lbl (Cbor.DeserialiseFailure offset hint) | offset >= fromIntegral size -> + "invalid or incomplete value of type '" <> lbl <> "': " <> T.pack (show hint) + Binary.DecoderErrorDeserialiseFailure lbl (Cbor.DeserialiseFailure offset hint) -> + "invalid CBOR found at offset [" <> T.pack (show offset) <> "] while decoding a value of type '" <> lbl <> "': " + <> T.pack (show hint) + Binary.DecoderErrorEmptyList{} -> + "couldn't decode due to internal constraint violations on a non-empty list: \ + \must not be empty" + Binary.DecoderErrorLeftover lbl bytes -> + "unexpected " <> T.pack (show (BS.length bytes)) <> " bytes found left after \ + \successfully deserialising a/an '" <> lbl <> "'" + Binary.DecoderErrorSizeMismatch lbl expected actual | expected >= actual -> + T.pack (show (expected - actual)) <> " missing element(s) in a \ + \data-structure of type '" <> lbl <> "'" + Binary.DecoderErrorSizeMismatch lbl expected actual -> + T.pack (show (actual - expected)) <> " extra element(s) in a \ + \data-structure of type '" <> lbl <> "'" + Binary.DecoderErrorUnknownTag lbl tag -> + "unknown binary tag (" <> T.pack (show tag) <> ") when decoding a value of type '" <> lbl <> "'\ + \; which is probably because I am trying to decode something else than what \ + \I encountered." + Binary.DecoderErrorVoid -> + "impossible: attempted to decode void. Please open an issue." + where + extractId = id + reduceNoise + = T.replace "\n" " " + . T.replace "Error: " "" + . T.replace "Record" "Object / Array" + . T.replace "Record RecD" "Object / Array" + . T.replace " ShelleyEra" "" + . T.replace " AllegraEra" "" + . T.replace " MaryEra" "" + . T.replace " AlonzoEra" "" + . T.replace " BabbageEra" "" + . T.replace " ConwayEra" "" + . T.replace "value of type ConwayTxBodyRaw" "transaction body" + . T.replace "value of type BabbageTxBodyRaw" "transaction body" + . T.replace "value of type AlonzoTxBodyRaw" "transaction body" + . T.replace "value of type AllegraTxBodyRaw ()" "transaction body" + . T.replace "value of type AllegraTxBodyRaw MultiAsset" "transaction body" + . T.replace "value of type ShelleyTxBodyRaw" "transaction body" + . T.replace "atbr" "" + . T.replace "stbr" "" + . T.replace "btbr" "" + . T.replace "ctbr" "" diff --git a/testgen-hs/Evaluation.hs b/testgen-hs/Evaluation.hs new file mode 100644 index 0000000..b1ebd49 --- /dev/null +++ b/testgen-hs/Evaluation.hs @@ -0,0 +1,182 @@ +{-# OPTIONS_GHC -Wno-orphans #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE PatternSynonyms #-} + +module Evaluation + ( WrappedTransactionScriptFailure (..), + writeJson, + eval'Conway + ) +where + +import Cardano.Ledger.Api (ConwayEra, PParams, TransactionScriptFailure) +import qualified Cardano.Ledger.Api as Ledger +import CLI (GenSize (..), NumCases (..), Seed (..)) +import Data.Aeson (ToJSON) +import qualified Data.Aeson as J +import qualified Data.ByteString.Lazy.Char8 as BL8 +import Data.Proxy (Proxy) +import qualified Data.Map as Map +import Response (PayloadResponse (..)) +import Cardano.Api.Internal.Orphans () +import PlutusCore.Evaluation.Machine.ExBudget (ExBudget (..)) +import PlutusCore.Evaluation.Machine.ExMemory (ExCPU (..), ExMemory (..)) +import qualified Test.QuickCheck as QC +import qualified Test.QuickCheck.Gen as QGen +import qualified Test.QuickCheck.Random as QCRandom +import Test.Consensus.Cardano.Generators () +import Cardano.Ledger.Api.Tx (BabbageEraTxBody, PlutusPurpose, RedeemerReport, Tx) +import Cardano.Slotting.EpochInfo (EpochInfo, fixedEpochInfo) +import Cardano.Slotting.Slot (EpochSize (..), SlotNo (..)) +import Cardano.Slotting.Time (SystemStart (..)) +import Data.Text (Text) +import Cardano.Ledger.Api.UTxO (UTxO (..)) +import Cardano.Ledger.Alonzo.Plutus.Evaluate (evalTxExUnits) +import Cardano.Ledger.Api.Scripts + ( ConwayEraScript, + pattern CertifyingPurpose, + pattern MintingPurpose, + pattern ProposingPurpose, + pattern RewardingPurpose, + pattern SpendingPurpose, + pattern VotingPurpose, + ) +import Cardano.Ledger.Alonzo.Scripts + ( AsIx (..), + ExUnits (..), + ) +import qualified Cardano.Ledger.Conway.Scripts as C + + +import qualified Data.Aeson.Encoding as AesonEncoding +import Encoder(serializeTransactionScriptFailure, ogmiosSuccess) +import Data.List (sortOn) +import qualified Data.List.NonEmpty as NE +import Data.Maybe (fromMaybe) + + +newtype WrappedTransactionScriptFailure era = WrappedTransactionScriptFailure + { unWrappedTransactionScriptFailure :: + TransactionScriptFailure era + } + +instance ToJSON (WrappedTransactionScriptFailure ConwayEra) where + toEncoding (WrappedTransactionScriptFailure x) = + serializeTransactionScriptFailure x + toJSON wrapped = + case J.decode (AesonEncoding.encodingToLazyByteString (J.toEncoding wrapped)) :: Maybe J.Value of + Just v -> v + Nothing -> + error "serializeTransactionScriptFailure produced invalid JSON" + + +-- | Conway-normalized script purpose index. +-- All inputs are upgraded to Conway before comparison. +newtype ConwayPurposeIx = + ConwayPurposeIx (PlutusPurpose AsIx ConwayEra) + +-- | Upgrade any compatible era to Conway. +toConwayPurposeIx + :: UpgradePlutusPurpose AsIx era ConwayEra + => PlutusPurpose AsIx era + -> ConwayPurposeIx +toConwayPurposeIx = + ConwayPurposeIx . C.upgradePlutusPurposeAsIx + + +-- | Extract the script index (Conway only). +purposeIx :: PlutusPurpose AsIx ConwayEra -> Word32 +purposeIx = \case + Ledger.ConwaySpending ix -> ix + Ledger.ConwayMinting ix -> ix + Ledger.ConwayCertifying (AsIx ix) -> ix + Ledger.ConwayRewarding ix -> ix + Ledger.ConwayVoting (AsIx ix) -> ix + +instance Eq ConwayPurposeIx where + (==) = (==) `on` (\(ConwayPurposeIx p) -> purposeIx p) + +instance Ord ConwayPurposeIx where + compare = compare `on` (\(ConwayPurposeIx p) -> purposeIx p) + + +instance QC.Arbitrary (Ledger.TransactionScriptFailure Ledger.ConwayEra) where + arbitrary = + QC.oneof + [ Ledger.RedeemerPointsToUnknownScriptHash <$> QC.arbitrary, + (`Ledger.MissingScript` Map.empty) <$> QC.arbitrary, + Ledger.MissingDatum <$> QC.arbitrary, + Ledger.UnknownTxIn <$> QC.arbitrary, + Ledger.InvalidTxIn <$> QC.arbitrary, + pure $ Ledger.IncompatibleBudget (ExBudget (ExCPU 999) (ExMemory 888)), + Ledger.NoCostModelInLedgerState <$> QC.arbitrary, + Ledger.ContextError <$> QC.arbitrary + ] + +writeJson :: + Proxy ConwayEra -> + Seed -> + GenSize -> + NumCases -> + IO () +writeJson _ (Seed seed) (GenSize size) (NumCases numCases) = do + let gen :: QGen.Gen [WrappedTransactionScriptFailure ConwayEra] + gen = + QC.vectorOf numCases $ + WrappedTransactionScriptFailure <$> (QC.arbitrary :: QC.Gen (TransactionScriptFailure ConwayEra)) + xs :: [WrappedTransactionScriptFailure ConwayEra] + xs = QGen.unGen gen (QCRandom.mkQCGen seed) size + BL8.putStrLn (J.encode xs) + +eval'Conway :: + PParams ConwayEra -> + Tx ConwayEra -> + UTxO ConwayEra -> + EpochInfo (Either Text) -> + SystemStart -> + J.Value +eval'Conway pparams tx utxo epochInfo systemStart = + case J.decode (AesonEncoding.encodingToLazyByteString (ogmiosSuccess redeemerReport)) of + Just v -> v + Nothing -> error "ogmiosSuccess produced invalid JSON" + + where + redeemerReport :: RedeemerReport ConwayEra + redeemerReport = evalTxExUnits pparams tx utxo epochInfo systemStart + + groupReports :: PlutusPurpose AsIx era + -> Either (Ledger.TransactionScriptFailure era) ExUnits + -> (Map.Map (PlutusPurpose AsIx era) [Ledger.TransactionScriptFailure era ], Map.Map (PlutusPurpose AsIx era) ExUnits) + -> (Map.Map (PlutusPurpose AsIx era) [Ledger.TransactionScriptFailure era], Map.Map (PlutusPurpose AsIx era) ExUnits) + groupReports purpose result (failures, successes) = + case result of + Left scriptFail -> (Map.unionWith (++) (Map.singleton purpose [scriptFail]) failures, successes) + Right exUnits -> (failures, Map.singleton purpose exUnits <> successes) + +-- | Return the most relevant script failure from a list of errors. +pickScriptFailure :: + [Ledger.TransactionScriptFailure era] + -> Ledger.TransactionScriptFailure era +pickScriptFailure = + NE.head + . fromMaybe (error "Empty list of script failures from the ledger!?") + . NE.nonEmpty + . sortOn scriptFailurePriority + where + scriptFailurePriority :: + Ledger.TransactionScriptFailure era + -> Word + scriptFailurePriority = \case + Ledger.UnknownTxIn{} -> 0 + Ledger.MissingScript{} -> 0 + Ledger.RedeemerPointsToUnknownScriptHash{} -> 1 + Ledger.NoCostModelInLedgerState{} -> 1 + Ledger.InvalidTxIn{} -> 2 + Ledger.MissingDatum{} -> 3 + Ledger.ContextError{} -> 4 + Ledger.ValidationFailure{} -> 5 + Ledger.IncompatibleBudget{} -> 999 diff --git a/testgen-hs/Generators.hs b/testgen-hs/Generators.hs index 01465e0..7065dd4 100644 --- a/testgen-hs/Generators.hs +++ b/testgen-hs/Generators.hs @@ -1,4 +1,5 @@ {-# OPTIONS_GHC -fno-warn-orphans #-} +{-# LANGUAGE FlexibleInstances #-} module Generators where @@ -11,6 +12,7 @@ import qualified Cardano.Binary import qualified Cardano.Chain.Slotting as CCS import qualified Cardano.Ledger.Api.Era import qualified Cardano.Ledger.Api.UTxO +import qualified Cardano.Ledger.Api as Ledger import qualified Cardano.Ledger.Core import qualified Cardano.TxSubmit.Types as CTT import qualified Codec.CBOR.Encoding as C @@ -39,6 +41,7 @@ import qualified SynthEvalTx import Test.Consensus.Cardano.Generators () import Test.QuickCheck (Arbitrary) import qualified Test.QuickCheck as QC +import Evaluation () -- | We define our own type class, to be able to include multiple complex -- encoders for our `newtype` wrappers under a single interface. @@ -176,6 +179,9 @@ newtype Tx'Conway instance Arbitrary Tx'Conway where arbitrary = Tx'Conway <$> SynthEvalTx.genTxUTxO +generateFailures :: IO [Ledger.TransactionScriptFailure Ledger.ConwayEra] +generateFailures = QC.generate $ QC.vectorOf 10 QC.arbitrary + instance OurCBOR Tx'Conway where unwrappedType (Tx'Conway (tx, _utxo)) = show . typeOf $ tx ourToCBOR (Tx'Conway (tx, _utxo)) = Cardano.Binary.toCBOR tx diff --git a/testgen-hs/Main.hs b/testgen-hs/Main.hs index a24694c..b5b04cb 100644 --- a/testgen-hs/Main.hs +++ b/testgen-hs/Main.hs @@ -1,4 +1,7 @@ {-# LANGUAGE TypeApplications #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE DeriveGeneric #-} + module Main where @@ -22,23 +25,25 @@ import qualified Data.ByteString.Base16 as B16 import qualified Data.ByteString.Char8 as B8 import qualified Data.ByteString.Lazy as BL import qualified Data.ByteString.Lazy.Char8 as BL8 +import qualified Data.ByteString as BS import Data.Foldable (foldl') import Data.Proxy (Proxy (..)) import Data.Text (Text) import qualified Data.Text as T import qualified Data.Text.Encoding as T -import qualified Data.Text.Encoding.Error as T +import qualified Data.Text.Encoding.Error as TEE import Data.Time (NominalDiffTime) import Data.Time.Clock.POSIX (getPOSIXTime) import Data.Word (Word16, Word64) import qualified Deserialize as D import GHC.Generics (Generic) import qualified Generators as G -import qualified SynthEvalTx import System.Exit (exitFailure) import System.IO (hPutStrLn, stderr) import qualified System.IO as SIO import qualified System.Random +import Evaluation (writeJson, eval'Conway) +import Encoder (serializeDecoderError) import Test.QuickCheck (Arbitrary) import qualified Test.QuickCheck as QC import qualified Test.QuickCheck.Gen as QC @@ -138,6 +143,7 @@ runGenerate (CLI.GenerateOptions maybeSeed genSize numCases command) = do CLI.ApplyTxErr'Alonzo -> writeRandom @G.ApplyTxErr'Alonzo Proxy CLI.ApplyTxErr'Babbage -> writeRandom @G.ApplyTxErr'Babbage Proxy CLI.ApplyTxErr'Conway -> writeRandom @G.ApplyTxErr'Conway Proxy + CLI.TxScriptFailure'Conway -> writeJson Proxy CLI.DataText -> writeRandom @Text Proxy CLI.GHCInteger -> writeRandom @Integer Proxy CLI.ExampleADT -> writeRandom @G.ExampleADT Proxy @@ -189,7 +195,7 @@ mkTestCase :: forall a. (Show a, G.OurCBOR a) => a -> TestCase a mkTestCase a = TestCase { cbor = - T.decodeUtf8With T.lenientDecode + T.decodeUtf8With TEE.lenientDecode . B16.encode . BL.toStrict . C.toLazyByteString @@ -220,7 +226,7 @@ cborToTestCase cbor' = let (typeTag', haskellRepr') = G.hfcEnvelopeShowInner a in TestCase { cbor = - T.decodeUtf8With T.lenientDecode + T.decodeUtf8With TEE.lenientDecode . B16.encode $ cbor', haskellRepr = T.pack haskellRepr', @@ -285,34 +291,33 @@ runEvaluateStream = do if eof then pure () else do - line <- B8.getLine + line <- B8.getLine -- This line is expected to be an EvalPayload case J.eitherDecodeStrict line of Left err -> do - BL8.putStrLn . J.encode $ PayloadResponse - { rJson = Nothing, - rError = Just . T.pack $ "Failed to parse line as EvalPayload" ++ err - } + let response = case J.eitherDecodeStrict' (B8.pack err) of + Left decodeErr -> PayloadResponse { rJson = Nothing, rError = Just (T.pack $ "Failed to decode error as JSON: " ++ decodeErr ++ ". Original error: " ++ err) } + Right jsonVal -> PayloadResponse { rJson = Nothing, rError = Just jsonVal } + BL8.putStrLn . J.encode $ response processLines initPayload pp ss ei Right (evalPayload :: EvalPayload) -> do - let evalResult = do - let txBytes = either (Left . show) Right $ B16.decode (T.encodeUtf8 (tx evalPayload)) - decodedTx <- either - Left - (decodeCborWith "Transaction" (Left . show) (Binary.decCBOR @(Cardano.Ledger.Core.Tx ConwayEra))) + let decodedValues = do + txBytes <- first (\e -> PayloadResponse (Just (T.pack e)) Nothing) $ B16.decode (T.encodeUtf8 (tx evalPayload)) + decodedTx <- + decodeCborWith + "Transaction" + (Left . (\e -> PayloadResponse (Just (serializeDecoderError (BS.length txBytes) e)) Nothing)) + (Binary.decCBOR @(Cardano.Ledger.Core.Tx ConwayEra)) txBytes - utxos <- decodeFromHex (utxos evalPayload) + utxos <- first (\e -> PayloadResponse (Just (T.pack e)) Nothing) $ decodeFromHex (utxos evalPayload) return (decodedTx, utxos) - case evalResult of - Left err -> do - BL8.putStrLn . J.encode $ PayloadResponse - { rJson = Nothing, - rError = Just . T.pack $ err - } + case decodedValues of + Left response -> do + BL8.putStrLn . J.encode $ response processLines initPayload pp ss ei Right (tx, utxos) -> do - let result = SynthEvalTx.eval'Conway tx utxos ei ss - BL8.putStrLn . J.encode $ PayloadResponse { rJson = Just ( result ), rError = Nothing } + let result = eval'Conway pp tx utxos ei ss + BL8.putStrLn . J.encode $ PayloadResponse { rJson = Just result, rError = Nothing } processLines initPayload pp ss ei -- | Creates an EpochInfo from the given SlotConfig @@ -333,13 +338,14 @@ decodeFromHex hexText = do -- Run a CBOR decoder for data in Conway era decodeCborWith :: Text -- ^ Label for error reporting - -> (Binary.DecoderError -> Either String a) -- ^ Error handler + -> (Binary.DecoderError -> Either e a) -- ^ Error handler -> (forall s. Binary.Decoder s a) -- ^ CBOR decoder -> ByteString -- ^ Input bytes - -> Either String a + -> Either e a decodeCborWith lbl handleErr decoder bytes = case Binary.decodeFullDecoder version lbl decoder (BL.fromStrict bytes) of - Left err -> handleErr err + Left cborErr -> handleErr cborErr Right val -> Right val where - version = Ledger.eraProtVerLow @ConwayEra \ No newline at end of file + version = Ledger.eraProtVerLow @ConwayEra + diff --git a/testgen-hs/Response.hs b/testgen-hs/Response.hs new file mode 100644 index 0000000..c961bbb --- /dev/null +++ b/testgen-hs/Response.hs @@ -0,0 +1,46 @@ +module Response + ( PayloadResponse (..), + OgmiosError (..) + ) where + +import Data.Aeson (ToJSON) +import qualified Data.Aeson as J +import Data.Int (Int64) +import Data.Text (Text) +import GHC.Generics (Generic) + +-- | Generic response envelope used across endpoints. +data PayloadResponse = PayloadResponse + { rError :: Maybe Text, + rJson :: Maybe J.Value + } + deriving (Generic, Show) + +instance ToJSON PayloadResponse where + toJSON = + J.genericToJSON + J.defaultOptions + { J.fieldLabelModifier = modifier, + J.omitNothingFields = True + } + where + modifier "rError" = "error" + modifier "rJson" = "json" + modifier s = s + +data OgmiosError = OgmiosError + { code :: Int64, + message :: Text, + value :: J.Value + } + deriving (Generic, Show) + +instance ToJSON OgmiosError where + toJSON = + J.genericToJSON + J.defaultOptions + { J.fieldLabelModifier = \case + "value" -> "data" + s -> s + } + diff --git a/testgen-hs/testgen-hs.cabal b/testgen-hs/testgen-hs.cabal index 7d23b37..f9d6278 100644 --- a/testgen-hs/testgen-hs.cabal +++ b/testgen-hs/testgen-hs.cabal @@ -18,15 +18,21 @@ executable testgen-hs , bytestring , cardano-api , cardano-binary + --, cardano-crypto , cardano-crypto-class + --, cardano-crypto-wrapper , cardano-ledger-alonzo - , cardano-ledger-binary , cardano-ledger-api + , cardano-ledger-binary + , cardano-ledger-babbage , cardano-ledger-byron + , cardano-ledger-conway , cardano-ledger-core , cardano-ledger-test , cardano-slotting , cardano-submit-api + , plutus-core + , plutus-ledger-api , cborg , containers , data-default @@ -43,6 +49,7 @@ executable testgen-hs , serialise , text , time + , prettyprinter other-modules: CLI @@ -50,6 +57,9 @@ executable testgen-hs Generators Paths_testgen_hs SynthEvalTx + Evaluation + Response + Encoder default-extensions: DeriveAnyClass From cd4c7e789a3467d5486a9c7df12996078640306f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C4=B1z=C4=B1r=20Sefa=20=C4=B0rken?= Date: Tue, 6 Jan 2026 17:58:39 +0300 Subject: [PATCH 05/10] Implement error filtering --- testgen-hs/Evaluation.hs | 77 ++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 51 deletions(-) diff --git a/testgen-hs/Evaluation.hs b/testgen-hs/Evaluation.hs index b1ebd49..e95fa80 100644 --- a/testgen-hs/Evaluation.hs +++ b/testgen-hs/Evaluation.hs @@ -21,7 +21,6 @@ import qualified Data.Aeson as J import qualified Data.ByteString.Lazy.Char8 as BL8 import Data.Proxy (Proxy) import qualified Data.Map as Map -import Response (PayloadResponse (..)) import Cardano.Api.Internal.Orphans () import PlutusCore.Evaluation.Machine.ExBudget (ExBudget (..)) import PlutusCore.Evaluation.Machine.ExMemory (ExCPU (..), ExMemory (..)) @@ -29,28 +28,15 @@ import qualified Test.QuickCheck as QC import qualified Test.QuickCheck.Gen as QGen import qualified Test.QuickCheck.Random as QCRandom import Test.Consensus.Cardano.Generators () -import Cardano.Ledger.Api.Tx (BabbageEraTxBody, PlutusPurpose, RedeemerReport, Tx) -import Cardano.Slotting.EpochInfo (EpochInfo, fixedEpochInfo) -import Cardano.Slotting.Slot (EpochSize (..), SlotNo (..)) +import Cardano.Ledger.Api.Tx ( PlutusPurpose, RedeemerReport, Tx) +import Cardano.Slotting.EpochInfo (EpochInfo) +import Cardano.Slotting.Slot () import Cardano.Slotting.Time (SystemStart (..)) import Data.Text (Text) import Cardano.Ledger.Api.UTxO (UTxO (..)) import Cardano.Ledger.Alonzo.Plutus.Evaluate (evalTxExUnits) -import Cardano.Ledger.Api.Scripts - ( ConwayEraScript, - pattern CertifyingPurpose, - pattern MintingPurpose, - pattern ProposingPurpose, - pattern RewardingPurpose, - pattern SpendingPurpose, - pattern VotingPurpose, - ) -import Cardano.Ledger.Alonzo.Scripts - ( AsIx (..), - ExUnits (..), - ) -import qualified Cardano.Ledger.Conway.Scripts as C +import Cardano.Ledger.Alonzo.Scripts ( AsIx (..),ExUnits (..),) import qualified Data.Aeson.Encoding as AesonEncoding import Encoder(serializeTransactionScriptFailure, ogmiosSuccess) @@ -74,36 +60,6 @@ instance ToJSON (WrappedTransactionScriptFailure ConwayEra) where error "serializeTransactionScriptFailure produced invalid JSON" --- | Conway-normalized script purpose index. --- All inputs are upgraded to Conway before comparison. -newtype ConwayPurposeIx = - ConwayPurposeIx (PlutusPurpose AsIx ConwayEra) - --- | Upgrade any compatible era to Conway. -toConwayPurposeIx - :: UpgradePlutusPurpose AsIx era ConwayEra - => PlutusPurpose AsIx era - -> ConwayPurposeIx -toConwayPurposeIx = - ConwayPurposeIx . C.upgradePlutusPurposeAsIx - - --- | Extract the script index (Conway only). -purposeIx :: PlutusPurpose AsIx ConwayEra -> Word32 -purposeIx = \case - Ledger.ConwaySpending ix -> ix - Ledger.ConwayMinting ix -> ix - Ledger.ConwayCertifying (AsIx ix) -> ix - Ledger.ConwayRewarding ix -> ix - Ledger.ConwayVoting (AsIx ix) -> ix - -instance Eq ConwayPurposeIx where - (==) = (==) `on` (\(ConwayPurposeIx p) -> purposeIx p) - -instance Ord ConwayPurposeIx where - compare = compare `on` (\(ConwayPurposeIx p) -> purposeIx p) - - instance QC.Arbitrary (Ledger.TransactionScriptFailure Ledger.ConwayEra) where arbitrary = QC.oneof @@ -146,9 +102,28 @@ eval'Conway pparams tx utxo epochInfo systemStart = where redeemerReport :: RedeemerReport ConwayEra - redeemerReport = evalTxExUnits pparams tx utxo epochInfo systemStart - - groupReports :: PlutusPurpose AsIx era + redeemerReport = selectSingleReport fullReport + + fullReport :: RedeemerReport ConwayEra + fullReport = evalTxExUnits pparams tx utxo epochInfo systemStart + + -- Collapse the full report down to a single entry, preferring failures. + selectSingleReport :: RedeemerReport ConwayEra -> RedeemerReport ConwayEra + selectSingleReport report = + case Map.toList failures of + (purpose, errors) : _ -> + Map.singleton purpose (Left (pickScriptFailure errors)) + [] -> + case Map.toList successes of + (purpose, exUnits) : _ -> + Map.singleton purpose (Right exUnits) + [] -> + error "Empty redeemer report from evalTxExUnits" + where + (failures, successes) = Map.foldrWithKey groupReports (Map.empty, Map.empty) report + + groupReports :: Ord (PlutusPurpose AsIx era) + => PlutusPurpose AsIx era -> Either (Ledger.TransactionScriptFailure era) ExUnits -> (Map.Map (PlutusPurpose AsIx era) [Ledger.TransactionScriptFailure era ], Map.Map (PlutusPurpose AsIx era) ExUnits) -> (Map.Map (PlutusPurpose AsIx era) [Ledger.TransactionScriptFailure era], Map.Map (PlutusPurpose AsIx era) ExUnits) From e4f97d9d39e4d49b0a763e53bbdf856f58a9dcf4 Mon Sep 17 00:00:00 2001 From: Michal Rus Date: Tue, 20 Jan 2026 14:44:29 +0100 Subject: [PATCH 06/10] chore: run code formatting checks on CI --- flake.nix | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/flake.nix b/flake.nix index b192741..a42318f 100644 --- a/flake.nix +++ b/flake.nix @@ -55,16 +55,22 @@ }; }; - flake.hydraJobs = { - testgen-hs = lib.genAttrs (config.systems ++ ["x86_64-windows"]) ( - targetSystem: inputs.self.internal.${targetSystem}.hydraPackage - ); - required = inputs.nixpkgs.legacyPackages.x86_64-linux.releaseTools.aggregate { - name = "github-required"; - meta.description = "All jobs required to pass CI"; - constituents = - lib.collect lib.isDerivation inputs.self.hydraJobs.testgen-hs; + flake.hydraJobs = let + allJobs = { + testgen-hs = lib.genAttrs (config.systems ++ ["x86_64-windows"]) ( + targetSystem: inputs.self.internal.${targetSystem}.hydraPackage + ); + inherit (inputs.self) checks; + }; + in + allJobs + // { + required = inputs.nixpkgs.legacyPackages.x86_64-linux.releaseTools.aggregate { + name = "github-required"; + meta.description = "All jobs required to pass CI"; + constituents = + lib.collect lib.isDerivation allJobs; + }; }; - }; }); } From fd68c7b45d28ec4ba534e87e33d2b70fa80495b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C4=B1z=C4=B1r=20Sefa=20=C4=B0rken?= Date: Fri, 23 Jan 2026 14:13:23 +0300 Subject: [PATCH 07/10] Apply PR feedback --- testgen-hs/Evaluation.hs | 39 +++++++++++------------- testgen-hs/Generators.hs | 4 --- testgen-hs/Main.hs | 65 +++++++++++++++++++--------------------- testgen-hs/Response.hs | 19 ------------ 4 files changed, 48 insertions(+), 79 deletions(-) diff --git a/testgen-hs/Evaluation.hs b/testgen-hs/Evaluation.hs index e95fa80..2f0bb9b 100644 --- a/testgen-hs/Evaluation.hs +++ b/testgen-hs/Evaluation.hs @@ -41,8 +41,6 @@ import Cardano.Ledger.Alonzo.Scripts ( AsIx (..),ExUnits (..),) import qualified Data.Aeson.Encoding as AesonEncoding import Encoder(serializeTransactionScriptFailure, ogmiosSuccess) import Data.List (sortOn) -import qualified Data.List.NonEmpty as NE -import Data.Maybe (fromMaybe) newtype WrappedTransactionScriptFailure era = WrappedTransactionScriptFailure @@ -136,22 +134,21 @@ eval'Conway pparams tx utxo epochInfo systemStart = pickScriptFailure :: [Ledger.TransactionScriptFailure era] -> Ledger.TransactionScriptFailure era -pickScriptFailure = - NE.head - . fromMaybe (error "Empty list of script failures from the ledger!?") - . NE.nonEmpty - . sortOn scriptFailurePriority - where - scriptFailurePriority :: - Ledger.TransactionScriptFailure era - -> Word - scriptFailurePriority = \case - Ledger.UnknownTxIn{} -> 0 - Ledger.MissingScript{} -> 0 - Ledger.RedeemerPointsToUnknownScriptHash{} -> 1 - Ledger.NoCostModelInLedgerState{} -> 1 - Ledger.InvalidTxIn{} -> 2 - Ledger.MissingDatum{} -> 3 - Ledger.ContextError{} -> 4 - Ledger.ValidationFailure{} -> 5 - Ledger.IncompatibleBudget{} -> 999 +pickScriptFailure xs = + case sortOn scriptFailurePriority xs of + [] -> error "Empty list of script failures from the ledger!?" + x : _ -> x + where + scriptFailurePriority :: + Ledger.TransactionScriptFailure era + -> Word + scriptFailurePriority = \case + Ledger.UnknownTxIn{} -> 0 + Ledger.MissingScript{} -> 0 + Ledger.RedeemerPointsToUnknownScriptHash{} -> 1 + Ledger.NoCostModelInLedgerState{} -> 1 + Ledger.InvalidTxIn{} -> 2 + Ledger.MissingDatum{} -> 3 + Ledger.ContextError{} -> 4 + Ledger.ValidationFailure{} -> 5 + Ledger.IncompatibleBudget{} -> 999 diff --git a/testgen-hs/Generators.hs b/testgen-hs/Generators.hs index 7065dd4..0f7071d 100644 --- a/testgen-hs/Generators.hs +++ b/testgen-hs/Generators.hs @@ -12,7 +12,6 @@ import qualified Cardano.Binary import qualified Cardano.Chain.Slotting as CCS import qualified Cardano.Ledger.Api.Era import qualified Cardano.Ledger.Api.UTxO -import qualified Cardano.Ledger.Api as Ledger import qualified Cardano.Ledger.Core import qualified Cardano.TxSubmit.Types as CTT import qualified Codec.CBOR.Encoding as C @@ -179,9 +178,6 @@ newtype Tx'Conway instance Arbitrary Tx'Conway where arbitrary = Tx'Conway <$> SynthEvalTx.genTxUTxO -generateFailures :: IO [Ledger.TransactionScriptFailure Ledger.ConwayEra] -generateFailures = QC.generate $ QC.vectorOf 10 QC.arbitrary - instance OurCBOR Tx'Conway where unwrappedType (Tx'Conway (tx, _utxo)) = show . typeOf $ tx ourToCBOR (Tx'Conway (tx, _utxo)) = Cardano.Binary.toCBOR tx diff --git a/testgen-hs/Main.hs b/testgen-hs/Main.hs index b5b04cb..b8c6072 100644 --- a/testgen-hs/Main.hs +++ b/testgen-hs/Main.hs @@ -16,6 +16,7 @@ import Cardano.Slotting.Time (SystemStart (..), mkSlotLength) import qualified Codec.CBOR.Write as C import qualified Control.Concurrent.Async as Async import Control.Concurrent.MVar (modifyMVar_, newMVar) +import Control.Monad (forever, when) import Data.Aeson (FromJSON, ToJSON) import qualified Data.Aeson as J import qualified Data.Aeson.Encode.Pretty as J @@ -38,7 +39,7 @@ import Data.Word (Word16, Word64) import qualified Deserialize as D import GHC.Generics (Generic) import qualified Generators as G -import System.Exit (exitFailure) +import System.Exit (exitFailure, exitSuccess) import System.IO (hPutStrLn, stderr) import qualified System.IO as SIO import qualified System.Random @@ -283,42 +284,37 @@ runEvaluateStream = do exitFailure Right (pp, ss) -> do BL8.putStrLn . J.encode $ PayloadResponse { rJson = Just (J.object []), rError = Nothing } - processLines initPayload pp ss ei + forever $ processLine pp ss ei where - processLines :: InitPayload -> PParams ConwayEra -> SystemStart -> EpochInfo (Either Text) -> IO () - processLines initPayload pp ss ei = do + processLine :: PParams ConwayEra -> SystemStart -> EpochInfo (Either Text) -> IO () + processLine pp ss ei = do eof <- SIO.isEOF - if eof - then pure () - else do - line <- B8.getLine -- This line is expected to be an EvalPayload - case J.eitherDecodeStrict line of - Left err -> do - let response = case J.eitherDecodeStrict' (B8.pack err) of - Left decodeErr -> PayloadResponse { rJson = Nothing, rError = Just (T.pack $ "Failed to decode error as JSON: " ++ decodeErr ++ ". Original error: " ++ err) } - Right jsonVal -> PayloadResponse { rJson = Nothing, rError = Just jsonVal } - BL8.putStrLn . J.encode $ response - processLines initPayload pp ss ei - Right (evalPayload :: EvalPayload) -> do - let decodedValues = do - txBytes <- first (\e -> PayloadResponse (Just (T.pack e)) Nothing) $ B16.decode (T.encodeUtf8 (tx evalPayload)) - decodedTx <- - decodeCborWith - "Transaction" - (Left . (\e -> PayloadResponse (Just (serializeDecoderError (BS.length txBytes) e)) Nothing)) - (Binary.decCBOR @(Cardano.Ledger.Core.Tx ConwayEra)) - txBytes - utxos <- first (\e -> PayloadResponse (Just (T.pack e)) Nothing) $ decodeFromHex (utxos evalPayload) - return (decodedTx, utxos) + when eof exitSuccess + line <- B8.getLine -- This line is expected to be an EvalPayload + case J.eitherDecodeStrict line of + Left err -> do + let response = case J.eitherDecodeStrict' (B8.pack err) of + Left decodeErr -> PayloadResponse { rJson = Nothing, rError = Just (T.pack $ "Failed to decode error as JSON: " ++ decodeErr ++ ". Original error: " ++ err) } + Right jsonVal -> PayloadResponse { rJson = Nothing, rError = Just jsonVal } + BL8.putStrLn . J.encode $ response + Right (evalPayload :: EvalPayload) -> do + let decodedValues = do + txBytes <- first (\e -> PayloadResponse (Just (T.pack e)) Nothing) $ B16.decode (T.encodeUtf8 (tx evalPayload)) + decodedTx <- + decodeCborWith + "Transaction" + (Left . (\e -> PayloadResponse (Just (serializeDecoderError (BS.length txBytes) e)) Nothing)) + (Binary.decCBOR @(Cardano.Ledger.Core.Tx ConwayEra)) + txBytes + utxos <- first (\e -> PayloadResponse (Just (T.pack e)) Nothing) $ decodeFromHex (utxos evalPayload) + return (decodedTx, utxos) - case decodedValues of - Left response -> do - BL8.putStrLn . J.encode $ response - processLines initPayload pp ss ei - Right (tx, utxos) -> do - let result = eval'Conway pp tx utxos ei ss - BL8.putStrLn . J.encode $ PayloadResponse { rJson = Just result, rError = Nothing } - processLines initPayload pp ss ei + case decodedValues of + Left response -> + BL8.putStrLn . J.encode $ response + Right (tx, utxos) -> do + let result = eval'Conway pp tx utxos ei ss + BL8.putStrLn . J.encode $ PayloadResponse { rJson = Just result, rError = Nothing } -- | Creates an EpochInfo from the given SlotConfig convertEpochInfo :: SlotConfig -> EpochInfo (Either Text) @@ -348,4 +344,3 @@ decodeCborWith lbl handleErr decoder bytes = Right val -> Right val where version = Ledger.eraProtVerLow @ConwayEra - diff --git a/testgen-hs/Response.hs b/testgen-hs/Response.hs index c961bbb..1a2fe85 100644 --- a/testgen-hs/Response.hs +++ b/testgen-hs/Response.hs @@ -1,11 +1,9 @@ module Response ( PayloadResponse (..), - OgmiosError (..) ) where import Data.Aeson (ToJSON) import qualified Data.Aeson as J -import Data.Int (Int64) import Data.Text (Text) import GHC.Generics (Generic) @@ -27,20 +25,3 @@ instance ToJSON PayloadResponse where modifier "rError" = "error" modifier "rJson" = "json" modifier s = s - -data OgmiosError = OgmiosError - { code :: Int64, - message :: Text, - value :: J.Value - } - deriving (Generic, Show) - -instance ToJSON OgmiosError where - toJSON = - J.genericToJSON - J.defaultOptions - { J.fieldLabelModifier = \case - "value" -> "data" - s -> s - } - From ee0ccca3500ea4e4bad3d94358fdabc00680d628 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C4=B1z=C4=B1r=20Sefa=20=C4=B0rken?= Date: Mon, 26 Jan 2026 17:29:31 +0300 Subject: [PATCH 08/10] Apply PR feedback - Remove dead code --- testgen-hs/Encoder.hs | 11 ++---- testgen-hs/SynthEvalTx.hs | 71 ++++++--------------------------------- 2 files changed, 13 insertions(+), 69 deletions(-) diff --git a/testgen-hs/Encoder.hs b/testgen-hs/Encoder.hs index bba4c4d..4b863c8 100644 --- a/testgen-hs/Encoder.hs +++ b/testgen-hs/Encoder.hs @@ -5,14 +5,8 @@ {-# LANGUAGE ViewPatterns #-} module Encoder - ( serializeScriptPurposeIndex - , serializeTransactionScriptFailure - , serializeTxIn - , serializeTxId - , serializeHash - , serializeFoldable + ( serializeTransactionScriptFailure , serializeDecoderError - , ogmiosError , ogmiosSuccess ) where @@ -155,8 +149,6 @@ serializeScriptPurposeIndex = \case J.pair "index" (A.toEncoding ix) <> J.pair "purpose" (J.text purpose) - - serializeExBudget :: P.ExBudget -> Encoding @@ -390,6 +382,7 @@ serializeTransactionScriptFailure = \case +-- | Human-readable CBOR decode errors. Kept for Ogmios compatibility & future-use even when unused. serializeDecoderError :: Int -> Binary.DecoderError -> Text serializeDecoderError size = extractId . reduceNoise . \case Binary.DecoderErrorCanonicityViolation lbl -> diff --git a/testgen-hs/SynthEvalTx.hs b/testgen-hs/SynthEvalTx.hs index e75d431..4173212 100644 --- a/testgen-hs/SynthEvalTx.hs +++ b/testgen-hs/SynthEvalTx.hs @@ -14,24 +14,11 @@ module SynthEvalTx (eval'Conway, eval'ConwayDummy, genTxUTxO, stubUTxO) where import Cardano.Crypto.Hash.Class (hashFromBytes) import Cardano.Ledger.Address (Addr (..)) import Cardano.Ledger.Alonzo.Plutus.Evaluate (evalTxExUnits) -import Cardano.Ledger.Alonzo.Scripts - ( AsIx (..), - ExUnits (..), - ) import qualified Cardano.Ledger.Alonzo.Scripts import qualified Cardano.Ledger.Alonzo.Tx import Cardano.Ledger.Api (EraTx, PParams, bodyTxL, collateralInputsTxBodyL, inputsTxBodyL, referenceInputsTxBodyL) import qualified Cardano.Ledger.Api.Era -import Cardano.Ledger.Api.Scripts - ( ConwayEraScript, - pattern CertifyingPurpose, - pattern MintingPurpose, - pattern ProposingPurpose, - pattern RewardingPurpose, - pattern SpendingPurpose, - pattern VotingPurpose, - ) -import Cardano.Ledger.Api.Tx (BabbageEraTxBody, PlutusPurpose, RedeemerReport, Tx) +import Cardano.Ledger.Api.Tx (BabbageEraTxBody, RedeemerReport, Tx) import Cardano.Ledger.Api.Tx.In (TxIn) import Cardano.Ledger.Api.Tx.Out ( TxOut (..), @@ -53,15 +40,16 @@ import Data.Aeson ( eitherDecodeStrict', ) import qualified Data.Aeson as J +import qualified Data.Aeson.Encoding as AesonEncoding import qualified Data.ByteString as BS import qualified Data.Default import Data.FileEmbed (embedFile) import qualified Data.Map.Strict as Map import qualified Data.Set as Set import Data.Text (Text) -import qualified Data.Text as T import Data.Time.Calendar (fromGregorian) import Data.Time.Clock (UTCTime (..), secondsToDiffTime) +import Encoder (ogmiosSuccess) import Lens.Micro ((^.)) import qualified Test.Cardano.Ledger.Generic.GenState import qualified Test.Cardano.Ledger.Generic.Proof as Proof @@ -100,14 +88,20 @@ eval'Conway :: EpochInfo (Either Text) -> SystemStart -> J.Value -eval'Conway tx utxo epochInfo systemStart = ogmiosSuccess redeemerReport +eval'Conway tx utxo epochInfo systemStart = + case J.decode (AesonEncoding.encodingToLazyByteString (ogmiosSuccess redeemerReport)) of + Just v -> v + Nothing -> error "ogmiosSuccess produced invalid JSON" where redeemerReport :: RedeemerReport (Cardano.Ledger.Api.Era.ConwayEra) redeemerReport = evalTxExUnits protocolParams tx utxo epochInfo systemStart -- | Version of eval'Conway that uses dummy epoch info and system start eval'ConwayDummy :: (Cardano.Ledger.Core.Tx (Cardano.Ledger.Api.Era.ConwayEra)) -> UTxO Cardano.Ledger.Api.Era.ConwayEra -> J.Value -eval'ConwayDummy tx utxo = ogmiosSuccess redeemerReport +eval'ConwayDummy tx utxo = + case J.decode (AesonEncoding.encodingToLazyByteString (ogmiosSuccess redeemerReport)) of + Just v -> v + Nothing -> error "ogmiosSuccess produced invalid JSON" where redeemerReport :: RedeemerReport (Cardano.Ledger.Api.Era.ConwayEra) redeemerReport = evalTxExUnits protocolParams tx utxo dummyEpochInfo dummySystemStart @@ -154,46 +148,3 @@ protocolParams = case eitherDecodeStrict' protocolParamsJSON of Left err -> error $ "Embedded protocol-parameters JSON is malformed:\n" <> err Right pp -> pp - --- | Render a ledger redeemer pointer the Ogmios way -redeemerPtrToText :: forall era. (ConwayEraScript era) => PlutusPurpose AsIx era -> Text -redeemerPtrToText = \case - SpendingPurpose (AsIx ix) -> "spend:" <> showT ix - CertifyingPurpose (AsIx ix) -> "publish:" <> showT ix - MintingPurpose (AsIx ix) -> "mint:" <> showT ix - RewardingPurpose (AsIx ix) -> "withdraw:" <> showT ix - ProposingPurpose (AsIx ix) -> "propose:" <> showT ix - VotingPurpose (AsIx ix) -> "vote:" <> showT ix - _ -> error "unreachable: unknown PlutusPurpose" -- matches _are_ exhaustive, but it’s not provable statically - where - showT = T.pack . show -- Word32 → Text - --- | Ogmios "budget" object from 'ExUnits'. -exUnitsToJSON :: ExUnits -> J.Value -exUnitsToJSON (ExUnits mem cpu) = - J.object ["memory" J..= mem, "cpu" J..= cpu] - --- | Build the JSON-RPC success envelope that Ogmios returns when every --- redeemer succeeds. -ogmiosSuccess :: - forall era err. - (ConwayEraScript era, Show err) => -- same constraint here - Map.Map (PlutusPurpose AsIx era) (Either err ExUnits) -> - J.Value -ogmiosSuccess report = - J.toJSON - [ case res of - Right exu -> - J.object - [ "validator" J..= redeemerPtrToText ptr, - "budget" J..= exUnitsToJSON exu - ] - Left err -> - J.object - [ "validator" J..= redeemerPtrToText ptr, - "error" - J..= J.object - ["message" J..= show err] -- simple text trace - ] - | (ptr, res) <- Map.toList report - ] \ No newline at end of file From b0db0e1df855912b8a46205f14d0b9c9b4d98a15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C4=B1z=C4=B1r=20Sefa=20=C4=B0rken?= Date: Mon, 26 Jan 2026 17:35:29 +0300 Subject: [PATCH 09/10] Apply PR feedback - Formatting --- testgen-hs/Encoder.hs | 509 ++++++++++++++++++------------------ testgen-hs/Evaluation.hs | 88 +++---- testgen-hs/Generators.hs | 4 +- testgen-hs/Main.hs | 73 +++--- testgen-hs/Response.hs | 3 +- testgen-hs/SynthEvalTx.hs | 2 +- testgen-hs/testgen-hs.cabal | 18 +- 7 files changed, 347 insertions(+), 350 deletions(-) diff --git a/testgen-hs/Encoder.hs b/testgen-hs/Encoder.hs index 4b863c8..afb83a4 100644 --- a/testgen-hs/Encoder.hs +++ b/testgen-hs/Encoder.hs @@ -1,190 +1,169 @@ -{-# OPTIONS_GHC -Wno-orphans #-} -{-# LANGUAGE TypeOperators #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeOperators #-} {-# LANGUAGE ViewPatterns #-} +{-# OPTIONS_GHC -Wno-orphans #-} module Encoder - ( serializeTransactionScriptFailure - , serializeDecoderError - , ogmiosSuccess - ) where - -import qualified Cardano.Ledger.Conway.Scripts as C -import qualified Cardano.Ledger.Conway.TxInfo as C - -import qualified Cardano.Ledger.Babbage.TxInfo as Ba - + ( serializeTransactionScriptFailure, + serializeDecoderError, + ogmiosSuccess, + ) +where +import qualified Cardano.Crypto.Hash.Class as CC import qualified Cardano.Ledger.Alonzo.Core as Al hiding - ( TranslationError - ) + ( TranslationError, + ) import qualified Cardano.Ledger.Alonzo.Plutus.TxInfo as Al import qualified Cardano.Ledger.Alonzo.Scripts as Al - import Cardano.Ledger.Api - ( AsIx (..) - , PlutusPurpose - ) - + ( AsIx (..), + PlutusPurpose, + ) +import qualified Cardano.Ledger.Api as Ledger +import qualified Cardano.Ledger.Babbage.TxInfo as Ba +import qualified Cardano.Ledger.BaseTypes as LedgerBase +import qualified Cardano.Ledger.Binary.Decoding as Binary +import qualified Cardano.Ledger.Conway.Scripts as C +import qualified Cardano.Ledger.Conway.TxInfo as C +import qualified Cardano.Ledger.Core as Ledger +import qualified Cardano.Ledger.Hashes as Hashes import Cardano.Ledger.Plutus - ( TxOutSource (..) - ) - -import qualified Cardano.Ledger.Api as Ledger -import qualified Cardano.Ledger.TxIn as Ledger -import qualified Cardano.Ledger.Core as Ledger + ( TxOutSource (..), + ) import qualified Cardano.Ledger.Plutus.Language as Ledger -import qualified Cardano.Ledger.BaseTypes as LedgerBase -import qualified Cardano.Ledger.Binary.Decoding as Binary - +import qualified Cardano.Ledger.TxIn as Ledger import qualified Codec.CBOR.Read as Cbor - +import qualified Data.Aeson as A +import Data.Aeson.Encoding (Encoding) +import qualified Data.Aeson.Encoding as J +import qualified Data.ByteString as BS +import qualified Data.ByteString.Base16 as B16 +import qualified Data.ByteString.Short as SBS +import Data.Foldable (toList) +import Data.Int (Int64) import qualified Data.Map as Map - -import qualified Cardano.Ledger.Hashes as Hashes -import qualified Cardano.Crypto.Hash.Class as CC - -import qualified Data.Aeson as A -import qualified Data.Aeson.Encoding as J -import Data.Aeson.Encoding (Encoding) -import Data.Foldable (toList) -import Data.Int (Int64) -import Data.Text (Text) -import qualified Data.Text as T -import qualified Data.Text.Encoding as TE - -import qualified Data.ByteString.Base16 as B16 - -import qualified Data.ByteString.Short as SBS - import Data.SatInt - ( fromSatInt - ) -import qualified Data.ByteString as BS - + ( fromSatInt, + ) +import Data.Text (Text) +import qualified Data.Text as T +import qualified Data.Text.Encoding as TE import qualified PlutusLedgerApi.Common as P - - -import Prettyprinter (pretty) +import Prettyprinter (pretty) -------------------------------------------------------------------------------- -- Generic helpers -------------------------------------------------------------------------------- -- | Encode any Foldable as a JSON array using an element encoder. -serializeFoldable - :: Foldable f - => (a -> Encoding) - -> f a - -> Encoding +serializeFoldable :: + (Foldable f) => + (a -> Encoding) -> + f a -> + Encoding serializeFoldable enc = - J.list enc . toList + J.list enc . toList -------------------------------------------------------------------------------- -- Hash / TxId / TxIn -------------------------------------------------------------------------------- -- | Serialize a SafeHash as lower-case hex JSON string. -serializeHash - :: Hashes.SafeHash crypto - -> Encoding +serializeHash :: + Hashes.SafeHash crypto -> + Encoding serializeHash safeH = - let CC.UnsafeHash short = Hashes.extractHash safeH - bs = SBS.fromShort short - hex = B16.encode bs - in J.text (TE.decodeUtf8 hex) + let CC.UnsafeHash short = Hashes.extractHash safeH + bs = SBS.fromShort short + hex = B16.encode bs + in J.text (TE.decodeUtf8 hex) -serializDataHash - :: Al.DataHash - -> Encoding +serializDataHash :: + Al.DataHash -> + Encoding serializDataHash = - serializeHash + serializeHash - -serializeTxId - :: Ledger.TxId - -> Encoding +serializeTxId :: + Ledger.TxId -> + Encoding serializeTxId txId = - J.pairs $ - J.pair "id" (serializeHash (Ledger.unTxId txId)) + J.pairs $ + J.pair "id" (serializeHash (Ledger.unTxId txId)) -serializeTxIn - :: Ledger.TxIn - -> Encoding +serializeTxIn :: + Ledger.TxIn -> + Encoding serializeTxIn (Ledger.TxIn txId ix) = - J.pairs $ - J.pair "transaction" (serializeTxId txId) - <> J.pair "index" (A.toEncoding ix) + J.pairs $ + J.pair "transaction" (serializeTxId txId) + <> J.pair "index" (A.toEncoding ix) -------------------------------------------------------------------------------- -- Script purpose index -------------------------------------------------------------------------------- -serializeScriptPurposeIndex - :: forall era. () - => C.ConwayPlutusPurpose AsIx era - -> Encoding +serializeScriptPurposeIndex :: + forall era. + () => + C.ConwayPlutusPurpose AsIx era -> + Encoding serializeScriptPurposeIndex = \case - C.ConwaySpending ix -> - mk ix "spend" - - C.ConwayMinting ix -> - mk ix "mint" - - C.ConwayCertifying (AsIx ix) -> - mk ix "publish" - - C.ConwayRewarding ix -> - mk ix "withdraw" - - C.ConwayVoting (AsIx ix) -> - mk ix "vote" - - C.ConwayProposing (AsIx ix) -> - mk ix "propose" + C.ConwaySpending ix -> + mk ix "spend" + C.ConwayMinting ix -> + mk ix "mint" + C.ConwayCertifying (AsIx ix) -> + mk ix "publish" + C.ConwayRewarding ix -> + mk ix "withdraw" + C.ConwayVoting (AsIx ix) -> + mk ix "vote" + C.ConwayProposing (AsIx ix) -> + mk ix "propose" where mk ix purpose = - J.pairs $ - J.pair "index" (A.toEncoding ix) + J.pairs $ + J.pair "index" (A.toEncoding ix) <> J.pair "purpose" (J.text purpose) -serializeExBudget - :: P.ExBudget - -> Encoding +serializeExBudget :: + P.ExBudget -> + Encoding serializeExBudget budget = - J.pairs $ - J.pair "memory" (J.integer (fromSatInt mem)) - <> J.pair "cpu" (J.integer (fromSatInt cpu)) + J.pairs $ + J.pair "memory" (J.integer (fromSatInt mem)) + <> J.pair "cpu" (J.integer (fromSatInt cpu)) where P.ExMemory mem = P.exBudgetMemory budget P.ExCPU cpu = P.exBudgetCPU budget - -serializeLanguage - :: Ledger.Language - -> Encoding +serializeLanguage :: + Ledger.Language -> + Encoding serializeLanguage = \case - Ledger.PlutusV1 -> "plutus:v1" - Ledger.PlutusV2 -> "plutus:v2" - Ledger.PlutusV3 -> "plutus:v3" + Ledger.PlutusV1 -> "plutus:v1" + Ledger.PlutusV2 -> "plutus:v2" + Ledger.PlutusV3 -> "plutus:v3" -- | Ogmios "budget" object from 'ExUnits'. serializeExUnits :: Al.ExUnits -> Encoding serializeExUnits (Al.ExUnits mem cpu) = - J.pairs $ - J.pair "memory" (J.integer (fromIntegral mem)) - <> J.pair "cpu" (J.integer (fromIntegral cpu)) + J.pairs $ + J.pair "memory" (J.integer (fromIntegral mem)) + <> J.pair "cpu" (J.integer (fromIntegral cpu)) -txInToText - :: Ledger.TxIn - -> Text +txInToText :: + Ledger.TxIn -> + Text txInToText (Ledger.TxIn txid (LedgerBase.TxIx ix)) = txIdToText txid <> "#" <> T.pack (show ix) txIdToText :: Ledger.TxId -> Text txIdToText (Ledger.TxId (Ledger.originalBytes -> bytes)) = - TE.decodeUtf8 (B16.encode bytes) + TE.decodeUtf8 (B16.encode bytes) redeemerPtrToText :: forall era. (Ledger.ConwayEraScript era) => PlutusPurpose AsIx era -> Text redeemerPtrToText = \case @@ -198,103 +177,102 @@ redeemerPtrToText = \case where showT = T.pack . show -- Word32 → Text - - -------------------------------------------------------------------------------- -- Ogmios-style error envelope -------------------------------------------------------------------------------- -ogmiosError - :: Int64 - -> Text - -> Encoding -- ^ payload / "data" - -> Encoding +ogmiosError :: + Int64 -> + Text -> + -- | payload / "data" + Encoding -> + Encoding ogmiosError code msg payload = J.pairs $ - J.pair "code" (A.toEncoding code) - <> J.pair "message" (J.text msg) - <> J.pair "data" payload + J.pair "code" (A.toEncoding code) + <> J.pair "message" (J.text msg) + <> J.pair "data" payload -- | Build the JSON-RPC success envelope that Ogmios returns when every -- redeemer succeeds. ogmiosSuccess :: Map.Map (PlutusPurpose AsIx Ledger.ConwayEra) - (Either (Ledger.TransactionScriptFailure Ledger.ConwayEra) Al.ExUnits) - -> J.Encoding + (Either (Ledger.TransactionScriptFailure Ledger.ConwayEra) Al.ExUnits) -> + J.Encoding ogmiosSuccess report = J.list - (\(ptr, res) -> + ( \(ptr, res) -> case res of Right exu -> J.pairs $ J.pair "validator" (J.text $ redeemerPtrToText ptr) - <> J.pair "budget" (serializeExUnits exu) - - Left err -> + <> J.pair "budget" (serializeExUnits exu) + Left err -> J.pairs $ J.pair "validator" (J.text $ redeemerPtrToText ptr) - <> J.pair "error" ( - J.pairs $ + <> J.pair + "error" + ( J.pairs $ J.pair "message" (serializeTransactionScriptFailure err) ) ) (Map.toList report) ---makeScriptExecFailure - -- :: Encoding - -- -> Encoding ---makeScriptExecFailure = ogmiosError - -- 3010 - -- "Some scripts of the transactions terminated with error(s)." - +-- makeScriptExecFailure +-- :: Encoding +-- -> Encoding +-- makeScriptExecFailure = ogmiosError +-- 3010 +-- "Some scripts of the transactions terminated with error(s)." -------------------------------------------------------------------------------- -- ContextError -------------------------------------------------------------------------------- -serializeContextError - :: ( PlutusPurpose AsIx era ~ C.ConwayPlutusPurpose AsIx era - ) - => C.ConwayContextError era - -> Encoding +serializeContextError :: + ( PlutusPurpose AsIx era ~ C.ConwayPlutusPurpose AsIx era + ) => + C.ConwayContextError era -> + Encoding serializeContextError err = J.text $ case err of - C.CertificateNotSupported{} -> - "A certificate in the transaction isn't supported in neither plutus:v1 nor plutus:v2. Use plutus:v3 or higher." - C.PlutusPurposeNotSupported{} -> - "A script purpose in the transaction isn't supported in neither plutus:v1 nor plutus:v2. Use plutus:v3 or higher." - C.CurrentTreasuryFieldNotSupported{} -> - "Unsupported field in transaction: 'treasury'. Use plutus:v3 or higher, or remove this field." - C.VotingProceduresFieldNotSupported{} -> - "Unsupported field in transaction: 'votes'. Use plutus:v3 or higher, or remove this field." - C.ProposalProceduresFieldNotSupported{} -> - "Unsupported field in transaction: 'proposals'. Use plutus:v3 or higher, or remove this field." - C.TreasuryDonationFieldNotSupported{} -> - "Unsupported field in transaction: 'donation'. Use plutus:v3 or higher, or remove this field." - C.BabbageContextError (Ba.ByronTxOutInContext TxOutFromInput{}) -> - "Found inputs locked by a (legacy) Byron/Bootstrap address. Don't use those." - C.BabbageContextError (Ba.ByronTxOutInContext TxOutFromOutput{}) -> - "Found outputs to a (legacy) Byron/Bootstrap address. Don't use those." - C.BabbageContextError (Ba.InlineDatumsNotSupported{}) -> - "Inline datums not supported in plutus:v1. Use plutus:v2 or higher." - C.BabbageContextError (Ba.ReferenceScriptsNotSupported{}) -> - "Reference scripts not supported in plutus:v1. Use plutus:v2 or higher." - C.BabbageContextError (Ba.ReferenceInputsNotSupported{}) -> - "Reference inputs not supported in plutus:v1. Use plutus:v2 or higher." - C.BabbageContextError (Ba.RedeemerPointerPointsToNothing purpose) -> - let (title, ptr) = - case purpose of - C.ConwaySpending (AsIx ix) -> ("spending input", ix) - C.ConwayMinting (AsIx ix) -> ("minting policy", ix) - C.ConwayCertifying (AsIx ix) -> ("publishing certificate", ix) - C.ConwayRewarding (AsIx ix) -> ("withdrawing from account", ix) - C.ConwayVoting (AsIx ix) -> ("voting as voter", ix) - C.ConwayProposing (AsIx ix) -> ("proposing governance proposal", ix) - in "Couldn't find corresponding redeemer for " <> title <> " #" <> T.pack (show ptr) <> ". Verify your transaction's construction." - C.BabbageContextError (Ba.AlonzoContextError (Al.TimeTranslationPastHorizon e)) -> - "Uncomputable slot arithmetic; transaction's validity bounds go beyond the foreseeable end of the current era: " <> e - C.BabbageContextError (Ba.AlonzoContextError (Al.TranslationLogicMissingInput i)) -> - "Unknown transaction input (missing from UTxO set): " <> txInToText i + C.CertificateNotSupported {} -> + "A certificate in the transaction isn't supported in neither plutus:v1 nor plutus:v2. Use plutus:v3 or higher." + C.PlutusPurposeNotSupported {} -> + "A script purpose in the transaction isn't supported in neither plutus:v1 nor plutus:v2. Use plutus:v3 or higher." + C.CurrentTreasuryFieldNotSupported {} -> + "Unsupported field in transaction: 'treasury'. Use plutus:v3 or higher, or remove this field." + C.VotingProceduresFieldNotSupported {} -> + "Unsupported field in transaction: 'votes'. Use plutus:v3 or higher, or remove this field." + C.ProposalProceduresFieldNotSupported {} -> + "Unsupported field in transaction: 'proposals'. Use plutus:v3 or higher, or remove this field." + C.TreasuryDonationFieldNotSupported {} -> + "Unsupported field in transaction: 'donation'. Use plutus:v3 or higher, or remove this field." + C.BabbageContextError (Ba.ByronTxOutInContext TxOutFromInput {}) -> + "Found inputs locked by a (legacy) Byron/Bootstrap address. Don't use those." + C.BabbageContextError (Ba.ByronTxOutInContext TxOutFromOutput {}) -> + "Found outputs to a (legacy) Byron/Bootstrap address. Don't use those." + C.BabbageContextError (Ba.InlineDatumsNotSupported {}) -> + "Inline datums not supported in plutus:v1. Use plutus:v2 or higher." + C.BabbageContextError (Ba.ReferenceScriptsNotSupported {}) -> + "Reference scripts not supported in plutus:v1. Use plutus:v2 or higher." + C.BabbageContextError (Ba.ReferenceInputsNotSupported {}) -> + "Reference inputs not supported in plutus:v1. Use plutus:v2 or higher." + C.BabbageContextError (Ba.RedeemerPointerPointsToNothing purpose) -> + let (title, ptr) = + case purpose of + C.ConwaySpending (AsIx ix) -> ("spending input", ix) + C.ConwayMinting (AsIx ix) -> ("minting policy", ix) + C.ConwayCertifying (AsIx ix) -> ("publishing certificate", ix) + C.ConwayRewarding (AsIx ix) -> ("withdrawing from account", ix) + C.ConwayVoting (AsIx ix) -> ("voting as voter", ix) + C.ConwayProposing (AsIx ix) -> ("proposing governance proposal", ix) + in "Couldn't find corresponding redeemer for " <> title <> " #" <> T.pack (show ptr) <> ". Verify your transaction's construction." + C.BabbageContextError (Ba.AlonzoContextError (Al.TimeTranslationPastHorizon e)) -> + "Uncomputable slot arithmetic; transaction's validity bounds go beyond the foreseeable end of the current era: " <> e + C.BabbageContextError (Ba.AlonzoContextError (Al.TranslationLogicMissingInput i)) -> + "Unknown transaction input (missing from UTxO set): " <> txInToText i + -------------------------------------------------------------------------------- -- TransactionScriptFailure -------------------------------------------------------------------------------- @@ -308,46 +286,47 @@ serializeTransactionScriptFailure = \case 3110 "Extraneous (non-required) redeemers found in the transaction." ( J.pairs $ - J.pair "extraneousRedeemers" + J.pair + "extraneousRedeemers" (serializeFoldable serializeScriptPurposeIndex [reedemers]) ) - Ledger.MissingScript purpose _ -> ogmiosError 3102 "An associated script witness is missing." ( J.pairs $ - J.pair "missingScripts" + J.pair + "missingScripts" (serializeFoldable serializeScriptPurposeIndex [purpose]) ) - Ledger.MissingDatum datum -> ogmiosError 3111 "Transaction failed because some Plutus scripts are missing their associated datums." ( J.pairs $ - J.pair "missingDatums" + J.pair + "missingDatums" (serializeFoldable serializDataHash [datum]) ) - Ledger.ValidationFailure _exUnits evalErr traces _ctx -> ogmiosError 3012 "Some of the scripts failed to evaluate to a positive outcome." ( J.pairs $ - J.pair "validationError" - (J.text (T.pack (show (pretty evalErr)))) - <> J.pair "traces" - (serializeFoldable A.toEncoding traces) + J.pair + "validationError" + (J.text (T.pack (show (pretty evalErr)))) + <> J.pair + "traces" + (serializeFoldable A.toEncoding traces) ) Ledger.UnknownTxIn txIn -> ogmiosError 3117 "The transaction contains unknown UTxO references as inputs." - (J.pairs $ + ( J.pairs $ J.pair "unknownOutputReferences" (serializeFoldable serializeTxIn [txIn]) ) - Ledger.InvalidTxIn txIn -> ogmiosError 3013 @@ -355,84 +334,102 @@ serializeTransactionScriptFailure = \case ( J.pairs $ J.pair "unsuitableOutputReference" (serializeTxIn txIn) ) - Ledger.IncompatibleBudget budget -> - ogmiosError + ogmiosError 3161 "The transaction ran out of execution budget!" - (J.pairs $ + ( J.pairs $ J.pair "budgetUsed" (serializeExBudget budget) ) - Ledger.NoCostModelInLedgerState lang -> - ogmiosError + ogmiosError 3115 "It seems like the transaction is using a Plutus version for which there's no available cost model yet." - (J.pairs $ + ( J.pairs $ J.pair "missingCostModels" (serializeFoldable serializeLanguage [lang]) ) - Ledger.ContextError ctxErr -> - ogmiosError + ogmiosError 3004 "Unable to create the evaluation context from the given transaction." - (J.pairs $ + ( J.pairs $ J.pair "reason" (serializeContextError ctxErr) ) - - -- | Human-readable CBOR decode errors. Kept for Ogmios compatibility & future-use even when unused. serializeDecoderError :: Int -> Binary.DecoderError -> Text -serializeDecoderError size = extractId . reduceNoise . \case +serializeDecoderError size = + extractId . reduceNoise . \case Binary.DecoderErrorCanonicityViolation lbl -> - "couldn't decode due to internal constraint violations on '" <> lbl <> "': \ - \ found CBOR that isn't canonical when I expected it to be." + "couldn't decode due to internal constraint violations on '" + <> lbl + <> "': \ + \ found CBOR that isn't canonical when I expected it to be." Binary.DecoderErrorCustom lbl hint -> - "couldn't decode due to internal constraint violations on '" <> lbl <> "': " <> hint - Binary.DecoderErrorDeserialiseFailure lbl (Cbor.DeserialiseFailure offset hint) | offset >= fromIntegral size -> - "invalid or incomplete value of type '" <> lbl <> "': " <> T.pack (show hint) + "couldn't decode due to internal constraint violations on '" <> lbl <> "': " <> hint + Binary.DecoderErrorDeserialiseFailure lbl (Cbor.DeserialiseFailure offset hint) + | offset >= fromIntegral size -> + "invalid or incomplete value of type '" <> lbl <> "': " <> T.pack (show hint) Binary.DecoderErrorDeserialiseFailure lbl (Cbor.DeserialiseFailure offset hint) -> - "invalid CBOR found at offset [" <> T.pack (show offset) <> "] while decoding a value of type '" <> lbl <> "': " + "invalid CBOR found at offset [" + <> T.pack (show offset) + <> "] while decoding a value of type '" + <> lbl + <> "': " <> T.pack (show hint) - Binary.DecoderErrorEmptyList{} -> - "couldn't decode due to internal constraint violations on a non-empty list: \ - \must not be empty" + Binary.DecoderErrorEmptyList {} -> + "couldn't decode due to internal constraint violations on a non-empty list: \ + \must not be empty" Binary.DecoderErrorLeftover lbl bytes -> - "unexpected " <> T.pack (show (BS.length bytes)) <> " bytes found left after \ - \successfully deserialising a/an '" <> lbl <> "'" - Binary.DecoderErrorSizeMismatch lbl expected actual | expected >= actual -> - T.pack (show (expected - actual)) <> " missing element(s) in a \ - \data-structure of type '" <> lbl <> "'" + "unexpected " + <> T.pack (show (BS.length bytes)) + <> " bytes found left after \ + \successfully deserialising a/an '" + <> lbl + <> "'" + Binary.DecoderErrorSizeMismatch lbl expected actual + | expected >= actual -> + T.pack (show (expected - actual)) + <> " missing element(s) in a \ + \data-structure of type '" + <> lbl + <> "'" Binary.DecoderErrorSizeMismatch lbl expected actual -> - T.pack (show (actual - expected)) <> " extra element(s) in a \ - \data-structure of type '" <> lbl <> "'" + T.pack (show (actual - expected)) + <> " extra element(s) in a \ + \data-structure of type '" + <> lbl + <> "'" Binary.DecoderErrorUnknownTag lbl tag -> - "unknown binary tag (" <> T.pack (show tag) <> ") when decoding a value of type '" <> lbl <> "'\ - \; which is probably because I am trying to decode something else than what \ - \I encountered." + "unknown binary tag (" + <> T.pack (show tag) + <> ") when decoding a value of type '" + <> lbl + <> "'\ + \; which is probably because I am trying to decode something else than what \ + \I encountered." Binary.DecoderErrorVoid -> - "impossible: attempted to decode void. Please open an issue." + "impossible: attempted to decode void. Please open an issue." where extractId = id - reduceNoise - = T.replace "\n" " " - . T.replace "Error: " "" - . T.replace "Record" "Object / Array" - . T.replace "Record RecD" "Object / Array" - . T.replace " ShelleyEra" "" - . T.replace " AllegraEra" "" - . T.replace " MaryEra" "" - . T.replace " AlonzoEra" "" - . T.replace " BabbageEra" "" - . T.replace " ConwayEra" "" - . T.replace "value of type ConwayTxBodyRaw" "transaction body" - . T.replace "value of type BabbageTxBodyRaw" "transaction body" - . T.replace "value of type AlonzoTxBodyRaw" "transaction body" - . T.replace "value of type AllegraTxBodyRaw ()" "transaction body" - . T.replace "value of type AllegraTxBodyRaw MultiAsset" "transaction body" - . T.replace "value of type ShelleyTxBodyRaw" "transaction body" - . T.replace "atbr" "" - . T.replace "stbr" "" - . T.replace "btbr" "" - . T.replace "ctbr" "" + reduceNoise = + T.replace "\n" " " + . T.replace "Error: " "" + . T.replace "Record" "Object / Array" + . T.replace "Record RecD" "Object / Array" + . T.replace " ShelleyEra" "" + . T.replace " AllegraEra" "" + . T.replace " MaryEra" "" + . T.replace " AlonzoEra" "" + . T.replace " BabbageEra" "" + . T.replace " ConwayEra" "" + . T.replace "value of type ConwayTxBodyRaw" "transaction body" + . T.replace "value of type BabbageTxBodyRaw" "transaction body" + . T.replace "value of type AlonzoTxBodyRaw" "transaction body" + . T.replace "value of type AllegraTxBodyRaw ()" "transaction body" + . T.replace "value of type AllegraTxBodyRaw MultiAsset" "transaction body" + . T.replace "value of type ShelleyTxBodyRaw" "transaction body" + . T.replace "atbr" "" + . T.replace "stbr" "" + . T.replace "btbr" "" + . T.replace "ctbr" "" diff --git a/testgen-hs/Evaluation.hs b/testgen-hs/Evaluation.hs index 2f0bb9b..ab4afe8 100644 --- a/testgen-hs/Evaluation.hs +++ b/testgen-hs/Evaluation.hs @@ -1,48 +1,45 @@ -{-# OPTIONS_GHC -Wno-orphans #-} -{-# LANGUAGE RankNTypes #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE DataKinds #-} +{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE PatternSynonyms #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# OPTIONS_GHC -Wno-orphans #-} module Evaluation ( WrappedTransactionScriptFailure (..), writeJson, - eval'Conway + eval'Conway, ) where +import CLI (GenSize (..), NumCases (..), Seed (..)) +import Cardano.Api.Internal.Orphans () +import Cardano.Ledger.Alonzo.Plutus.Evaluate (evalTxExUnits) +import Cardano.Ledger.Alonzo.Scripts (AsIx (..), ExUnits (..)) import Cardano.Ledger.Api (ConwayEra, PParams, TransactionScriptFailure) import qualified Cardano.Ledger.Api as Ledger -import CLI (GenSize (..), NumCases (..), Seed (..)) +import Cardano.Ledger.Api.Tx (PlutusPurpose, RedeemerReport, Tx) +import Cardano.Ledger.Api.UTxO (UTxO (..)) +import Cardano.Slotting.EpochInfo (EpochInfo) +import Cardano.Slotting.Slot () +import Cardano.Slotting.Time (SystemStart (..)) import Data.Aeson (ToJSON) import qualified Data.Aeson as J +import qualified Data.Aeson.Encoding as AesonEncoding import qualified Data.ByteString.Lazy.Char8 as BL8 -import Data.Proxy (Proxy) +import Data.List (sortOn) import qualified Data.Map as Map -import Cardano.Api.Internal.Orphans () +import Data.Proxy (Proxy) +import Data.Text (Text) +import Encoder (ogmiosSuccess, serializeTransactionScriptFailure) import PlutusCore.Evaluation.Machine.ExBudget (ExBudget (..)) import PlutusCore.Evaluation.Machine.ExMemory (ExCPU (..), ExMemory (..)) +import Test.Consensus.Cardano.Generators () import qualified Test.QuickCheck as QC import qualified Test.QuickCheck.Gen as QGen import qualified Test.QuickCheck.Random as QCRandom -import Test.Consensus.Cardano.Generators () -import Cardano.Ledger.Api.Tx ( PlutusPurpose, RedeemerReport, Tx) -import Cardano.Slotting.EpochInfo (EpochInfo) -import Cardano.Slotting.Slot () -import Cardano.Slotting.Time (SystemStart (..)) -import Data.Text (Text) -import Cardano.Ledger.Api.UTxO (UTxO (..)) -import Cardano.Ledger.Alonzo.Plutus.Evaluate (evalTxExUnits) - -import Cardano.Ledger.Alonzo.Scripts ( AsIx (..),ExUnits (..),) -import qualified Data.Aeson.Encoding as AesonEncoding -import Encoder(serializeTransactionScriptFailure, ogmiosSuccess) -import Data.List (sortOn) - - newtype WrappedTransactionScriptFailure era = WrappedTransactionScriptFailure { unWrappedTransactionScriptFailure :: TransactionScriptFailure era @@ -57,7 +54,6 @@ instance ToJSON (WrappedTransactionScriptFailure ConwayEra) where Nothing -> error "serializeTransactionScriptFailure produced invalid JSON" - instance QC.Arbitrary (Ledger.TransactionScriptFailure Ledger.ConwayEra) where arbitrary = QC.oneof @@ -88,22 +84,21 @@ writeJson _ (Seed seed) (GenSize size) (NumCases numCases) = do eval'Conway :: PParams ConwayEra -> - Tx ConwayEra -> + Tx ConwayEra -> UTxO ConwayEra -> - EpochInfo (Either Text) -> + EpochInfo (Either Text) -> SystemStart -> J.Value eval'Conway pparams tx utxo epochInfo systemStart = case J.decode (AesonEncoding.encodingToLazyByteString (ogmiosSuccess redeemerReport)) of Just v -> v Nothing -> error "ogmiosSuccess produced invalid JSON" - where redeemerReport :: RedeemerReport ConwayEra redeemerReport = selectSingleReport fullReport fullReport :: RedeemerReport ConwayEra - fullReport = evalTxExUnits pparams tx utxo epochInfo systemStart + fullReport = evalTxExUnits pparams tx utxo epochInfo systemStart -- Collapse the full report down to a single entry, preferring failures. selectSingleReport :: RedeemerReport ConwayEra -> RedeemerReport ConwayEra @@ -120,11 +115,12 @@ eval'Conway pparams tx utxo epochInfo systemStart = where (failures, successes) = Map.foldrWithKey groupReports (Map.empty, Map.empty) report - groupReports :: Ord (PlutusPurpose AsIx era) - => PlutusPurpose AsIx era - -> Either (Ledger.TransactionScriptFailure era) ExUnits - -> (Map.Map (PlutusPurpose AsIx era) [Ledger.TransactionScriptFailure era ], Map.Map (PlutusPurpose AsIx era) ExUnits) - -> (Map.Map (PlutusPurpose AsIx era) [Ledger.TransactionScriptFailure era], Map.Map (PlutusPurpose AsIx era) ExUnits) + groupReports :: + (Ord (PlutusPurpose AsIx era)) => + PlutusPurpose AsIx era -> + Either (Ledger.TransactionScriptFailure era) ExUnits -> + (Map.Map (PlutusPurpose AsIx era) [Ledger.TransactionScriptFailure era], Map.Map (PlutusPurpose AsIx era) ExUnits) -> + (Map.Map (PlutusPurpose AsIx era) [Ledger.TransactionScriptFailure era], Map.Map (PlutusPurpose AsIx era) ExUnits) groupReports purpose result (failures, successes) = case result of Left scriptFail -> (Map.unionWith (++) (Map.singleton purpose [scriptFail]) failures, successes) @@ -132,23 +128,23 @@ eval'Conway pparams tx utxo epochInfo systemStart = -- | Return the most relevant script failure from a list of errors. pickScriptFailure :: - [Ledger.TransactionScriptFailure era] - -> Ledger.TransactionScriptFailure era + [Ledger.TransactionScriptFailure era] -> + Ledger.TransactionScriptFailure era pickScriptFailure xs = case sortOn scriptFailurePriority xs of [] -> error "Empty list of script failures from the ledger!?" x : _ -> x where scriptFailurePriority :: - Ledger.TransactionScriptFailure era - -> Word + Ledger.TransactionScriptFailure era -> + Word scriptFailurePriority = \case - Ledger.UnknownTxIn{} -> 0 - Ledger.MissingScript{} -> 0 - Ledger.RedeemerPointsToUnknownScriptHash{} -> 1 - Ledger.NoCostModelInLedgerState{} -> 1 - Ledger.InvalidTxIn{} -> 2 - Ledger.MissingDatum{} -> 3 - Ledger.ContextError{} -> 4 - Ledger.ValidationFailure{} -> 5 - Ledger.IncompatibleBudget{} -> 999 + Ledger.UnknownTxIn {} -> 0 + Ledger.MissingScript {} -> 0 + Ledger.RedeemerPointsToUnknownScriptHash {} -> 1 + Ledger.NoCostModelInLedgerState {} -> 1 + Ledger.InvalidTxIn {} -> 2 + Ledger.MissingDatum {} -> 3 + Ledger.ContextError {} -> 4 + Ledger.ValidationFailure {} -> 5 + Ledger.IncompatibleBudget {} -> 999 diff --git a/testgen-hs/Generators.hs b/testgen-hs/Generators.hs index 0f7071d..612453c 100644 --- a/testgen-hs/Generators.hs +++ b/testgen-hs/Generators.hs @@ -1,5 +1,5 @@ -{-# OPTIONS_GHC -fno-warn-orphans #-} {-# LANGUAGE FlexibleInstances #-} +{-# OPTIONS_GHC -fno-warn-orphans #-} module Generators where @@ -24,6 +24,7 @@ import Data.Text (Text) import qualified Data.Text.Encoding as T import qualified Data.Text.Encoding.Error as T import Data.Typeable (Typeable, typeOf) +import Evaluation () import GHC.Generics (Generic) import Generic.Random (GenericArbitraryU (..)) import qualified Ouroboros.Consensus.Byron.Ledger as OCBL @@ -40,7 +41,6 @@ import qualified SynthEvalTx import Test.Consensus.Cardano.Generators () import Test.QuickCheck (Arbitrary) import qualified Test.QuickCheck as QC -import Evaluation () -- | We define our own type class, to be able to include multiple complex -- encoders for our `newtype` wrappers under a single interface. diff --git a/testgen-hs/Main.hs b/testgen-hs/Main.hs index b8c6072..7244e04 100644 --- a/testgen-hs/Main.hs +++ b/testgen-hs/Main.hs @@ -1,7 +1,6 @@ -{-# LANGUAGE TypeApplications #-} -{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE DeriveGeneric #-} - +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE TypeApplications #-} module Main where @@ -9,7 +8,9 @@ import CLI (GenSize (..), NumCases (..), Seed (..)) import qualified CLI import Cardano.Binary (FromCBOR, decodeFull') import Cardano.Ledger.Api (ConwayEra, PParams) +import qualified Cardano.Ledger.Binary.Decoding as Binary import qualified Cardano.Ledger.Core +import qualified Cardano.Ledger.Core as Ledger import Cardano.Slotting.EpochInfo (EpochInfo, fixedEpochInfo) import Cardano.Slotting.Slot (EpochSize (..)) import Cardano.Slotting.Time (SystemStart (..), mkSlotLength) @@ -22,11 +23,11 @@ import qualified Data.Aeson as J import qualified Data.Aeson.Encode.Pretty as J import Data.Bifunctor (first) import Data.ByteString (ByteString) +import qualified Data.ByteString as BS import qualified Data.ByteString.Base16 as B16 import qualified Data.ByteString.Char8 as B8 import qualified Data.ByteString.Lazy as BL import qualified Data.ByteString.Lazy.Char8 as BL8 -import qualified Data.ByteString as BS import Data.Foldable (foldl') import Data.Proxy (Proxy (..)) import Data.Text (Text) @@ -37,21 +38,19 @@ import Data.Time (NominalDiffTime) import Data.Time.Clock.POSIX (getPOSIXTime) import Data.Word (Word16, Word64) import qualified Deserialize as D +import Encoder (serializeDecoderError) +import Evaluation (eval'Conway, writeJson) import GHC.Generics (Generic) import qualified Generators as G import System.Exit (exitFailure, exitSuccess) import System.IO (hPutStrLn, stderr) import qualified System.IO as SIO import qualified System.Random -import Evaluation (writeJson, eval'Conway) -import Encoder (serializeDecoderError) import Test.QuickCheck (Arbitrary) import qualified Test.QuickCheck as QC import qualified Test.QuickCheck.Gen as QC import Test.QuickCheck.Instances.Text () import qualified Test.QuickCheck.Random as QC -import qualified Cardano.Ledger.Binary.Decoding as Binary -import qualified Cardano.Ledger.Core as Ledger main :: IO () main = @@ -81,8 +80,8 @@ data TestCase a = TestCase -- This data type implements Generic, Show and FromJSON type classes for serialization -- and debugging purposes. data InitPayload = InitPayload - { systemStart :: Text, --cbor - protocolParams :: Text, --cbor + { systemStart :: Text, -- cbor + protocolParams :: Text, -- cbor slotConfig :: SlotConfig, era :: Word16 } @@ -96,9 +95,9 @@ data SlotConfig = SlotConfig } deriving (Generic, Show, FromJSON) - -- This is used as a generic response to any incoming request. +-- This is used as a generic response to any incoming request. data PayloadResponse = PayloadResponse - { rError :: Maybe Text, + { rError :: Maybe Text, rJson :: Maybe J.Value } deriving (Generic, Show) @@ -110,7 +109,6 @@ instance ToJSON PayloadResponse where modifier "rJson" = "json" modifier s = s - -- | -- EvalPayload represents the data structure for transaction evaluation payload. -- @@ -263,10 +261,11 @@ runEvaluateStream = do line <- B8.getLine case J.eitherDecodeStrict line of Left err -> do - BL8.putStrLn . J.encode $ PayloadResponse - { rJson = Nothing, - rError = Just . T.pack $ "Expected InitPayload first, but failed to parse line: " ++ err - } + BL8.putStrLn . J.encode $ + PayloadResponse + { rJson = Nothing, + rError = Just . T.pack $ "Expected InitPayload first, but failed to parse line: " ++ err + } runEvaluateStream Right initPayload -> do let ei = convertEpochInfo (slotConfig initPayload) @@ -277,13 +276,14 @@ runEvaluateStream = do case combinedResult of Left err -> do - BL8.putStrLn . J.encode $ PayloadResponse - { rJson = Nothing, - rError = Just . T.pack $ "Failed to decode initial payload " ++ err - } + BL8.putStrLn . J.encode $ + PayloadResponse + { rJson = Nothing, + rError = Just . T.pack $ "Failed to decode initial payload " ++ err + } exitFailure Right (pp, ss) -> do - BL8.putStrLn . J.encode $ PayloadResponse { rJson = Just (J.object []), rError = Nothing } + BL8.putStrLn . J.encode $ PayloadResponse {rJson = Just (J.object []), rError = Nothing} forever $ processLine pp ss ei where processLine :: PParams ConwayEra -> SystemStart -> EpochInfo (Either Text) -> IO () @@ -294,8 +294,8 @@ runEvaluateStream = do case J.eitherDecodeStrict line of Left err -> do let response = case J.eitherDecodeStrict' (B8.pack err) of - Left decodeErr -> PayloadResponse { rJson = Nothing, rError = Just (T.pack $ "Failed to decode error as JSON: " ++ decodeErr ++ ". Original error: " ++ err) } - Right jsonVal -> PayloadResponse { rJson = Nothing, rError = Just jsonVal } + Left decodeErr -> PayloadResponse {rJson = Nothing, rError = Just (T.pack $ "Failed to decode error as JSON: " ++ decodeErr ++ ". Original error: " ++ err)} + Right jsonVal -> PayloadResponse {rJson = Nothing, rError = Just jsonVal} BL8.putStrLn . J.encode $ response Right (evalPayload :: EvalPayload) -> do let decodedValues = do @@ -314,7 +314,7 @@ runEvaluateStream = do BL8.putStrLn . J.encode $ response Right (tx, utxos) -> do let result = eval'Conway pp tx utxos ei ss - BL8.putStrLn . J.encode $ PayloadResponse { rJson = Just result, rError = Nothing } + BL8.putStrLn . J.encode $ PayloadResponse {rJson = Just result, rError = Nothing} -- | Creates an EpochInfo from the given SlotConfig convertEpochInfo :: SlotConfig -> EpochInfo (Either Text) @@ -330,17 +330,20 @@ decodeFromHex hexText = do -- 2. Decode from CBOR. first show $ decodeFull' cborBytes - -- Run a CBOR decoder for data in Conway era -decodeCborWith - :: Text -- ^ Label for error reporting - -> (Binary.DecoderError -> Either e a) -- ^ Error handler - -> (forall s. Binary.Decoder s a) -- ^ CBOR decoder - -> ByteString -- ^ Input bytes - -> Either e a +decodeCborWith :: + -- | Label for error reporting + Text -> + -- | Error handler + (Binary.DecoderError -> Either e a) -> + -- | CBOR decoder + (forall s. Binary.Decoder s a) -> + -- | Input bytes + ByteString -> + Either e a decodeCborWith lbl handleErr decoder bytes = - case Binary.decodeFullDecoder version lbl decoder (BL.fromStrict bytes) of - Left cborErr -> handleErr cborErr - Right val -> Right val + case Binary.decodeFullDecoder version lbl decoder (BL.fromStrict bytes) of + Left cborErr -> handleErr cborErr + Right val -> Right val where version = Ledger.eraProtVerLow @ConwayEra diff --git a/testgen-hs/Response.hs b/testgen-hs/Response.hs index 1a2fe85..301aac8 100644 --- a/testgen-hs/Response.hs +++ b/testgen-hs/Response.hs @@ -1,6 +1,7 @@ module Response ( PayloadResponse (..), - ) where + ) +where import Data.Aeson (ToJSON) import qualified Data.Aeson as J diff --git a/testgen-hs/SynthEvalTx.hs b/testgen-hs/SynthEvalTx.hs index 4173212..17b3e3e 100644 --- a/testgen-hs/SynthEvalTx.hs +++ b/testgen-hs/SynthEvalTx.hs @@ -83,7 +83,7 @@ genTxUTxO = do proof = Proof.Conway eval'Conway :: - (Cardano.Ledger.Core.Tx (Cardano.Ledger.Api.Era.ConwayEra)) -> + (Cardano.Ledger.Core.Tx (Cardano.Ledger.Api.Era.ConwayEra)) -> UTxO (Cardano.Ledger.Api.Era.ConwayEra) -> EpochInfo (Either Text) -> SystemStart -> diff --git a/testgen-hs/testgen-hs.cabal b/testgen-hs/testgen-hs.cabal index f9d6278..6d281fc 100644 --- a/testgen-hs/testgen-hs.cabal +++ b/testgen-hs/testgen-hs.cabal @@ -18,21 +18,17 @@ executable testgen-hs , bytestring , cardano-api , cardano-binary - --, cardano-crypto , cardano-crypto-class - --, cardano-crypto-wrapper , cardano-ledger-alonzo , cardano-ledger-api - , cardano-ledger-binary , cardano-ledger-babbage + , cardano-ledger-binary , cardano-ledger-byron , cardano-ledger-conway , cardano-ledger-core , cardano-ledger-test , cardano-slotting , cardano-submit-api - , plutus-core - , plutus-ledger-api , cborg , containers , data-default @@ -43,23 +39,27 @@ executable testgen-hs , ouroboros-consensus , ouroboros-consensus-cardano , ouroboros-consensus-cardano:unstable-cardano-testlib + , plutus-core + , plutus-ledger-api + , prettyprinter , QuickCheck , quickcheck-instances , random , serialise , text , time - , prettyprinter + --, cardano-crypto + --, cardano-crypto-wrapper other-modules: CLI Deserialize + Encoder + Evaluation Generators Paths_testgen_hs - SynthEvalTx - Evaluation Response - Encoder + SynthEvalTx default-extensions: DeriveAnyClass From dfa705889ddbfcfbacd487a3b9589123c02f7d03 Mon Sep 17 00:00:00 2001 From: Michal Rus Date: Thu, 12 Feb 2026 10:38:16 +0100 Subject: [PATCH 10/10] =?UTF-8?q?fix:=20disable=20`yamlfmt`=20on=20`x86=5F?= =?UTF-8?q?64-darwin`=20=E2=80=93=20it=E2=80=99s=20broken?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index a42318f..60d2d4d 100644 --- a/flake.nix +++ b/flake.nix @@ -51,7 +51,7 @@ programs.ormolu.enable = true; # Haskell programs.cabal-fmt.enable = true; programs.shfmt.enable = true; - programs.yamlfmt.enable = true; + programs.yamlfmt.enable = pkgs.system != "x86_64-darwin"; # a treefmt-nix+yamlfmt bug on Intel Macs }; };