From 4553f964f370438084c71001ac34d629070e08a1 Mon Sep 17 00:00:00 2001 From: Rene Vergara Date: Tue, 6 Aug 2024 13:38:00 -0500 Subject: [PATCH] Implement `listaccounts` --- src/Zenith/DB.hs | 9 ++++++++ src/Zenith/RPC.hs | 51 +++++++++++++++++++++++++++++++++++++++++++-- src/Zenith/Types.hs | 18 ++++++++++++++++ test/ServerSpec.hs | 29 ++++++++++++++++++++++++++ zenith-openrpc.json | 21 +++++++++++++------ 5 files changed, 120 insertions(+), 8 deletions(-) diff --git a/src/Zenith/DB.hs b/src/Zenith/DB.hs index 4f9a712..cc598fc 100644 --- a/src/Zenith/DB.hs +++ b/src/Zenith/DB.hs @@ -75,6 +75,7 @@ import Zenith.Types , ScopeDB(..) , TransparentSpendingKeyDB , UnifiedAddressDB(..) + , ZcashAccountAPI(..) , ZcashNetDB(..) , ZcashPool(..) , ZcashWalletAPI(..) @@ -275,6 +276,14 @@ toZcashWalletAPI w = (zcashWalletBirthdayHeight $ 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 -- | Initializes the database initDb :: diff --git a/src/Zenith/RPC.hs b/src/Zenith/RPC.hs index 6bfe11f..547a685 100644 --- a/src/Zenith/RPC.hs +++ b/src/Zenith/RPC.hs @@ -17,6 +17,7 @@ import Control.Monad.Logger (runNoLoggingT) import Data.Aeson import qualified Data.Text as T import qualified Data.Vector as V +import Database.Esqueleto.Experimental (toSqlKey) import Servant import ZcashHaskell.Types ( RpcError(..) @@ -25,18 +26,28 @@ import ZcashHaskell.Types , ZebraGetInfo(..) ) import Zenith.Core (checkBlockChain, checkZebra) -import Zenith.DB (getWallets, initDb, initPool, toZcashWalletAPI) -import Zenith.Types (Config(..), ZcashWalletAPI(..)) +import Zenith.DB + ( getAccounts + , getWallets + , initDb + , initPool + , toZcashAccountAPI + , toZcashWalletAPI + ) +import Zenith.Types (Config(..), ZcashAccountAPI(..), ZcashWalletAPI(..)) +import Zenith.Utils (jsonNumber) data ZenithMethod = GetInfo | ListWallets + | ListAccounts | UnknownMethod deriving (Eq, Prelude.Show) instance ToJSON ZenithMethod where toJSON GetInfo = Data.Aeson.String "getinfo" toJSON ListWallets = Data.Aeson.String "listwallets" + toJSON ListAccounts = Data.Aeson.String "listaccounts" toJSON UnknownMethod = Data.Aeson.Null instance FromJSON ZenithMethod where @@ -44,22 +55,26 @@ instance FromJSON ZenithMethod where withText "ZenithMethod" $ \case "getinfo" -> pure GetInfo "listwallets" -> pure ListWallets + "listaccounts" -> pure ListAccounts _ -> pure UnknownMethod data ZenithParams = BlankParams | BadParams + | AccountsParams !Int | TestParams !T.Text deriving (Eq, Prelude.Show) instance ToJSON ZenithParams where toJSON BlankParams = Data.Aeson.Array V.empty 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] data ZenithResponse = InfoResponse !T.Text !ZenithInfo | WalletListResponse !T.Text ![ZcashWalletAPI] + | AccountListResponse !T.Text ![ZcashAccountAPI] | ErrorResponse !T.Text !Double !T.Text deriving (Eq, Prelude.Show) @@ -68,6 +83,8 @@ instance ToJSON ZenithResponse where object ["jsonrpc" .= ("2.0" :: String), "id" .= t, "result" .= i] toJSON (WalletListResponse i 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) = object [ "jsonrpc" .= ("2.0" :: String) @@ -162,6 +179,16 @@ instance FromJSON RpcCall where if null (p :: [Value]) then pure $ RpcCall v i GetInfo BlankParams 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 = "status" :> Get '[ JSON] Value :<|> BasicAuth "zenith-realm" Bool :> ReqBody @@ -211,6 +238,26 @@ zenithServer config = getinfo :<|> handleRPC "No wallets available. Please create one first" _anyOther -> 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 -> case parameters req of BlankParams -> do diff --git a/src/Zenith/Types.hs b/src/Zenith/Types.hs index d4688af..b30e072 100644 --- a/src/Zenith/Types.hs +++ b/src/Zenith/Types.hs @@ -130,6 +130,24 @@ instance FromJSON ZcashWalletAPI where l <- obj .: "lastSync" 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` -- | Type for modeling the tree state response data ZebraTreeInfo = ZebraTreeInfo diff --git a/test/ServerSpec.hs b/test/ServerSpec.hs index ab645d7..cb3b8ff 100644 --- a/test/ServerSpec.hs +++ b/test/ServerSpec.hs @@ -95,6 +95,35 @@ main = do "zh" (-32001) "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 = do diff --git a/zenith-openrpc.json b/zenith-openrpc.json index 7ecbe7f..091e1e2 100644 --- a/zenith-openrpc.json +++ b/zenith-openrpc.json @@ -21,7 +21,7 @@ "name": "getinfo", "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", - "tags": [ { "$ref": "#/components/tags/information" }], + "tags": [], "result" : { "name": "Zenith information", "schema": { "$ref": "#/components/schemas/ZenithInfo" } @@ -52,7 +52,7 @@ "name": "listwallets", "summary": "Get the list of available wallets", "description": "Returns a list of available wallets per the network that the Zebra node is running on.", - "tags": [ { "$ref": "#/components/tags/wallet" }], + "tags": [], "result": { "name": "Wallets", "schema": { @@ -98,9 +98,9 @@ }, { "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", - "tags": [ { "$ref": "#/components/tags/wallet" }], + "tags": [], "result": { "name": "Accounts", "schema": { @@ -127,6 +127,16 @@ "result": { "name": "ListAccounts result", "value": [ + { + "index": 3, + "name": "Business", + "wallet": 1 + }, + { + "index": 1, + "name": "Savings", + "wallet": 1 + } ] } @@ -180,8 +190,7 @@ }, "examples": {}, "tags": { - "information": {"name": "Information"}, - "wallet": {"name": "Wallet"} + "draft": {"name": "Draft"} }, "errors": { "ZebraNotAvailable": {