2024-07-23 18:46:37 +00:00
|
|
|
{-# LANGUAGE TypeOperators #-}
|
|
|
|
{-# LANGUAGE DataKinds #-}
|
|
|
|
{-# LANGUAGE OverloadedStrings #-}
|
2024-07-24 21:03:49 +00:00
|
|
|
{-# LANGUAGE FlexibleContexts #-}
|
|
|
|
{-# LANGUAGE FlexibleInstances #-}
|
|
|
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
|
|
|
{-# LANGUAGE ScopedTypeVariables #-}
|
|
|
|
{-# LANGUAGE TypeFamilies #-}
|
|
|
|
{-# LANGUAGE UndecidableInstances #-}
|
2024-08-05 17:54:02 +00:00
|
|
|
{-# LANGUAGE LambdaCase #-}
|
2024-07-23 18:46:37 +00:00
|
|
|
|
|
|
|
module Zenith.RPC where
|
|
|
|
|
2024-08-03 12:01:11 +00:00
|
|
|
import Control.Exception (try)
|
|
|
|
import Control.Monad.IO.Class (liftIO)
|
2024-08-05 17:54:02 +00:00
|
|
|
import Control.Monad.Logger (runNoLoggingT)
|
2024-07-23 18:46:37 +00:00
|
|
|
import Data.Aeson
|
|
|
|
import qualified Data.Text as T
|
2024-08-05 17:54:02 +00:00
|
|
|
import qualified Data.Vector as V
|
2024-07-23 18:46:37 +00:00
|
|
|
import Servant
|
2024-08-05 17:54:02 +00:00
|
|
|
import ZcashHaskell.Types
|
|
|
|
( RpcError(..)
|
|
|
|
, ZcashNet(..)
|
|
|
|
, ZebraGetBlockChainInfo(..)
|
|
|
|
, ZebraGetInfo(..)
|
2024-07-24 21:03:49 +00:00
|
|
|
)
|
2024-08-05 17:54:02 +00:00
|
|
|
import Zenith.Core (checkBlockChain, checkZebra)
|
|
|
|
import Zenith.DB (getWallets, initDb, initPool, toZcashWalletAPI)
|
|
|
|
import Zenith.Types (Config(..), ZcashWalletAPI(..))
|
|
|
|
|
|
|
|
data ZenithMethod
|
|
|
|
= GetInfo
|
|
|
|
| ListWallets
|
|
|
|
| UnknownMethod
|
|
|
|
deriving (Eq, Prelude.Show)
|
|
|
|
|
|
|
|
instance ToJSON ZenithMethod where
|
|
|
|
toJSON GetInfo = Data.Aeson.String "getinfo"
|
|
|
|
toJSON ListWallets = Data.Aeson.String "listwallets"
|
|
|
|
toJSON UnknownMethod = Data.Aeson.Null
|
|
|
|
|
|
|
|
instance FromJSON ZenithMethod where
|
|
|
|
parseJSON =
|
|
|
|
withText "ZenithMethod" $ \case
|
|
|
|
"getinfo" -> pure GetInfo
|
|
|
|
"listwallets" -> pure ListWallets
|
|
|
|
_ -> pure UnknownMethod
|
|
|
|
|
|
|
|
data ZenithParams
|
|
|
|
= BlankParams
|
|
|
|
| BadParams
|
|
|
|
| TestParams !T.Text
|
|
|
|
deriving (Eq, Prelude.Show)
|
|
|
|
|
|
|
|
instance ToJSON ZenithParams where
|
|
|
|
toJSON BlankParams = Data.Aeson.Array V.empty
|
|
|
|
toJSON BadParams = Data.Aeson.Null
|
|
|
|
toJSON (TestParams t) = Data.Aeson.Array $ V.fromList [Data.Aeson.String t]
|
|
|
|
|
|
|
|
data ZenithResponse
|
|
|
|
= InfoResponse !T.Text !ZenithInfo
|
|
|
|
| WalletListResponse !T.Text ![ZcashWalletAPI]
|
|
|
|
| ErrorResponse !T.Text !Double !T.Text
|
|
|
|
deriving (Eq, Prelude.Show)
|
|
|
|
|
|
|
|
instance ToJSON ZenithResponse where
|
|
|
|
toJSON (InfoResponse t i) =
|
|
|
|
object ["jsonrpc" .= ("2.0" :: String), "id" .= t, "result" .= i]
|
|
|
|
toJSON (WalletListResponse i w) =
|
|
|
|
object ["jsonrpc" .= ("2.0" :: String), "id" .= i, "result" .= w]
|
|
|
|
toJSON (ErrorResponse i c m) =
|
|
|
|
object
|
|
|
|
[ "jsonrpc" .= ("2.0" :: String)
|
|
|
|
, "id" .= i
|
|
|
|
, "error" .= object ["code" .= c, "message" .= m]
|
|
|
|
]
|
|
|
|
|
|
|
|
instance FromJSON ZenithResponse where
|
|
|
|
parseJSON =
|
|
|
|
withObject "ZenithParams" $ \obj -> do
|
|
|
|
jr <- obj .: "jsonrpc"
|
|
|
|
i <- obj .: "id"
|
|
|
|
e <- obj .:? "error"
|
|
|
|
r <- obj .:? "result"
|
|
|
|
if jr /= ("2.0" :: String)
|
|
|
|
then fail "Malformed JSON"
|
|
|
|
else do
|
|
|
|
case e of
|
|
|
|
Nothing -> do
|
|
|
|
case r of
|
|
|
|
Nothing -> fail "Malformed JSON"
|
|
|
|
Just r1 ->
|
|
|
|
case r1 of
|
|
|
|
Object k -> do
|
|
|
|
v <- k .:? "version"
|
|
|
|
case (v :: Maybe String) of
|
|
|
|
Nothing -> fail "Unknown result"
|
|
|
|
Just _v' -> do
|
|
|
|
k1 <- parseJSON r1
|
|
|
|
pure $ InfoResponse i k1
|
|
|
|
Array n -> do
|
|
|
|
if V.null n
|
|
|
|
then fail "Malformed JSON"
|
|
|
|
else do
|
|
|
|
case V.head n of
|
|
|
|
Object n' -> do
|
|
|
|
v1 <- n' .:? "lastSync"
|
|
|
|
case (v1 :: Maybe Int) of
|
|
|
|
Just _v1' -> do
|
|
|
|
k2 <- parseJSON r1
|
|
|
|
pure $ WalletListResponse i k2
|
|
|
|
Nothing -> fail "Unknown object"
|
|
|
|
_anyOther -> fail "Malformed JSON"
|
|
|
|
_anyOther -> fail "Malformed JSON"
|
|
|
|
Just e1 -> pure $ ErrorResponse i (ecode e1) (emessage e1)
|
|
|
|
|
|
|
|
data ZenithInfo = ZenithInfo
|
|
|
|
{ zi_version :: !T.Text
|
|
|
|
, zi_network :: !ZcashNet
|
|
|
|
, zi_zebra :: !T.Text
|
|
|
|
} deriving (Eq, Prelude.Show)
|
|
|
|
|
|
|
|
instance ToJSON ZenithInfo where
|
|
|
|
toJSON (ZenithInfo v n z) =
|
|
|
|
object ["version" .= v, "network" .= n, "zebraVersion" .= z]
|
|
|
|
|
|
|
|
instance FromJSON ZenithInfo where
|
|
|
|
parseJSON =
|
|
|
|
withObject "ZenithInfo" $ \obj -> do
|
|
|
|
v <- obj .: "version"
|
|
|
|
n <- obj .: "network"
|
|
|
|
z <- obj .: "zebraVersion"
|
|
|
|
pure $ ZenithInfo v n z
|
|
|
|
|
|
|
|
-- | A type to model Zenith RPC calls
|
|
|
|
data RpcCall = RpcCall
|
|
|
|
{ jsonrpc :: !T.Text
|
|
|
|
, callId :: !T.Text
|
|
|
|
, method :: !ZenithMethod
|
|
|
|
, parameters :: !ZenithParams
|
|
|
|
} deriving (Eq, Prelude.Show)
|
|
|
|
|
|
|
|
instance ToJSON RpcCall where
|
|
|
|
toJSON (RpcCall jr i m p) =
|
|
|
|
object ["jsonrpc" .= jr, "id" .= i, "method" .= m, "params" .= p]
|
|
|
|
|
|
|
|
instance FromJSON RpcCall where
|
|
|
|
parseJSON =
|
|
|
|
withObject "RpcCall" $ \obj -> do
|
|
|
|
v <- obj .: "jsonrpc"
|
|
|
|
i <- obj .: "id"
|
|
|
|
m <- obj .: "method"
|
|
|
|
case m of
|
|
|
|
UnknownMethod -> pure $ RpcCall v i UnknownMethod BlankParams
|
|
|
|
ListWallets -> do
|
|
|
|
p <- obj .: "params"
|
|
|
|
if null (p :: [Value])
|
|
|
|
then pure $ RpcCall v i ListWallets BlankParams
|
|
|
|
else pure $ RpcCall v i ListWallets BadParams
|
|
|
|
GetInfo -> do
|
|
|
|
p <- obj .: "params"
|
|
|
|
if null (p :: [Value])
|
|
|
|
then pure $ RpcCall v i GetInfo BlankParams
|
|
|
|
else pure $ RpcCall v i GetInfo BadParams
|
2024-07-23 18:46:37 +00:00
|
|
|
|
|
|
|
type ZenithRPC
|
2024-07-24 21:13:13 +00:00
|
|
|
= "status" :> Get '[ JSON] Value :<|> BasicAuth "zenith-realm" Bool :> ReqBody
|
2024-07-23 18:46:37 +00:00
|
|
|
'[ JSON]
|
2024-08-03 12:01:11 +00:00
|
|
|
RpcCall :> Post '[ JSON] ZenithResponse
|
2024-07-23 18:46:37 +00:00
|
|
|
|
2024-07-24 21:03:49 +00:00
|
|
|
zenithServer :: Config -> Server ZenithRPC
|
|
|
|
zenithServer config = getinfo :<|> handleRPC
|
2024-07-23 18:46:37 +00:00
|
|
|
where
|
|
|
|
getinfo :: Handler Value
|
|
|
|
getinfo =
|
|
|
|
return $
|
|
|
|
object
|
|
|
|
[ "version" .= ("0.7.0.0-beta" :: String)
|
|
|
|
, "network" .= ("testnet" :: String)
|
|
|
|
]
|
2024-08-03 12:01:11 +00:00
|
|
|
handleRPC :: Bool -> RpcCall -> Handler ZenithResponse
|
2024-07-24 21:13:13 +00:00
|
|
|
handleRPC isAuth req =
|
2024-07-23 18:46:37 +00:00
|
|
|
case method req of
|
2024-07-24 21:03:49 +00:00
|
|
|
UnknownMethod ->
|
2024-08-03 12:01:11 +00:00
|
|
|
return $ ErrorResponse (callId req) (-32601) "Method not found"
|
2024-08-05 17:54:02 +00:00
|
|
|
ListWallets ->
|
|
|
|
case parameters req of
|
|
|
|
BlankParams -> do
|
|
|
|
let dbPath = c_dbPath config
|
|
|
|
let host = c_zebraHost config
|
|
|
|
let port = c_zebraPort config
|
|
|
|
bc <-
|
|
|
|
liftIO $ try $ checkBlockChain host port :: Handler
|
|
|
|
(Either IOError ZebraGetBlockChainInfo)
|
|
|
|
case bc of
|
|
|
|
Left _e1 ->
|
|
|
|
return $
|
|
|
|
ErrorResponse (callId req) (-32000) "Zebra not available"
|
|
|
|
Right chainInfo -> do
|
|
|
|
pool <- liftIO $ runNoLoggingT $ initPool dbPath
|
|
|
|
walList <- liftIO $ getWallets pool $ zgb_net chainInfo
|
|
|
|
if not (null walList)
|
|
|
|
then return $
|
|
|
|
WalletListResponse
|
|
|
|
(callId req)
|
|
|
|
(map toZcashWalletAPI walList)
|
|
|
|
else return $
|
|
|
|
ErrorResponse
|
|
|
|
(callId req)
|
|
|
|
(-32001)
|
|
|
|
"No wallets available. Please create one first"
|
|
|
|
_anyOther ->
|
|
|
|
return $ ErrorResponse (callId req) (-32602) "Invalid params"
|
2024-07-23 18:46:37 +00:00
|
|
|
GetInfo ->
|
|
|
|
case parameters req of
|
2024-08-03 12:01:11 +00:00
|
|
|
BlankParams -> do
|
|
|
|
let host = c_zebraHost config
|
|
|
|
let port = c_zebraPort config
|
|
|
|
zInfo <-
|
|
|
|
liftIO $ try $ checkZebra host port :: Handler
|
|
|
|
(Either IOError ZebraGetInfo)
|
|
|
|
case zInfo of
|
2024-08-05 17:54:02 +00:00
|
|
|
Left _e ->
|
2024-08-03 12:01:11 +00:00
|
|
|
return $
|
|
|
|
ErrorResponse (callId req) (-32000) "Zebra not available"
|
|
|
|
Right zI -> do
|
|
|
|
bInfo <-
|
|
|
|
liftIO $ try $ checkBlockChain host port :: Handler
|
|
|
|
(Either IOError ZebraGetBlockChainInfo)
|
|
|
|
case bInfo of
|
2024-08-05 17:54:02 +00:00
|
|
|
Left _e1 ->
|
2024-08-03 12:01:11 +00:00
|
|
|
return $
|
|
|
|
ErrorResponse (callId req) (-32000) "Zebra not available"
|
|
|
|
Right bI ->
|
|
|
|
return $
|
|
|
|
InfoResponse
|
|
|
|
(callId req)
|
|
|
|
(ZenithInfo "0.7.0.0-beta" (zgb_net bI) (zgi_build zI))
|
2024-07-23 18:46:37 +00:00
|
|
|
_anyOtherParams ->
|
2024-08-03 12:01:11 +00:00
|
|
|
return $ ErrorResponse (callId req) (-32602) "Invalid params"
|
2024-07-24 21:03:49 +00:00
|
|
|
|
|
|
|
authenticate :: Config -> BasicAuthCheck Bool
|
|
|
|
authenticate config = BasicAuthCheck check
|
|
|
|
where
|
|
|
|
check (BasicAuthData username password) =
|
|
|
|
if username == c_zenithUser config && password == c_zenithPwd config
|
|
|
|
then return $ Authorized True
|
|
|
|
else return Unauthorized
|