Compare commits

..

1 commit

Author SHA1 Message Date
83fa73bc0a
RPC: Shield and de-shield funds (#110)
This PR contains the functionality for the RPC to shield and de-shield funds.

Co-authored-by: Rene Vergara A. <rvergara59@protonmail.com>
Reviewed-on: #110
Co-authored-by: Rene Vergara <rene@vergara.network>
Co-committed-by: Rene Vergara <rene@vergara.network>
2025-01-02 18:43:41 +00:00
15 changed files with 568 additions and 187 deletions

View file

@ -67,7 +67,7 @@ main = do
zebraPort zebraPort
(zgb_net chainInfo) (zgb_net chainInfo)
threadDelay 90000000 threadDelay 90000000
putStrLn "Zenith RPC Server 0.7.0.0-beta" putStrLn "Zenith RPC Server 0.8.0.0-beta"
putStrLn "------------------------------" putStrLn "------------------------------"
putStrLn $ putStrLn $
"Connected to " ++ "Connected to " ++

Binary file not shown.

Binary file not shown.

View file

@ -79,6 +79,7 @@ import Data.Scientific (Scientific, scientific)
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 Data.Time.Clock.POSIX (posixSecondsToUTCTime) import Data.Time.Clock.POSIX (posixSecondsToUTCTime)
import qualified Data.UUID as U
import qualified Data.Vector as Vec import qualified Data.Vector as Vec
import Database.Persist import Database.Persist
import Database.Persist.Sqlite import Database.Persist.Sqlite
@ -116,6 +117,7 @@ import Zenith.Types
, ValidAddressAPI(..) , ValidAddressAPI(..)
, ZcashNetDB(..) , ZcashNetDB(..)
, ZenithStatus(..) , ZenithStatus(..)
, ZenithUuid(..)
) )
import Zenith.Utils import Zenith.Utils
( displayTaz ( displayTaz
@ -778,8 +780,8 @@ listDrawTx znet sel tx =
else displayTaz amt else displayTaz amt
fmtAmt = fmtAmt =
if amt > 0 if amt > 0
then "" <> dispAmount <> " " then " " <> dispAmount <> " "
else " " <> dispAmount <> "" else " " <> dispAmount <> " "
selStr s = selStr s =
if sel if sel
then withAttr customAttr (txt $ "> " <> s) then withAttr customAttr (txt $ "> " <> s)
@ -1283,45 +1285,23 @@ appEvent (BT.VtyEvent e) = do
Just (_j, w1) -> return w1 Just (_j, w1) -> return w1
Just (_k, w) -> return w Just (_k, w) -> return w
fs1 <- BT.zoom deshieldForm $ BT.gets formState fs1 <- BT.zoom deshieldForm $ BT.gets formState
let tAddrMaybe =
Transparent <$>
((decodeTransparentAddress .
E.encodeUtf8 .
encodeTransparentReceiver (s ^. network)) =<<
(t_rec =<<
(isValidUnifiedAddress .
E.encodeUtf8 .
getUA . walletAddressUAddress)
(entityVal selAddr)))
bl <- bl <-
liftIO $ liftIO $
getChainTip (s ^. zebraHost) (s ^. zebraPort) getChainTip (s ^. zebraHost) (s ^. zebraPort)
case tAddrMaybe of _ <-
Nothing -> do liftIO $
BT.modify $ forkIO $
set deshieldTransaction
msg pool
"Failed to obtain transparent address" (s ^. eventDispatch)
BT.modify $ set displayBox MsgDisplay (s ^. zebraHost)
BT.modify $ set dialogBox Blank (s ^. zebraPort)
Just tAddr -> do (s ^. network)
_ <- (entityKey selAcc)
liftIO $ bl
forkIO $ (fs1 ^. shAmt)
deshieldTransaction BT.modify $ set displayBox SendDisplay
pool BT.modify $ set dialogBox Blank
(s ^. eventDispatch)
(s ^. zebraHost)
(s ^. zebraPort)
(s ^. network)
(entityKey selAcc)
bl
(ProposedNote
(ValidAddressAPI tAddr)
(fs1 ^. shAmt)
Nothing)
BT.modify $ set displayBox SendDisplay
BT.modify $ set dialogBox Blank
else do else do
BT.modify $ set msg "Invalid inputs" BT.modify $ set msg "Invalid inputs"
BT.modify $ set displayBox MsgDisplay BT.modify $ set displayBox MsgDisplay
@ -2050,19 +2030,20 @@ shieldTransaction ::
shieldTransaction pool chan zHost zPort znet accId bl = do shieldTransaction pool chan zHost zPort znet accId bl = do
BC.writeBChan chan $ TickMsg "Preparing shielding transaction..." BC.writeBChan chan $ TickMsg "Preparing shielding transaction..."
res <- runNoLoggingT $ shieldTransparentNotes pool zHost zPort znet accId bl res <- runNoLoggingT $ shieldTransparentNotes pool zHost zPort znet accId bl
forM_ res $ \case ops <-
Left e -> BC.writeBChan chan $ TickMsg $ show e mapM
Right rawTx -> do (\case
BC.writeBChan chan $ TickMsg "Transaction ready, sending to Zebra..." Left e -> return $ T.pack $ show e
resp <- Right x -> do
makeZebraCall thisOp <- getOperation pool x
zHost case thisOp of
zPort Nothing -> return ""
"sendrawtransaction" Just o ->
[Data.Aeson.String $ toText rawTx] return $
case resp of (U.toText . getUuid . operationUuid $ entityVal o) <>
Left e1 -> BC.writeBChan chan $ TickMsg $ "Zebra error: " ++ show e1 ": " <> (T.pack . show . operationStatus $ entityVal o))
Right txId -> BC.writeBChan chan $ TickTx txId res
BC.writeBChan chan $ TickMsg $ T.unpack $ T.intercalate "\n" ops
deshieldTransaction :: deshieldTransaction ::
ConnectionPool ConnectionPool
@ -2072,7 +2053,7 @@ deshieldTransaction ::
-> ZcashNet -> ZcashNet
-> ZcashAccountId -> ZcashAccountId
-> Int -> Int
-> ProposedNote -> Scientific
-> IO () -> IO ()
deshieldTransaction pool chan zHost zPort znet accId bl pnote = do deshieldTransaction pool chan zHost zPort znet accId bl pnote = do
BC.writeBChan chan $ TickMsg "Deshielding funds..." BC.writeBChan chan $ TickMsg "Deshielding funds..."

View file

@ -3,6 +3,7 @@
-- | Core wallet functionality for Zenith -- | Core wallet functionality for Zenith
module Zenith.Core where module Zenith.Core where
import Control.Concurrent (forkIO)
import Control.Exception (throwIO, try) import Control.Exception (throwIO, try)
import Control.Monad (forM, unless, when) import Control.Monad (forM, unless, when)
import Control.Monad.IO.Class (liftIO) import Control.Monad.IO.Class (liftIO)
@ -25,6 +26,8 @@ import Data.Scientific (Scientific, scientific, toBoundedInteger)
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 Data.Time import Data.Time
import qualified Data.UUID as U
import Data.UUID.V4 (nextRandom)
import qualified Database.Esqueleto.Experimental as ESQ import qualified Database.Esqueleto.Experimental as ESQ
import Database.Persist import Database.Persist
import Database.Persist.Sqlite import Database.Persist.Sqlite
@ -41,6 +44,7 @@ import ZcashHaskell.Orchard
, getOrchardTreeParts , getOrchardTreeParts
, getOrchardWitness , getOrchardWitness
, isValidUnifiedAddress , isValidUnifiedAddress
, parseAddress
, updateOrchardCommitmentTree , updateOrchardCommitmentTree
, updateOrchardWitness , updateOrchardWitness
) )
@ -80,7 +84,10 @@ import Zenith.Types
, ValidAddressAPI(..) , ValidAddressAPI(..)
, ZcashNetDB(..) , ZcashNetDB(..)
, ZebraTreeInfo(..) , ZebraTreeInfo(..)
, ZenithStatus(..)
, ZenithUuid(..)
) )
import Zenith.Utils (getTransparentFromUA)
-- * Zebra Node interaction -- * Zebra Node interaction
-- | Checks the status of the `zebrad` node -- | Checks the status of the `zebrad` node
@ -746,14 +753,37 @@ deshieldNotes ::
-> ZcashNet -> ZcashNet
-> ZcashAccountId -> ZcashAccountId
-> Int -> Int
-> ProposedNote -> Scientific
-> NoLoggingT IO (Either TxError HexString) -> NoLoggingT IO (Either TxError HexString)
deshieldNotes pool zebraHost zebraPort znet za bh pnote = do deshieldNotes pool zebraHost zebraPort znet za bh pnote = do
bal <- liftIO $ getShieldedBalance pool za bal <- liftIO $ getShieldedBalance pool za
let zats = pn_amt pnote * scientific 1 8 addrs <- getAddresses pool za
if fromInteger bal > (scientific 2 4 + zats) let defAddr =
then prepareTxV2 pool zebraHost zebraPort znet za bh [pnote] Low parseAddress $
else return $ Left InsufficientFunds E.encodeUtf8 $ getUA $ walletAddressUAddress $ entityVal $ head addrs
case defAddr of
Nothing -> return $ Left ZHError
Just (Unified x) -> do
case getTransparentFromUA x of
Nothing -> return $ Left ZHError
Just ta -> do
let zats = pnote * scientific 1 8
if fromInteger bal > (scientific 2 4 + zats)
then prepareTxV2
pool
zebraHost
zebraPort
znet
za
bh
[ ProposedNote
(ValidAddressAPI $ Transparent ta)
pnote
Nothing
]
Low
else return $ Left InsufficientFunds
_anyOther -> return $ Left ZHError
shieldTransparentNotes :: shieldTransparentNotes ::
ConnectionPool ConnectionPool
@ -762,8 +792,8 @@ shieldTransparentNotes ::
-> ZcashNet -> ZcashNet
-> ZcashAccountId -> ZcashAccountId
-> Int -> Int
-> NoLoggingT IO [Either TxError HexString] -> NoLoggingT IO [Either TxError U.UUID]
shieldTransparentNotes pool zebraHost zebraPort znet za bh = do shieldTransparentNotes pool zHost zPort znet za bh = do
accRead <- liftIO $ getAccountById pool za accRead <- liftIO $ getAccountById pool za
logDebugN $ T.pack $ "Target block: " ++ show bh logDebugN $ T.pack $ "Target block: " ++ show bh
case accRead of case accRead of
@ -772,58 +802,94 @@ shieldTransparentNotes pool zebraHost zebraPort znet za bh = do
return [Left ZHError] return [Left ZHError]
Just acc -> do Just acc -> do
trNotes' <- liftIO $ getWalletUnspentTrNotes pool za trNotes' <- liftIO $ getWalletUnspentTrNotes pool za
dRecvs <- liftIO $ getReceivers pool trNotes' if null trNotes'
let fNotes = then return [Left InsufficientFunds]
map else do
(\x -> dRecvs <- liftIO $ getReceivers pool trNotes'
filter (\y -> walletTrNoteAddress (entityVal y) == x) trNotes') let fNotes =
dRecvs map
sTree <- liftIO $ getSaplingTree pool (\x ->
oTree <- liftIO $ getOrchardTree pool filter
forM fNotes $ \trNotes -> do (\y -> walletTrNoteAddress (entityVal y) == x)
let noteTotal = getTotalAmount (trNotes, [], []) trNotes')
tSpends <- dRecvs
liftIO $ sTree <- liftIO $ getSaplingTree pool
prepTSpends oTree <- liftIO $ getOrchardTree pool
(getTranSK $ zcashAccountTPrivateKey $ entityVal acc) forM fNotes $ \trNotes -> do
trNotes opid <- liftIO nextRandom
chgAddr <- getInternalAddresses pool $ entityKey acc startTime <- liftIO getCurrentTime
let internalUA = opkey <-
getUA $ walletAddressUAddress $ entityVal $ head chgAddr liftIO $
let oRcvr = saveOperation pool $
fromJust $ Operation (ZenithUuid opid) startTime Nothing Processing Nothing
o_rec =<< isValidUnifiedAddress (E.encodeUtf8 internalUA) case opkey of
let dummy = Nothing -> return $ Left ZHError
OutgoingNote Just opkey' -> do
4 let noteTotal = getTotalAmount (trNotes, [], [])
(getBytes $ getOrchSK $ zcashAccountOrchSpendKey $ entityVal acc) tSpends <-
(getBytes oRcvr) liftIO $
(fromIntegral $ noteTotal - 500) prepTSpends
"" (getTranSK $ zcashAccountTPrivateKey $ entityVal acc)
True trNotes
let feeAmt = calculateTxFee (trNotes, [], []) [dummy] chgAddr <- getInternalAddresses pool $ entityKey acc
let snote = let internalUA =
OutgoingNote getUA $ walletAddressUAddress $ entityVal $ head chgAddr
4 let oRcvr =
(getBytes $ getOrchSK $ zcashAccountOrchSpendKey $ entityVal acc) fromJust $
(getBytes oRcvr) o_rec =<< isValidUnifiedAddress (E.encodeUtf8 internalUA)
(fromIntegral $ noteTotal - fromIntegral feeAmt) let dummy =
"" OutgoingNote
True 4
tx <- (getBytes $
liftIO $ getOrchSK $ zcashAccountOrchSpendKey $ entityVal acc)
createTransaction (getBytes oRcvr)
(maybe (hexString "00") (getHash . value . fst) sTree) (fromIntegral $ noteTotal - 500)
(maybe (hexString "00") (getHash . value . fst) oTree) ""
tSpends True
[] let feeAmt = calculateTxFee (trNotes, [], []) [dummy]
[] let snote =
[snote] OutgoingNote
znet 4
(bh + 3) (getBytes $
True getOrchSK $ zcashAccountOrchSpendKey $ entityVal acc)
logDebugN $ T.pack $ show tx (getBytes oRcvr)
return tx (fromIntegral $ noteTotal - fromIntegral feeAmt)
""
True
_ <-
liftIO $
forkIO $ do
tx <-
liftIO $
createTransaction
(maybe (hexString "00") (getHash . value . fst) sTree)
(maybe (hexString "00") (getHash . value . fst) oTree)
tSpends
[]
[]
[snote]
znet
(bh + 3)
True
case tx of
Left e ->
finalizeOperation pool opkey' Failed $ T.pack $ show e
Right rawTx -> do
zebraRes <-
makeZebraCall
zHost
zPort
"sendrawtransaction"
[Data.Aeson.String $ toText rawTx]
case zebraRes of
Left e1 ->
finalizeOperation pool opkey' Failed $
T.pack $ show e1
Right txId ->
finalizeOperation pool opkey' Successful $
"Tx ID: " <> toText txId
logDebugN $ T.pack $ show opid
return $ Right opid
where where
getTotalAmount :: getTotalAmount ::
( [Entity WalletTrNote] ( [Entity WalletTrNote]

View file

@ -630,6 +630,7 @@ getAddresses pool a =
addrs <- from $ table @WalletAddress addrs <- from $ table @WalletAddress
where_ (addrs ^. WalletAddressAccId ==. val a) where_ (addrs ^. WalletAddressAccId ==. val a)
where_ (addrs ^. WalletAddressScope ==. val (ScopeDB External)) where_ (addrs ^. WalletAddressScope ==. val (ScopeDB External))
orderBy [asc $ addrs ^. WalletAddressId]
pure addrs pure addrs
getAddressById :: getAddressById ::

View file

@ -28,6 +28,7 @@ import Data.Scientific (Scientific, fromFloatDigits)
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 Data.Time.Clock.POSIX (posixSecondsToUTCTime) import Data.Time.Clock.POSIX (posixSecondsToUTCTime)
import qualified Data.UUID as U
import Database.Esqueleto.Experimental (ConnectionPool, fromSqlKey) import Database.Esqueleto.Experimental (ConnectionPool, fromSqlKey)
import Database.Persist import Database.Persist
import Lens.Micro ((&), (+~), (.~), (?~), (^.), set) import Lens.Micro ((&), (+~), (.~), (?~), (^.), set)
@ -1750,19 +1751,20 @@ shieldTransaction config znet accId sendMsg = do
pool <- runNoLoggingT $ initPool dbPath pool <- runNoLoggingT $ initPool dbPath
bl <- getChainTip zHost zPort bl <- getChainTip zHost zPort
res <- runNoLoggingT $ shieldTransparentNotes pool zHost zPort znet accId bl res <- runNoLoggingT $ shieldTransparentNotes pool zHost zPort znet accId bl
forM_ res $ \case ops <-
Left e -> sendMsg $ ShowError $ T.pack (show e) mapM
Right rawTx -> do (\case
sendMsg $ ShowMsg "Transaction ready, sending to Zebra..." Left e -> return $ T.pack $ show e
resp <- Right x -> do
makeZebraCall thisOp <- getOperation pool x
zHost case thisOp of
zPort Nothing -> return ""
"sendrawtransaction" Just o ->
[Data.Aeson.String $ toText rawTx] return $
case resp of (U.toText . getUuid . operationUuid $ entityVal o) <>
Left e1 -> sendMsg $ ShowError $ "Zebra error: " <> T.pack (show e1) ": " <> (T.pack . show . operationStatus $ entityVal o))
Right txId -> sendMsg $ ShowTxId txId res
sendMsg $ ShowMsg $ T.intercalate "\n" ops
deshieldTransaction :: deshieldTransaction ::
Config Config
@ -1782,40 +1784,20 @@ deshieldTransaction config znet accId addR pnote sendMsg = do
let zPort = c_zebraPort config let zPort = c_zebraPort config
pool <- runNoLoggingT $ initPool dbPath pool <- runNoLoggingT $ initPool dbPath
bl <- getChainTip zHost zPort bl <- getChainTip zHost zPort
let tAddrMaybe = res <- runNoLoggingT $ deshieldNotes pool zHost zPort znet accId bl pnote
Transparent <$> case res of
((decodeTransparentAddress . Left e -> sendMsg $ ShowError $ T.pack (show e)
E.encodeUtf8 . encodeTransparentReceiver znet) =<< Right rawTx -> do
(t_rec =<< sendMsg $ ShowModal "Transaction ready, sending to Zebra..."
(isValidUnifiedAddress . resp <-
E.encodeUtf8 . getUA . walletAddressUAddress) makeZebraCall
(entityVal addr)))
case tAddrMaybe of
Nothing -> sendMsg $ ShowError "No transparent address available"
Just tAddr -> do
res <-
runNoLoggingT $
deshieldNotes
pool
zHost zHost
zPort zPort
znet "sendrawtransaction"
accId [Data.Aeson.String $ toText rawTx]
bl case resp of
(ProposedNote (ValidAddressAPI tAddr) pnote Nothing) Left e1 -> sendMsg $ ShowError $ "Zebra error: " <> showt e1
case res of Right txId -> sendMsg $ ShowTxId txId
Left e -> sendMsg $ ShowError $ T.pack (show e)
Right rawTx -> do
sendMsg $ ShowModal "Transaction ready, sending to Zebra..."
resp <-
makeZebraCall
zHost
zPort
"sendrawtransaction"
[Data.Aeson.String $ toText rawTx]
case resp of
Left e1 -> sendMsg $ ShowError $ "Zebra error: " <> showt e1
Right txId -> sendMsg $ ShowTxId txId
sendTransaction :: sendTransaction ::
Config Config

View file

@ -20,7 +20,7 @@ import Control.Monad.Logger (runFileLoggingT, runNoLoggingT, runStderrLoggingT)
import Data.Aeson import Data.Aeson
import qualified Data.HexString as H import qualified Data.HexString as H
import Data.Int import Data.Int
import Data.Scientific (floatingOrInteger) import Data.Scientific (Scientific(..), floatingOrInteger)
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 Data.Time.Clock (getCurrentTime) import Data.Time.Clock (getCurrentTime)
@ -50,7 +50,9 @@ import Zenith.Core
( checkBlockChain ( checkBlockChain
, createCustomWalletAddress , createCustomWalletAddress
, createZcashAccount , createZcashAccount
, deshieldNotes
, prepareTxV2 , prepareTxV2
, shieldTransparentNotes
, syncWallet , syncWallet
, updateCommitmentTrees , updateCommitmentTrees
) )
@ -121,6 +123,8 @@ data ZenithMethod
| GetNewAddress | GetNewAddress
| GetOperationStatus | GetOperationStatus
| SendMany | SendMany
| ShieldNotes
| DeshieldFunds
| UnknownMethod | UnknownMethod
deriving (Eq, Prelude.Show) deriving (Eq, Prelude.Show)
@ -136,6 +140,8 @@ instance ToJSON ZenithMethod where
toJSON GetNewAddress = Data.Aeson.String "getnewaddress" toJSON GetNewAddress = Data.Aeson.String "getnewaddress"
toJSON GetOperationStatus = Data.Aeson.String "getoperationstatus" toJSON GetOperationStatus = Data.Aeson.String "getoperationstatus"
toJSON SendMany = Data.Aeson.String "sendmany" toJSON SendMany = Data.Aeson.String "sendmany"
toJSON ShieldNotes = Data.Aeson.String "shieldnotes"
toJSON DeshieldFunds = Data.Aeson.String "deshieldfunds"
toJSON UnknownMethod = Data.Aeson.Null toJSON UnknownMethod = Data.Aeson.Null
instance FromJSON ZenithMethod where instance FromJSON ZenithMethod where
@ -152,6 +158,8 @@ instance FromJSON ZenithMethod where
"getnewaddress" -> pure GetNewAddress "getnewaddress" -> pure GetNewAddress
"getoperationstatus" -> pure GetOperationStatus "getoperationstatus" -> pure GetOperationStatus
"sendmany" -> pure SendMany "sendmany" -> pure SendMany
"shieldnotes" -> pure ShieldNotes
"deshieldfunds" -> pure DeshieldFunds
_ -> pure UnknownMethod _ -> pure UnknownMethod
data ZenithParams data ZenithParams
@ -167,6 +175,8 @@ data ZenithParams
| OpParams !ZenithUuid | OpParams !ZenithUuid
| SendParams !Int ![ProposedNote] !PrivacyPolicy | SendParams !Int ![ProposedNote] !PrivacyPolicy
| TestParams !T.Text | TestParams !T.Text
| ShieldNotesParams !Int
| DeshieldParams !Int !Scientific
deriving (Eq, Prelude.Show) deriving (Eq, Prelude.Show)
instance ToJSON ZenithParams where instance ToJSON ZenithParams where
@ -191,6 +201,9 @@ instance ToJSON ZenithParams where
Data.Aeson.Array $ V.fromList [Data.Aeson.String $ U.toText $ getUuid i] Data.Aeson.Array $ V.fromList [Data.Aeson.String $ U.toText $ getUuid i]
toJSON (SendParams i ns p) = toJSON (SendParams i ns p) =
Data.Aeson.Array $ V.fromList [jsonNumber i, toJSON ns, toJSON p] Data.Aeson.Array $ V.fromList [jsonNumber i, toJSON ns, toJSON p]
toJSON (ShieldNotesParams i) = Data.Aeson.Array $ V.fromList [jsonNumber i]
toJSON (DeshieldParams i s) =
Data.Aeson.Array $ V.fromList [jsonNumber i, Data.Aeson.Number s]
data ZenithResponse data ZenithResponse
= InfoResponse !T.Text !ZenithInfo = InfoResponse !T.Text !ZenithInfo
@ -203,6 +216,7 @@ data ZenithResponse
| NewAddrResponse !T.Text !ZcashAddressAPI | NewAddrResponse !T.Text !ZcashAddressAPI
| OpResponse !T.Text !Operation | OpResponse !T.Text !Operation
| SendResponse !T.Text !U.UUID | SendResponse !T.Text !U.UUID
| MultiOpResponse !T.Text ![T.Text]
| ErrorResponse !T.Text !Double !T.Text | ErrorResponse !T.Text !Double !T.Text
deriving (Eq, Prelude.Show) deriving (Eq, Prelude.Show)
@ -224,6 +238,7 @@ instance ToJSON ZenithResponse where
toJSON (NewAddrResponse i a) = packRpcResponse i a toJSON (NewAddrResponse i a) = packRpcResponse i a
toJSON (OpResponse i u) = packRpcResponse i u toJSON (OpResponse i u) = packRpcResponse i u
toJSON (SendResponse i o) = packRpcResponse i o toJSON (SendResponse i o) = packRpcResponse i o
toJSON (MultiOpResponse i o) = packRpcResponse i o
instance FromJSON ZenithResponse where instance FromJSON ZenithResponse where
parseJSON = parseJSON =
@ -298,6 +313,9 @@ instance FromJSON ZenithResponse where
k5 <- parseJSON r1 k5 <- parseJSON r1
pure $ NoteListResponse i k5 pure $ NoteListResponse i k5
Nothing -> fail "Unknown object" Nothing -> fail "Unknown object"
String s -> do
k7 <- parseJSON r1
pure $ MultiOpResponse i k7
_anyOther -> fail "Malformed JSON" _anyOther -> fail "Malformed JSON"
Number k -> do Number k -> do
case floatingOrInteger k of case floatingOrInteger k of
@ -489,6 +507,27 @@ instance FromJSON RpcCall where
_anyOther -> pure $ RpcCall v i SendMany BadParams _anyOther -> pure $ RpcCall v i SendMany BadParams
else pure $ RpcCall v i SendMany BadParams else pure $ RpcCall v i SendMany BadParams
_anyOther -> pure $ RpcCall v i SendMany BadParams _anyOther -> pure $ RpcCall v i SendMany BadParams
ShieldNotes -> do
p <- obj .: "params"
case p of
Array a ->
if V.length a == 1
then do
x <- parseJSON $ a V.! 0
pure $ RpcCall v i ShieldNotes (ShieldNotesParams x)
else pure $ RpcCall v i ShieldNotes BadParams
_anyOther -> pure $ RpcCall v i ShieldNotes BadParams
DeshieldFunds -> do
p <- obj .: "params"
case p of
Array a ->
if V.length a == 2
then do
x <- parseJSON $ a V.! 0
y <- parseJSON $ a V.! 1
pure $ RpcCall v i DeshieldFunds (DeshieldParams x y)
else pure $ RpcCall v i DeshieldFunds BadParams
_anyOther -> pure $ RpcCall v i DeshieldFunds BadParams
type ZenithRPC type ZenithRPC
= "status" :> Get '[ JSON] Value :<|> BasicAuth "zenith-realm" Bool :> ReqBody = "status" :> Get '[ JSON] Value :<|> BasicAuth "zenith-realm" Bool :> ReqBody
@ -511,7 +550,7 @@ zenithServer state = getinfo :<|> handleRPC
getinfo = getinfo =
return $ return $
object object
[ "version" .= ("0.7.0.0-beta" :: String) [ "version" .= ("0.8.0.0-beta" :: String)
, "network" .= ("testnet" :: String) , "network" .= ("testnet" :: String)
] ]
handleRPC :: Bool -> RpcCall -> Handler ZenithResponse handleRPC :: Bool -> RpcCall -> Handler ZenithResponse
@ -587,7 +626,7 @@ zenithServer state = getinfo :<|> handleRPC
return $ return $
InfoResponse InfoResponse
(callId req) (callId req)
(ZenithInfo "0.7.0.0-beta" (w_network state) (w_build state)) (ZenithInfo "0.8.0.0-beta" (w_network state) (w_build state))
_anyOtherParams -> _anyOtherParams ->
return $ ErrorResponse (callId req) (-32602) "Invalid params" return $ ErrorResponse (callId req) (-32602) "Invalid params"
ListReceived -> ListReceived ->
@ -871,6 +910,137 @@ zenithServer state = getinfo :<|> handleRPC
"Account does not exist." "Account does not exist."
_anyOtherParams -> _anyOtherParams ->
return $ ErrorResponse (callId req) (-32602) "Invalid params" return $ ErrorResponse (callId req) (-32602) "Invalid params"
ShieldNotes -> do
case parameters req of
ShieldNotesParams i -> do
let dbPath = w_dbPath state
let net = w_network state
let zHost = w_host state
let zPort = w_port state
pool <- liftIO $ runNoLoggingT $ initPool dbPath
syncChk <- liftIO $ isSyncing pool
if syncChk
then return $
ErrorResponse
(callId req)
(-32012)
"The Zenith server is syncing, please try again later."
else do
acc <-
liftIO $ getAccountById pool $ toSqlKey $ fromIntegral i
case acc of
Just acc' -> do
bl <-
liftIO $
getLastSyncBlock
pool
(zcashAccountWalletId $ entityVal acc')
opids <-
liftIO $
runNoLoggingT $
shieldTransparentNotes
pool
zHost
zPort
net
(entityKey acc')
bl
let ops =
map
(\case
Left e -> T.pack $ show e
Right op -> U.toText op)
opids
return $ MultiOpResponse (callId req) ops
Nothing ->
return $
ErrorResponse
(callId req)
(-32006)
"Account does not exist."
_anyOtherParams ->
return $ ErrorResponse (callId req) (-32602) "Invalid params"
DeshieldFunds -> do
case parameters req of
DeshieldParams i k -> do
let dbPath = w_dbPath state
let net = w_network state
let zHost = w_host state
let zPort = w_port state
pool <- liftIO $ runNoLoggingT $ initPool dbPath
syncChk <- liftIO $ isSyncing pool
if syncChk
then return $
ErrorResponse
(callId req)
(-32012)
"The Zenith server is syncing, please try again later."
else do
opid <- liftIO nextRandom
startTime <- liftIO getCurrentTime
opkey <-
liftIO $
saveOperation pool $
Operation
(ZenithUuid opid)
startTime
Nothing
Processing
Nothing
case opkey of
Nothing ->
return $
ErrorResponse (callId req) (-32010) "Internal Error"
Just opkey' -> do
acc <-
liftIO $ getAccountById pool $ toSqlKey $ fromIntegral i
case acc of
Just acc' -> do
bl <-
liftIO $
getLastSyncBlock
pool
(zcashAccountWalletId $ entityVal acc')
_ <-
liftIO $
forkIO $ do
res <-
runNoLoggingT $
deshieldNotes
pool
zHost
zPort
net
(entityKey acc')
bl
k
case res of
Left e ->
finalizeOperation pool opkey' Failed $
T.pack $ show e
Right rawTx -> do
zebraRes <-
makeZebraCall
zHost
zPort
"sendrawtransaction"
[Data.Aeson.String $ H.toText rawTx]
case zebraRes of
Left e1 ->
finalizeOperation pool opkey' Failed $
T.pack $ show e1
Right txId ->
finalizeOperation pool opkey' Successful $
"Tx ID: " <> H.toText txId
return $ SendResponse (callId req) opid
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

View file

@ -235,7 +235,7 @@ isValidString c = do
padWithZero :: Int -> String -> String padWithZero :: Int -> String -> String
padWithZero n s padWithZero n s
| (length s) >= n = s | length s >= n = s
| otherwise = padWithZero n ("0" ++ s) | otherwise = padWithZero n ("0" ++ s)
isEmpty :: [a] -> Bool isEmpty :: [a] -> Bool
@ -248,3 +248,6 @@ getChainTip zHost zPort = do
case r of case r of
Left e1 -> pure 0 Left e1 -> pure 0
Right i -> pure i Right i -> pure i
getTransparentFromUA :: UnifiedAddress -> Maybe TransparentAddress
getTransparentFromUA ua = TransparentAddress (ua_net ua) <$> t_rec ua

View file

@ -86,7 +86,7 @@ main = do
Left e -> assertFailure e Left e -> assertFailure e
Right r -> Right r ->
r `shouldBe` r `shouldBe`
InfoResponse "zh" (ZenithInfo "0.7.0.0-beta" TestNet "v1.9.0") InfoResponse "zh" (ZenithInfo "0.8.0.0-beta" TestNet "v2.1.0")
describe "Wallets" $ do describe "Wallets" $ do
describe "listwallet" $ do describe "listwallet" $ do
it "bad credentials" $ do it "bad credentials" $ do
@ -676,6 +676,54 @@ main = do
case res of case res of
Left e -> assertFailure e Left e -> assertFailure e
Right (SendResponse i o) -> o `shouldNotBe` U.nil Right (SendResponse i o) -> o `shouldNotBe` U.nil
describe "Shield notes" $ do
it "bad credentials" $ do
res <-
makeZenithCall
"127.0.0.1"
nodePort
"baduser"
"idontknow"
ShieldNotes
BlankParams
res `shouldBe` Left "Invalid credentials"
describe "correct credentials" $ do
it "no parameters" $ do
res <-
makeZenithCall
"127.0.0.1"
nodePort
nodeUser
nodePwd
ShieldNotes
BlankParams
case res of
Left e -> assertFailure e
Right (ErrorResponse i c m) -> c `shouldBe` (-32602)
it "invalid account" $ do
res <-
makeZenithCall
"127.0.0.1"
nodePort
nodeUser
nodePwd
ShieldNotes
(ShieldNotesParams 27)
case res of
Left e -> assertFailure e
Right (ErrorResponse i c m) -> c `shouldBe` (-32006)
it "valid account" $ do
res <-
makeZenithCall
"127.0.0.1"
nodePort
nodeUser
nodePwd
ShieldNotes
(ShieldNotesParams 1)
case res of
Left e -> assertFailure e
Right (MultiOpResponse i c) -> c `shouldNotBe` []
startAPI :: Config -> IO () startAPI :: Config -> IO ()
startAPI config = do startAPI config = do

View file

@ -643,8 +643,7 @@ main = do
case ix of case ix of
Nothing -> assertFailure "couldn't find index at block" Nothing -> assertFailure "couldn't find index at block"
Just i -> do Just i -> do
updatedTree <- updatedTree <- runNoLoggingT $ truncateTree oTree i
runFileLoggingT "test.log" $ truncateTree oTree i
let finalAnchor = let finalAnchor =
getOrchardTreeAnchor $ getOrchardTreeAnchor $
OrchardCommitmentTree $ ztiOrchard zebraTreesIn OrchardCommitmentTree $ ztiOrchard zebraTreesIn
@ -737,7 +736,7 @@ main = do
Just ua -> do Just ua -> do
pool <- runNoLoggingT $ initPool "/home/rav/Zenith/zenith.db" pool <- runNoLoggingT $ initPool "/home/rav/Zenith/zenith.db"
tx <- tx <-
runFileLoggingT "zenith.log" $ runNoLoggingT $
prepareTxV2 prepareTxV2
pool pool
"localhost" "localhost"
@ -763,7 +762,7 @@ main = do
Just ua -> do Just ua -> do
pool <- runNoLoggingT $ initPool "/home/rav/Zenith/zenith.db" pool <- runNoLoggingT $ initPool "/home/rav/Zenith/zenith.db"
tx <- tx <-
runFileLoggingT "zenith.log" $ runNoLoggingT $
prepareTxV2 prepareTxV2
pool pool
"localhost" "localhost"
@ -787,7 +786,7 @@ main = do
Just ua -> do Just ua -> do
pool <- runNoLoggingT $ initPool "/home/rav/Zenith/zenith.db" pool <- runNoLoggingT $ initPool "/home/rav/Zenith/zenith.db"
tx <- tx <-
runFileLoggingT "zenith.log" $ runNoLoggingT $
prepareTxV2 prepareTxV2
pool pool
"localhost" "localhost"
@ -815,7 +814,7 @@ main = do
Just ua -> do Just ua -> do
pool <- runNoLoggingT $ initPool "/home/rav/Zenith/zenith.db" pool <- runNoLoggingT $ initPool "/home/rav/Zenith/zenith.db"
tx <- tx <-
runFileLoggingT "zenith.log" $ runNoLoggingT $
prepareTxV2 prepareTxV2
pool pool
"localhost" "localhost"
@ -847,7 +846,7 @@ main = do
Just ua -> do Just ua -> do
pool <- runNoLoggingT $ initPool "/home/rav/Zenith/zenith.db" pool <- runNoLoggingT $ initPool "/home/rav/Zenith/zenith.db"
tx <- tx <-
runFileLoggingT "zenith.log" $ runNoLoggingT $
prepareTxV2 prepareTxV2
pool pool
"localhost" "localhost"
@ -873,7 +872,7 @@ main = do
Just ua -> do Just ua -> do
pool <- runNoLoggingT $ initPool "/home/rav/Zenith/zenith.db" pool <- runNoLoggingT $ initPool "/home/rav/Zenith/zenith.db"
tx <- tx <-
runFileLoggingT "zenith.log" $ runNoLoggingT $
prepareTxV2 prepareTxV2
pool pool
"localhost" "localhost"
@ -897,7 +896,7 @@ main = do
Just ua -> do Just ua -> do
pool <- runNoLoggingT $ initPool "/home/rav/Zenith/zenith.db" pool <- runNoLoggingT $ initPool "/home/rav/Zenith/zenith.db"
tx <- tx <-
runFileLoggingT "zenith.log" $ runNoLoggingT $
prepareTxV2 prepareTxV2
pool pool
"localhost" "localhost"
@ -926,7 +925,7 @@ main = do
Just ua -> do Just ua -> do
pool <- runNoLoggingT $ initPool "/home/rav/Zenith/zenith.db" pool <- runNoLoggingT $ initPool "/home/rav/Zenith/zenith.db"
tx <- tx <-
runFileLoggingT "zenith.log" $ runNoLoggingT $
prepareTxV2 prepareTxV2
pool pool
"localhost" "localhost"
@ -957,7 +956,7 @@ main = do
Just ua -> do Just ua -> do
pool <- runNoLoggingT $ initPool "/home/rav/Zenith/zenith.db" pool <- runNoLoggingT $ initPool "/home/rav/Zenith/zenith.db"
tx <- tx <-
runFileLoggingT "zenith.log" $ runNoLoggingT $
prepareTxV2 prepareTxV2
pool pool
"localhost" "localhost"
@ -983,7 +982,7 @@ main = do
Just ua -> do Just ua -> do
pool <- runNoLoggingT $ initPool "/home/rav/Zenith/zenith.db" pool <- runNoLoggingT $ initPool "/home/rav/Zenith/zenith.db"
tx <- tx <-
runFileLoggingT "zenith.log" $ runNoLoggingT $
prepareTxV2 prepareTxV2
pool pool
"localhost" "localhost"
@ -1007,7 +1006,7 @@ main = do
Just ua -> do Just ua -> do
pool <- runNoLoggingT $ initPool "/home/rav/Zenith/zenith.db" pool <- runNoLoggingT $ initPool "/home/rav/Zenith/zenith.db"
tx <- tx <-
runFileLoggingT "zenith.log" $ runNoLoggingT $
prepareTxV2 prepareTxV2
pool pool
"localhost" "localhost"
@ -1034,7 +1033,7 @@ main = do
Just ua -> do Just ua -> do
pool <- runNoLoggingT $ initPool "/home/rav/Zenith/zenith.db" pool <- runNoLoggingT $ initPool "/home/rav/Zenith/zenith.db"
tx <- tx <-
runFileLoggingT "zenith.log" $ runNoLoggingT $
prepareTxV2 prepareTxV2
pool pool
"localhost" "localhost"
@ -1061,7 +1060,7 @@ main = do
Just ua -> do Just ua -> do
pool <- runNoLoggingT $ initPool "/home/rav/Zenith/zenith.db" pool <- runNoLoggingT $ initPool "/home/rav/Zenith/zenith.db"
tx <- tx <-
runFileLoggingT "zenith.log" $ runNoLoggingT $
prepareTxV2 prepareTxV2
pool pool
"localhost" "localhost"
@ -1086,7 +1085,7 @@ main = do
Just ua -> do Just ua -> do
pool <- runNoLoggingT $ initPool "/home/rav/Zenith/zenith.db" pool <- runNoLoggingT $ initPool "/home/rav/Zenith/zenith.db"
tx <- tx <-
runFileLoggingT "zenith.log" $ runNoLoggingT $
prepareTxV2 prepareTxV2
pool pool
"localhost" "localhost"

@ -1 +1 @@
Subproject commit 4289a9ded67ef2ca432abc412934fb5b8b59a9cf Subproject commit 7d3ae36d2b48b8ed91a70e40a77fb7efe57765a0

View file

@ -1,7 +1,7 @@
{ {
"openrpc": "1.0.0-rc1", "openrpc": "1.0.0-rc1",
"info": { "info": {
"version": "0.7.0.0-beta", "version": "0.8.0.0-beta",
"title": "Zenith RPC", "title": "Zenith RPC",
"description": "The RPC methods to interact with the Zenith Zcash wallet", "description": "The RPC methods to interact with the Zenith Zcash wallet",
"license": { "license": {
@ -605,10 +605,9 @@
], ],
"paramStructure": "by-position", "paramStructure": "by-position",
"result": { "result": {
"name": "Operation ID(s)", "name": "Operation ID",
"schema": { "schema": {
"type": "array", "$ref": "#/components/contentDescriptors/OperationId"
"items": { "$ref": "#/components/contentDescriptors/OperationId"}
} }
}, },
"examples": [ "examples": [
@ -620,7 +619,7 @@
{ {
"name": "Account index", "name": "Account index",
"summary": "The index for the account to use", "summary": "The index for the account to use",
"value": "1" "value": 1
}, },
{ {
"name": "Privacy Policy", "name": "Privacy Policy",
@ -641,9 +640,8 @@
], ],
"result": { "result": {
"name": "SendMany result", "name": "SendMany result",
"value": [ "value": "3cc31c07-07cf-4a6e-9190-156c4b8c4088"
"3cc31c07-07cf-4a6e-9190-156c4b8c4088"
]
} }
} }
], ],
@ -669,6 +667,130 @@
"errors": [ "errors": [
{ "$ref": "#/components/errors/OpNotFound" } { "$ref": "#/components/errors/OpNotFound" }
] ]
},
{
"name": "shieldnotes",
"summary": "Shield all transparent notes into the Orchard pool for the given account",
"description": "Creates one or more transactions, grouping all the unspent transparent notes for the given account by their transparent address to avoid associating different transparent addresses. These notes are sent to the given account's internal change address as shielded Orchard notes.",
"tags": [],
"params": [
{ "$ref": "#/components/contentDescriptors/AccountId"}
],
"paramStructure": "by-position",
"result": {
"name": "Operation ID(s)",
"schema": {
"type": "array",
"items": { "$ref": "#/components/contentDescriptors/OperationId"}
}
},
"examples": [
{
"name": "Shield transparent notes",
"summary": "Shield transparent notes",
"description": "Shield the transparent notes in a given account",
"params": [
{
"name": "Account index",
"summary": "The index for the account to use",
"value": "3"
}
],
"result": {
"name": "ShieldNotes result",
"value": [
"ab350df0-9f57-44c0-9e0d-f7b8af1f4231",
"8c6f2656-22ef-4f9d-b465-80ddd13fc485"
]
}
},
{
"name": "No transparent funds",
"summary": "Shield transparent notes with no transparent funds",
"description": "Attempt to shield the transparent notes in a given account, when account has none",
"params": [
{
"name": "Account index",
"summary": "The index for the account to use",
"value": "3"
}
],
"result": {
"name": "ShieldNotes result",
"value": [
"InsufficientFunds"
]
}
}
],
"errors": [
{ "$ref": "#/components/errors/ZebraNotAvailable" },
{ "$ref": "#/components/errors/ZenithBusy" },
{ "$ref": "#/components/errors/InvalidAccount" }
]
},
{
"name": "deshieldfunds",
"summary": "De-shield the given amount of ZEC from the given account",
"description": "Creates a new internal transaction with the requested amount of ZEC to the transparent pool. The fee is not included in the given amount.",
"tags": [],
"params": [
{ "$ref": "#/components/contentDescriptors/AccountId"},
{ "$ref": "#/components/contentDescriptors/Amount"}
],
"paramStructure": "by-position",
"result": {
"name": "Operation ID",
"schema": {
"$ref": "#/components/contentDescriptors/OperationId"
}
},
"examples": [
{
"name": "De-Shield funds",
"summary": "De-shield funds",
"description": "Move the given amount of ZEC for the given acount from the shielded pool to the transparent pool",
"params": [
{
"name": "Account index",
"summary": "The index for the account to use",
"value": "3"
},
{
"name": "Amount",
"summary": "The amount of ZEC to use",
"value": 1.23
}
],
"result": {
"name": "Deshield funds result",
"value": "ab350df0-9f57-44c0-9e0d-f7b8af1f4231"
}
},
{
"name": "No transparent funds",
"summary": "Shield transparent notes with no transparent funds",
"description": "Attempt to shield the transparent notes in a given account, when account has none",
"params": [
{
"name": "Account index",
"summary": "The index for the account to use",
"value": "3"
}
],
"result": {
"name": "ShieldNotes result",
"value": [
"InsufficientFunds"
]
}
}
],
"errors": [
{ "$ref": "#/components/errors/ZebraNotAvailable" },
{ "$ref": "#/components/errors/ZenithBusy" },
{ "$ref": "#/components/errors/InvalidAccount" }
]
} }
], ],
"components": { "components": {
@ -700,6 +822,15 @@
"type": "string" "type": "string"
} }
}, },
"Amount": {
"name": "A numeric amount",
"summary": "A numeric amount",
"description": "A number that represents an amount to be used by a function as an input",
"required": true,
"schema": {
"type": "number"
}
},
"Name": { "Name": {
"name": "Name", "name": "Name",
"summary": "A user-friendly name", "summary": "A user-friendly name",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 329 KiB