add better server error messages, cbor vs raw hex

This commit is contained in:
Emily Martins 2022-06-27 13:30:27 +02:00
parent a7ed7989f1
commit 910416f4ab
7 changed files with 109 additions and 97 deletions

View file

@ -6,7 +6,8 @@
-} -}
module Data.Cache.Cached ( module Data.Cache.Cached (
cached, cached,
cachedFor, cachedM,
cachedForM,
) where ) where
import Control.Monad.IO.Class (MonadIO (liftIO)) import Control.Monad.IO.Class (MonadIO (liftIO))
@ -20,7 +21,14 @@ import System.Clock (TimeSpec)
Uses a HashMap under the hood. Uses a HashMap under the hood.
-} -}
cached :: (Monad m, MonadIO m, Hashable k, Ord k) => (k -> v) -> IO (k -> m v) cached :: (Monad m, MonadIO m, Hashable k, Ord k) => (k -> v) -> IO (k -> m v)
cached = cachedFor Nothing cached f = cachedForM Nothing (pure . f)
{- | 'cachedFor' but items last forever.
Uses a HashMap under the hood.
-}
cachedM :: (Monad m, MonadIO m, Hashable k, Ord k) => (k -> m v) -> IO (k -> m v)
cachedM = cachedForM Nothing
{- | Create a cached version of a function tainting result with MonadIO context. {- | Create a cached version of a function tainting result with MonadIO context.
@ -28,13 +36,13 @@ cached = cachedFor Nothing
Uses a HashMap under the hood. Uses a HashMap under the hood.
-} -}
cachedFor :: (Monad m, MonadIO m, Hashable k, Ord k) => Maybe TimeSpec -> (k -> v) -> IO (k -> m v) cachedForM :: (Monad m, MonadIO m, Hashable k, Ord k) => Maybe TimeSpec -> (k -> m v) -> IO (k -> m v)
cachedFor t f = cachedForM t f =
Cache.newCache t <&> \cache k -> do Cache.newCache t <&> \cache k -> do
res <- liftIO $ Cache.lookup cache k res <- liftIO $ Cache.lookup cache k
case res of case res of
Nothing -> do Nothing -> do
let v = f k v <- f k
liftIO $ Cache.insert cache k v liftIO $ Cache.insert cache k v
pure v pure v
Just v -> do Just v -> do

View file

