Implement getnewwallet RPC method

This commit is contained in:
Rene Vergara 2024-08-24 07:45:42 -05:00
parent 934bff1454
commit 4ee09238d8
Signed by: pitmutt
GPG key ID: 65122AD495A7F5B2
5 changed files with 173 additions and 7 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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."
}
}
}