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
|
- `listaddresses` RPC method
|
||||||
- `listreceived` RPC method
|
- `listreceived` RPC method
|
||||||
- `getbalance` RPC method
|
- `getbalance` RPC method
|
||||||
|
- `getnewwallet` RPC method
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue