Milestone 3: RPC server, ZIP-320 #104

Merged
pitmutt merged 152 commits from milestone3 into master 2024-11-21 15:39:19 +00:00
6 changed files with 133 additions and 7 deletions
Showing only changes of commit 934bff1454 - Show all commits

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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