@ -15,7 +15,7 @@ import Agora.Governor.Scripts (authorityTokenFromGovernor, authorityTokenSymbolF
import Agora.Proposal (Proposal) import Agora.Proposal (Proposal)
import Agora.Proposal.Scripts (proposalPolicy, proposalValidator) import Agora.Proposal.Scripts (proposalPolicy, proposalValidator)
import Agora.SafeMoney (GTTag) import Agora.SafeMoney (GTTag)
import Agora.ScriptInfo (PolicyInfo, ValidatorInfo, mkPolicyInfo, mkValidatorInfo) import Agora.ScriptInfo (ScriptInfo, mkPolicyInfo, mkValidatorInfo)
import Agora.Stake (Stake) import Agora.Stake (Stake)
import Agora.Stake.Scripts (stakePolicy, stakeValidator) import Agora.Stake.Scripts (stakePolicy, stakeValidator)
import Agora.Treasury (treasuryValidator) import Agora.Treasury (treasuryValidator)
@ -119,14 +119,14 @@ data ScriptParams where
@since 0.2.0 @since 0.2.0
-} -}
data AgoraScripts = AgoraScripts data AgoraScripts = AgoraScripts
{ governorPolicyInfo :: PolicyInfo { governorPolicyInfo :: ScriptInfo
, governorValidatorInfo :: ValidatorInfo , governorValidatorInfo :: ScriptInfo
, stakePolicyInfo :: PolicyInfo , stakePolicyInfo :: ScriptInfo
, stakeValidatorInfo :: ValidatorInfo , stakeValidatorInfo :: ScriptInfo
, proposalPolicyInfo :: PolicyInfo , proposalPolicyInfo :: ScriptInfo
, proposalValidatorInfo :: ValidatorInfo , proposalValidatorInfo :: ScriptInfo
, treasuryValidatorInfo :: ValidatorInfo , treasuryValidatorInfo :: ScriptInfo
, authorityTokenPolicyInfo :: PolicyInfo , authorityTokenPolicyInfo :: ScriptInfo
} }
deriving anyclass deriving anyclass
( -- | @since 0.2.0 ( -- | @since 0.2.0

View file

@ -11,7 +11,7 @@ module ScriptExport.API (
import Codec.Serialise.Orphans () import Codec.Serialise.Orphans ()
import Data.Aeson qualified as Aeson import Data.Aeson qualified as Aeson
import Data.Cache.Cached (cachedFor) import Data.Cache.Cached (cachedForM)
import Data.Function ((&)) import Data.Function ((&))
import Data.Proxy (Proxy (Proxy)) import Data.Proxy (Proxy (Proxy))
import Data.Text (Text) import Data.Text (Text)
@ -35,8 +35,11 @@ import Text.Printf (printf)
@since 0.2.0 @since 0.2.0
-} -}
type API = type API =
"query-script" :> ReqBody '[JSON] ScriptQuery :> Post '[JSON] Aeson.Value "query-script"
:<|> "info" :> Get '[JSON] ServerInfo :> ReqBody '[JSON] ScriptQuery
:> Post '[JSON] Aeson.Value
:<|> "info"
:> Get '[JSON] ServerInfo
{- | Information about the server. {- | Information about the server.
@ -86,7 +89,7 @@ runServer revision builders options = do
corsMiddleware = cors . const $ Just corsPolicy corsMiddleware = cors . const $ Just corsPolicy
-- Scripts stay cached for five minutes -- Scripts stay cached for five minutes
query <- cachedFor (Just $ TimeSpec 300 0) (`runQuery` builders) query <- cachedForM (Just $ TimeSpec 300 0) (`runQuery` builders)
let serverInfo = let serverInfo =
ServerInfo ServerInfo

View file

@ -14,6 +14,7 @@ module ScriptExport.Types (
) where ) where
import Data.Aeson qualified as Aeson import Data.Aeson qualified as Aeson
import Data.ByteString.Lazy.Char8 qualified as LBS
import Data.Coerce (coerce) import Data.Coerce (coerce)
import Data.Default.Class (Default (def)) import Data.Default.Class (Default (def))
import Data.Hashable (Hashable) import Data.Hashable (Hashable)
@ -21,6 +22,7 @@ import Data.Map.Strict (Map)
import Data.Map.Strict qualified as Map import Data.Map.Strict qualified as Map
import Data.Text (Text) import Data.Text (Text)
import GHC.Generics qualified as GHC import GHC.Generics qualified as GHC
import Servant qualified
{- | Query data for getting script info. {- | Query data for getting script info.
@ -55,16 +57,20 @@ data ScriptQuery = ScriptQuery
@since 0.2.0 @since 0.2.0
-} -}
runQuery :: ScriptQuery -> Builders -> Aeson.Value runQuery :: ScriptQuery -> Builders -> Servant.Handler Aeson.Value
runQuery s = runQuery s =
maybe Aeson.Null ($ s.paramsPayload) . Map.lookup s.name . getBuilders maybe
(Servant.throwError Servant.err404 {Servant.errBody = "Builder not found"})
($ s.paramsPayload)
. Map.lookup s.name
. getBuilders
{- | Represents a list of named pure functions. {- | Represents a list of named pure functions.
@since 0.2.0 @since 0.2.0
-} -}
newtype Builders = Builders newtype Builders = Builders
{ getBuilders :: Map Text (Aeson.Value -> Aeson.Value) { getBuilders :: Map Text (Aeson.Value -> Servant.Handler Aeson.Value)
} }
-- | @since 0.2.0 -- | @since 0.2.0
@ -84,7 +90,19 @@ insertBuilder ::
Builders Builders
insertBuilder k = coerce . Map.insert k . throughJSON insertBuilder k = coerce . Map.insert k . throughJSON
where where
throughJSON f = Aeson.toJSON . \case { Aeson.Error _ -> Nothing; Aeson.Success v -> Just (f v) } . Aeson.fromJSON throughJSON ::
forall p s.
(Aeson.FromJSON p, Aeson.ToJSON s) =>
(p -> s) ->
(Aeson.Value -> Servant.Handler Aeson.Value)
throughJSON f v =
case Aeson.fromJSON v of
Aeson.Error e ->
Servant.throwError $
Servant.err400
{ Servant.errBody = LBS.pack e
}
Aeson.Success v' -> pure . Aeson.toJSON $ f v'
{- | Get a list of the available builders. {- | Get a list of the available builders.

View file

@ -91,7 +91,9 @@ common deps
, ansi-terminal , ansi-terminal
, base >=4.14 && <5 , base >=4.14 && <5
, base-compat , base-compat
, base16
, bytestring , bytestring
, cardano-binary
, cardano-prelude , cardano-prelude
, containers , containers
, data-default , data-default

View file

@ -20,6 +20,7 @@ import Data.Text.Encoding qualified as T
import PlutusLedgerApi.V1 qualified as Plutus import PlutusLedgerApi.V1 qualified as Plutus
import PlutusLedgerApi.V1.Bytes qualified as Plutus import PlutusLedgerApi.V1.Bytes qualified as Plutus
import PlutusLedgerApi.V1.Scripts qualified as Plutus
import PlutusLedgerApi.V1.Value qualified as Plutus import PlutusLedgerApi.V1.Value qualified as Plutus
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@ -109,6 +110,15 @@ deriving via
instance instance
(Aeson.FromJSON Plutus.ValidatorHash) (Aeson.FromJSON Plutus.ValidatorHash)
deriving via
(AsBase16Bytes Plutus.ScriptHash)
instance
(Aeson.ToJSON Plutus.ScriptHash)
deriving via
(AsBase16Bytes Plutus.ScriptHash)
instance
(Aeson.FromJSON Plutus.ScriptHash)
deriving via deriving via
(AsBase16Bytes Plutus.BuiltinByteString) (AsBase16Bytes Plutus.BuiltinByteString)
instance instance

View file

@ -1,3 +1,5 @@
{-# LANGUAGE TemplateHaskell #-}
{- | {- |
Module : Agora.ScriptInfo Module : Agora.ScriptInfo
Maintainer : emi@haskell.fyi Maintainer : emi@haskell.fyi
@ -7,8 +9,6 @@ Exportable script bundles for off-chain consumption.
-} -}
module Agora.ScriptInfo ( module Agora.ScriptInfo (
-- * Types -- * Types
PolicyInfo (..),
ValidatorInfo (..),
ScriptInfo (..), ScriptInfo (..),
-- * Introduction functions -- * Introduction functions
@ -17,100 +17,71 @@ module Agora.ScriptInfo (
) where ) where
import Agora.Aeson.Orphans () import Agora.Aeson.Orphans ()
import Cardano.Binary qualified as CBOR
import Codec.Serialise qualified as Codec
import Data.Aeson qualified as Aeson import Data.Aeson qualified as Aeson
import Data.ByteString.Base16 qualified as Base16
import Data.ByteString.Lazy qualified as LBS
import Data.ByteString.Short qualified as SBS
import Data.Text (Text)
import GHC.Generics qualified as GHC import GHC.Generics qualified as GHC
import Plutarch.Api.V1 (PMintingPolicy, PValidator, mintingPolicySymbol, mkMintingPolicy, mkValidator, validatorHash) import Plutarch.Api.V1 (PMintingPolicy, PValidator, mkMintingPolicy, mkValidator, scriptHash)
import PlutusLedgerApi.V1 ( import PlutusLedgerApi.V1 (
BuiltinByteString, MintingPolicy (getMintingPolicy),
CurrencySymbol,
MintingPolicy,
Script, Script,
Validator, Validator (getValidator),
ValidatorHash,
) )
import PlutusLedgerApi.V1.Scripts (ScriptHash)
-- | Bundle containing a 'Script' and its hash.
data ScriptInfo = ScriptInfo
{ script :: Script
-- ^ The validator script.
, hash :: BuiltinByteString
-- ^ Hash of the script.
}
deriving stock (Show, Eq, GHC.Generic)
deriving anyclass (Aeson.ToJSON, Aeson.FromJSON)
{- | Bundle containing a 'Validator' and its hash. {- | Bundle containing a 'Validator' and its hash.
@since 0.1.0 @since 0.2.0
-} -}
data ValidatorInfo = ValidatorInfo data ScriptInfo = ScriptInfo
{ script :: Validator { cborHex :: Text
-- ^ The validator script. -- ^ The validator script encoded as cbor hex.
, hash :: ValidatorHash , rawHex :: Text
-- ^ The validator script encoded as raw hex.
, hash :: ScriptHash
-- ^ Hash of the validator. -- ^ Hash of the validator.
} }
deriving stock deriving stock
( -- | @since 0.1.0 ( -- | @since 0.2.0
Show Show
, -- | @since 0.1.0 , -- | @since 0.2.0
Eq Eq
, -- | @since 0.1.0 , -- | @since 0.2.0
GHC.Generic GHC.Generic
) )
deriving anyclass deriving anyclass
( -- | @since 0.1.0 ( -- | @since 0.2.0
Aeson.ToJSON Aeson.ToJSON
, -- | @since 0.1.0 , -- | @since 0.2.0
Aeson.FromJSON Aeson.FromJSON
) )
{- | Create a 'ValidatorInfo' given a Plutarch term. mkScriptInfo :: Script -> ScriptInfo
mkScriptInfo script =
let scriptRaw = LBS.toStrict $ Codec.serialise script
scriptCBOR = CBOR.serialize' $ SBS.toShort scriptRaw
in ScriptInfo
{ cborHex = Base16.encodeBase16 scriptCBOR
, rawHex = Base16.encodeBase16 scriptRaw
, hash = scriptHash script
}
@since 0.1.0 {- | Create a 'ScriptInfo' given a Plutarch term of a policy.
@since 0.2.0
-} -}
mkValidatorInfo :: ClosedTerm PValidator -> ValidatorInfo mkPolicyInfo :: ClosedTerm PMintingPolicy -> ScriptInfo
mkValidatorInfo term =
ValidatorInfo
{ script = validator
, hash = validatorHash validator
}
where
validator = mkValidator term
{- | Bundle containing a 'MintingPolicy' and its symbol.
@since 0.1.0
-}
data PolicyInfo = PolicyInfo
{ policy :: MintingPolicy
-- ^ The minting policy.
, currencySymbol :: CurrencySymbol
-- ^ The symbol given by the minting policy.
}
deriving stock
( -- | @since 0.1.0
Show
, -- | @since 0.1.0
Eq
, -- | @since 0.1.0
GHC.Generic
)
deriving anyclass
( -- | @since 0.1.0
Aeson.ToJSON
, -- | @since 0.1.0
Aeson.FromJSON
)
{- | Create a 'PolicyInfo' given a Plutarch term.
@since 0.1.0
-}
mkPolicyInfo :: ClosedTerm PMintingPolicy -> PolicyInfo
mkPolicyInfo term = mkPolicyInfo term =
PolicyInfo mkScriptInfo (getMintingPolicy $ mkMintingPolicy term)
{ policy = policy
, currencySymbol = mintingPolicySymbol policy {- | Create a 'ScriptInfo' given a Plutarch term of a validator.
}
where @since 0.2.0
policy = mkMintingPolicy term -}
mkValidatorInfo :: ClosedTerm PValidator -> ScriptInfo
mkValidatorInfo term =
mkScriptInfo (getValidator $ mkValidator term)