RPC Server #103
5 changed files with 173 additions and 7 deletions
|
@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- `listaddresses` RPC method
|
||||
- `listreceived` RPC method
|
||||
- `getbalance` RPC method
|
||||
- `getnewwallet` RPC method
|
||||
|
||||
### Changed
|
||||
|
||||
|
|
|
@ -16,12 +16,14 @@ import Control.Monad.IO.Class (liftIO)
|
|||
import Control.Monad.Logger (runNoLoggingT)
|
||||
import Data.Aeson
|
||||
import Data.Int
|
||||
import Data.Scientific (floatingOrInteger)
|
||||
import qualified Data.Text as T
|
||||
import qualified Data.Text.Encoding as E
|
||||
import qualified Data.Vector as V
|
||||
import Database.Esqueleto.Experimental (entityKey, toSqlKey)
|
||||
import Database.Esqueleto.Experimental (entityKey, fromSqlKey, toSqlKey)
|
||||
import Servant
|
||||
import Text.Read (readMaybe)
|
||||
import ZcashHaskell.Keys (generateWalletSeedPhrase)
|
||||
import ZcashHaskell.Orchard (parseAddress)
|
||||
import ZcashHaskell.Types
|
||||
( RpcError(..)
|
||||
|
@ -31,7 +33,8 @@ import ZcashHaskell.Types
|
|||
)
|
||||
import Zenith.Core (checkBlockChain, checkZebra)
|
||||
import Zenith.DB
|
||||
( findNotesByAddress
|
||||
( ZcashWallet(..)
|
||||
, findNotesByAddress
|
||||
, getAccountById
|
||||
, getAccounts
|
||||
, getAddressById
|
||||
|
@ -42,6 +45,7 @@ import Zenith.DB
|
|||
, getWalletNotes
|
||||
, getWallets
|
||||
, initPool
|
||||
, saveWallet
|
||||
, toZcashAccountAPI
|
||||
, toZcashAddressAPI
|
||||
, toZcashWalletAPI
|
||||
|
@ -49,8 +53,10 @@ import Zenith.DB
|
|||
import Zenith.Types
|
||||
( AccountBalance(..)
|
||||
, Config(..)
|
||||
, PhraseDB(..)
|
||||
, ZcashAccountAPI(..)
|
||||
, ZcashAddressAPI(..)
|
||||
, ZcashNetDB(..)
|
||||
, ZcashNoteAPI(..)
|
||||
, ZcashWalletAPI(..)
|
||||
)
|
||||
|
@ -63,6 +69,7 @@ data ZenithMethod
|
|||
| ListAddresses
|
||||
| ListReceived
|
||||
| GetBalance
|
||||
| GetNewWallet
|
||||
| UnknownMethod
|
||||
deriving (Eq, Prelude.Show)
|
||||
|
||||
|
@ -73,6 +80,7 @@ instance ToJSON ZenithMethod where
|
|||
toJSON ListAddresses = Data.Aeson.String "listaddresses"
|
||||
toJSON ListReceived = Data.Aeson.String "listreceived"
|
||||
toJSON GetBalance = Data.Aeson.String "getbalance"
|
||||
toJSON GetNewWallet = Data.Aeson.String "getnewwallet"
|
||||
toJSON UnknownMethod = Data.Aeson.Null
|
||||
|
||||
instance FromJSON ZenithMethod where
|
||||
|
@ -84,6 +92,7 @@ instance FromJSON ZenithMethod where
|
|||
"listaddresses" -> pure ListAddresses
|
||||
"listreceived" -> pure ListReceived
|
||||
"getbalance" -> pure GetBalance
|
||||
"getnewwallet" -> pure GetNewWallet
|
||||
_ -> pure UnknownMethod
|
||||
|
||||
data ZenithParams
|
||||
|
@ -93,6 +102,7 @@ data ZenithParams
|
|||
| AddressesParams !Int
|
||||
| NotesParams !T.Text
|
||||
| BalanceParams !Int64
|
||||
| NameParams !T.Text
|
||||
| TestParams !T.Text
|
||||
deriving (Eq, Prelude.Show)
|
||||
|
||||
|
@ -103,6 +113,7 @@ instance ToJSON ZenithParams where
|
|||
toJSON (AddressesParams n) = Data.Aeson.Array $ V.fromList [jsonNumber n]
|
||||
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 (NameParams t) = Data.Aeson.Array $ V.fromList [Data.Aeson.String t]
|
||||
toJSON (BalanceParams n) =
|
||||
Data.Aeson.Array $ V.fromList [jsonNumber $ fromIntegral n]
|
||||
|
||||
|
@ -113,6 +124,7 @@ data ZenithResponse
|
|||
| AddressListResponse !T.Text ![ZcashAddressAPI]
|
||||
| NoteListResponse !T.Text ![ZcashNoteAPI]
|
||||
| BalanceResponse !T.Text !AccountBalance !AccountBalance
|
||||
| NewItemResponse !T.Text !Int64
|
||||
| ErrorResponse !T.Text !Double !T.Text
|
||||
deriving (Eq, Prelude.Show)
|
||||
|
||||
|
@ -130,6 +142,7 @@ instance ToJSON ZenithResponse where
|
|||
]
|
||||
toJSON (BalanceResponse i c u) =
|
||||
packRpcResponse i $ object ["confirmed" .= c, "unconfirmed" .= u]
|
||||
toJSON (NewItemResponse i ix) = packRpcResponse i ix
|
||||
|
||||
instance FromJSON ZenithResponse where
|
||||
parseJSON =
|
||||
|
@ -193,6 +206,10 @@ instance FromJSON ZenithResponse where
|
|||
pure $ NoteListResponse i k5
|
||||
Nothing -> fail "Unknown object"
|
||||
_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"
|
||||
Just e1 -> pure $ ErrorResponse i (ecode e1) (emessage e1)
|
||||
|
||||
|
@ -284,6 +301,16 @@ instance FromJSON RpcCall where
|
|||
pure $ RpcCall v i GetBalance (BalanceParams x)
|
||||
else 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
|
||||
= "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."
|
||||
_anyOtherParams ->
|
||||
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 check
|
||||
|
|
|
@ -26,7 +26,7 @@ import Zenith.RPC
|
|||
, authenticate
|
||||
, zenithServer
|
||||
)
|
||||
import Zenith.Types (Config(..))
|
||||
import Zenith.Types (Config(..), ZcashWalletAPI(..))
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
|
@ -95,6 +95,75 @@ main = do
|
|||
"zh"
|
||||
(-32001)
|
||||
"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 "listaccounts" $ do
|
||||
it "bad credentials" $ do
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 939ae687e8485f5ffce2f09d49c23aac7e14bf72
|
||||
Subproject commit 0b2fae2b5db6878b7669d639a5cb8c73b986906e
|
|
@ -101,14 +101,33 @@
|
|||
"name": "getnewwallet",
|
||||
"summary": "Create a new wallet",
|
||||
"description": "Create a new wallet for Zenith.",
|
||||
"tags": [{"$ref": "#/components/tags/draft"}],
|
||||
"params": [],
|
||||
"tags": [],
|
||||
"params": [
|
||||
{ "$ref": "#/components/contentDescriptors/Name"}
|
||||
],
|
||||
"paramStructure": "by-position",
|
||||
"result": {
|
||||
"name": "Wallet",
|
||||
"schema": {
|
||||
"$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",
|
||||
|
@ -439,6 +458,15 @@
|
|||
"schema": {
|
||||
"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": {
|
||||
|
@ -536,6 +564,10 @@
|
|||
"InvalidAccount": {
|
||||
"code": -32006,
|
||||
"message": "Account does not exist."
|
||||
},
|
||||
"DuplicateName": {
|
||||
"code": -32007,
|
||||
"message": "Entity with that name already exists."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue