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
|
||||
- `listaccounts` RPC method
|
||||
- `listaddresses` RPC method
|
||||
- `listreceived` RPC method
|
||||
- `getbalance` RPC method
|
||||
|
||||
### Changed
|
||||
|
||||
|
|
|
@ -72,7 +72,8 @@ import ZcashHaskell.Types
|
|||
, ZcashNet(..)
|
||||
)
|
||||
import Zenith.Types
|
||||
( HexStringDB(..)
|
||||
( AccountBalance(..)
|
||||
, HexStringDB(..)
|
||||
, OrchardSpendingKeyDB(..)
|
||||
, PhraseDB(..)
|
||||
, RseedDB(..)
|
||||
|
@ -1693,6 +1694,32 @@ getUnconfirmedBalance pool za = do
|
|||
let oBal = sum oAmts
|
||||
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 pool = do
|
||||
runNoLoggingT $
|
||||
|
|
|
@ -19,13 +19,12 @@ import Data.Int
|
|||
import qualified Data.Text as T
|
||||
import qualified Data.Text.Encoding as E
|
||||
import qualified Data.Vector as V
|
||||
import Database.Esqueleto.Experimental (toSqlKey)
|
||||
import Database.Esqueleto.Experimental (entityKey, toSqlKey)
|
||||
import Servant
|
||||
import Text.Read (readMaybe)
|
||||
import ZcashHaskell.Orchard (parseAddress)
|
||||
import ZcashHaskell.Types
|
||||
( RpcError(..)
|
||||
, ValidAddress(..)
|
||||
, ZcashNet(..)
|
||||
, ZebraGetBlockChainInfo(..)
|
||||
, ZebraGetInfo(..)
|
||||
|
@ -33,10 +32,13 @@ import ZcashHaskell.Types
|
|||
import Zenith.Core (checkBlockChain, checkZebra)
|
||||
import Zenith.DB
|
||||
( findNotesByAddress
|
||||
, getAccountById
|
||||
, getAccounts
|
||||
, getAddressById
|
||||
, getAddresses
|
||||
, getExternalAddresses
|
||||
, getPoolBalance
|
||||
, getUnconfPoolBalance
|
||||
, getWalletNotes
|
||||
, getWallets
|
||||
, initPool
|
||||
|
@ -45,7 +47,8 @@ import Zenith.DB
|
|||
, toZcashWalletAPI
|
||||
)
|
||||
import Zenith.Types
|
||||
( Config(..)
|
||||
( AccountBalance(..)
|
||||
, Config(..)
|
||||
, ZcashAccountAPI(..)
|
||||
, ZcashAddressAPI(..)
|
||||
, ZcashNoteAPI(..)
|
||||
|
@ -59,6 +62,7 @@ data ZenithMethod
|
|||
| ListAccounts
|
||||
| ListAddresses
|
||||
| ListReceived
|
||||
| GetBalance
|
||||
| UnknownMethod
|
||||
deriving (Eq, Prelude.Show)
|
||||
|
||||
|
@ -68,6 +72,7 @@ instance ToJSON ZenithMethod where
|
|||
toJSON ListAccounts = Data.Aeson.String "listaccounts"
|
||||
toJSON ListAddresses = Data.Aeson.String "listaddresses"
|
||||
toJSON ListReceived = Data.Aeson.String "listreceived"
|
||||
toJSON GetBalance = Data.Aeson.String "getbalance"
|
||||
toJSON UnknownMethod = Data.Aeson.Null
|
||||
|
||||
instance FromJSON ZenithMethod where
|
||||
|
@ -78,6 +83,7 @@ instance FromJSON ZenithMethod where
|
|||
"listaccounts" -> pure ListAccounts
|
||||
"listaddresses" -> pure ListAddresses
|
||||
"listreceived" -> pure ListReceived
|
||||
"getbalance" -> pure GetBalance
|
||||
_ -> pure UnknownMethod
|
||||
|
||||
data ZenithParams
|
||||
|
@ -86,6 +92,7 @@ data ZenithParams
|
|||
| AccountsParams !Int
|
||||
| AddressesParams !Int
|
||||
| NotesParams !T.Text
|
||||
| BalanceParams !Int64
|
||||
| TestParams !T.Text
|
||||
deriving (Eq, Prelude.Show)
|
||||
|
||||
|
@ -96,6 +103,8 @@ 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 (BalanceParams n) =
|
||||
Data.Aeson.Array $ V.fromList [jsonNumber $ fromIntegral n]
|
||||
|
||||
data ZenithResponse
|
||||
= InfoResponse !T.Text !ZenithInfo
|
||||
|
@ -103,6 +112,7 @@ data ZenithResponse
|
|||
| AccountListResponse !T.Text ![ZcashAccountAPI]
|
||||
| AddressListResponse !T.Text ![ZcashAddressAPI]
|
||||
| NoteListResponse !T.Text ![ZcashNoteAPI]
|
||||
| BalanceResponse !T.Text !AccountBalance !AccountBalance
|
||||
| ErrorResponse !T.Text !Double !T.Text
|
||||
deriving (Eq, Prelude.Show)
|
||||
|
||||
|
@ -118,6 +128,8 @@ instance ToJSON ZenithResponse where
|
|||
, "id" .= i
|
||||
, "error" .= object ["code" .= c, "message" .= m]
|
||||
]
|
||||
toJSON (BalanceResponse i c u) =
|
||||
packRpcResponse i $ object ["confirmed" .= c, "unconfirmed" .= u]
|
||||
|
||||
instance FromJSON ZenithResponse where
|
||||
parseJSON =
|
||||
|
@ -137,11 +149,19 @@ instance FromJSON ZenithResponse where
|
|||
case r1 of
|
||||
Object k -> do
|
||||
v <- k .:? "version"
|
||||
v5 <- k .:? "unconfirmed"
|
||||
case (v :: Maybe String) of
|
||||
Nothing -> fail "Unknown result"
|
||||
Just _v' -> do
|
||||
k1 <- parseJSON r1
|
||||
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
|
||||
if V.null n
|
||||
then fail "Malformed JSON"
|
||||
|
@ -151,6 +171,7 @@ instance FromJSON ZenithResponse where
|
|||
v1 <- n' .:? "lastSync"
|
||||
v2 <- n' .:? "wallet"
|
||||
v3 <- n' .:? "ua"
|
||||
v4 <- n' .:? "amountZats"
|
||||
case (v1 :: Maybe Int) of
|
||||
Just _v1' -> do
|
||||
k2 <- parseJSON r1
|
||||
|
@ -165,6 +186,11 @@ instance FromJSON ZenithResponse where
|
|||
Just _v3' -> do
|
||||
k4 <- parseJSON r1
|
||||
pure $ AddressListResponse i k4
|
||||
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"
|
||||
|
@ -248,6 +274,16 @@ instance FromJSON RpcCall where
|
|||
pure $ RpcCall v i ListReceived (NotesParams x)
|
||||
else 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
|
||||
= "status" :> Get '[ JSON] Value :<|> BasicAuth "zenith-realm" Bool :> ReqBody
|
||||
|
@ -400,6 +436,22 @@ zenithServer config = getinfo :<|> handleRPC
|
|||
return $ NoteListResponse (callId req) nList
|
||||
_anyOtherParams ->
|
||||
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 check
|
||||
|
|
|
@ -175,6 +175,14 @@ data ZcashNoteAPI = 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`
|
||||
-- | Type for modeling the tree state response
|
||||
data ZebraTreeInfo = ZebraTreeInfo
|
||||
|
|
|
@ -190,6 +190,43 @@ main = do
|
|||
case res of
|
||||
Left e -> assertFailure e
|
||||
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 = do
|
||||
|
|
|
@ -247,7 +247,7 @@
|
|||
"name": "getbalance",
|
||||
"summary": "Get the balance of the given account",
|
||||
"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"}],
|
||||
"result": {
|
||||
"name": "Balance",
|
||||
|
|
Loading…
Reference in a new issue