Milestone 3: RPC server, ZIP-320 #104
6 changed files with 133 additions and 7 deletions
|
@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
- `listwallets` RPC method
|
- `listwallets` RPC method
|
||||||
- `listaccounts` RPC method
|
- `listaccounts` RPC method
|
||||||
- `listaddresses` RPC method
|
- `listaddresses` RPC method
|
||||||
|
- `listreceived` RPC method
|
||||||
|
- `getbalance` RPC method
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
|
|
@ -72,7 +72,8 @@ import ZcashHaskell.Types
|
||||||
, ZcashNet(..)
|
, ZcashNet(..)
|
||||||
)
|
)
|
||||||
import Zenith.Types
|
import Zenith.Types
|
||||||
( HexStringDB(..)
|
( AccountBalance(..)
|
||||||
|
, HexStringDB(..)
|
||||||
, OrchardSpendingKeyDB(..)
|
, OrchardSpendingKeyDB(..)
|
||||||
, PhraseDB(..)
|
, PhraseDB(..)
|
||||||
, RseedDB(..)
|
, RseedDB(..)
|
||||||
|
@ -1693,6 +1694,32 @@ getUnconfirmedBalance pool za = do
|
||||||
let oBal = sum oAmts
|
let oBal = sum oAmts
|
||||||
return . fromIntegral $ tBal + sBal + oBal
|
return . fromIntegral $ tBal + sBal + oBal
|
||||||
|
|
||||||
|
getPoolBalance :: ConnectionPool -> ZcashAccountId -> IO AccountBalance
|
||||||
|
getPoolBalance pool za = do
|
||||||
|
trNotes <- getWalletUnspentTrNotes pool za
|
||||||
|
let tAmts = map (walletTrNoteValue . entityVal) trNotes
|
||||||
|
let tBal = sum tAmts
|
||||||
|
sapNotes <- getWalletUnspentSapNotes pool za
|
||||||
|
let sAmts = map (walletSapNoteValue . entityVal) sapNotes
|
||||||
|
let sBal = sum sAmts
|
||||||
|
orchNotes <- getWalletUnspentOrchNotes pool za
|
||||||
|
let oAmts = map (walletOrchNoteValue . entityVal) orchNotes
|
||||||
|
let oBal = sum oAmts
|
||||||
|
return $ AccountBalance tBal sBal oBal
|
||||||
|
|
||||||
|
getUnconfPoolBalance :: ConnectionPool -> ZcashAccountId -> IO AccountBalance
|
||||||
|
getUnconfPoolBalance pool za = do
|
||||||
|
trNotes <- getWalletUnspentUnconfirmedTrNotes pool za
|
||||||
|
let tAmts = map (walletTrNoteValue . entityVal) trNotes
|
||||||
|
let tBal = sum tAmts
|
||||||
|
sapNotes <- getWalletUnspentUnconfirmedSapNotes pool za
|
||||||
|
let sAmts = map (walletSapNoteValue . entityVal) sapNotes
|
||||||
|
let sBal = sum sAmts
|
||||||
|
orchNotes <- getWalletUnspentUnconfirmedOrchNotes pool za
|
||||||
|
let oAmts = map (walletOrchNoteValue . entityVal) orchNotes
|
||||||
|
let oBal = sum oAmts
|
||||||
|
return $ AccountBalance tBal sBal oBal
|
||||||
|
|
||||||
clearWalletTransactions :: ConnectionPool -> IO ()
|
clearWalletTransactions :: ConnectionPool -> IO ()
|
||||||
clearWalletTransactions pool = do
|
clearWalletTransactions pool = do
|
||||||
runNoLoggingT $
|
runNoLoggingT $
|
||||||
|
|
|
@ -19,13 +19,12 @@ import Data.Int
|
||||||
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 (toSqlKey)
|
import Database.Esqueleto.Experimental (entityKey, toSqlKey)
|
||||||
import Servant
|
import Servant
|
||||||
import Text.Read (readMaybe)
|
import Text.Read (readMaybe)
|
||||||
import ZcashHaskell.Orchard (parseAddress)
|
import ZcashHaskell.Orchard (parseAddress)
|
||||||
import ZcashHaskell.Types
|
import ZcashHaskell.Types
|
||||||
( RpcError(..)
|
( RpcError(..)
|
||||||
, ValidAddress(..)
|
|
||||||
, ZcashNet(..)
|
, ZcashNet(..)
|
||||||
, ZebraGetBlockChainInfo(..)
|
, ZebraGetBlockChainInfo(..)
|
||||||
, ZebraGetInfo(..)
|
, ZebraGetInfo(..)
|
||||||
|
@ -33,10 +32,13 @@ import ZcashHaskell.Types
|
||||||
import Zenith.Core (checkBlockChain, checkZebra)
|
import Zenith.Core (checkBlockChain, checkZebra)
|
||||||
import Zenith.DB
|
import Zenith.DB
|
||||||
( findNotesByAddress
|
( findNotesByAddress
|
||||||
|
, getAccountById
|
||||||
, getAccounts
|
, getAccounts
|
||||||
, getAddressById
|
, getAddressById
|
||||||
, getAddresses
|
, getAddresses
|
||||||
, getExternalAddresses
|
, getExternalAddresses
|
||||||
|
, getPoolBalance
|
||||||
|
, getUnconfPoolBalance
|
||||||
, getWalletNotes
|
, getWalletNotes
|
||||||
, getWallets
|
, getWallets
|
||||||
, initPool
|
, initPool
|
||||||
|
@ -45,7 +47,8 @@ import Zenith.DB
|
||||||
, toZcashWalletAPI
|
, toZcashWalletAPI
|
||||||
)
|
)
|
||||||
import Zenith.Types
|
import Zenith.Types
|
||||||
( Config(..)
|
( AccountBalance(..)
|
||||||
|
, Config(..)
|
||||||
, ZcashAccountAPI(..)
|
, ZcashAccountAPI(..)
|
||||||
, ZcashAddressAPI(..)
|
, ZcashAddressAPI(..)
|
||||||
, ZcashNoteAPI(..)
|
, ZcashNoteAPI(..)
|
||||||
|
@ -59,6 +62,7 @@ data ZenithMethod
|
||||||
| ListAccounts
|
| ListAccounts
|
||||||
| ListAddresses
|
| ListAddresses
|
||||||
| ListReceived
|
| ListReceived
|
||||||
|
| GetBalance
|
||||||
| UnknownMethod
|
| UnknownMethod
|
||||||
deriving (Eq, Prelude.Show)
|
deriving (Eq, Prelude.Show)
|
||||||
|
|
||||||
|
@ -68,6 +72,7 @@ instance ToJSON ZenithMethod where
|
||||||
toJSON ListAccounts = Data.Aeson.String "listaccounts"
|
toJSON ListAccounts = Data.Aeson.String "listaccounts"
|
||||||
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 UnknownMethod = Data.Aeson.Null
|
toJSON UnknownMethod = Data.Aeson.Null
|
||||||
|
|
||||||
instance FromJSON ZenithMethod where
|
instance FromJSON ZenithMethod where
|
||||||
|
@ -78,6 +83,7 @@ instance FromJSON ZenithMethod where
|
||||||
"listaccounts" -> pure ListAccounts
|
"listaccounts" -> pure ListAccounts
|
||||||
"listaddresses" -> pure ListAddresses
|
"listaddresses" -> pure ListAddresses
|
||||||
"listreceived" -> pure ListReceived
|
"listreceived" -> pure ListReceived
|
||||||
|
"getbalance" -> pure GetBalance
|
||||||
_ -> pure UnknownMethod
|
_ -> pure UnknownMethod
|
||||||
|
|
||||||
data ZenithParams
|
data ZenithParams
|
||||||
|
@ -86,6 +92,7 @@ data ZenithParams
|
||||||
| AccountsParams !Int
|
| AccountsParams !Int
|
||||||
| AddressesParams !Int
|
| AddressesParams !Int
|
||||||
| NotesParams !T.Text
|
| NotesParams !T.Text
|
||||||
|
| BalanceParams !Int64
|
||||||
| TestParams !T.Text
|
| TestParams !T.Text
|
||||||
deriving (Eq, Prelude.Show)
|
deriving (Eq, Prelude.Show)
|
||||||
|
|
||||||
|
@ -96,6 +103,8 @@ 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 (BalanceParams n) =
|
||||||
|
Data.Aeson.Array $ V.fromList [jsonNumber $ fromIntegral n]
|
||||||
|
|
||||||
data ZenithResponse
|
data ZenithResponse
|
||||||
= InfoResponse !T.Text !ZenithInfo
|
= InfoResponse !T.Text !ZenithInfo
|
||||||
|
@ -103,6 +112,7 @@ data ZenithResponse
|
||||||
| AccountListResponse !T.Text ![ZcashAccountAPI]
|
| AccountListResponse !T.Text ![ZcashAccountAPI]
|
||||||
| AddressListResponse !T.Text ![ZcashAddressAPI]
|
| AddressListResponse !T.Text ![ZcashAddressAPI]
|
||||||
| NoteListResponse !T.Text ![ZcashNoteAPI]
|
| NoteListResponse !T.Text ![ZcashNoteAPI]
|
||||||
|
| BalanceResponse !T.Text !AccountBalance !AccountBalance
|
||||||
| ErrorResponse !T.Text !Double !T.Text
|
| ErrorResponse !T.Text !Double !T.Text
|
||||||
deriving (Eq, Prelude.Show)
|
deriving (Eq, Prelude.Show)
|
||||||
|
|
||||||
|
@ -118,6 +128,8 @@ instance ToJSON ZenithResponse where
|
||||||
, "id" .= i
|
, "id" .= i
|
||||||
, "error" .= object ["code" .= c, "message" .= m]
|
, "error" .= object ["code" .= c, "message" .= m]
|
||||||
]
|
]
|
||||||
|
toJSON (BalanceResponse i c u) =
|
||||||
|
packRpcResponse i $ object ["confirmed" .= c, "unconfirmed" .= u]
|
||||||
|
|
||||||
instance FromJSON ZenithResponse where
|
instance FromJSON ZenithResponse where
|
||||||
parseJSON =
|
parseJSON =
|
||||||
|
@ -137,11 +149,19 @@ instance FromJSON ZenithResponse where
|
||||||
case r1 of
|
case r1 of
|
||||||
Object k -> do
|
Object k -> do
|
||||||
v <- k .:? "version"
|
v <- k .:? "version"
|
||||||
|
v5 <- k .:? "unconfirmed"
|
||||||
case (v :: Maybe String) of
|
case (v :: Maybe String) of
|
||||||
Nothing -> fail "Unknown result"
|
|
||||||
Just _v' -> do
|
Just _v' -> do
|
||||||
k1 <- parseJSON r1
|
k1 <- parseJSON r1
|
||||||
pure $ InfoResponse i k1
|
pure $ InfoResponse i k1
|
||||||
|
Nothing ->
|
||||||
|
case (v5 :: Maybe AccountBalance) of
|
||||||
|
Just _v5' -> do
|
||||||
|
k6 <- parseJSON r1
|
||||||
|
j1 <- k6 .: "confirmed"
|
||||||
|
j2 <- k6 .: "unconfirmed"
|
||||||
|
pure $ BalanceResponse i j1 j2
|
||||||
|
Nothing -> fail "Unknown object"
|
||||||
Array n -> do
|
Array n -> do
|
||||||
if V.null n
|
if V.null n
|
||||||
then fail "Malformed JSON"
|
then fail "Malformed JSON"
|
||||||
|
@ -151,6 +171,7 @@ instance FromJSON ZenithResponse where
|
||||||
v1 <- n' .:? "lastSync"
|
v1 <- n' .:? "lastSync"
|
||||||
v2 <- n' .:? "wallet"
|
v2 <- n' .:? "wallet"
|
||||||
v3 <- n' .:? "ua"
|
v3 <- n' .:? "ua"
|
||||||
|
v4 <- n' .:? "amountZats"
|
||||||
case (v1 :: Maybe Int) of
|
case (v1 :: Maybe Int) of
|
||||||
Just _v1' -> do
|
Just _v1' -> do
|
||||||
k2 <- parseJSON r1
|
k2 <- parseJSON r1
|
||||||
|
@ -165,7 +186,12 @@ instance FromJSON ZenithResponse where
|
||||||
Just _v3' -> do
|
Just _v3' -> do
|
||||||
k4 <- parseJSON r1
|
k4 <- parseJSON r1
|
||||||
pure $ AddressListResponse i k4
|
pure $ AddressListResponse i k4
|
||||||
Nothing -> fail "Unknown object"
|
Nothing ->
|
||||||
|
case (v4 :: Maybe Int) of
|
||||||
|
Just _v4' -> do
|
||||||
|
k5 <- parseJSON r1
|
||||||
|
pure $ NoteListResponse i k5
|
||||||
|
Nothing -> fail "Unknown object"
|
||||||
_anyOther -> fail "Malformed JSON"
|
_anyOther -> fail "Malformed JSON"
|
||||||
_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)
|
||||||
|
@ -248,6 +274,16 @@ instance FromJSON RpcCall where
|
||||||
pure $ RpcCall v i ListReceived (NotesParams x)
|
pure $ RpcCall v i ListReceived (NotesParams x)
|
||||||
else pure $ RpcCall v i ListReceived BadParams
|
else pure $ RpcCall v i ListReceived BadParams
|
||||||
_anyOther -> pure $ RpcCall v i ListReceived BadParams
|
_anyOther -> pure $ RpcCall v i ListReceived BadParams
|
||||||
|
GetBalance -> 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 GetBalance (BalanceParams x)
|
||||||
|
else pure $ RpcCall v i GetBalance BadParams
|
||||||
|
_anyOther -> pure $ RpcCall v i GetBalance BadParams
|
||||||
|
|
||||||
type ZenithRPC
|
type ZenithRPC
|
||||||
= "status" :> Get '[ JSON] Value :<|> BasicAuth "zenith-realm" Bool :> ReqBody
|
= "status" :> Get '[ JSON] Value :<|> BasicAuth "zenith-realm" Bool :> ReqBody
|
||||||
|
@ -400,6 +436,22 @@ zenithServer config = getinfo :<|> handleRPC
|
||||||
return $ NoteListResponse (callId req) nList
|
return $ NoteListResponse (callId req) nList
|
||||||
_anyOtherParams ->
|
_anyOtherParams ->
|
||||||
return $ ErrorResponse (callId req) (-32602) "Invalid params"
|
return $ ErrorResponse (callId req) (-32602) "Invalid params"
|
||||||
|
GetBalance ->
|
||||||
|
case parameters req of
|
||||||
|
BalanceParams i -> do
|
||||||
|
let dbPath = c_dbPath config
|
||||||
|
pool <- liftIO $ runNoLoggingT $ initPool dbPath
|
||||||
|
acc <- liftIO $ getAccountById pool $ toSqlKey i
|
||||||
|
case acc of
|
||||||
|
Just acc' -> do
|
||||||
|
c <- liftIO $ getPoolBalance pool $ entityKey acc'
|
||||||
|
u <- liftIO $ getUnconfPoolBalance pool $ entityKey acc'
|
||||||
|
return $ BalanceResponse (callId req) c u
|
||||||
|
Nothing ->
|
||||||
|
return $
|
||||||
|
ErrorResponse (callId req) (-32006) "Account does not exist."
|
||||||
|
_anyOtherParams ->
|
||||||
|
return $ ErrorResponse (callId req) (-32602) "Invalid params"
|
||||||
|
|
||||||
authenticate :: Config -> BasicAuthCheck Bool
|
authenticate :: Config -> BasicAuthCheck Bool
|
||||||
authenticate config = BasicAuthCheck check
|
authenticate config = BasicAuthCheck check
|
||||||
|
|
|
@ -175,6 +175,14 @@ data ZcashNoteAPI = ZcashNoteAPI
|
||||||
|
|
||||||
$(deriveJSON defaultOptions {fieldLabelModifier = drop 3} ''ZcashNoteAPI)
|
$(deriveJSON defaultOptions {fieldLabelModifier = drop 3} ''ZcashNoteAPI)
|
||||||
|
|
||||||
|
data AccountBalance = AccountBalance
|
||||||
|
{ acb_transparent :: !Int64
|
||||||
|
, acb_sapling :: !Int64
|
||||||
|
, acb_orchard :: !Int64
|
||||||
|
} deriving (Eq, Prelude.Show)
|
||||||
|
|
||||||
|
$(deriveJSON defaultOptions {fieldLabelModifier = drop 4} ''AccountBalance)
|
||||||
|
|
||||||
-- ** `zebrad`
|
-- ** `zebrad`
|
||||||
-- | Type for modeling the tree state response
|
-- | Type for modeling the tree state response
|
||||||
data ZebraTreeInfo = ZebraTreeInfo
|
data ZebraTreeInfo = ZebraTreeInfo
|
||||||
|
|
|
@ -190,6 +190,43 @@ main = do
|
||||||
case res of
|
case res of
|
||||||
Left e -> assertFailure e
|
Left e -> assertFailure e
|
||||||
Right (ErrorResponse i c m) -> c `shouldBe` (-32004)
|
Right (ErrorResponse i c m) -> c `shouldBe` (-32004)
|
||||||
|
describe "Balance" $ do
|
||||||
|
describe "getbalance" $ do
|
||||||
|
it "bad credentials" $ do
|
||||||
|
res <-
|
||||||
|
makeZenithCall
|
||||||
|
"127.0.0.1"
|
||||||
|
nodePort
|
||||||
|
"baduser"
|
||||||
|
"idontknow"
|
||||||
|
GetBalance
|
||||||
|
BlankParams
|
||||||
|
res `shouldBe` Left "Invalid credentials"
|
||||||
|
describe "correct credentials" $ do
|
||||||
|
it "no parameters" $ do
|
||||||
|
res <-
|
||||||
|
makeZenithCall
|
||||||
|
"127.0.0.1"
|
||||||
|
nodePort
|
||||||
|
nodeUser
|
||||||
|
nodePwd
|
||||||
|
GetBalance
|
||||||
|
BlankParams
|
||||||
|
case res of
|
||||||
|
Left e -> assertFailure e
|
||||||
|
Right (ErrorResponse i c m) -> c `shouldBe` (-32602)
|
||||||
|
it "unknown index" $ do
|
||||||
|
res <-
|
||||||
|
makeZenithCall
|
||||||
|
"127.0.0.1"
|
||||||
|
nodePort
|
||||||
|
nodeUser
|
||||||
|
nodePwd
|
||||||
|
GetBalance
|
||||||
|
(BalanceParams 17)
|
||||||
|
case res of
|
||||||
|
Left e -> assertFailure e
|
||||||
|
Right (ErrorResponse i c m) -> c `shouldBe` (-32006)
|
||||||
|
|
||||||
startAPI :: Config -> IO ()
|
startAPI :: Config -> IO ()
|
||||||
startAPI config = do
|
startAPI config = do
|
||||||
|
|
|
@ -247,7 +247,7 @@
|
||||||
"name": "getbalance",
|
"name": "getbalance",
|
||||||
"summary": "Get the balance of the given account",
|
"summary": "Get the balance of the given account",
|
||||||
"description": "Get the balance of the given account, including any unconfirmed balance.",
|
"description": "Get the balance of the given account, including any unconfirmed balance.",
|
||||||
"tags": [{"$ref": "#/components/tags/draft"},{"$ref": "#/components/tags/wip"}],
|
"tags": [],
|
||||||
"params": [{ "$ref": "#/components/contentDescriptors/AccountId"}],
|
"params": [{ "$ref": "#/components/contentDescriptors/AccountId"}],
|
||||||
"result": {
|
"result": {
|
||||||
"name": "Balance",
|
"name": "Balance",
|
||||||
|
|
Loading…
Reference in a new issue