RPC Server #103
6 changed files with 129 additions and 6 deletions
|
@ -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]
|
||||||
|
|
||||||
|
|
|
@ -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 ::
|
||||||
|
|
|
@ -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,11 +137,17 @@ 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 -> fail "Unknown object"
|
Nothing ->
|
||||||
|
case (v2 :: Maybe Int) of
|
||||||
|
Just _v2' -> do
|
||||||
|
k3 <- parseJSON r1
|
||||||
|
pure $ AccountListResponse i k3
|
||||||
|
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)
|
||||||
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue