RPC: Shield and de-shield funds #110

Merged
pitmutt merged 165 commits from rav001 into milestone4 2025-01-02 18:43:42 +00:00
7 changed files with 164 additions and 78 deletions
Showing only changes of commit a303ca9fed - Show all commits

View file

@ -1285,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
@ -2075,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

@ -44,6 +44,7 @@ import ZcashHaskell.Orchard
, getOrchardTreeParts , getOrchardTreeParts
, getOrchardWitness , getOrchardWitness
, isValidUnifiedAddress , isValidUnifiedAddress
, parseAddress
, updateOrchardCommitmentTree , updateOrchardCommitmentTree
, updateOrchardWitness , updateOrchardWitness
) )
@ -86,6 +87,7 @@ import Zenith.Types
, ZenithStatus(..) , ZenithStatus(..)
, ZenithUuid(..) , 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
@ -751,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

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

@ -1784,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,6 +50,7 @@ import Zenith.Core
( checkBlockChain ( checkBlockChain
, createCustomWalletAddress , createCustomWalletAddress
, createZcashAccount , createZcashAccount
, deshieldNotes
, prepareTxV2 , prepareTxV2
, shieldTransparentNotes , shieldTransparentNotes
, syncWallet , syncWallet
@ -123,6 +124,7 @@ data ZenithMethod
| GetOperationStatus | GetOperationStatus
| SendMany | SendMany
| ShieldNotes | ShieldNotes
| DeshieldFunds
| UnknownMethod | UnknownMethod
deriving (Eq, Prelude.Show) deriving (Eq, Prelude.Show)
@ -139,6 +141,7 @@ instance ToJSON ZenithMethod where
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 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
@ -156,6 +159,7 @@ instance FromJSON ZenithMethod where
"getoperationstatus" -> pure GetOperationStatus "getoperationstatus" -> pure GetOperationStatus
"sendmany" -> pure SendMany "sendmany" -> pure SendMany
"shieldnotes" -> pure ShieldNotes "shieldnotes" -> pure ShieldNotes
"deshieldfunds" -> pure DeshieldFunds
_ -> pure UnknownMethod _ -> pure UnknownMethod
data ZenithParams data ZenithParams
@ -172,6 +176,7 @@ data ZenithParams
| SendParams !Int ![ProposedNote] !PrivacyPolicy | SendParams !Int ![ProposedNote] !PrivacyPolicy
| TestParams !T.Text | TestParams !T.Text
| ShieldNotesParams !Int | ShieldNotesParams !Int
| DeshieldParams !Int !Scientific
deriving (Eq, Prelude.Show) deriving (Eq, Prelude.Show)
instance ToJSON ZenithParams where instance ToJSON ZenithParams where
@ -197,6 +202,8 @@ instance ToJSON ZenithParams where
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 (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
@ -510,6 +517,17 @@ instance FromJSON RpcCall where
pure $ RpcCall v i ShieldNotes (ShieldNotesParams x) pure $ RpcCall v i ShieldNotes (ShieldNotesParams x)
else pure $ RpcCall v i ShieldNotes BadParams else pure $ RpcCall v i ShieldNotes BadParams
_anyOther -> 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
@ -942,6 +960,87 @@ 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"
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

@ -732,8 +732,8 @@
{ {
"name": "deshieldfunds", "name": "deshieldfunds",
"summary": "De-shield the given amount of ZEC from the given account", "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.", "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": [{ "$ref": "#/components/tags/draft"}, { "$ref": "#/components/tags/wip"}], "tags": [],
"params": [ "params": [
{ "$ref": "#/components/contentDescriptors/AccountId"}, { "$ref": "#/components/contentDescriptors/AccountId"},
{ "$ref": "#/components/contentDescriptors/Amount"} { "$ref": "#/components/contentDescriptors/Amount"}