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 129 additions and 6 deletions
Showing only changes of commit b8980bd219 - Show all commits

View file

@ -10,6 +10,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- RPC module - RPC module
- OpenRPC specification
- `listwallets` RPC method
- `listaccounts` RPC method
- `listaddresses` RPC method
## [0.6.0.0-beta] ## [0.6.0.0-beta]

View file

@ -42,7 +42,8 @@ import Haskoin.Transaction.Common
, txHashToHex , txHashToHex
) )
import qualified Lens.Micro as ML ((&), (.~), (^.)) import qualified Lens.Micro as ML ((&), (.~), (^.))
import ZcashHaskell.Orchard (isValidUnifiedAddress) import ZcashHaskell.Orchard (getSaplingFromUA, isValidUnifiedAddress)
import ZcashHaskell.Transparent (encodeTransparentReceiver)
import ZcashHaskell.Types import ZcashHaskell.Types
( DecodedNote(..) ( DecodedNote(..)
, OrchardAction(..) , OrchardAction(..)
@ -62,7 +63,7 @@ import ZcashHaskell.Types
, TransparentBundle(..) , TransparentBundle(..)
, TransparentReceiver(..) , TransparentReceiver(..)
, UnifiedAddress(..) , UnifiedAddress(..)
, ZcashNet , ZcashNet(..)
, decodeHexText , decodeHexText
) )
import Zenith.Types import Zenith.Types
@ -76,6 +77,7 @@ import Zenith.Types
, TransparentSpendingKeyDB , TransparentSpendingKeyDB
, UnifiedAddressDB(..) , UnifiedAddressDB(..)
, ZcashAccountAPI(..) , ZcashAccountAPI(..)
, ZcashAddressAPI(..)
, ZcashNetDB(..) , ZcashNetDB(..)
, ZcashPool(..) , ZcashPool(..)
, ZcashWalletAPI(..) , ZcashWalletAPI(..)
@ -284,6 +286,27 @@ toZcashAccountAPI a =
(fromIntegral $ fromSqlKey $ zcashAccountWalletId $ entityVal a) (fromIntegral $ fromSqlKey $ zcashAccountWalletId $ entityVal a)
(zcashAccountName $ entityVal a) (zcashAccountName $ entityVal a)
-- | @WalletAddress@
toZcashAddressAPI :: Entity WalletAddress -> ZcashAddressAPI
toZcashAddressAPI a =
ZcashAddressAPI
(fromIntegral $ fromSqlKey $ entityKey a)
(fromIntegral $ fromSqlKey $ walletAddressAccId $ entityVal a)
(walletAddressName $ entityVal a)
(getUA $ walletAddressUAddress $ entityVal a)
(getSaplingFromUA $
TE.encodeUtf8 $ getUA $ walletAddressUAddress $ entityVal a)
(encodeTransparentReceiver
(maybe
TestNet
ua_net
((isValidUnifiedAddress .
TE.encodeUtf8 . getUA . walletAddressUAddress) $
entityVal a)) <$>
(t_rec =<<
(isValidUnifiedAddress . TE.encodeUtf8 . getUA . walletAddressUAddress)
(entityVal a)))
-- * Database functions -- * Database functions
-- | Initializes the database -- | Initializes the database
initDb :: initDb ::

View file

