Milestone 3: RPC server, ZIP-320 #104

Merged
pitmutt merged 152 commits from milestone3 into master 2024-11-21 15:39:19 +00:00
5 changed files with 173 additions and 7 deletions
Showing only changes of commit 4ee09238d8 - Show all commits

View file

@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `listaddresses` RPC method - `listaddresses` RPC method
- `listreceived` RPC method - `listreceived` RPC method
- `getbalance` RPC method - `getbalance` RPC method
- `getnewwallet` RPC method
### Changed ### Changed

View file

@ -16,12 +16,14 @@ import Control.Monad.IO.Class (liftIO)
import Control.Monad.Logger (runNoLoggingT) import Control.Monad.Logger (runNoLoggingT)
import Data.Aeson import Data.Aeson
import Data.Int import Data.Int
import Data.Scientific (floatingOrInteger)
import qualified Data.Text as T import qualified Data.Text as T
import qualified Data.Text.Encoding as E import qualified Data.Text.Encoding as E
import qualified Data.Vector as V import qualified Data.Vector as V
import Database.Esqueleto.Experimental (entityKey, toSqlKey) import Database.Esqueleto.Experimental (entityKey, fromSqlKey, toSqlKey)
import Servant import Servant
import Text.Read (readMaybe) import Text.Read (readMaybe)
import ZcashHaskell.Keys (generateWalletSeedPhrase)
import ZcashHaskell.Orchard (parseAddress) import ZcashHaskell.Orchard (parseAddress)
import ZcashHaskell.Types import ZcashHaskell.Types
( RpcError(..) ( RpcError(..)
@ -31,7 +33,8 @@ import ZcashHaskell.Types
) )
import Zenith.Core (checkBlockChain, checkZebra) import Zenith.Core (checkBlockChain, checkZebra)
import Zenith.DB import Zenith.DB
( findNotesByAddress ( ZcashWallet(..)
, findNotesByAddress
, getAccountById , getAccountById
, getAccounts , getAccounts
, getAddressById , getAddressById
@ -42,6 +45,7 @@ import Zenith.DB
, getWalletNotes , getWalletNotes
, getWallets , getWallets
, initPool , initPool
, saveWallet
, toZcashAccountAPI , toZcashAccountAPI
, toZcashAddressAPI , toZcashAddressAPI
, toZcashWalletAPI , toZcashWalletAPI
@ -49,8 +53,10 @@ import Zenith.DB
import Zenith.Types import Zenith.Types
( AccountBalance(..) ( AccountBalance(..)
, Config(..) , Config(..)
, PhraseDB(..)
, ZcashAccountAPI(..) , ZcashAccountAPI(..)
, ZcashAddressAPI(..) , ZcashAddressAPI(..)
, ZcashNetDB(..)
, ZcashNoteAPI(..) , ZcashNoteAPI(..)
, ZcashWalletAPI(..) , ZcashWalletAPI(..)
) )
@ -63,6 +69,7 @@ data ZenithMethod
| ListAddresses | ListAddresses
| ListReceived | ListReceived
| GetBalance | GetBalance
| GetNewWallet
| UnknownMethod | UnknownMethod
deriving (Eq, Prelude.Show) deriving (Eq, Prelude.Show)
@ -73,6 +80,7 @@ instance ToJSON ZenithMethod where
toJSON ListAddresses = Data.Aeson.String "listaddresses" toJSON ListAddresses = Data.Aeson.String "listaddresses"
toJSON ListReceived = Data.Aeson.String "listreceived" toJSON ListReceived = Data.Aeson.String "listreceived"
toJSON GetBalance = Data.Aeson.String "getbalance" toJSON GetBalance = Data.Aeson.String "getbalance"
toJSON GetNewWallet = Data.Aeson.String "getnewwallet"
toJSON UnknownMethod = Data.Aeson.Null toJSON UnknownMethod = Data.Aeson.Null
instance FromJSON ZenithMethod where instance FromJSON ZenithMethod where
@ -84,6 +92,7 @@ instance FromJSON ZenithMethod where
"listaddresses" -> pure ListAddresses "listaddresses" -> pure ListAddresses
"listreceived" -> pure ListReceived "listreceived" -> pure ListReceived
"getbalance" -> pure GetBalance "getbalance" -> pure GetBalance
"getnewwallet" -> pure GetNewWallet
_ -> pure UnknownMethod _ -> pure UnknownMethod
data ZenithParams data ZenithParams
@ -93,6 +102,7 @@ data ZenithParams
| AddressesParams !Int | AddressesParams !Int
| NotesParams !T.Text | NotesParams !T.Text
| BalanceParams !Int64 | BalanceParams !Int64
| NameParams !T.Text
| TestParams !T.Text | TestParams !T.Text
deriving (Eq, Prelude.Show) deriving (Eq, Prelude.Show)
@ -103,6 +113,7 @@ instance ToJSON ZenithParams where
toJSON (AddressesParams 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] toJSON (TestParams t) = Data.Aeson.Array $ V.fromList [Data.Aeson.String t]
toJSON (NotesParams t) = Data.Aeson.Array $ V.fromList [Data.Aeson.String t] toJSON (NotesParams t) = Data.Aeson.Array $ V.fromList [Data.Aeson.String t]
toJSON (NameParams t) = Data.Aeson.Array $ V.fromList [Data.Aeson.String t]
toJSON (BalanceParams n) = toJSON (BalanceParams n) =
Data.Aeson.Array $ V.fromList [jsonNumber $ fromIntegral n] Data.Aeson.Array $ V.fromList [jsonNumber $ fromIntegral n]
@ -113,6 +124,7 @@ data ZenithResponse
| AddressListResponse !T.Text ![ZcashAddressAPI] | AddressListResponse !T.Text ![ZcashAddressAPI]
| NoteListResponse !T.Text ![ZcashNoteAPI] | NoteListResponse !T.Text ![ZcashNoteAPI]
| BalanceResponse !T.Text !AccountBalance !AccountBalance | BalanceResponse !T.Text !AccountBalance !AccountBalance
| NewItemResponse !T.Text !Int64
| ErrorResponse !T.Text !Double !T.Text | ErrorResponse !T.Text !Double !T.Text
deriving (Eq, Prelude.Show) deriving (Eq, Prelude.Show)
@ -130,6 +142,7 @@ instance ToJSON ZenithResponse where
] ]
toJSON (BalanceResponse i c u) = toJSON (BalanceResponse i c u) =
packRpcResponse i $ object ["confirmed" .= c, "unconfirmed" .= u] packRpcResponse i $ object ["confirmed" .= c, "unconfirmed" .= u]
toJSON (NewItemResponse i ix) = packRpcResponse i ix
instance FromJSON ZenithResponse where instance FromJSON ZenithResponse where
parseJSON = parseJSON =
@ -193,6 +206,10 @@ instance FromJSON ZenithResponse where
pure $ NoteListResponse i k5 pure $ NoteListResponse i k5
Nothing -> fail "Unknown object" Nothing -> fail "Unknown object"
_anyOther -> fail "Malformed JSON" _anyOther -> fail "Malformed JSON"
Number k -> do
case floatingOrInteger k of
Left _e -> fail "Unknown value"
Right k' -> pure $ NewItemResponse i k'
_anyOther -> fail "Malformed JSON" _anyOther -> fail "Malformed JSON"
Just e1 -> pure $ ErrorResponse i (ecode e1) (emessage e1) Just e1 -> pure $ ErrorResponse i (ecode e1) (emessage e1)
@ -284,6 +301,16 @@ instance FromJSON RpcCall where
pure $ RpcCall v i GetBalance (BalanceParams x) pure $ RpcCall v i GetBalance (BalanceParams x)
else pure $ RpcCall v i GetBalance BadParams else pure $ RpcCall v i GetBalance BadParams
_anyOther -> pure $ RpcCall v i GetBalance BadParams _anyOther -> pure $ RpcCall v i GetBalance BadParams
GetNewWallet -> 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 GetNewWallet (NameParams x)
else pure $ RpcCall v i GetNewWallet BadParams
_anyOther -> pure $ RpcCall v i GetNewWallet BadParams
type ZenithRPC type ZenithRPC
= "status" :> Get '[ JSON] Value :<|> BasicAuth "zenith-realm" Bool :> ReqBody = "status" :> Get '[ JSON] Value :<|> BasicAuth "zenith-realm" Bool :> ReqBody
@ -452,6 +479,43 @@ zenithServer config = getinfo :<|> handleRPC
ErrorResponse (callId req) (-32006) "Account does not exist." ErrorResponse (callId req) (-32006) "Account does not exist."
_anyOtherParams -> _anyOtherParams ->
return $ ErrorResponse (callId req) (-32602) "Invalid params" return $ ErrorResponse (callId req) (-32602) "Invalid params"
GetNewWallet ->
case parameters req of
NameParams t -> do
let host = c_zebraHost config
let port = c_zebraPort config
let dbPath = c_dbPath config
sP <- liftIO generateWalletSeedPhrase
pool <- liftIO $ runNoLoggingT $ initPool dbPath
bInfo <-
liftIO $ try $ checkBlockChain host port :: Handler
(Either IOError ZebraGetBlockChainInfo)
case bInfo of
Left _e1 ->
return $
ErrorResponse (callId req) (-32000) "Zebra not available"
Right bI -> do
r <-
liftIO $
saveWallet pool $
ZcashWallet
t
(ZcashNetDB $ zgb_net bI)
(PhraseDB sP)
(zgb_blocks bI)
0
case r of
Nothing ->
return $
ErrorResponse
(callId req)
(-32007)
"Entity with that name already exists."
Just r' ->
return $
NewItemResponse (callId req) $ fromSqlKey $ entityKey r'
_anyOtherParams ->
return $ ErrorResponse (callId req) (-32602) "Invalid params"
authenticate :: Config -> BasicAuthCheck Bool authenticate :: Config -> BasicAuthCheck Bool
authenticate config = BasicAuthCheck check authenticate config = BasicAuthCheck check

