module Conformance.Refusal
( RefusalMismatch (..)
, RefusalRole (..)
, TracedRefusal (..)
, untraced
, attributeRefusalReceipt
, matchRefusal
, refusalScriptHashes
, refusalWritesReceipt
, trimRefusal
, wrongReasonMarker
) where
import Conformance.Receipt
( Outcome (..)
, Receipt (..)
, RefusalInfo (..)
, ReplayCorrespondence (..)
, ReplayEvidence (..)
, Verdict (..)
, writeReceiptFile
)
import Control.Monad (when)
import Data.Char (isHexDigit)
import Data.List (intercalate, isInfixOf, isPrefixOf, nub, sortOn)
import Data.Maybe (catMaybes)
import Data.Text qualified as T
data RefusalMismatch
=
NotPhase2
{ RefusalMismatch -> String
refusalReason :: String
}
|
MarkerAbsent
{ RefusalMismatch -> String
refusalMarker :: String
, refusalReason :: String
}
deriving stock (Int -> RefusalMismatch -> ShowS
[RefusalMismatch] -> ShowS
RefusalMismatch -> String
(Int -> RefusalMismatch -> ShowS)
-> (RefusalMismatch -> String)
-> ([RefusalMismatch] -> ShowS)
-> Show RefusalMismatch
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> RefusalMismatch -> ShowS
showsPrec :: Int -> RefusalMismatch -> ShowS
$cshow :: RefusalMismatch -> String
show :: RefusalMismatch -> String
$cshowList :: [RefusalMismatch] -> ShowS
showList :: [RefusalMismatch] -> ShowS
Show, RefusalMismatch -> RefusalMismatch -> Bool
(RefusalMismatch -> RefusalMismatch -> Bool)
-> (RefusalMismatch -> RefusalMismatch -> Bool)
-> Eq RefusalMismatch
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: RefusalMismatch -> RefusalMismatch -> Bool
== :: RefusalMismatch -> RefusalMismatch -> Bool
$c/= :: RefusalMismatch -> RefusalMismatch -> Bool
/= :: RefusalMismatch -> RefusalMismatch -> Bool
Eq)
matchRefusal :: String -> String -> Either RefusalMismatch ()
matchRefusal :: String -> String -> Either RefusalMismatch ()
matchRefusal String
marker String
reason
| Bool -> Bool
not (String -> Bool
isPhase2 String
reason) =
RefusalMismatch -> Either RefusalMismatch ()
forall a b. a -> Either a b
Left (String -> RefusalMismatch
NotPhase2 String
reason)
| String
marker String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` String -> [String]
refusalScriptHashes String
reason =
RefusalMismatch -> Either RefusalMismatch ()
forall a b. a -> Either a b
Left (String -> String -> RefusalMismatch
MarkerAbsent String
marker String
reason)
| Bool
otherwise = () -> Either RefusalMismatch ()
forall a b. b -> Either a b
Right ()
isPhase2 :: String -> Bool
isPhase2 :: String -> Bool
isPhase2 String
reason =
String
"PlutusFailure" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isInfixOf` String
reason
Bool -> Bool -> Bool
|| String
"CekError" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isInfixOf` String
reason
trimRefusal :: String -> String
trimRefusal :: ShowS
trimRefusal String
text =
case [String]
parts of
[] -> Int -> ShowS
forall a. Int -> [a] -> [a]
take Int
500 String
text String -> ShowS
forall a. Semigroup a => a -> a -> a
<> String
" [unparsed]"
[String]
_ -> Int -> ShowS
forall a. Int -> [a] -> [a]
take Int
2000 (String -> [String] -> String
forall a. [a] -> [[a]] -> [a]
intercalate String
" | " [String]
parts)
where
parts :: [String]
parts =
[Maybe String] -> [String]
forall a. [Maybe a] -> [a]
catMaybes
[ String -> Maybe String
evalHead String
text
, String -> Maybe String
plutusFailed String
text
, String -> Maybe String
scriptHashes String
text
, String -> Maybe String
cekError String
text
]
evalHead :: String -> Maybe String
evalHead :: String -> Maybe String
evalHead String
text
| String
"\\\"ValidationFailure" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isInfixOf` String
text =
String -> Maybe String
forall a. a -> Maybe a
Just (String -> ShowS
takeUntil String
"\\\"ValidationFailure" String
text)
| Bool
otherwise = Maybe String
forall a. Maybe a
Nothing
plutusFailed :: String -> Maybe String
plutusFailed :: String -> Maybe String
plutusFailed String
text
| String
"The PlutusV3 script failed" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isInfixOf` String
text =
String -> Maybe String
forall a. a -> Maybe a
Just String
"PlutusV3 script failed (node-submit)"
| Bool
otherwise = Maybe String
forall a. Maybe a
Nothing
refusalScriptHashes :: String -> [String]
refusalScriptHashes :: String -> [String]
refusalScriptHashes String
text = [String] -> [String]
forall a. Eq a => [a] -> [a]
nub ((String -> Bool) -> [String] -> [String]
forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not (Bool -> Bool) -> (String -> Bool) -> String -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null) (String -> [String]
go String
text))
where
go :: String -> [String]
go String
s = case String -> String -> Maybe String
findAfter String
hashMarker String
s of
Maybe String
Nothing -> []
Just String
after ->
let digits :: String
digits = (Char -> Bool) -> ShowS
forall a. (a -> Bool) -> [a] -> [a]
dropWhile Char -> Bool
isQuoteOrEscape String
after
h :: String
h = (Char -> Bool) -> ShowS
forall a. (a -> Bool) -> [a] -> [a]
takeWhile Char -> Bool
isHexDigit String
digits
in String
h String -> [String] -> [String]
forall a. a -> [a] -> [a]
: String -> [String]
go (Int -> ShowS
forall a. Int -> [a] -> [a]
drop (String -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length String
h) String
digits)
hashMarker :: String
hashMarker = String
"ScriptHash "
isQuoteOrEscape :: Char -> Bool
isQuoteOrEscape Char
c = Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'"' Bool -> Bool -> Bool
|| Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'\\'
scriptHashes :: String -> Maybe String
scriptHashes :: String -> Maybe String
scriptHashes String
text = case String -> [String]
refusalScriptHashes String
text of
[] -> Maybe String
forall a. Maybe a
Nothing
[String]
hs -> String -> Maybe String
forall a. a -> Maybe a
Just (String
"scriptHash=" String -> ShowS
forall a. Semigroup a => a -> a -> a
<> String -> [String] -> String
forall a. [a] -> [[a]] -> [a]
intercalate String
"," [String]
hs)
cekError :: String -> Maybe String
cekError :: String -> Maybe String
cekError String
text = do
after <- String -> String -> Maybe String
findAfter String
"CekError " String
text
pure
( "cek="
<> take 500 (takeUntilAny [") []", "\\nThe protocol version is:"] after)
)
findAfter :: String -> String -> Maybe String
findAfter :: String -> String -> Maybe String
findAfter String
marker String
text =
case String -> ShowS
forall {a}. Eq a => [a] -> [a] -> [a]
dropUntil String
marker String
text of
[] -> Maybe String
forall a. Maybe a
Nothing
String
rest -> String -> Maybe String
forall a. a -> Maybe a
Just (Int -> ShowS
forall a. Int -> [a] -> [a]
drop (String -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length String
marker) String
rest)
where
dropUntil :: [a] -> [a] -> [a]
dropUntil [a]
_ [] = []
dropUntil [a]
m s :: [a]
s@(a
_ : [a]
cs)
| [a]
m [a] -> [a] -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` [a]
s = [a]
s
| Bool
otherwise = [a] -> [a] -> [a]
dropUntil [a]
m [a]
cs
takeUntil :: String -> String -> String
takeUntil :: String -> ShowS
takeUntil String
end String
text =
case String -> ShowS
forall {a}. Eq a => [a] -> [a] -> [a]
dropUntil String
end String
text of
[] -> String
text
String
rest -> Int -> ShowS
forall a. Int -> [a] -> [a]
take (String -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length String
text Int -> Int -> Int
forall a. Num a => a -> a -> a
- String -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length String
rest) String
text
where
dropUntil :: [a] -> [a] -> [a]
dropUntil [a]
_ [] = []
dropUntil [a]
m s :: [a]
s@(a
_ : [a]
cs)
| [a]
m [a] -> [a] -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` [a]
s = [a]
s
| Bool
otherwise = [a] -> [a] -> [a]
dropUntil [a]
m [a]
cs
takeUntilAny :: [String] -> String -> String
takeUntilAny :: [String] -> ShowS
takeUntilAny [String]
ends String
text =
case (String -> Int) -> [String] -> [String]
forall b a. Ord b => (a -> b) -> [a] -> [a]
sortOn String -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [String -> ShowS
takeUntil String
e String
text | String
e <- [String]
ends, String
e String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isInfixOf` String
text] of
[] -> String
text
(String
shortest : [String]
_) -> String
shortest
wrongReasonMarker :: String
wrongReasonMarker :: String
wrongReasonMarker =
String
"wrong-reason-control marker that no node reason can ever contain"
data RefusalRole = RefusalRow | RefusalControl
deriving stock (Int -> RefusalRole -> ShowS
[RefusalRole] -> ShowS
RefusalRole -> String
(Int -> RefusalRole -> ShowS)
-> (RefusalRole -> String)
-> ([RefusalRole] -> ShowS)
-> Show RefusalRole
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> RefusalRole -> ShowS
showsPrec :: Int -> RefusalRole -> ShowS
$cshow :: RefusalRole -> String
show :: RefusalRole -> String
$cshowList :: [RefusalRole] -> ShowS
showList :: [RefusalRole] -> ShowS
Show, RefusalRole -> RefusalRole -> Bool
(RefusalRole -> RefusalRole -> Bool)
-> (RefusalRole -> RefusalRole -> Bool) -> Eq RefusalRole
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: RefusalRole -> RefusalRole -> Bool
== :: RefusalRole -> RefusalRole -> Bool
$c/= :: RefusalRole -> RefusalRole -> Bool
/= :: RefusalRole -> RefusalRole -> Bool
Eq)
refusalWritesReceipt :: RefusalRole -> Bool
refusalWritesReceipt :: RefusalRole -> Bool
refusalWritesReceipt RefusalRole
role = case RefusalRole
role of
RefusalRole
RefusalRow -> Bool
True
RefusalRole
RefusalControl -> Bool
False
data TracedRefusal = TracedRefusal
{ TracedRefusal -> Maybe Text
tracedAdmitted :: Maybe T.Text
, TracedRefusal -> [ReplayEvidence]
tracedReplay :: [ReplayEvidence]
, TracedRefusal -> Maybe ReplayCorrespondence
tracedCorrespondence :: Maybe ReplayCorrespondence
}
deriving stock (Int -> TracedRefusal -> ShowS
[TracedRefusal] -> ShowS
TracedRefusal -> String
(Int -> TracedRefusal -> ShowS)
-> (TracedRefusal -> String)
-> ([TracedRefusal] -> ShowS)
-> Show TracedRefusal
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> TracedRefusal -> ShowS
showsPrec :: Int -> TracedRefusal -> ShowS
$cshow :: TracedRefusal -> String
show :: TracedRefusal -> String
$cshowList :: [TracedRefusal] -> ShowS
showList :: [TracedRefusal] -> ShowS
Show, TracedRefusal -> TracedRefusal -> Bool
(TracedRefusal -> TracedRefusal -> Bool)
-> (TracedRefusal -> TracedRefusal -> Bool) -> Eq TracedRefusal
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: TracedRefusal -> TracedRefusal -> Bool
== :: TracedRefusal -> TracedRefusal -> Bool
$c/= :: TracedRefusal -> TracedRefusal -> Bool
/= :: TracedRefusal -> TracedRefusal -> Bool
Eq)
untraced :: TracedRefusal
untraced :: TracedRefusal
untraced = Maybe Text
-> [ReplayEvidence] -> Maybe ReplayCorrespondence -> TracedRefusal
TracedRefusal Maybe Text
forall a. Maybe a
Nothing [] Maybe ReplayCorrespondence
forall a. Maybe a
Nothing
attributeRefusalReceipt
:: RefusalRole
-> FilePath
-> String
-> Verdict
-> String
-> String
-> String
-> String
-> String
-> Bool
-> String
-> String
-> TracedRefusal
-> IO (Either RefusalMismatch ())
attributeRefusalReceipt :: RefusalRole
-> String
-> String
-> Verdict
-> String
-> String
-> String
-> String
-> String
-> Bool
-> String
-> String
-> TracedRefusal
-> IO (Either RefusalMismatch ())
attributeRefusalReceipt RefusalRole
role String
dir String
row Verdict
verdict String
script String
marker String
text String
rejectedTxid String
base Bool
dirty String
node String
blueprint TracedRefusal
traced =
case String -> String -> Either RefusalMismatch ()
matchRefusal String
marker String
text of
Left RefusalMismatch
m -> Either RefusalMismatch () -> IO (Either RefusalMismatch ())
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (RefusalMismatch -> Either RefusalMismatch ()
forall a b. a -> Either a b
Left RefusalMismatch
m)
Right () -> do
let recorded :: String
recorded = String -> ShowS
recordedReason String
text String
marker
admitted :: Maybe Text
admitted = TracedRefusal -> Maybe Text
tracedAdmitted TracedRefusal
traced
replay :: Maybe [ReplayEvidence]
replay = case TracedRefusal -> [ReplayEvidence]
tracedReplay TracedRefusal
traced of
[] -> Maybe [ReplayEvidence]
forall a. Maybe a
Nothing
[ReplayEvidence]
entries -> [ReplayEvidence] -> Maybe [ReplayEvidence]
forall a. a -> Maybe a
Just [ReplayEvidence]
entries
Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (RefusalRole -> Bool
refusalWritesReceipt RefusalRole
role) (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$
String -> Receipt -> IO ()
writeReceiptFile
String
dir
Receipt
{ receiptRow :: Text
receiptRow = String -> Text
T.pack String
row
, receiptOutcome :: Outcome
receiptOutcome = Outcome
Refused
, receiptVerdict :: Verdict
receiptVerdict = Verdict
verdict
, receiptTransactions :: [Text]
receiptTransactions = []
, receiptRefusal :: Maybe RefusalInfo
receiptRefusal =
RefusalInfo -> Maybe RefusalInfo
forall a. a -> Maybe a
Just
( RefusalInfo
{ refusalScript :: Text
refusalScript = String -> Text
T.pack String
script
, refusalReason :: Text
refusalReason = String -> Text
T.pack String
recorded
, refusalPhase :: Text
refusalPhase = Text
"phase-2"
, refusalHashes :: [Text]
refusalHashes =
(String -> Text) -> [String] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map String -> Text
T.pack (String -> [String]
refusalScriptHashes String
text)
, refusalBranch :: Maybe Text
refusalBranch = Maybe Text
admitted
, refusalReplay :: Maybe [ReplayEvidence]
refusalReplay = Maybe [ReplayEvidence]
replay
, refusalLimit :: Maybe Text
refusalLimit = case Maybe Text
admitted of
Just Text
_ -> Maybe Text
forall a. Maybe a
Nothing
Maybe Text
Nothing ->
Text -> Maybe Text
forall a. a -> Maybe a
Just
Text
"no named validator branch in this compiled trace; attribution is script hash plus phase-2 only"
}
)
, receiptMem :: Maybe Integer
receiptMem = Maybe Integer
forall a. Maybe a
Nothing
, receiptCpu :: Maybe Integer
receiptCpu = Maybe Integer
forall a. Maybe a
Nothing
, receiptTxSize :: Maybe Integer
receiptTxSize = Maybe Integer
forall a. Maybe a
Nothing
, receiptBase :: Text
receiptBase = String -> Text
T.pack String
base
, receiptNode :: Text
receiptNode = String -> Text
T.pack String
node
, receiptBlueprint :: Text
receiptBlueprint = String -> Text
T.pack String
blueprint
, receiptVenue :: Text
receiptVenue = Text
"node-submit"
, receiptRejected :: Maybe Text
receiptRejected = Text -> Maybe Text
forall a. a -> Maybe a
Just (String -> Text
T.pack String
rejectedTxid)
, receiptDirty :: Bool
receiptDirty = Bool
dirty
, receiptPartial :: Maybe PartialInfo
receiptPartial = Maybe PartialInfo
forall a. Maybe a
Nothing
, receiptDerivation :: Maybe [DerivationEvidence]
receiptDerivation = Maybe [DerivationEvidence]
forall a. Maybe a
Nothing
, receiptSteps :: Maybe [Value]
receiptSteps = Maybe [Value]
forall a. Maybe a
Nothing
, receiptReplayCorrespondence :: Maybe ReplayCorrespondence
receiptReplayCorrespondence =
TracedRefusal -> Maybe ReplayCorrespondence
tracedCorrespondence TracedRefusal
traced Maybe ReplayCorrespondence
-> Maybe [ReplayEvidence] -> Maybe ReplayCorrespondence
forall a b. Maybe a -> Maybe b -> Maybe a
forall (f :: * -> *) a b. Applicative f => f a -> f b -> f a
<* Maybe [ReplayEvidence]
replay
}
Either RefusalMismatch () -> IO (Either RefusalMismatch ())
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (() -> Either RefusalMismatch ()
forall a b. b -> Either a b
Right ())
recordedReason :: String -> String -> String
recordedReason :: String -> ShowS
recordedReason String
text String
marker =
let trimmed :: String
trimmed = ShowS
trimRefusal String
text
in if String
marker String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isInfixOf` String
trimmed then String
trimmed else Int -> ShowS
forall a. Int -> [a] -> [a]
take Int
2000 String
text