From b8980bd219b8650a6b635ba51093042e53c06db3 Mon Sep 17 00:00:00 2001 From: Rene Vergara Date: Wed, 7 Aug 2024 10:21:04 -0500 Subject: [PATCH] Implement `listaddresses` --- CHANGELOG.md | 4 ++++ src/Zenith/DB.hs | 27 ++++++++++++++++++++-- src/Zenith/RPC.hs | 55 +++++++++++++++++++++++++++++++++++++++++++-- test/ServerSpec.hs | 29 ++++++++++++++++++++++++ zenith-openrpc.json | 18 ++++++++++++++- zenith.cabal | 2 +- 6 files changed, 129 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f7d280..bf372d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - RPC module +- OpenRPC specification +- `listwallets` RPC method +- `listaccounts` RPC method +- `listaddresses` RPC method ## [0.6.0.0-beta] diff --git a/src/Zenith/DB.hs b/src/Zenith/DB.hs index cc598fc..3d2f966 100644 --- a/src/Zenith/DB.hs +++ b/src/Zenith/DB.hs @@ -42,7 +42,8 @@ import Haskoin.Transaction.Common , txHashToHex ) import qualified Lens.Micro as ML ((&), (.~), (^.)) -import ZcashHaskell.Orchard (isValidUnifiedAddress) +import ZcashHaskell.Orchard (getSaplingFromUA, isValidUnifiedAddress) +import ZcashHaskell.Transparent (encodeTransparentReceiver) import ZcashHaskell.Types ( DecodedNote(..) , OrchardAction(..) @@ -62,7 +63,7 @@ import ZcashHaskell.Types , TransparentBundle(..) , TransparentReceiver(..) , UnifiedAddress(..) - , ZcashNet + , ZcashNet(..) , decodeHexText ) import Zenith.Types @@ -76,6 +77,7 @@ import Zenith.Types , TransparentSpendingKeyDB , UnifiedAddressDB(..) , ZcashAccountAPI(..) + , ZcashAddressAPI(..) , ZcashNetDB(..) , ZcashPool(..) , ZcashWalletAPI(..) @@ -284,6 +286,27 @@ toZcashAccountAPI a = (fromIntegral $ fromSqlKey $ zcashAccountWalletId $ entityVal a) (zcashAccountName $ entityVal a) +-- | @WalletAddress@ +toZcashAddressAPI :: Entity WalletAddress -> ZcashAddressAPI +toZcashAddressAPI a = + ZcashAddressAPI + (fromIntegral $ fromSqlKey $ entityKey a) + (fromIntegral $ fromSqlKey $ walletAddressAccId $ entityVal a) + (walletAddressName $ entityVal a) + (getUA $ walletAddressUAddress $ entityVal a) + (getSaplingFromUA $ + TE.encodeUtf8 $ getUA $ walletAddressUAddress $ entityVal a) + (encodeTransparentReceiver + (maybe + TestNet + ua_net + ((isValidUnifiedAddress . + TE.encodeUtf8 . getUA . walletAddressUAddress) $ + entityVal a)) <$> + (t_rec =<< + (isValidUnifiedAddress . TE.encodeUtf8 . getUA . walletAddressUAddress) + (entityVal a))) + -- * Database functions -- | Initializes the database initDb :: diff --git a/src/Zenith/RPC.hs b/src/Zenith/RPC.hs index 547a685..8dc2cba 100644 --- a/src/Zenith/RPC.hs +++ b/src/Zenith/RPC.hs @@ -28,19 +28,27 @@ import ZcashHaskell.Types import Zenith.Core (checkBlockChain, checkZebra) import Zenith.DB ( getAccounts + , getAddresses , getWallets , initDb , initPool , toZcashAccountAPI + , toZcashAddressAPI , toZcashWalletAPI ) -import Zenith.Types (Config(..), ZcashAccountAPI(..), ZcashWalletAPI(..)) +import Zenith.Types + ( Config(..) + , ZcashAccountAPI(..) + , ZcashAddressAPI(..) + , ZcashWalletAPI(..) + ) import Zenith.Utils (jsonNumber) data ZenithMethod = GetInfo | ListWallets | ListAccounts + | ListAddresses | UnknownMethod deriving (Eq, Prelude.Show) @@ -48,6 +56,7 @@ instance ToJSON ZenithMethod where toJSON GetInfo = Data.Aeson.String "getinfo" toJSON ListWallets = Data.Aeson.String "listwallets" toJSON ListAccounts = Data.Aeson.String "listaccounts" + toJSON ListAddresses = Data.Aeson.String "listaddresses" toJSON UnknownMethod = Data.Aeson.Null instance FromJSON ZenithMethod where @@ -56,12 +65,14 @@ instance FromJSON ZenithMethod where "getinfo" -> pure GetInfo "listwallets" -> pure ListWallets "listaccounts" -> pure ListAccounts + "listaddresses" -> pure ListAddresses _ -> pure UnknownMethod data ZenithParams = BlankParams | BadParams | AccountsParams !Int + | AddressesParams !Int | TestParams !T.Text deriving (Eq, Prelude.Show) @@ -69,12 +80,14 @@ 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 (AddressesParams 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] + | AddressListResponse !T.Text ![ZcashAddressAPI] | ErrorResponse !T.Text !Double !T.Text deriving (Eq, Prelude.Show) @@ -85,6 +98,8 @@ instance ToJSON ZenithResponse where object ["jsonrpc" .= ("2.0" :: String), "id" .= i, "result" .= w] toJSON (AccountListResponse i a) = object ["jsonrpc" .= ("2.0" :: String), "id" .= i, "result" .= a] + toJSON (AddressListResponse i a) = + object ["jsonrpc" .= ("2.0" :: String), "id" .= i, "result" .= a] toJSON (ErrorResponse i c m) = object [ "jsonrpc" .= ("2.0" :: String) @@ -122,11 +137,17 @@ instance FromJSON ZenithResponse where case V.head n of Object n' -> do v1 <- n' .:? "lastSync" + v2 <- n' .:? "wallet" case (v1 :: Maybe Int) of Just _v1' -> do k2 <- parseJSON r1 pure $ WalletListResponse i k2 - Nothing -> fail "Unknown object" + Nothing -> + case (v2 :: Maybe Int) of + Just _v2' -> do + k3 <- parseJSON r1 + pure $ AccountListResponse i k3 + Nothing -> fail "Unknown object" _anyOther -> fail "Malformed JSON" _anyOther -> fail "Malformed JSON" Just e1 -> pure $ ErrorResponse i (ecode e1) (emessage e1) @@ -189,6 +210,16 @@ instance FromJSON RpcCall where pure $ RpcCall v i ListAccounts (AccountsParams w) else pure $ RpcCall v i ListAccounts BadParams _anyOther -> pure $ RpcCall v i ListAccounts BadParams + ListAddresses -> do + p <- obj .: "params" + case p of + Array a -> + if V.length a == 1 + then do + x <- parseJSON $ V.head a + pure $ RpcCall v i ListAddresses (AddressesParams x) + else pure $ RpcCall v i ListAddresses BadParams + _anyOther -> pure $ RpcCall v i ListAddresses BadParams type ZenithRPC = "status" :> Get '[ JSON] Value :<|> BasicAuth "zenith-realm" Bool :> ReqBody @@ -258,6 +289,26 @@ zenithServer config = getinfo :<|> handleRPC "No accounts available for this wallet. Please create one first" _anyOther -> return $ ErrorResponse (callId req) (-32602) "Invalid params" + ListAddresses -> + case parameters req of + AddressesParams a -> do + let dbPath = c_dbPath config + pool <- liftIO $ runNoLoggingT $ initPool dbPath + addrList <- + liftIO $ + runNoLoggingT $ getAddresses pool (toSqlKey $ fromIntegral a) + if not (null addrList) + then return $ + AddressListResponse + (callId req) + (map toZcashAddressAPI addrList) + else return $ + ErrorResponse + (callId req) + (-32003) + "No addresses available for this account. Please create one first" + _anyOther -> + return $ ErrorResponse (callId req) (-32602) "Invalid params" GetInfo -> case parameters req of BlankParams -> do diff --git a/test/ServerSpec.hs b/test/ServerSpec.hs index cb3b8ff..33c31aa 100644 --- a/test/ServerSpec.hs +++ b/test/ServerSpec.hs @@ -124,6 +124,35 @@ main = do "zh" (-32002) "No accounts available for this wallet. Please create one first" + describe "Addresses" $ do + describe "listaddresses" $ do + it "bad credentials" $ do + res <- + makeZenithCall + "127.0.0.1" + nodePort + "baduser" + "idontknow" + ListAddresses + BlankParams + res `shouldBe` Left "Invalid credentials" + it "correct credentials, no addresses" $ do + res <- + makeZenithCall + "127.0.0.1" + nodePort + nodeUser + nodePwd + ListAddresses + (AddressesParams 1) + case res of + Left e -> assertFailure e + Right r -> + r `shouldBe` + ErrorResponse + "zh" + (-32003) + "No addresses available for this account. Please create one first" startAPI :: Config -> IO () startAPI config = do diff --git a/zenith-openrpc.json b/zenith-openrpc.json index 0578bf8..829ce84 100644 --- a/zenith-openrpc.json +++ b/zenith-openrpc.json @@ -152,7 +152,7 @@ "name": "listaddresses", "summary": "List existing addresses for an account ID", "description": "List existing addresses for the given account ID or provide an error if none", - "tags": [{ "$ref": "#/components/tags/draft"} ], + "tags": [], "result": { "name": "Addresses", "schema": { @@ -179,6 +179,22 @@ "result": { "name": "ListAddresses result", "value": [ + { + "index": 3, + "account": 1, + "name": "Clothes", + "ua": "utest13dq4u4dnf3yddw8lq2n6zdclshra6xsp8zgkc5ydyu6k20zrsscmuex46qa4vh84rgd78sqnlleapznnz7mnzx9wv0unts8pv32paj8se5ca3kves2u4a89uy6e8cf4hnarxydxh7hq2e9uu39punfmm53k5h45xn9k3dx35la8j7munh9td7774m8gkqgc4mn40t69w20uu2gtks7a", + "legacy": "ztestsapling188csdsvhdny25am8ume03qr2026hdy03zpg5pq7jmmfxtxtct0e93p0rg80yfxvynqd4gwlwft5", + "transparent": "tmMouLwVfRYrF91fWjDJToivmsTWBhxfX4E" + }, + { + "index": 2, + "account": 1, + "name": "Vacation", + "ua": "utest1hhggl4nxfdx63evps6r7qz50cgacgtdpt9k7dl0734w63zn5qmrp6c2xdv9rkqyfkj6kgau4kz48xtm80e67l534qp02teqq86zuzetxql6z5v32yglg9n2un5zsu0hwcvaunzdfg5qnry6syh2dh9x8eu27de03j9pjfvrqda6acgtc6f0emdfh6r5jvfanmjml4ms5wwj9wfqmamq", + "legacy": "ztestsapling1mpup3xv2k9clxaf9wjcr0dt5gnmkprz9s9qsn298mqs356pf39wmh30q3pgsp0w5vyrmj6mrzw2", + "transparent": "tmX8qCB96Dq49YZkww3bSty7eZDA4Fq6F4R" + } ] } diff --git a/zenith.cabal b/zenith.cabal index e6cccf1..37a537c 100644 --- a/zenith.cabal +++ b/zenith.cabal @@ -1,6 +1,6 @@ cabal-version: 3.0 name: zenith -version: 0.6.0.0-beta +version: 0.7.0.0-beta license: MIT license-file: LICENSE author: Rene Vergara