Milestone 3: RPC server, ZIP-320 #104
5 changed files with 120 additions and 8 deletions
|
@ -75,6 +75,7 @@ import Zenith.Types
|
||||||
, ScopeDB(..)
|
, ScopeDB(..)
|
||||||
, TransparentSpendingKeyDB
|
, TransparentSpendingKeyDB
|
||||||
, UnifiedAddressDB(..)
|
, UnifiedAddressDB(..)
|
||||||
|
, ZcashAccountAPI(..)
|
||||||
, ZcashNetDB(..)
|
, ZcashNetDB(..)
|
||||||
, ZcashPool(..)
|
, ZcashPool(..)
|
||||||
, ZcashWalletAPI(..)
|
, ZcashWalletAPI(..)
|
||||||
|
@ -275,6 +276,14 @@ toZcashWalletAPI w =
|
||||||
(zcashWalletBirthdayHeight $ entityVal w)
|
(zcashWalletBirthdayHeight $ entityVal w)
|
||||||
(zcashWalletLastSync $ entityVal w)
|
(zcashWalletLastSync $ entityVal w)
|
||||||
|
|
||||||
|
-- | @ZcashAccount@
|
||||||
|
toZcashAccountAPI :: Entity ZcashAccount -> ZcashAccountAPI
|
||||||
|
toZcashAccountAPI a =
|
||||||
|
ZcashAccountAPI
|
||||||
|
(fromIntegral $ fromSqlKey $ entityKey a)
|
||||||
|
(fromIntegral $ fromSqlKey $ zcashAccountWalletId $ entityVal a)
|
||||||
|
(zcashAccountName $ entityVal a)
|
||||||
|
|
||||||
-- * Database functions
|
-- * Database functions
|
||||||
-- | Initializes the database
|
-- | Initializes the database
|
||||||
initDb ::
|
initDb ::
|
||||||
|
|
|
@ -17,6 +17,7 @@ import Control.Monad.Logger (runNoLoggingT)
|
||||||
import Data.Aeson
|
import Data.Aeson
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import qualified Data.Vector as V
|
import qualified Data.Vector as V
|
||||||
|
import Database.Esqueleto.Experimental (toSqlKey)
|
||||||
import Servant
|
import Servant
|
||||||
import ZcashHaskell.Types
|
import ZcashHaskell.Types
|
||||||
( RpcError(..)
|
( RpcError(..)
|
||||||
|
@ -25,18 +26,28 @@ import ZcashHaskell.Types
|
||||||
, ZebraGetInfo(..)
|
, ZebraGetInfo(..)
|
||||||
)
|
)
|
||||||
import Zenith.Core (checkBlockChain, checkZebra)
|
import Zenith.Core (checkBlockChain, checkZebra)
|
||||||
import Zenith.DB (getWallets, initDb, initPool, toZcashWalletAPI)
|
import Zenith.DB
|
||||||
import Zenith.Types (Config(..), ZcashWalletAPI(..))
|
( getAccounts
|
||||||
|
, getWallets
|
||||||
|
, initDb
|
||||||
|
, initPool
|
||||||
|
, toZcashAccountAPI
|
||||||
|
, toZcashWalletAPI
|
||||||
|
)
|
||||||
|
import Zenith.Types (Config(..), ZcashAccountAPI(..), ZcashWalletAPI(..))
|
||||||
|
import Zenith.Utils (jsonNumber)
|
||||||
|
|
||||||
data ZenithMethod
|
data ZenithMethod
|
||||||
= GetInfo
|
= GetInfo
|
||||||
| ListWallets
|
| ListWallets
|
||||||
|
| ListAccounts
|
||||||
| UnknownMethod
|
| UnknownMethod
|
||||||
deriving (Eq, Prelude.Show)
|
deriving (Eq, Prelude.Show)
|
||||||
|
|
||||||
instance ToJSON ZenithMethod where
|
instance ToJSON ZenithMethod where
|
||||||
toJSON GetInfo = Data.Aeson.String "getinfo"
|
toJSON GetInfo = Data.Aeson.String "getinfo"
|
||||||
toJSON ListWallets = Data.Aeson.String "listwallets"
|
toJSON ListWallets = Data.Aeson.String "listwallets"
|
||||||
|
toJSON ListAccounts = Data.Aeson.String "listaccounts"
|
||||||
toJSON UnknownMethod = Data.Aeson.Null
|
toJSON UnknownMethod = Data.Aeson.Null
|
||||||
|
|
||||||
instance FromJSON ZenithMethod where
|
instance FromJSON ZenithMethod where
|
||||||
|
@ -44,22 +55,26 @@ instance FromJSON ZenithMethod where
|
||||||
withText "ZenithMethod" $ \case
|
withText "ZenithMethod" $ \case
|
||||||
"getinfo" -> pure GetInfo
|
"getinfo" -> pure GetInfo
|
||||||
"listwallets" -> pure ListWallets
|
"listwallets" -> pure ListWallets
|
||||||
|
"listaccounts" -> pure ListAccounts
|
||||||
_ -> pure UnknownMethod
|
_ -> pure UnknownMethod
|
||||||
|
|
||||||
data ZenithParams
|
data ZenithParams
|
||||||
= BlankParams
|
= BlankParams
|
||||||
| BadParams
|
| BadParams
|
||||||
|
| AccountsParams !Int
|
||||||
| TestParams !T.Text
|
| TestParams !T.Text
|
||||||
deriving (Eq, Prelude.Show)
|
deriving (Eq, Prelude.Show)
|
||||||
|
|
||||||
instance ToJSON ZenithParams where
|
instance ToJSON ZenithParams where
|
||||||
toJSON BlankParams = Data.Aeson.Array V.empty
|
toJSON BlankParams = Data.Aeson.Array V.empty
|
||||||
toJSON BadParams = Data.Aeson.Null
|
toJSON BadParams = Data.Aeson.Null
|
||||||
|
toJSON (AccountsParams n) = Data.Aeson.Array $ V.fromList [jsonNumber n]
|
||||||
toJSON (TestParams t) = Data.Aeson.Array $ V.fromList [Data.Aeson.String t]
|
toJSON (TestParams t) = Data.Aeson.Array $ V.fromList [Data.Aeson.String t]
|
||||||
|
|
||||||
data ZenithResponse
|
data ZenithResponse
|
||||||
= InfoResponse !T.Text !ZenithInfo
|
= InfoResponse !T.Text !ZenithInfo
|
||||||
| WalletListResponse !T.Text ![ZcashWalletAPI]
|
| WalletListResponse !T.Text ![ZcashWalletAPI]
|
||||||
|
| AccountListResponse !T.Text ![ZcashAccountAPI]
|
||||||
| ErrorResponse !T.Text !Double !T.Text
|
| ErrorResponse !T.Text !Double !T.Text
|
||||||
deriving (Eq, Prelude.Show)
|
deriving (Eq, Prelude.Show)
|
||||||
|
|
||||||
|
@ -68,6 +83,8 @@ instance ToJSON ZenithResponse where
|
||||||
object ["jsonrpc" .= ("2.0" :: String), "id" .= t, "result" .= i]
|
object ["jsonrpc" .= ("2.0" :: String), "id" .= t, "result" .= i]
|
||||||
toJSON (WalletListResponse i w) =
|
toJSON (WalletListResponse i w) =
|
||||||
object ["jsonrpc" .= ("2.0" :: String), "id" .= i, "result" .= w]
|
object ["jsonrpc" .= ("2.0" :: String), "id" .= i, "result" .= w]
|
||||||
|
toJSON (AccountListResponse i a) =
|
||||||
|
object ["jsonrpc" .= ("2.0" :: String), "id" .= i, "result" .= a]
|
||||||
toJSON (ErrorResponse i c m) =
|
toJSON (ErrorResponse i c m) =
|
||||||
object
|
object
|
||||||
[ "jsonrpc" .= ("2.0" :: String)
|
[ "jsonrpc" .= ("2.0" :: String)
|
||||||
|
@ -162,6 +179,16 @@ instance FromJSON RpcCall where
|
||||||
if null (p :: [Value])
|
if null (p :: [Value])
|
||||||
then pure $ RpcCall v i GetInfo BlankParams
|
then pure $ RpcCall v i GetInfo BlankParams
|
||||||
else pure $ RpcCall v i GetInfo BadParams
|
else pure $ RpcCall v i GetInfo BadParams
|
||||||
|
ListAccounts -> do
|
||||||
|
p <- obj .: "params"
|
||||||
|
case p of
|
||||||
|
Array a ->
|
||||||
|
if V.length a == 1
|
||||||
|
then do
|
||||||
|
w <- parseJSON $ V.head a
|
||||||
|
pure $ RpcCall v i ListAccounts (AccountsParams w)
|
||||||
|
else pure $ RpcCall v i ListAccounts BadParams
|
||||||
|
_anyOther -> pure $ RpcCall v i ListAccounts BadParams
|
||||||
|
|
||||||
type ZenithRPC
|
type ZenithRPC
|
||||||
= "status" :> Get '[ JSON] Value :<|> BasicAuth "zenith-realm" Bool :> ReqBody
|
= "status" :> Get '[ JSON] Value :<|> BasicAuth "zenith-realm" Bool :> ReqBody
|
||||||
|
@ -211,6 +238,26 @@ zenithServer config = getinfo :<|> handleRPC
|
||||||
"No wallets available. Please create one first"
|
"No wallets available. Please create one first"
|
||||||
_anyOther ->
|
_anyOther ->
|
||||||
return $ ErrorResponse (callId req) (-32602) "Invalid params"
|
return $ ErrorResponse (callId req) (-32602) "Invalid params"
|
||||||
|
ListAccounts ->
|
||||||
|
case parameters req of
|
||||||
|
AccountsParams w -> do
|
||||||
|
let dbPath = c_dbPath config
|
||||||
|
pool <- liftIO $ runNoLoggingT $ initPool dbPath
|
||||||
|
accList <-
|
||||||
|
liftIO $
|
||||||
|
runNoLoggingT $ getAccounts pool (toSqlKey $ fromIntegral w)
|
||||||
|
if not (null accList)
|
||||||
|
then return $
|
||||||
|
AccountListResponse
|
||||||
|
(callId req)
|
||||||
|
(map toZcashAccountAPI accList)
|
||||||
|
else return $
|
||||||
|
ErrorResponse
|
||||||
|
(callId req)
|
||||||
|
(-32002)
|
||||||
|
"No accounts available for this wallet. Please create one first"
|
||||||
|
_anyOther ->
|
||||||
|
return $ ErrorResponse (callId req) (-32602) "Invalid params"
|
||||||
GetInfo ->
|
GetInfo ->
|
||||||
case parameters req of
|
case parameters req of
|
||||||
BlankParams -> do
|
BlankParams -> do
|
||||||
|
|
|
@ -130,6 +130,24 @@ instance FromJSON ZcashWalletAPI where
|
||||||
l <- obj .: "lastSync"
|
l <- obj .: "lastSync"
|
||||||
pure $ ZcashWalletAPI i n net b l
|
pure $ ZcashWalletAPI i n net b l
|
||||||
|
|
||||||
|
data ZcashAccountAPI = ZcashAccountAPI
|
||||||
|
{ za_index :: !Int
|
||||||
|
, za_wallet :: !Int
|
||||||
|
, za_name :: !T.Text
|
||||||
|
} deriving (Eq, Prelude.Show)
|
||||||
|
|
||||||
|
instance ToJSON ZcashAccountAPI where
|
||||||
|
toJSON (ZcashAccountAPI i w n) =
|
||||||
|
object ["index" .= i, "wallet" .= w, "name" .= n]
|
||||||
|
|
||||||
|
instance FromJSON ZcashAccountAPI where
|
||||||
|
parseJSON =
|
||||||
|
withObject "ZcashAccountAPI" $ \obj -> do
|
||||||
|
i <- obj .: "index"
|
||||||
|
w <- obj .: "wallet"
|
||||||
|
n <- obj .: "name"
|
||||||
|
pure $ ZcashAccountAPI i w n
|
||||||
|
|
||||||
-- ** `zebrad`
|
-- ** `zebrad`
|
||||||
-- | Type for modeling the tree state response
|
-- | Type for modeling the tree state response
|
||||||
data ZebraTreeInfo = ZebraTreeInfo
|
data ZebraTreeInfo = ZebraTreeInfo
|
||||||
|
|
|
@ -95,6 +95,35 @@ main = do
|
||||||
"zh"
|
"zh"
|
||||||
(-32001)
|
(-32001)
|
||||||
"No wallets available. Please create one first"
|
"No wallets available. Please create one first"
|
||||||
|
describe "Accounts" $ do
|
||||||
|
describe "listaccounts" $ do
|
||||||
|
it "bad credentials" $ do
|
||||||
|
res <-
|
||||||
|
makeZenithCall
|
||||||
|
"127.0.0.1"
|
||||||
|
nodePort
|
||||||
|
"baduser"
|
||||||
|
"idontknow"
|
||||||
|
ListAccounts
|
||||||
|
BlankParams
|
||||||
|
res `shouldBe` Left "Invalid credentials"
|
||||||
|
it "correct credentials, no accounts" $ do
|
||||||
|
res <-
|
||||||
|
makeZenithCall
|
||||||
|
"127.0.0.1"
|
||||||
|
nodePort
|
||||||
|
nodeUser
|
||||||
|
nodePwd
|
||||||
|
ListAccounts
|
||||||
|
(AccountsParams 1)
|
||||||
|
case res of
|
||||||
|
Left e -> assertFailure e
|
||||||
|
Right r ->
|
||||||
|
r `shouldBe`
|
||||||
|
ErrorResponse
|
||||||
|
"zh"
|
||||||
|
(-32002)
|
||||||
|
"No accounts available for this wallet. Please create one first"
|
||||||
|
|
||||||
startAPI :: Config -> IO ()
|
startAPI :: Config -> IO ()
|
||||||
startAPI config = do
|
startAPI config = do
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
"name": "getinfo",
|
"name": "getinfo",
|
||||||
"summary": "Get basic Zenith information",
|
"summary": "Get basic Zenith information",
|
||||||
"description": "Get basic information about Zenith, such as the network it is running on and the version of Zebra it is connected to",
|
"description": "Get basic information about Zenith, such as the network it is running on and the version of Zebra it is connected to",
|
||||||
"tags": [ { "$ref": "#/components/tags/information" }],
|
"tags": [],
|
||||||
"result" : {
|
"result" : {
|
||||||
"name": "Zenith information",
|
"name": "Zenith information",
|
||||||
"schema": { "$ref": "#/components/schemas/ZenithInfo" }
|
"schema": { "$ref": "#/components/schemas/ZenithInfo" }
|
||||||
|
@ -52,7 +52,7 @@
|
||||||
"name": "listwallets",
|
"name": "listwallets",
|
||||||
"summary": "Get the list of available wallets",
|
"summary": "Get the list of available wallets",
|
||||||
"description": "Returns a list of available wallets per the network that the Zebra node is running on.",
|
"description": "Returns a list of available wallets per the network that the Zebra node is running on.",
|
||||||
"tags": [ { "$ref": "#/components/tags/wallet" }],
|
"tags": [],
|
||||||
"result": {
|
"result": {
|
||||||
"name": "Wallets",
|
"name": "Wallets",
|
||||||
"schema": {
|
"schema": {
|
||||||
|
@ -98,9 +98,9 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "listaccounts",
|
"name": "listaccounts",
|
||||||
"summary": "DRAFT: List existing accounts for a wallet ID",
|
"summary": "List existing accounts for a wallet ID",
|
||||||
"description": "List existing accounts for the given wallet ID or provide an error if none",
|
"description": "List existing accounts for the given wallet ID or provide an error if none",
|
||||||
"tags": [ { "$ref": "#/components/tags/wallet" }],
|
"tags": [],
|
||||||
"result": {
|
"result": {
|
||||||
"name": "Accounts",
|
"name": "Accounts",
|
||||||
"schema": {
|
"schema": {
|
||||||
|
@ -127,6 +127,16 @@
|
||||||
"result": {
|
"result": {
|
||||||
"name": "ListAccounts result",
|
"name": "ListAccounts result",
|
||||||
"value": [
|
"value": [
|
||||||
|
{
|
||||||
|
"index": 3,
|
||||||
|
"name": "Business",
|
||||||
|
"wallet": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 1,
|
||||||
|
"name": "Savings",
|
||||||
|
"wallet": 1
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,8 +190,7 @@
|
||||||
},
|
},
|
||||||
"examples": {},
|
"examples": {},
|
||||||
"tags": {
|
"tags": {
|
||||||
"information": {"name": "Information"},
|
"draft": {"name": "Draft"}
|
||||||
"wallet": {"name": "Wallet"}
|
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"ZebraNotAvailable": {
|
"ZebraNotAvailable": {
|
||||||
|
|
Loading…
Reference in a new issue