View file

@ -26,7 +26,7 @@ import Zenith.RPC
, authenticate , authenticate
, zenithServer , zenithServer
) )
import Zenith.Types (Config(..)) import Zenith.Types (Config(..), ZcashWalletAPI(..))
main :: IO () main :: IO ()
main = do main = do
@ -95,6 +95,75 @@ main = do
"zh" "zh"
(-32001) (-32001)
"No wallets available. Please create one first" "No wallets available. Please create one first"
describe "getnewwallet" $ do
it "bad credentials" $ do
res <-
makeZenithCall
"127.0.0.1"
nodePort
"baduser"
"idontknow"
GetNewWallet
BlankParams
res `shouldBe` Left "Invalid credentials"
describe "correct credentials" $ do
it "no params" $ do
res <-
makeZenithCall
"127.0.0.1"
nodePort
nodeUser
nodePwd
GetNewWallet
BlankParams
case res of
Left e -> assertFailure e
Right r ->
r `shouldBe` ErrorResponse "zh" (-32602) "Invalid params"
it "Valid params" $ do
res <-
makeZenithCall
"127.0.0.1"
nodePort
nodeUser
nodePwd
GetNewWallet
(NameParams "Main")
case res of
Left e -> assertFailure e
Right r -> r `shouldBe` NewItemResponse "zh" 1
it "duplicate name" $ do
res <-
makeZenithCall
"127.0.0.1"
nodePort
nodeUser
nodePwd
GetNewWallet
(NameParams "Main")
case res of
Left e -> assertFailure e
Right r ->
r `shouldBe`
ErrorResponse
"zh"
(-32007)
"Entity with that name already exists."
describe "listwallet" $ do
it "wallet exists" $ do
res <-
makeZenithCall
"127.0.0.1"
nodePort
nodeUser
nodePwd
ListWallets
BlankParams
case res of
Left e -> assertFailure e
Right (WalletListResponse i k) ->
zw_name (head k) `shouldBe` "Main"
Right _ -> assertFailure "Unexpected response"
describe "Accounts" $ do describe "Accounts" $ do
describe "listaccounts" $ do describe "listaccounts" $ do
it "bad credentials" $ do it "bad credentials" $ do