@ -28,19 +28,27 @@ import ZcashHaskell.Types
import Zenith.Core (checkBlockChain, checkZebra) import Zenith.Core (checkBlockChain, checkZebra)
import Zenith.DB import Zenith.DB
( getAccounts ( getAccounts
, getAddresses
, getWallets , getWallets
, initDb , initDb
, initPool , initPool
, toZcashAccountAPI , toZcashAccountAPI
, toZcashAddressAPI
, toZcashWalletAPI , toZcashWalletAPI
) )
import Zenith.Types (Config(..), ZcashAccountAPI(..), ZcashWalletAPI(..)) import Zenith.Types
( Config(..)
, ZcashAccountAPI(..)
, ZcashAddressAPI(..)
, ZcashWalletAPI(..)
)
import Zenith.Utils (jsonNumber) import Zenith.Utils (jsonNumber)
data ZenithMethod data ZenithMethod
= GetInfo = GetInfo
| ListWallets | ListWallets
| ListAccounts | ListAccounts
| ListAddresses
| UnknownMethod | UnknownMethod
deriving (Eq, Prelude.Show) deriving (Eq, Prelude.Show)
@ -48,6 +56,7 @@ instance ToJSON ZenithMethod where
toJSON GetInfo = Data.Aeson.String "getinfo" toJSON GetInfo = Data.Aeson.String "getinfo"
toJSON ListWallets = Data.Aeson.String "listwallets" toJSON ListWallets = Data.Aeson.String "listwallets"
toJSON ListAccounts = Data.Aeson.String "listaccounts" toJSON ListAccounts = Data.Aeson.String "listaccounts"
toJSON ListAddresses = Data.Aeson.String "listaddresses"
toJSON UnknownMethod = Data.Aeson.Null toJSON UnknownMethod = Data.Aeson.Null
instance FromJSON ZenithMethod where instance FromJSON ZenithMethod where
@ -56,12 +65,14 @@ instance FromJSON ZenithMethod where
"getinfo" -> pure GetInfo "getinfo" -> pure GetInfo
"listwallets" -> pure ListWallets "listwallets" -> pure ListWallets
"listaccounts" -> pure ListAccounts "listaccounts" -> pure ListAccounts
"listaddresses" -> pure ListAddresses
_ -> pure UnknownMethod _ -> pure UnknownMethod
data ZenithParams data ZenithParams
= BlankParams = BlankParams
| BadParams | BadParams
| AccountsParams !Int | AccountsParams !Int
| AddressesParams !Int
| TestParams !T.Text | TestParams !T.Text
deriving (Eq, Prelude.Show) deriving (Eq, Prelude.Show)
@ -69,12 +80,14 @@ instance ToJSON ZenithParams where
toJSON BlankParams = Data.Aeson.Array V.empty toJSON BlankParams = Data.Aeson.Array V.empty
toJSON BadParams = Data.Aeson.Null toJSON BadParams = Data.Aeson.Null
toJSON (AccountsParams n) = Data.Aeson.Array $ V.fromList [jsonNumber n] toJSON (AccountsParams 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]
data ZenithResponse data ZenithResponse
= InfoResponse !T.Text !ZenithInfo = InfoResponse !T.Text !ZenithInfo
| WalletListResponse !T.Text ![ZcashWalletAPI] | WalletListResponse !T.Text ![ZcashWalletAPI]
| AccountListResponse !T.Text ![ZcashAccountAPI] | AccountListResponse !T.Text ![ZcashAccountAPI]
| AddressListResponse !T.Text ![ZcashAddressAPI]
| ErrorResponse !T.Text !Double !T.Text | ErrorResponse !T.Text !Double !T.Text
deriving (Eq, Prelude.Show) deriving (Eq, Prelude.Show)
@ -85,6 +98,8 @@ instance ToJSON ZenithResponse where
object ["jsonrpc" .= ("2.0" :: String), "id" .= i, "result" .= w] object ["jsonrpc" .= ("2.0" :: String), "id" .= i, "result" .= w]
toJSON (AccountListResponse i a) = toJSON (AccountListResponse i a) =
object ["jsonrpc" .= ("2.0" :: String), "id" .= i, "result" .= a] object ["jsonrpc" .= ("2.0" :: String), "id" .= i, "result" .= a]
toJSON (AddressListResponse i a) =
object ["jsonrpc" .= ("2.0" :: String), "id" .= i, "result" .= a]
toJSON (ErrorResponse i c m) = toJSON (ErrorResponse i c m) =
object object
[ "jsonrpc" .= ("2.0" :: String) [ "jsonrpc" .= ("2.0" :: String)
@ -122,10 +137,16 @@ instance FromJSON ZenithResponse where
case V.head n of case V.head n of
Object n' -> do Object n' -> do
v1 <- n' .:? "lastSync" v1 <- n' .:? "lastSync"
v2 <- n' .:? "wallet"
case (v1 :: Maybe Int) of case (v1 :: Maybe Int) of
Just _v1' -> do Just _v1' -> do
k2 <- parseJSON r1 k2 <- parseJSON r1
pure $ WalletListResponse i k2 pure $ WalletListResponse i k2
Nothing ->
case (v2 :: Maybe Int) of
Just _v2' -> do
k3 <- parseJSON r1
pure $ AccountListResponse i k3
Nothing -> fail "Unknown object" Nothing -> fail "Unknown object"
_anyOther -> fail "Malformed JSON" _anyOther -> fail "Malformed JSON"
_anyOther -> fail "Malformed JSON" _anyOther -> fail "Malformed JSON"
@ -189,6 +210,16 @@ instance FromJSON RpcCall where
pure $ RpcCall v i ListAccounts (AccountsParams w) pure $ RpcCall v i ListAccounts (AccountsParams w)
else pure $ RpcCall v i ListAccounts BadParams else pure $ RpcCall v i ListAccounts BadParams
_anyOther -> pure $ RpcCall v i ListAccounts BadParams _anyOther -> pure $ RpcCall v i ListAccounts BadParams
ListAddresses -> 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 ListAddresses (AddressesParams x)
else pure $ RpcCall v i ListAddresses BadParams
_anyOther -> pure $ RpcCall v i ListAddresses BadParams
type ZenithRPC type ZenithRPC
= "status" :> Get '[ JSON] Value :<|> BasicAuth "zenith-realm" Bool :> ReqBody = "status" :> Get '[ JSON] Value :<|> BasicAuth "zenith-realm" Bool :> ReqBody
@ -258,6 +289,26 @@ zenithServer config = getinfo :<|> handleRPC
"No accounts available for this wallet. Please create one first" "No accounts available for this wallet. Please create one first"
_anyOther -> _anyOther ->
return $ ErrorResponse (callId req) (-32602) "Invalid params" return $ ErrorResponse (callId req) (-32602) "Invalid params"
ListAddresses ->
case parameters req of
AddressesParams a -> do
let dbPath = c_dbPath config
pool <- liftIO $ runNoLoggingT $ initPool dbPath
addrList <-
liftIO $
runNoLoggingT $ getAddresses pool (toSqlKey $ fromIntegral a)
if not (null addrList)
then return $
AddressListResponse
(callId req)
(map toZcashAddressAPI addrList)
else return $
ErrorResponse
(callId req)
(-32003)
"No addresses available for this account. Please create one first"
_anyOther ->
return $ ErrorResponse (callId req) (-32602) "Invalid params"
GetInfo -> GetInfo ->
case parameters req of case parameters req of
BlankParams -> do BlankParams -> do

View file

@ -124,6 +124,35 @@ main = do
"zh" "zh"
(-32002) (-32002)
"No accounts available for this wallet. Please create one first" "No accounts available for this wallet. Please create one first"
describe "Addresses" $ do
describe "listaddresses" $ do
it "bad credentials" $ do
res <-
makeZenithCall
"127.0.0.1"
nodePort
"baduser"
"idontknow"
ListAddresses
BlankParams
res `shouldBe` Left "Invalid credentials"
it "correct credentials, no addresses" $ do
res <-
makeZenithCall
"127.0.0.1"
nodePort
nodeUser
nodePwd
ListAddresses
(AddressesParams 1)
case res of
Left e -> assertFailure e
Right r ->
r `shouldBe`
ErrorResponse
"zh"
(-32003)
"No addresses available for this account. Please create one first"
startAPI :: Config -> IO () startAPI :: Config -> IO ()
startAPI config = do startAPI config = do

View file

@ -152,7 +152,7 @@
"name": "listaddresses", "name": "listaddresses",
"summary": "List existing addresses for an account ID", "summary": "List existing addresses for an account ID",
"description": "List existing addresses for the given account ID or provide an error if none", "description": "List existing addresses for the given account ID or provide an error if none",
"tags": [{ "$ref": "#/components/tags/draft"} ], "tags": [],
"result": { "result": {
"name": "Addresses", "name": "Addresses",
"schema": { "schema": {
@ -179,6 +179,22 @@
"result": { "result": {
"name": "ListAddresses result", "name": "ListAddresses result",
"value": [ "value": [
{
"index": 3,
"account": 1,
"name": "Clothes",
"ua": "utest13dq4u4dnf3yddw8lq2n6zdclshra6xsp8zgkc5ydyu6k20zrsscmuex46qa4vh84rgd78sqnlleapznnz7mnzx9wv0unts8pv32paj8se5ca3kves2u4a89uy6e8cf4hnarxydxh7hq2e9uu39punfmm53k5h45xn9k3dx35la8j7munh9td7774m8gkqgc4mn40t69w20uu2gtks7a",
"legacy": "ztestsapling188csdsvhdny25am8ume03qr2026hdy03zpg5pq7jmmfxtxtct0e93p0rg80yfxvynqd4gwlwft5",
"transparent": "tmMouLwVfRYrF91fWjDJToivmsTWBhxfX4E"
},
{
"index": 2,
"account": 1,
"name": "Vacation",
"ua": "utest1hhggl4nxfdx63evps6r7qz50cgacgtdpt9k7dl0734w63zn5qmrp6c2xdv9rkqyfkj6kgau4kz48xtm80e67l534qp02teqq86zuzetxql6z5v32yglg9n2un5zsu0hwcvaunzdfg5qnry6syh2dh9x8eu27de03j9pjfvrqda6acgtc6f0emdfh6r5jvfanmjml4ms5wwj9wfqmamq",
"legacy": "ztestsapling1mpup3xv2k9clxaf9wjcr0dt5gnmkprz9s9qsn298mqs356pf39wmh30q3pgsp0w5vyrmj6mrzw2",
"transparent": "tmX8qCB96Dq49YZkww3bSty7eZDA4Fq6F4R"
}
] ]
} }

View file

@ -1,6 +1,6 @@
cabal-version: 3.0 cabal-version: 3.0
name: zenith name: zenith
version: 0.6.0.0-beta version: 0.7.0.0-beta
license: MIT license: MIT
license-file: LICENSE license-file: LICENSE
author: Rene Vergara author: Rene Vergara