@ -1 +1 @@
Subproject commit 939ae687e8485f5ffce2f09d49c23aac7e14bf72 Subproject commit 0b2fae2b5db6878b7669d639a5cb8c73b986906e

View file

@ -101,14 +101,33 @@
"name": "getnewwallet", "name": "getnewwallet",
"summary": "Create a new wallet", "summary": "Create a new wallet",
"description": "Create a new wallet for Zenith.", "description": "Create a new wallet for Zenith.",
"tags": [{"$ref": "#/components/tags/draft"}], "tags": [],
"params": [], "params": [
{ "$ref": "#/components/contentDescriptors/Name"}
],
"paramStructure": "by-position",
"result": { "result": {
"name": "Wallet", "name": "Wallet",
"schema": { "schema": {
"$ref": "#/components/contentDescriptors/WalletId" "$ref": "#/components/contentDescriptors/WalletId"
} }
},
"examples": [
{
"name": "GetNewWallet example",
"summary": "Create a wallet",
"description": "Creates a new wallet with the given name",
"params": [ "Main" ],
"result": {
"name": "GetNewWallet result",
"value": 1
} }
}
],
"errors": [
{ "$ref": "#/components/errors/ZebraNotAvailable" },
{ "$ref": "#/components/errors/DuplicateName" }
]
}, },
{ {
"name": "listaccounts", "name": "listaccounts",
@ -439,6 +458,15 @@
"schema": { "schema": {
"type": "string" "type": "string"
} }
},
"Name": {
"name": "Name",
"summary": "A user-friendly name",
"description": "A string that represents an entity in Zenith, like a wallet, an account or an address.",
"required": true,
"schema": {
"type": "string"
}
} }
}, },
"schemas": { "schemas": {
@ -536,6 +564,10 @@
"InvalidAccount": { "InvalidAccount": {
"code": -32006, "code": -32006,
"message": "Account does not exist." "message": "Account does not exist."
},
"DuplicateName": {
"code": -32007,
"message": "Entity with that name already exists."
} }
} }
} }