Compare commits

..

11 commits

Author SHA1 Message Date
0d5161cdb2 rvv001 - Privacy Policy check added to Send transaction window
Files affected: Zenith/GUI.hs
			, Zenith/CLI.hs
			, Zenith/Utils.hs
2024-09-30 20:16:09 -04:00
165977eecf rvv001 - Saving work in progress.... 2024-09-29 20:57:32 -04:00
5c8a611dfc rvv001 - Saving Work in progress... 2024-09-29 20:43:12 -04:00
6255ea3142 rvv001 - Merge with rav001 branch 2024-09-29 20:22:29 -04:00
01cdf9eb14 rvv001 - temporary commit 2024-09-29 18:18:46 -04:00
b95213ae5c
feat: refactor ZcashPool type 2024-09-29 17:11:06 -05:00
302cfb0b76
Add Logging to sync 2024-09-29 12:32:12 -05:00
6a4bbb587c
feat: set Orchard anchor depth to 5 blocks 2024-09-26 07:31:50 -05:00
95b89e28ed
tests: Add transaction preparation tests 2024-09-25 13:01:48 -05:00
93240325df
feat!: add re-org detection and rewind 2024-09-24 14:34:19 -05:00
0e14228a0e
feat!: add block tracking to data model
Adds new `ZcashBlock` table to database to track block information and
creates a relationship between `ZcashTransaction` records and the block
they belong to.
Database getters and setters are updated to use the block record for
confirmations, height, time data.
2024-09-23 13:55:14 -05:00
12 changed files with 992 additions and 385 deletions

View file

@ -24,6 +24,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- Detection of changes in database schema for automatic re-scan - Detection of changes in database schema for automatic re-scan
- Block tracking for chain re-org detection
- Refactored `ZcashPool`
## [0.6.0.0-beta] ## [0.6.0.0-beta]

View file

@ -230,6 +230,7 @@ main = do
"gui" -> runZenithGUI myConfig "gui" -> runZenithGUI myConfig
"tui" -> runZenithTUI myConfig "tui" -> runZenithTUI myConfig
"rescan" -> rescanZebra zebraHost zebraPort dbFilePath "rescan" -> rescanZebra zebraHost zebraPort dbFilePath
"resync" -> clearSync myConfig
_ -> printUsage _ -> printUsage
else printUsage else printUsage

View file

@ -62,9 +62,14 @@ import qualified Brick.Widgets.List as L
import qualified Brick.Widgets.ProgressBar as P import qualified Brick.Widgets.ProgressBar as P
import Control.Concurrent (forkIO, threadDelay) import Control.Concurrent (forkIO, threadDelay)
import Control.Exception (throw, throwIO, try) import Control.Exception (throw, throwIO, try)
import Control.Monad (forever, void, when) import Control.Monad (forever, unless, void, when)
import Control.Monad.IO.Class (liftIO) import Control.Monad.IO.Class (liftIO)
import Control.Monad.Logger (runFileLoggingT, runNoLoggingT) import Control.Monad.Logger
( LoggingT
, logDebugN
, runFileLoggingT
, runNoLoggingT
)
import Data.Aeson import Data.Aeson
import Data.HexString (HexString(..), toText) import Data.HexString (HexString(..), toText)
import Data.Maybe import Data.Maybe
@ -89,9 +94,10 @@ import ZcashHaskell.Types
import ZcashHaskell.Utils (getBlockTime, makeZebraCall) import ZcashHaskell.Utils (getBlockTime, makeZebraCall)
import Zenith.Core import Zenith.Core
import Zenith.DB import Zenith.DB
import Zenith.Scanner (processTx, rescanZebra, updateConfs) import Zenith.Scanner (checkIntegrity, processTx, rescanZebra, updateConfs)
import Zenith.Types import Zenith.Types
( Config(..) ( Config(..)
, HexStringDB(..)
, PhraseDB(..) , PhraseDB(..)
, UnifiedAddressDB(..) , UnifiedAddressDB(..)
, ZcashNetDB(..) , ZcashNetDB(..)
@ -102,7 +108,7 @@ import Zenith.Utils
, displayZec , displayZec
, isRecipientValid , isRecipientValid
, jsonNumber , jsonNumber
, parseAddress , parseAddressUA
, showAddress , showAddress
, validBarValue , validBarValue
) )
@ -612,16 +618,16 @@ mkInputForm =
mkSendForm :: Integer -> SendInput -> Form SendInput e Name mkSendForm :: Integer -> SendInput -> Form SendInput e Name
mkSendForm bal = mkSendForm bal =
newForm newForm
[ label "To: " @@= editTextField sendTo RecField (Just 1) [ label "Privacy Level :" @@=
radioField policyField [ (Full, PrivacyFullField, "Full")
, (Medium, PrivacyMediumField, "Medium")
, (Low, PrivacyLowField, "Low")
, (None, PrivacyNoneField, "None")
]
, label "To: " @@= editTextField sendTo RecField (Just 1)
, label "Amount: " @@= , label "Amount: " @@=
editShowableFieldWithValidate sendAmt AmtField (isAmountValid bal) editShowableFieldWithValidate sendAmt AmtField (isAmountValid bal)
, label "Memo: " @@= editTextField sendMemo MemoField (Just 1) , label "Memo: " @@= editTextField sendMemo MemoField (Just 1)
, label "Privacy Level :" @@=
radioField policyField [ (None, PrivacyNoneField, "None")
, (Low, PrivacyLowField, "Low")
, (Medium, PrivacyMediumField, "Medium")
, (Full, PrivacyFullField, "Full")
]
] ]
where where
isAmountValid :: Integer -> Float -> Bool isAmountValid :: Integer -> Float -> Bool
@ -731,31 +737,42 @@ abMBarAttr :: A.AttrName
abMBarAttr = A.attrName "menubar" abMBarAttr = A.attrName "menubar"
scanZebra :: scanZebra ::
T.Text -> T.Text -> Int -> Int -> BC.BChan Tick -> ZcashNet -> IO () T.Text
-> T.Text
-> Int
-> Int
-> BC.BChan Tick
-> ZcashNet
-> LoggingT IO ()
scanZebra dbP zHost zPort b eChan znet = do scanZebra dbP zHost zPort b eChan znet = do
bStatus <- liftIO $ checkBlockChain zHost zPort bStatus <- liftIO $ checkBlockChain zHost zPort
pool <- runNoLoggingT $ initPool dbP pool <- liftIO $ runNoLoggingT $ initPool dbP
dbBlock <- getMaxBlock pool $ ZcashNetDB znet dbBlock <- liftIO $ getMaxBlock pool $ ZcashNetDB znet
confUp <- try $ updateConfs zHost zPort pool :: IO (Either IOError ()) chkBlock <- liftIO $ checkIntegrity dbP zHost zPort dbBlock 1
logDebugN $
"dbBlock: " <>
T.pack (show dbBlock) <> " chkBlock: " <> T.pack (show chkBlock)
let sb = max dbBlock b
if sb > zgb_blocks bStatus || sb < 1
then do
liftIO $ BC.writeBChan eChan $ TickMsg "Invalid starting block for scan"
else do
let bList = [(sb + 1) .. (zgb_blocks bStatus)]
if not (null bList)
then do
let step =
(1.0 :: Float) / fromIntegral (zgb_blocks bStatus - (sb + 1))
mapM_ (liftIO . processBlock pool step) bList
else liftIO $ BC.writeBChan eChan $ TickVal 1.0
confUp <-
liftIO $ try $ updateConfs zHost zPort pool :: LoggingT
IO
(Either IOError ())
case confUp of case confUp of
Left _e0 -> Left _e0 ->
liftIO $ liftIO $
BC.writeBChan eChan $ TickMsg "Failed to update unconfirmed transactions" BC.writeBChan eChan $ TickMsg "Failed to update unconfirmed transactions"
Right _ -> do Right _ -> return ()
let sb = max dbBlock b
if sb > zgb_blocks bStatus || sb < 1
then do
liftIO $
BC.writeBChan eChan $ TickMsg "Invalid starting block for scan"
else do
let bList = [(sb + 1) .. (zgb_blocks bStatus)]
if not (null bList)
then do
let step =
(1.0 :: Float) /
fromIntegral (zgb_blocks bStatus - (sb + 1))
mapM_ (processBlock pool step) bList
else liftIO $ BC.writeBChan eChan $ TickVal 1.0
where where
processBlock :: ConnectionPool -> Float -> Int -> IO () processBlock :: ConnectionPool -> Float -> Int -> IO ()
processBlock pool step bl = do processBlock pool step bl = do
@ -780,16 +797,16 @@ scanZebra dbP zHost zPort b eChan znet = do
Left e2 -> liftIO $ BC.writeBChan eChan $ TickMsg e2 Left e2 -> liftIO $ BC.writeBChan eChan $ TickMsg e2
Right hb -> do Right hb -> do
let blockTime = getBlockTime hb let blockTime = getBlockTime hb
mapM_ (processTx zHost zPort blockTime pool (ZcashNetDB znet)) $ bi <-
bl_txs $ addTime blk blockTime saveBlock pool $
ZcashBlock
(fromIntegral $ bl_height blk)
(HexStringDB $ bl_hash blk)
(fromIntegral $ bl_confirmations blk)
blockTime
(ZcashNetDB znet)
mapM_ (processTx zHost zPort bi pool) $ bl_txs blk
liftIO $ BC.writeBChan eChan $ TickVal step liftIO $ BC.writeBChan eChan $ TickVal step
addTime :: BlockResponse -> Int -> BlockResponse
addTime bl t =
BlockResponse
(bl_confirmations bl)
(bl_height bl)
(fromIntegral t)
(bl_txs bl)
appEvent :: BT.BrickEvent Name Tick -> BT.EventM Name State () appEvent :: BT.BrickEvent Name Tick -> BT.EventM Name State ()
appEvent (BT.AppEvent t) = do appEvent (BT.AppEvent t) = do
@ -834,6 +851,7 @@ appEvent (BT.AppEvent t) = do
Just (_k, w) -> return w Just (_k, w) -> return w
_ <- _ <-
liftIO $ liftIO $
runFileLoggingT "zenith.log" $
syncWallet syncWallet
(Config (Config
(s ^. dbPath) (s ^. dbPath)
@ -870,6 +888,7 @@ appEvent (BT.AppEvent t) = do
_ <- _ <-
liftIO $ liftIO $
forkIO $ forkIO $
runFileLoggingT "zenith.log" $
scanZebra scanZebra
(s ^. dbPath) (s ^. dbPath)
(s ^. zebraHost) (s ^. zebraHost)
@ -1141,7 +1160,7 @@ appEvent (BT.VtyEvent e) = do
(addressBookAbaddress (entityVal a)) (addressBookAbaddress (entityVal a))
0.0 0.0
"" ""
None) Full)
BT.modify $ set dialogBox SendTx BT.modify $ set dialogBox SendTx
_ -> do _ -> do
BT.modify $ BT.modify $
@ -1313,7 +1332,7 @@ appEvent (BT.VtyEvent e) = do
V.EvKey (V.KChar 's') [] -> do V.EvKey (V.KChar 's') [] -> do
BT.modify $ BT.modify $
set txForm $ set txForm $
mkSendForm (s ^. balance) (SendInput "" 0.0 "" None) mkSendForm (s ^. balance) (SendInput "" 0.0 "" Full)
BT.modify $ set dialogBox SendTx BT.modify $ set dialogBox SendTx
V.EvKey (V.KChar 'b') [] -> V.EvKey (V.KChar 'b') [] ->
BT.modify $ set dialogBox AdrBook BT.modify $ set dialogBox AdrBook
@ -1448,7 +1467,7 @@ runZenithTUI config = do
1.0 1.0
eventChan eventChan
0 0
(mkSendForm 0 $ SendInput "" 0.0 "" None) (mkSendForm 0 $ SendInput "" 0.0 "" Full)
(L.list ABList (Vec.fromList abookList) 1) (L.list ABList (Vec.fromList abookList) 1)
(mkNewABForm (AdrBookEntry "" "")) (mkNewABForm (AdrBookEntry "" ""))
"" ""
@ -1670,7 +1689,7 @@ sendTransaction ::
-> IO () -> IO ()
sendTransaction pool chan zHost zPort znet accId bl amt ua memo = do sendTransaction pool chan zHost zPort znet accId bl amt ua memo = do
BC.writeBChan chan $ TickMsg "Preparing transaction..." BC.writeBChan chan $ TickMsg "Preparing transaction..."
case parseAddress ua znet of case parseAddressUA ua znet of
Nothing -> BC.writeBChan chan $ TickMsg "Incorrect address" Nothing -> BC.writeBChan chan $ TickMsg "Incorrect address"
Just outUA -> do Just outUA -> do
res <- res <-

View file

@ -772,98 +772,254 @@ prepareTxV2 pool zebraHost zebraPort zn za bh amt va memo policy = do
{-firstPass <- liftIO $ selectUnspentNotes pool za zats-} {-firstPass <- liftIO $ selectUnspentNotes pool za zats-}
--let fee = calculateTxFee firstPass $ fst recipient --let fee = calculateTxFee firstPass $ fst recipient
--logDebugN $ T.pack $ "calculated fee " ++ show fee --logDebugN $ T.pack $ "calculated fee " ++ show fee
(tList, sList, oList) <- liftIO $ selectUnspentNotes pool za (zats + 5000) notePlan <-
logDebugN "selected notes"
logDebugN $ T.pack $ show tList
logDebugN $ T.pack $ show sList
logDebugN $ T.pack $ show oList
let noteTotal = getTotalAmount (tList, sList, oList)
tSpends <-
liftIO $ liftIO $
prepTSpends (getTranSK $ zcashAccountTPrivateKey $ entityVal acc) tList selectUnspentNotesV2 pool za (zats + 10000) (fst recipient) policy
--print tSpends case notePlan of
sSpends <- Right (tList, sList, oList) -> do
liftIO $ logDebugN "selected notes"
prepSSpends (getSapSK $ zcashAccountSapSpendKey $ entityVal acc) sList
--print sSpends
oSpends <-
liftIO $
prepOSpends (getOrchSK $ zcashAccountOrchSpendKey $ entityVal acc) oList
--print oSpends
dummy <-
liftIO $ makeOutgoing acc recipient zats (noteTotal - 5000 - zats)
logDebugN "Calculating fee"
let feeResponse =
createTransaction
(Just sT)
(Just oT)
tSpends
sSpends
oSpends
dummy
zn
(bh + 3)
False
case feeResponse of
Left e1 -> return $ Left Fee
Right fee -> do
let feeAmt =
fromIntegral (runGet getInt64le $ LBS.fromStrict $ toBytes fee)
(tList1, sList1, oList1) <-
liftIO $ selectUnspentNotes pool za (zats + feeAmt)
logDebugN $ T.pack $ "selected notes with fee" ++ show feeAmt
logDebugN $ T.pack $ show tList logDebugN $ T.pack $ show tList
logDebugN $ T.pack $ show sList logDebugN $ T.pack $ show sList
logDebugN $ T.pack $ show oList logDebugN $ T.pack $ show oList
outgoing <- let noteTotal = getTotalAmount (tList, sList, oList)
liftIO $ makeOutgoing acc recipient zats (noteTotal - feeAmt - zats) tSpends <-
logDebugN $ T.pack $ show outgoing liftIO $
let tx = prepTSpends
createTransaction (getTranSK $ zcashAccountTPrivateKey $ entityVal acc)
(Just sT) tList
(Just oT) --print tSpends
tSpends sSpends <-
sSpends liftIO $
oSpends prepSSpends
outgoing (getSapSK $ zcashAccountSapSpendKey $ entityVal acc)
zn sList
(bh + 3) --print sSpends
True oSpends <-
logDebugN $ T.pack $ show tx liftIO $
return tx prepOSpends
(getOrchSK $ zcashAccountOrchSpendKey $ entityVal acc)
oList
--print oSpends
dummy' <-
liftIO $
makeOutgoing acc recipient zats (noteTotal - 5000 - zats) policy
case dummy' of
Left e -> return $ Left e
Right dummy -> do
logDebugN "Calculating fee"
let feeResponse =
createTransaction
(Just sT)
(Just oT)
tSpends
sSpends
oSpends
dummy
zn
(bh + 3)
False
case feeResponse of
Left e1 -> return $ Left Fee
Right fee -> do
let feeAmt =
fromIntegral
(runGet getInt64le $ LBS.fromStrict $ toBytes fee)
finalNotePlan <-
liftIO $
selectUnspentNotesV2
pool
za
(zats + feeAmt)
(fst recipient)
policy
case finalNotePlan of
Right (tList1, sList1, oList1) -> do
logDebugN $
T.pack $ "selected notes with fee" ++ show feeAmt
logDebugN $ T.pack $ show tList1
logDebugN $ T.pack $ show sList1
logDebugN $ T.pack $ show oList1
tSpends1 <-
liftIO $
prepTSpends
(getTranSK $ zcashAccountTPrivateKey $ entityVal acc)
tList1
sSpends1 <-
liftIO $
prepSSpends
(getSapSK $ zcashAccountSapSpendKey $ entityVal acc)
sList1
oSpends1 <-
liftIO $
prepOSpends
(getOrchSK $ zcashAccountOrchSpendKey $ entityVal acc)
oList1
let noteTotal1 = getTotalAmount (tList1, sList1, oList1)
outgoing' <-
liftIO $
makeOutgoing
acc
recipient
zats
(noteTotal1 - feeAmt - zats)
policy
logDebugN $ T.pack $ show outgoing'
case outgoing' of
Left e -> return $ Left e
Right outgoing -> do
let tx =
createTransaction
(Just sT)
(Just oT)
tSpends1
sSpends1
oSpends1
outgoing
zn
(bh + 3)
True
logDebugN $ T.pack $ show tx
return tx
Left e -> return $ Left e
Left e -> do
logErrorN $ T.pack $ show e
return $ Left e
where where
makeOutgoing :: makeOutgoing ::
Entity ZcashAccount Entity ZcashAccount
-> (Int, BS.ByteString) -> (Int, BS.ByteString)
-> Integer -> Integer
-> Integer -> Integer
-> IO [OutgoingNote] -> PrivacyPolicy
makeOutgoing acc (k, recvr) zats chg = do -> IO (Either TxError [OutgoingNote])
makeOutgoing acc (k, recvr) zats chg policy = do
chgAddr <- runNoLoggingT $ getInternalAddresses pool $ entityKey acc chgAddr <- runNoLoggingT $ getInternalAddresses pool $ entityKey acc
let internalUA = getUA $ walletAddressUAddress $ entityVal $ head chgAddr let internalUA = getUA $ walletAddressUAddress $ entityVal $ head chgAddr
let chgRcvr = case k of
fromJust $ o_rec =<< isValidUnifiedAddress (E.encodeUtf8 internalUA) 4 ->
return case policy of
[ OutgoingNote None ->
4 return $
(getBytes $ getOrchSK $ zcashAccountOrchSpendKey $ entityVal acc) Left $
(getBytes chgRcvr) PrivacyPolicyError "Recipient not allowed by privacy policy"
(fromIntegral chg) _anyOther -> do
"" let chgRcvr =
True fromJust $
, OutgoingNote o_rec =<< isValidUnifiedAddress (E.encodeUtf8 internalUA)
(fromIntegral k) return $
(case k of Right
4 -> [ OutgoingNote
getBytes $ getOrchSK $ zcashAccountOrchSpendKey $ entityVal acc 4
3 -> (getBytes $
getBytes $ getSapSK $ zcashAccountSapSpendKey $ entityVal acc getOrchSK $ zcashAccountOrchSpendKey $ entityVal acc)
_ -> "") (getBytes chgRcvr)
recvr (fromIntegral chg)
(fromIntegral zats) ""
(E.encodeUtf8 memo) True
False , OutgoingNote
] 4
(getBytes $
getOrchSK $ zcashAccountOrchSpendKey $ entityVal acc)
recvr
(fromIntegral zats)
(E.encodeUtf8 memo)
False
]
3 ->
case policy of
None ->
return $
Left $
PrivacyPolicyError "Receiver not compatible with privacy policy"
Full -> do
let chgRcvr =
fromJust $
s_rec =<< isValidUnifiedAddress (E.encodeUtf8 internalUA)
return $
Right
[ OutgoingNote
3
(getBytes $
getSapSK $ zcashAccountSapSpendKey $ entityVal acc)
(getBytes chgRcvr)
(fromIntegral chg)
""
True
, OutgoingNote
3
(getBytes $
getSapSK $ zcashAccountSapSpendKey $ entityVal acc)
recvr
(fromIntegral zats)
(E.encodeUtf8 memo)
False
]
_anyOther -> do
let chgRcvr =
fromJust $
o_rec =<< isValidUnifiedAddress (E.encodeUtf8 internalUA)
return $
Right
[ OutgoingNote
4
(getBytes $
getOrchSK $ zcashAccountOrchSpendKey $ entityVal acc)
(getBytes chgRcvr)
(fromIntegral chg)
""
True
, OutgoingNote
3
(getBytes $
getSapSK $ zcashAccountSapSpendKey $ entityVal acc)
recvr
(fromIntegral zats)
(E.encodeUtf8 memo)
False
]
2 ->
if policy <= Low
then do
let chgRcvr =
fromJust $
t_rec =<< isValidUnifiedAddress (E.encodeUtf8 internalUA)
return $
Right
[ OutgoingNote
1
BS.empty
(toBytes $ tr_bytes chgRcvr)
(fromIntegral chg)
""
True
, OutgoingNote 2 BS.empty recvr (fromIntegral zats) "" False
]
else return $
Left $
PrivacyPolicyError
"Receiver not compatible with privacy policy"
1 ->
if policy <= Low
then do
let chgRcvr =
fromJust $
t_rec =<< isValidUnifiedAddress (E.encodeUtf8 internalUA)
return $
Right
[ OutgoingNote
1
BS.empty
(toBytes $ tr_bytes chgRcvr)
(fromIntegral chg)
""
True
, OutgoingNote 1 BS.empty recvr (fromIntegral zats) "" False
]
else return $
Left $
PrivacyPolicyError
"Receiver not compatible with privacy policy"
_anyOther -> return $ Left ZHError
getTotalAmount :: getTotalAmount ::
( [Entity WalletTrNote] ( [Entity WalletTrNote]
, [Entity WalletSapNote] , [Entity WalletSapNote]
@ -951,22 +1107,30 @@ prepareTxV2 pool zebraHost zebraPort zn za bh amt va memo policy = do
syncWallet :: syncWallet ::
Config -- ^ configuration parameters Config -- ^ configuration parameters
-> Entity ZcashWallet -> Entity ZcashWallet
-> IO () -> LoggingT IO ()
syncWallet config w = do syncWallet config w = do
startTime <- liftIO getCurrentTime startTime <- liftIO getCurrentTime
let walletDb = c_dbPath config let walletDb = c_dbPath config
let znet = zcashWalletNetwork $ entityVal w let znet = zcashWalletNetwork $ entityVal w
pool <- runNoLoggingT $ initPool walletDb pool <- liftIO $ runNoLoggingT $ initPool walletDb
accs <- runNoLoggingT $ getAccounts pool $ entityKey w accs <- liftIO $ runNoLoggingT $ getAccounts pool $ entityKey w
addrs <- concat <$> mapM (runNoLoggingT . getAddresses pool . entityKey) accs logDebugN $ "Accounts: " <> T.pack (show accs)
addrs <-
concat <$>
mapM (liftIO . runNoLoggingT . getAddresses pool . entityKey) accs
logDebugN $ "addrs: " <> T.pack (show addrs)
intAddrs <- intAddrs <-
concat <$> mapM (runNoLoggingT . getInternalAddresses pool . entityKey) accs concat <$>
chainTip <- getMaxBlock pool znet mapM (liftIO . runNoLoggingT . getInternalAddresses pool . entityKey) accs
chainTip <- liftIO $ getMaxBlock pool znet
logDebugN $ "chain tip: " <> T.pack (show chainTip)
let lastBlock = zcashWalletLastSync $ entityVal w let lastBlock = zcashWalletLastSync $ entityVal w
logDebugN $ "last block: " <> T.pack (show lastBlock)
let startBlock = let startBlock =
if lastBlock > 0 if lastBlock > 0
then lastBlock then lastBlock
else zcashWalletBirthdayHeight $ entityVal w else zcashWalletBirthdayHeight $ entityVal w
logDebugN $ "start block: " <> T.pack (show startBlock)
mapM_ (liftIO . findTransparentNotes pool startBlock znet) addrs mapM_ (liftIO . findTransparentNotes pool startBlock znet) addrs
mapM_ (liftIO . findTransparentNotes pool startBlock znet) intAddrs mapM_ (liftIO . findTransparentNotes pool startBlock znet) intAddrs
mapM_ (liftIO . findTransparentSpends pool . entityKey) accs mapM_ (liftIO . findTransparentSpends pool . entityKey) accs
@ -980,7 +1144,7 @@ syncWallet config w = do
mapM mapM
(findOrchardActions config startBlock (zcashWalletNetwork $ entityVal w)) (findOrchardActions config startBlock (zcashWalletNetwork $ entityVal w))
accs accs
_ <- updateSaplingWitnesses pool _ <- liftIO $ updateSaplingWitnesses pool
_ <- updateOrchardWitnesses pool _ <- liftIO $ updateOrchardWitnesses pool
_ <- liftIO $ updateWalletSync pool chainTip (entityKey w) _ <- liftIO $ updateWalletSync pool chainTip (entityKey w)
mapM_ (runNoLoggingT . getWalletTransactions pool) addrs mapM_ (liftIO . runNoLoggingT . getWalletTransactions pool) addrs

View file

@ -18,7 +18,7 @@
module Zenith.DB where module Zenith.DB where
import Control.Exception (SomeException(..), throwIO, try) import Control.Exception (SomeException(..), throw, throwIO, try)
import Control.Monad (when) import Control.Monad (when)
import Control.Monad.IO.Class (MonadIO, liftIO) import Control.Monad.IO.Class (MonadIO, liftIO)
import Control.Monad.Logger (NoLoggingT, runNoLoggingT) import Control.Monad.Logger (NoLoggingT, runNoLoggingT)
@ -69,6 +69,7 @@ import ZcashHaskell.Types
, TransparentAddress(..) , TransparentAddress(..)
, TransparentBundle(..) , TransparentBundle(..)
, TransparentReceiver(..) , TransparentReceiver(..)
, TxError(..)
, UnifiedAddress(..) , UnifiedAddress(..)
, ValidAddress(..) , ValidAddress(..)
, ZcashNet(..) , ZcashNet(..)
@ -78,6 +79,7 @@ import Zenith.Types
, HexStringDB(..) , HexStringDB(..)
, OrchardSpendingKeyDB(..) , OrchardSpendingKeyDB(..)
, PhraseDB(..) , PhraseDB(..)
, PrivacyPolicy(..)
, RseedDB(..) , RseedDB(..)
, SaplingSpendingKeyDB(..) , SaplingSpendingKeyDB(..)
, ScopeDB(..) , ScopeDB(..)
@ -202,23 +204,28 @@ share
value Int64 value Int64
UniqueOrchSpend tx accId UniqueOrchSpend tx accId
deriving Show Eq deriving Show Eq
ZcashTransaction ZcashBlock
block Int height Int
txId HexStringDB hash HexStringDB
conf Int conf Int
time Int time Int
network ZcashNetDB network ZcashNetDB
UniqueTx block txId network UniqueBlock height network
deriving Show Eq
ZcashTransaction
blockId ZcashBlockId OnDeleteCascade OnUpdateCascade
txId HexStringDB
UniqueTx blockId txId
deriving Show Eq deriving Show Eq
TransparentNote TransparentNote
tx ZcashTransactionId tx ZcashTransactionId OnDeleteCascade OnUpdateCascade
value Int64 value Int64
script BS.ByteString script BS.ByteString
position Int position Int
UniqueTNPos tx position UniqueTNPos tx position
deriving Show Eq deriving Show Eq
TransparentSpend TransparentSpend
tx ZcashTransactionId tx ZcashTransactionId OnDeleteCascade OnUpdateCascade
outPointHash HexStringDB outPointHash HexStringDB
outPointIndex Word64 outPointIndex Word64
script BS.ByteString script BS.ByteString
@ -227,7 +234,7 @@ share
UniqueTSPos tx position UniqueTSPos tx position
deriving Show Eq deriving Show Eq
OrchAction OrchAction
tx ZcashTransactionId tx ZcashTransactionId OnDeleteCascade OnUpdateCascade
nf HexStringDB nf HexStringDB
rk HexStringDB rk HexStringDB
cmx HexStringDB cmx HexStringDB
@ -240,7 +247,7 @@ share
UniqueOAPos tx position UniqueOAPos tx position
deriving Show Eq deriving Show Eq
ShieldOutput ShieldOutput
tx ZcashTransactionId tx ZcashTransactionId OnDeleteCascade OnUpdateCascade
cv HexStringDB cv HexStringDB
cmu HexStringDB cmu HexStringDB
ephKey HexStringDB ephKey HexStringDB
@ -251,7 +258,7 @@ share
UniqueSOPos tx position UniqueSOPos tx position
deriving Show Eq deriving Show Eq
ShieldSpend ShieldSpend
tx ZcashTransactionId tx ZcashTransactionId OnDeleteCascade OnUpdateCascade
cv HexStringDB cv HexStringDB
anchor HexStringDB anchor HexStringDB
nullifier HexStringDB nullifier HexStringDB
@ -340,7 +347,7 @@ trToZcashNoteAPI pool n = do
return $ return $
ZcashNoteAPI ZcashNoteAPI
(getHex $ walletTransactionTxId $ entityVal t') -- tx ID (getHex $ walletTransactionTxId $ entityVal t') -- tx ID
Zenith.Types.Transparent -- pool Zenith.Types.TransparentPool -- pool
(fromIntegral (walletTrNoteValue (entityVal n)) / 100000000.0) -- zec (fromIntegral (walletTrNoteValue (entityVal n)) / 100000000.0) -- zec
(walletTrNoteValue $ entityVal n) -- zats (walletTrNoteValue $ entityVal n) -- zats
"" -- memo "" -- memo
@ -361,7 +368,7 @@ sapToZcashNoteAPI pool n = do
return $ return $
ZcashNoteAPI ZcashNoteAPI
(getHex $ walletTransactionTxId $ entityVal t') -- tx ID (getHex $ walletTransactionTxId $ entityVal t') -- tx ID
Zenith.Types.Sapling -- pool Zenith.Types.SaplingPool -- pool
(fromIntegral (walletSapNoteValue (entityVal n)) / 100000000.0) -- zec (fromIntegral (walletSapNoteValue (entityVal n)) / 100000000.0) -- zec
(walletSapNoteValue $ entityVal n) -- zats (walletSapNoteValue $ entityVal n) -- zats
(walletSapNoteMemo $ entityVal n) -- memo (walletSapNoteMemo $ entityVal n) -- memo
@ -382,7 +389,7 @@ orchToZcashNoteAPI pool n = do
return $ return $
ZcashNoteAPI ZcashNoteAPI
(getHex $ walletTransactionTxId $ entityVal t') -- tx ID (getHex $ walletTransactionTxId $ entityVal t') -- tx ID
Orchard OrchardPool
(fromIntegral (walletOrchNoteValue (entityVal n)) / 100000000.0) -- zec (fromIntegral (walletOrchNoteValue (entityVal n)) / 100000000.0) -- zec
(walletOrchNoteValue $ entityVal n) -- zats (walletOrchNoteValue $ entityVal n) -- zats
(walletOrchNoteMemo $ entityVal n) -- memo (walletOrchNoteMemo $ entityVal n) -- memo
@ -437,10 +444,10 @@ initDb dbName = do
clearWalletTransactions pool clearWalletTransactions pool
clearWalletData pool clearWalletData pool
m <- m <-
try $ PS.runSqlite dbName $ runMigrationQuiet migrateAll :: IO try $ PS.runSqlite dbName $ runMigrationUnsafeQuiet migrateAll :: IO
(Either SomeException [T.Text]) (Either SomeException [T.Text])
case m of case m of
Left _e2 -> return $ Left "Failed to migrate data tables" Left e2 -> return $ Left $ "Failed to migrate data tables" ++ show e2
Right _ -> return $ Right True Right _ -> return $ Right True
Right _ -> return $ Right False Right _ -> return $ Right False
@ -579,14 +586,13 @@ getMaxBlock pool net = do
PS.retryOnBusy $ PS.retryOnBusy $
flip PS.runSqlPool pool $ do flip PS.runSqlPool pool $ do
selectOne $ do selectOne $ do
txs <- from $ table @ZcashTransaction bls <- from $ table @ZcashBlock
where_ (txs ^. ZcashTransactionBlock >. val 0) where_ (bls ^. ZcashBlockNetwork ==. val net)
where_ (txs ^. ZcashTransactionNetwork ==. val net) orderBy [desc $ bls ^. ZcashBlockHeight]
orderBy [desc $ txs ^. ZcashTransactionBlock] pure bls
pure txs
case b of case b of
Nothing -> return $ -1 Nothing -> return $ -1
Just x -> return $ zcashTransactionBlock $ entityVal x Just x -> return $ zcashBlockHeight $ entityVal x
-- | Returns a list of addresses associated with the given account -- | Returns a list of addresses associated with the given account
getAddresses :: getAddresses ::
@ -677,20 +683,33 @@ saveAddress pool w =
runNoLoggingT $ runNoLoggingT $
PS.retryOnBusy $ flip PS.runSqlPool pool $ insertUniqueEntity w PS.retryOnBusy $ flip PS.runSqlPool pool $ insertUniqueEntity w
-- | Save a block to the database
saveBlock :: ConnectionPool -> ZcashBlock -> IO (Key ZcashBlock)
saveBlock pool b =
runNoLoggingT $ PS.retryOnBusy $ flip PS.runSqlPool pool $ do insert b
-- | Read a block by height
getBlock :: ConnectionPool -> Int -> IO (Maybe (Entity ZcashBlock))
getBlock pool b =
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
selectOne $ do
bl <- from $ table @ZcashBlock
where_ $ bl ^. ZcashBlockHeight ==. val b
pure bl
-- | Save a transaction to the data model -- | Save a transaction to the data model
saveTransaction :: saveTransaction ::
ConnectionPool -- ^ the database path ConnectionPool -- ^ the database path
-> Int -- ^ block time -> ZcashBlockId -- ^ The block the transaction is in
-> ZcashNetDB -- ^ the network
-> Transaction -- ^ The transaction to save -> Transaction -- ^ The transaction to save
-> NoLoggingT IO (Key ZcashTransaction) -> NoLoggingT IO (Key ZcashTransaction)
saveTransaction pool t n wt = saveTransaction pool bi wt =
PS.retryOnBusy $ PS.retryOnBusy $
flip PS.runSqlPool pool $ do flip PS.runSqlPool pool $ do
let ix = [0 ..] let ix = [0 ..]
w <- w <- insert $ ZcashTransaction bi (HexStringDB $ tx_id wt)
insert $
ZcashTransaction (tx_height wt) (HexStringDB $ tx_id wt) (tx_conf wt) t n
when (isJust $ tx_transpBundle wt) $ do when (isJust $ tx_transpBundle wt) $ do
_ <- _ <-
insertMany_ $ insertMany_ $
@ -776,10 +795,13 @@ getZcashTransactions pool b net =
PS.retryOnBusy $ PS.retryOnBusy $
flip PS.runSqlPool pool $ do flip PS.runSqlPool pool $ do
select $ do select $ do
txs <- from $ table @ZcashTransaction (blks :& txs) <-
where_ (txs ^. ZcashTransactionBlock >. val b) from $ table @ZcashBlock `innerJoin` table @ZcashTransaction `on`
where_ (txs ^. ZcashTransactionNetwork ==. val (ZcashNetDB net)) (\(blks :& txs) ->
orderBy [asc $ txs ^. ZcashTransactionBlock] blks ^. ZcashBlockId ==. txs ^. ZcashTransactionBlockId)
where_ (blks ^. ZcashBlockHeight >. val b)
where_ (blks ^. ZcashBlockNetwork ==. val (ZcashNetDB net))
orderBy [asc $ blks ^. ZcashBlockHeight]
return txs return txs
-- ** QR codes -- ** QR codes
@ -876,16 +898,25 @@ saveWalletTransaction pool za zt = do
runNoLoggingT $ runNoLoggingT $
PS.retryOnBusy $ PS.retryOnBusy $
flip PS.runSqlPool pool $ do flip PS.runSqlPool pool $ do
t <- b <-
upsert selectOne $ do
(WalletTransaction blks <- from $ table @ZcashBlock
(zcashTransactionTxId zT') where_ (blks ^. ZcashBlockId ==. val (zcashTransactionBlockId zT'))
za pure blks
(zcashTransactionBlock zT') case b of
(zcashTransactionConf zT') Nothing ->
(zcashTransactionTime zT')) throw $ userError "invalid block for saving wallet transaction"
[] Just blk -> do
return $ entityKey t t <-
upsert
(WalletTransaction
(zcashTransactionTxId zT')
za
(zcashBlockHeight $ entityVal blk)
(zcashBlockConf $ entityVal blk)
(zcashBlockTime $ entityVal blk))
[]
return $ entityKey t
-- | Save a @WalletSapNote@ -- | Save a @WalletSapNote@
saveWalletSapNote :: saveWalletSapNote ::
@ -976,14 +1007,17 @@ findTransparentNotes pool b net t = do
PS.retryOnBusy $ PS.retryOnBusy $
flip PS.runSqlPool pool $ do flip PS.runSqlPool pool $ do
select $ do select $ do
(txs :& tNotes) <- (blks :& txs :& tNotes) <-
from $ table @ZcashTransaction `innerJoin` table @TransparentNote `on` from $ table @ZcashBlock `innerJoin` table @ZcashTransaction `on`
(\(txs :& tNotes) -> (\(blks :& txs) ->
blks ^. ZcashBlockId ==. txs ^. ZcashTransactionBlockId) `innerJoin`
table @TransparentNote `on`
(\(_ :& txs :& tNotes) ->
txs ^. ZcashTransactionId ==. tNotes ^. TransparentNoteTx) txs ^. ZcashTransactionId ==. tNotes ^. TransparentNoteTx)
where_ (txs ^. ZcashTransactionBlock >. val b) where_ (blks ^. ZcashBlockHeight >. val b)
where_ (txs ^. ZcashTransactionNetwork ==. val net) where_ (blks ^. ZcashBlockNetwork ==. val net)
where_ (tNotes ^. TransparentNoteScript ==. val s) where_ (tNotes ^. TransparentNoteScript ==. val s)
pure (txs, tNotes) pure (blks, txs, tNotes)
mapM_ mapM_
(saveWalletTrNote (saveWalletTrNote
pool pool
@ -999,10 +1033,11 @@ saveWalletTrNote ::
-> Scope -> Scope
-> ZcashAccountId -> ZcashAccountId
-> WalletAddressId -> WalletAddressId
-> (Entity ZcashTransaction, Entity TransparentNote) -> (Entity ZcashBlock, Entity ZcashTransaction, Entity TransparentNote)
-> IO () -> IO ()
saveWalletTrNote pool ch za wa (zt, tn) = do saveWalletTrNote pool ch za wa (blk, zt, tn) = do
let zT' = entityVal zt let zT' = entityVal zt
let b = entityVal blk
runNoLoggingT $ runNoLoggingT $
PS.retryOnBusy $ PS.retryOnBusy $
flip PS.runSqlPool pool $ do flip PS.runSqlPool pool $ do
@ -1011,9 +1046,9 @@ saveWalletTrNote pool ch za wa (zt, tn) = do
(WalletTransaction (WalletTransaction
(zcashTransactionTxId zT') (zcashTransactionTxId zT')
za za
(zcashTransactionBlock zT') (zcashBlockHeight b)
(zcashTransactionConf zT') (zcashBlockConf b)
(zcashTransactionTime zT')) (zcashBlockTime b))
[] []
insert_ $ insert_ $
WalletTrNote WalletTrNote
@ -1042,12 +1077,15 @@ getShieldedOutputs pool b net =
PS.retryOnBusy $ PS.retryOnBusy $
flip PS.runSqlPool pool $ do flip PS.runSqlPool pool $ do
select $ do select $ do
(txs :& sOutputs) <- (blks :& txs :& sOutputs) <-
from $ table @ZcashTransaction `innerJoin` table @ShieldOutput `on` from $ table @ZcashBlock `innerJoin` table @ZcashTransaction `on`
(\(txs :& sOutputs) -> (\(blks :& txs) ->
blks ^. ZcashBlockId ==. txs ^. ZcashTransactionBlockId) `innerJoin`
table @ShieldOutput `on`
(\(_ :& txs :& sOutputs) ->
txs ^. ZcashTransactionId ==. sOutputs ^. ShieldOutputTx) txs ^. ZcashTransactionId ==. sOutputs ^. ShieldOutputTx)
where_ (txs ^. ZcashTransactionBlock >=. val b) where_ (blks ^. ZcashBlockHeight >=. val b)
where_ (txs ^. ZcashTransactionNetwork ==. val net) where_ (blks ^. ZcashBlockNetwork ==. val net)
orderBy orderBy
[ asc $ txs ^. ZcashTransactionId [ asc $ txs ^. ZcashTransactionId
, asc $ sOutputs ^. ShieldOutputPosition , asc $ sOutputs ^. ShieldOutputPosition
@ -1065,12 +1103,15 @@ getOrchardActions pool b net =
PS.retryOnBusy $ PS.retryOnBusy $
flip PS.runSqlPool pool $ do flip PS.runSqlPool pool $ do
select $ do select $ do
(txs :& oActions) <- (blks :& txs :& oActions) <-
from $ table @ZcashTransaction `innerJoin` table @OrchAction `on` from $ table @ZcashBlock `innerJoin` table @ZcashTransaction `on`
(\(txs :& oActions) -> (\(blks :& txs) ->
blks ^. ZcashBlockId ==. txs ^. ZcashTransactionBlockId) `innerJoin`
table @OrchAction `on`
(\(_ :& txs :& oActions) ->
txs ^. ZcashTransactionId ==. oActions ^. OrchActionTx) txs ^. ZcashTransactionId ==. oActions ^. OrchActionTx)
where_ (txs ^. ZcashTransactionBlock >=. val b) where_ (blks ^. ZcashBlockHeight >=. val b)
where_ (txs ^. ZcashTransactionNetwork ==. val net) where_ (blks ^. ZcashBlockNetwork ==. val net)
orderBy orderBy
[asc $ txs ^. ZcashTransactionId, asc $ oActions ^. OrchActionPosition] [asc $ txs ^. ZcashTransactionId, asc $ oActions ^. OrchActionPosition]
pure (txs, oActions) pure (txs, oActions)
@ -1570,15 +1611,29 @@ getOrchardCmxs pool zt = do
getMaxOrchardNote :: Pool SqlBackend -> IO OrchActionId getMaxOrchardNote :: Pool SqlBackend -> IO OrchActionId
getMaxOrchardNote pool = do getMaxOrchardNote pool = do
flip PS.runSqlPool pool $ do flip PS.runSqlPool pool $ do
x <- maxBlock <-
selectOne $ do selectOne $ do
n <- from $ table @OrchAction blks <- from $ table @ZcashBlock
where_ (n ^. OrchActionId >. val (toSqlKey 0)) where_ $ blks ^. ZcashBlockHeight >. val 0
orderBy [desc $ n ^. OrchActionId] pure $ blks ^. ZcashBlockHeight
pure (n ^. OrchActionId) case maxBlock of
case x of
Nothing -> return $ toSqlKey 0 Nothing -> return $ toSqlKey 0
Just (Value y) -> return y Just (Value mb) -> do
x <-
selectOne $ do
(blks :& txs :& n) <-
from $ table @ZcashBlock `innerJoin` table @ZcashTransaction `on`
(\(blks :& txs) ->
blks ^. ZcashBlockId ==. txs ^. ZcashTransactionBlockId) `innerJoin`
table @OrchAction `on`
(\(_ :& txs :& n) ->
txs ^. ZcashTransactionId ==. n ^. OrchActionTx)
where_ (blks ^. ZcashBlockHeight <=. val (mb - 5))
orderBy [desc $ n ^. OrchActionId]
pure (n ^. OrchActionId)
case x of
Nothing -> return $ toSqlKey 0
Just (Value y) -> return y
updateOrchNoteRecord :: updateOrchNoteRecord ::
Pool SqlBackend Pool SqlBackend
@ -1640,15 +1695,23 @@ upsertWalTx ::
=> ZcashTransaction => ZcashTransaction
-> ZcashAccountId -> ZcashAccountId
-> SqlPersistT m (Entity WalletTransaction) -> SqlPersistT m (Entity WalletTransaction)
upsertWalTx zt za = upsertWalTx zt za = do
upsert blk <-
(WalletTransaction selectOne $ do
(zcashTransactionTxId zt) blks <- from $ table @ZcashBlock
za where_ (blks ^. ZcashBlockId ==. val (zcashTransactionBlockId zt))
(zcashTransactionBlock zt) pure blks
(zcashTransactionConf zt) case blk of
(zcashTransactionTime zt)) Nothing -> throw $ userError "Invalid block for transaction"
[] Just b ->
upsert
(WalletTransaction
(zcashTransactionTxId zt)
za
(zcashBlockHeight $ entityVal b)
(zcashBlockConf $ entityVal b)
(zcashBlockTime $ entityVal b))
[]
getSaplingOutIndex :: ConnectionPool -> ShieldOutputId -> IO Int getSaplingOutIndex :: ConnectionPool -> ShieldOutputId -> IO Int
getSaplingOutIndex pool i = do getSaplingOutIndex pool i = do
@ -1801,6 +1864,9 @@ clearWalletData pool = do
delete $ do delete $ do
_ <- from $ table @ZcashTransaction _ <- from $ table @ZcashTransaction
return () return ()
delete $ do
_ <- from $ table @ZcashBlock
return ()
getWalletUnspentTrNotes :: getWalletUnspentTrNotes ::
ConnectionPool -> ZcashAccountId -> IO [Entity WalletTrNote] ConnectionPool -> ZcashAccountId -> IO [Entity WalletTrNote]
@ -1982,6 +2048,130 @@ selectUnspentNotes pool za amt = do
, n : snd (checkOrchard (x - walletOrchNoteValue (entityVal n)) ns)) , n : snd (checkOrchard (x - walletOrchNoteValue (entityVal n)) ns))
else (0, [n]) else (0, [n])
selectUnspentNotesV2 ::
ConnectionPool
-> ZcashAccountId
-> Integer
-> Int
-> PrivacyPolicy
-> IO
(Either
TxError
( [Entity WalletTrNote]
, [Entity WalletSapNote]
, [Entity WalletOrchNote]))
selectUnspentNotesV2 pool za amt recv policy = do
case policy of
Full ->
case recv of
4 -> do
orchNotes <- getWalletUnspentOrchNotes pool za
let (a1, oList) = checkOrchard (fromIntegral amt) orchNotes
if a1 > 0
then return $
Left $ PrivacyPolicyError "Not enough notes for Full privacy"
else return $ Right ([], [], oList)
3 -> do
sapNotes <- getWalletUnspentSapNotes pool za
let (a2, sList) = checkSapling (fromIntegral amt) sapNotes
if a2 > 0
then return $
Left $ PrivacyPolicyError "Not enough notes for Full privacy"
else return $ Right ([], sList, [])
_anyOther ->
return $
Left $ PrivacyPolicyError "Receiver not capable of Full privacy"
Medium ->
if recv > 2
then do
orchNotes <- getWalletUnspentOrchNotes pool za
let (a1, oList) = checkOrchard (fromIntegral amt) orchNotes
if a1 > 0
then do
sapNotes <- getWalletUnspentSapNotes pool za
let (a2, sList) = checkSapling a1 sapNotes
if a2 > 0
then return $
Left $
PrivacyPolicyError "Not enough notes for Medium privacy"
else return $ Right ([], sList, oList)
else return $ Right ([], [], oList)
else return $
Left $ PrivacyPolicyError "Receiver not capable of Medium privacy"
Low ->
if recv == 0
then return $ Left ZHError
else do
case recv of
3 -> do
sapNotes <- getWalletUnspentSapNotes pool za
let (a1, sList) = checkSapling (fromIntegral amt) sapNotes
if a1 > 0
then do
orchNotes <- getWalletUnspentOrchNotes pool za
let (a2, oList) = checkOrchard a1 orchNotes
if a2 > 0
then return $
Left $
PrivacyPolicyError "Not enough notes for Low privacy"
else return $ Right ([], sList, oList)
else return $ Right ([], sList, [])
_anyOther -> do
orchNotes <- getWalletUnspentOrchNotes pool za
let (a1, oList) = checkOrchard (fromIntegral amt) orchNotes
if a1 > 0
then do
sapNotes <- getWalletUnspentSapNotes pool za
let (a2, sList) = checkSapling a1 sapNotes
if a2 > 0
then return $
Left $
PrivacyPolicyError "Not enough notes for Low privacy"
else return $ Right ([], sList, oList)
else return $ Right ([], [], oList)
None -> do
orchNotes <- getWalletUnspentOrchNotes pool za
let (a1, oList) = checkOrchard (fromIntegral amt) orchNotes
if a1 > 0
then do
sapNotes <- getWalletUnspentSapNotes pool za
let (a2, sList) = checkSapling a1 sapNotes
if a2 > 0
then do
trNotes <- getWalletUnspentTrNotes pool za
let (a3, tList) = checkTransparent a2 trNotes
if a3 > 0
then return $ Left InsufficientFunds
else return $ Right (tList, sList, oList)
else return $ Right ([], sList, oList)
else return $ Right ([], [], oList)
where
checkTransparent ::
Int64 -> [Entity WalletTrNote] -> (Int64, [Entity WalletTrNote])
checkTransparent x [] = (x, [])
checkTransparent x (n:ns) =
if walletTrNoteValue (entityVal n) < x
then ( fst (checkTransparent (x - walletTrNoteValue (entityVal n)) ns)
, n :
snd (checkTransparent (x - walletTrNoteValue (entityVal n)) ns))
else (0, [n])
checkSapling ::
Int64 -> [Entity WalletSapNote] -> (Int64, [Entity WalletSapNote])
checkSapling x [] = (x, [])
checkSapling x (n:ns) =
if walletSapNoteValue (entityVal n) < x
then ( fst (checkSapling (x - walletSapNoteValue (entityVal n)) ns)
, n : snd (checkSapling (x - walletSapNoteValue (entityVal n)) ns))
else (0, [n])
checkOrchard ::
Int64 -> [Entity WalletOrchNote] -> (Int64, [Entity WalletOrchNote])
checkOrchard x [] = (x, [])
checkOrchard x (n:ns) =
if walletOrchNoteValue (entityVal n) < x
then ( fst (checkOrchard (x - walletOrchNoteValue (entityVal n)) ns)
, n : snd (checkOrchard (x - walletOrchNoteValue (entityVal n)) ns))
else (0, [n])
getWalletTxId :: getWalletTxId ::
ConnectionPool -> WalletTransactionId -> IO (Maybe (Value HexStringDB)) ConnectionPool -> WalletTransactionId -> IO (Maybe (Value HexStringDB))
getWalletTxId pool wId = do getWalletTxId pool wId = do
@ -2013,6 +2203,9 @@ saveConfs pool b c = do
update $ \t -> do update $ \t -> do
set t [WalletTransactionConf =. val c] set t [WalletTransactionConf =. val c]
where_ $ t ^. WalletTransactionBlock ==. val b where_ $ t ^. WalletTransactionBlock ==. val b
update $ \bl -> do
set bl [ZcashBlockConf =. val c]
where_ $ bl ^. ZcashBlockHeight ==. val b
-- | Helper function to extract a Unified Address from the database -- | Helper function to extract a Unified Address from the database
readUnifiedAddressDB :: WalletAddress -> Maybe UnifiedAddress readUnifiedAddressDB :: WalletAddress -> Maybe UnifiedAddress
@ -2105,3 +2298,14 @@ finalizeOperation pool op status result = do
, OperationResult =. val (Just result) , OperationResult =. val (Just result)
] ]
where_ (ops ^. OperationId ==. val op) where_ (ops ^. OperationId ==. val op)
-- | Rewind the data store to a given block height
rewindWalletData :: ConnectionPool -> Int -> IO ()
rewindWalletData pool b = do
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $
delete $ do
blk <- from $ table @ZcashBlock
where_ $ blk ^. ZcashBlockHeight >=. val b
clearWalletTransactions pool

View file

@ -10,7 +10,7 @@ import Codec.QRCode
import Codec.QRCode.JuicyPixels import Codec.QRCode.JuicyPixels
import Control.Concurrent (threadDelay) import Control.Concurrent (threadDelay)
import Control.Exception (throwIO, try) import Control.Exception (throwIO, try)
import Control.Monad (when) import Control.Monad (unless, when)
import Control.Monad.IO.Class (liftIO) import Control.Monad.IO.Class (liftIO)
import Control.Monad.Logger (runFileLoggingT, runNoLoggingT) import Control.Monad.Logger (runFileLoggingT, runNoLoggingT)
import Data.Aeson import Data.Aeson
@ -47,20 +47,19 @@ import ZcashHaskell.Utils (getBlockTime, makeZebraCall)
import Zenith.Core import Zenith.Core
import Zenith.DB import Zenith.DB
import Zenith.GUI.Theme import Zenith.GUI.Theme
import Zenith.Scanner (processTx, rescanZebra, updateConfs) import Zenith.Scanner (checkIntegrity, processTx, rescanZebra, updateConfs)
import Zenith.Types hiding (ZcashAddress(..)) import Zenith.Types hiding (ZcashAddress(..))
import Zenith.Utils import Zenith.Utils
( displayAmount ( displayAmount
, getZenithPath
, isEmpty
, isRecipientValid , isRecipientValid
, isRecipientValidGUI
, isZecAddressValid
, isValidString , isValidString
, jsonNumber , jsonNumber
, padWithZero , padWithZero
, parseAddress , parseAddressUA
, showAddress , showAddress
, validBarValue , validBarValue
, validateAddressBool
) )
data AppEvent data AppEvent
@ -116,7 +115,6 @@ data AppEvent
| CheckValidAddress !T.Text | CheckValidAddress !T.Text
| CheckValidDescrip !T.Text | CheckValidDescrip !T.Text
| SaveNewABEntry | SaveNewABEntry
| SaveABDescription !T.Text
| UpdateABEntry !T.Text !T.Text | UpdateABEntry !T.Text !T.Text
| CloseUpdABEntry | CloseUpdABEntry
| ShowMessage !T.Text | ShowMessage !T.Text
@ -125,6 +123,7 @@ data AppEvent
| CopyABAdress !T.Text | CopyABAdress !T.Text
| DeleteABEntry !T.Text | DeleteABEntry !T.Text
| UpdateABDescrip !T.Text !T.Text | UpdateABDescrip !T.Text !T.Text
| ResetRecipientValid
deriving (Eq, Show) deriving (Eq, Show)
data AppModel = AppModel data AppModel = AppModel
@ -418,43 +417,43 @@ buildUI wenv model = widgetTree
[ vstack [ vstack
[ tooltip "Unified" $ [ tooltip "Unified" $
box_ box_
[onClick (SetPool Orchard)] [onClick (SetPool OrchardPool)]
(remixIcon remixShieldCheckFill `styleBasic` (remixIcon remixShieldCheckFill `styleBasic`
[ textSize 14 [ textSize 14
, padding 4 , padding 4
, styleIf , styleIf
(model ^. selPool == Orchard) (model ^. selPool == OrchardPool)
(bgColor btnColor) (bgColor btnColor)
, styleIf , styleIf
(model ^. selPool == Orchard) (model ^. selPool == OrchardPool)
(textColor white) (textColor white)
]) ])
, filler , filler
, tooltip "Legacy Shielded" $ , tooltip "Legacy Shielded" $
box_ box_
[onClick (SetPool Sapling)] [onClick (SetPool SaplingPool)]
(remixIcon remixShieldLine `styleBasic` (remixIcon remixShieldLine `styleBasic`
[ textSize 14 [ textSize 14
, padding 4 , padding 4
, styleIf , styleIf
(model ^. selPool == Sapling) (model ^. selPool == SaplingPool)
(bgColor btnColor) (bgColor btnColor)
, styleIf , styleIf
(model ^. selPool == Sapling) (model ^. selPool == SaplingPool)
(textColor white) (textColor white)
]) ])
, filler , filler
, tooltip "Transparent" $ , tooltip "Transparent" $
box_ box_
[onClick (SetPool Transparent)] [onClick (SetPool TransparentPool)]
(remixIcon remixEyeLine `styleBasic` (remixIcon remixEyeLine `styleBasic`
[ textSize 14 [ textSize 14
, padding 4 , padding 4
, styleIf , styleIf
(model ^. selPool == Transparent) (model ^. selPool == TransparentPool)
(bgColor btnColor) (bgColor btnColor)
, styleIf , styleIf
(model ^. selPool == Transparent) (model ^. selPool == TransparentPool)
(textColor white) (textColor white)
]) ])
] `styleBasic` ] `styleBasic`
@ -467,10 +466,10 @@ buildUI wenv model = widgetTree
(hstack (hstack
[ label [ label
(case model ^. selPool of (case model ^. selPool of
Orchard -> "Unified" OrchardPool -> "Unified"
Sapling -> "Legacy Shielded" SaplingPool -> "Legacy Shielded"
Transparent -> "Transparent" TransparentPool -> "Transparent"
Sprout -> "Unknown") `styleBasic` SproutPool -> "Unknown") `styleBasic`
[textColor white] [textColor white]
, remixIcon remixFileCopyFill `styleBasic` , remixIcon remixFileCopyFill `styleBasic`
[textSize 14, padding 4, textColor white] [textSize 14, padding 4, textColor white]
@ -606,7 +605,28 @@ buildUI wenv model = widgetTree
, separatorLine `styleBasic` [fgColor btnColor] , separatorLine `styleBasic` [fgColor btnColor]
, spacer , spacer
, hstack , hstack
[ label "To:" `styleBasic` [width 50] [
label "Privacy Level:" `styleBasic` [width 70, textFont "Bold"]
, spacer
, label "Full " `styleBasic` [width 40]
, radio Full privacyChoice
, spacer
, label "Medium " `styleBasic` [width 40]
, radio Medium privacyChoice
]
, hstack
[
label " " `styleBasic` [width 70, textFont "Bold"]
, spacer
, label "Low " `styleBasic` [width 40]
, radio Low privacyChoice
, spacer
, label "None " `styleBasic` [width 40]
, radio None privacyChoice
]
, spacer
, hstack
[ label "To:" `styleBasic` [width 50, textFont "Bold"]
, spacer , spacer
, textField_ sendRecipient [onChange CheckRecipient] `styleBasic` , textField_ sendRecipient [onChange CheckRecipient] `styleBasic`
[ width 150 [ width 150
@ -616,7 +636,7 @@ buildUI wenv model = widgetTree
] ]
] ]
, hstack , hstack
[ label "Amount:" `styleBasic` [width 50] [ label "Amount:" `styleBasic` [width 50, textFont "Bold"]
, spacer , spacer
, numericField_ , numericField_
sendAmount sendAmount
@ -634,35 +654,13 @@ buildUI wenv model = widgetTree
] ]
] ]
, hstack , hstack
[ label "Memo:" `styleBasic` [width 50] [ label "Memo:" `styleBasic` [width 50, textFont "Bold"]
, spacer , spacer
, textArea sendMemo `styleBasic` , textArea sendMemo `styleBasic`
[width 150, height 40] [width 150, height 40]
] ]
, spacer , spacer
-- Radio button group for privacy level -- Radio button group for privacy level
, hstack
[
label "Privacy Level:" `styleBasic` [width 70]
, spacer
, label "None " `styleBasic` [width 40]
, radio None privacyChoice
, spacer
, label "Low " `styleBasic` [width 40]
, radio Low privacyChoice
]
, hstack
[
label " " `styleBasic` [width 70]
, spacer
, label "Medium " `styleBasic` [width 40]
, radio Medium privacyChoice
, spacer
, label "Full " `styleBasic` [width 40]
, radio Full privacyChoice
]
, spacer
, box_ , box_
[alignMiddle] [alignMiddle]
(hstack (hstack
@ -972,9 +970,9 @@ generateQRCodes config = do
if not (null s) if not (null s)
then return () then return ()
else do else do
generateOneQr pool Orchard wAddr generateOneQr pool OrchardPool wAddr
generateOneQr pool Sapling wAddr generateOneQr pool SaplingPool wAddr
generateOneQr pool Transparent wAddr generateOneQr pool TransparentPool wAddr
generateOneQr :: generateOneQr ::
ConnectionPool -> ZcashPool -> Entity WalletAddress -> IO () ConnectionPool -> ZcashPool -> Entity WalletAddress -> IO ()
generateOneQr p zp wAddr = generateOneQr p zp wAddr =
@ -1009,7 +1007,7 @@ generateQRCodes config = do
dispAddr :: ZcashPool -> WalletAddress -> Maybe T.Text dispAddr :: ZcashPool -> WalletAddress -> Maybe T.Text
dispAddr zp w = dispAddr zp w =
case zp of case zp of
Transparent -> TransparentPool ->
T.append "zcash:" . T.append "zcash:" .
encodeTransparentReceiver encodeTransparentReceiver
(maybe (maybe
@ -1021,11 +1019,12 @@ generateQRCodes config = do
(t_rec =<< (t_rec =<<
(isValidUnifiedAddress . E.encodeUtf8 . getUA . walletAddressUAddress) (isValidUnifiedAddress . E.encodeUtf8 . getUA . walletAddressUAddress)
w) w)
Sapling -> SaplingPool ->
T.append "zcash:" <$> T.append "zcash:" <$>
(getSaplingFromUA . E.encodeUtf8 . getUA . walletAddressUAddress) w (getSaplingFromUA . E.encodeUtf8 . getUA . walletAddressUAddress) w
Orchard -> Just $ (T.append "zcash:" . getUA . walletAddressUAddress) w OrchardPool ->
Sprout -> Nothing Just $ (T.append "zcash:" . getUA . walletAddressUAddress) w
SproutPool -> Nothing
handleEvent :: handleEvent ::
WidgetEnv AppModel AppEvent WidgetEnv AppModel AppEvent
@ -1080,7 +1079,7 @@ handleEvent wenv node model evt =
] ]
ConfirmCancel -> [Model $ model & confirmTitle .~ Nothing & mainInput .~ ""] ConfirmCancel -> [Model $ model & confirmTitle .~ Nothing & mainInput .~ ""]
ShowSeed -> [Model $ model & showSeed .~ True & menuPopup .~ False] ShowSeed -> [Model $ model & showSeed .~ True & menuPopup .~ False]
ShowSend -> [Model $ model & openSend .~ True] ShowSend -> [Model $ model & openSend .~ True & privacyChoice .~ Full & recipientValid .~ False]
SendTx -> SendTx ->
case currentAccount of case currentAccount of
Nothing -> [Event $ ShowError "No account available", Event CancelSend] Nothing -> [Event $ ShowError "No account available", Event CancelSend]
@ -1139,7 +1138,7 @@ handleEvent wenv node model evt =
Just wAddr -> getUserTx dbPool $ entityKey wAddr Just wAddr -> getUserTx dbPool $ entityKey wAddr
] ]
SwitchQr q -> [Model $ model & qrCodeWidget .~ q] SwitchQr q -> [Model $ model & qrCodeWidget .~ q]
SwitchAddr i -> [Model $ model & selAddr .~ i, Event $ SetPool Orchard] SwitchAddr i -> [Model $ model & selAddr .~ i, Event $ SetPool OrchardPool]
SwitchAcc i -> SwitchAcc i ->
[ Model $ model & selAcc .~ i [ Model $ model & selAcc .~ i
, Task $ , Task $
@ -1157,7 +1156,7 @@ handleEvent wenv node model evt =
b <- getBalance dbPool $ entityKey acc b <- getBalance dbPool $ entityKey acc
u <- getUnconfirmedBalance dbPool $ entityKey acc u <- getUnconfirmedBalance dbPool $ entityKey acc
return (b, u) return (b, u)
, Event $ SetPool Orchard , Event $ SetPool OrchardPool
] ]
SwitchWal i -> SwitchWal i ->
[ Model $ model & selWallet .~ i & selAcc .~ 0 & selAddr .~ 0 [ Model $ model & selWallet .~ i & selAcc .~ 0 & selAddr .~ 0
@ -1180,14 +1179,15 @@ handleEvent wenv node model evt =
, setClipboardData $ , setClipboardData $
ClipboardText $ ClipboardText $
case model ^. selPool of case model ^. selPool of
Orchard -> maybe "None" (getUA . walletAddressUAddress . entityVal) a OrchardPool ->
Sapling -> maybe "None" (getUA . walletAddressUAddress . entityVal) a
SaplingPool ->
fromMaybe "None" $ fromMaybe "None" $
(getSaplingFromUA . (getSaplingFromUA .
E.encodeUtf8 . getUA . walletAddressUAddress . entityVal) =<< E.encodeUtf8 . getUA . walletAddressUAddress . entityVal) =<<
a a
Sprout -> "None" SproutPool -> "None"
Transparent -> TransparentPool ->
maybe "None" (encodeTransparentReceiver (model ^. network)) $ maybe "None" (encodeTransparentReceiver (model ^. network)) $
t_rec =<< t_rec =<<
(isValidUnifiedAddress . (isValidUnifiedAddress .
@ -1210,7 +1210,7 @@ handleEvent wenv node model evt =
if not (null a) if not (null a)
then [ Model $ model & addresses .~ a then [ Model $ model & addresses .~ a
, Event $ SwitchAddr $ model ^. selAddr , Event $ SwitchAddr $ model ^. selAddr
, Event $ SetPool Orchard , Event $ SetPool OrchardPool
] ]
else [Event $ NewAddress currentAccount] else [Event $ NewAddress currentAccount]
LoadAccs a -> LoadAccs a ->
@ -1248,7 +1248,8 @@ handleEvent wenv node model evt =
case currentWallet of case currentWallet of
Nothing -> return $ ShowError "No wallet available" Nothing -> return $ ShowError "No wallet available"
Just cW -> do Just cW -> do
syncWallet (model ^. configuration) cW runFileLoggingT "zenith.log" $
syncWallet (model ^. configuration) cW
pool <- pool <-
runNoLoggingT $ runNoLoggingT $
initPool $ c_dbPath $ model ^. configuration initPool $ c_dbPath $ model ^. configuration
@ -1261,7 +1262,10 @@ handleEvent wenv node model evt =
("Wallet Sync: " <> ("Wallet Sync: " <>
T.pack (printf "%.2f%%" (model ^. barValue * 100))) T.pack (printf "%.2f%%" (model ^. barValue * 100)))
] ]
CheckRecipient a -> [Model $ model & recipientValid .~ isRecipientValid a] ResetRecipientValid -> [Model $ model & recipientValid .~ False]
CheckRecipient a -> [Model $
model & recipientValid .~ isRecipientValidGUI (model ^.privacyChoice) a ]
-- model & recipientValid .~ ((model ^. privacyChoice) == Low) ]
CheckAmount i -> CheckAmount i ->
[ Model $ [ Model $
model & amountValid .~ model & amountValid .~
@ -1272,7 +1276,7 @@ handleEvent wenv node model evt =
-- | Address Book Events -- | Address Book Events
-- | -- |
CheckValidAddress a -> CheckValidAddress a ->
[Model $ model & abAddressValid .~ isRecipientValid a] [Model $ model & abAddressValid .~ isZecAddressValid a]
CheckValidDescrip a -> [Model $ model & abDescripValid .~ isValidString a] CheckValidDescrip a -> [Model $ model & abDescripValid .~ isValidString a]
ShowAdrBook -> ShowAdrBook ->
if null (model ^. abaddressList) if null (model ^. abaddressList)
@ -1467,20 +1471,25 @@ scanZebra dbPath zHost zPort net sendMsg = do
pool <- runNoLoggingT $ initPool dbPath pool <- runNoLoggingT $ initPool dbPath
b <- liftIO $ getMinBirthdayHeight pool b <- liftIO $ getMinBirthdayHeight pool
dbBlock <- getMaxBlock pool $ ZcashNetDB net dbBlock <- getMaxBlock pool $ ZcashNetDB net
let sb = max dbBlock b chkBlock <- checkIntegrity dbPath zHost zPort dbBlock 1
unless (chkBlock == dbBlock) $ rewindWalletData pool chkBlock
let sb =
if chkBlock == dbBlock
then max dbBlock b
else max chkBlock b
if sb > zgb_blocks bStatus || sb < 1
then sendMsg (ShowError "Invalid starting block for scan")
else do
let bList = [(sb + 1) .. (zgb_blocks bStatus)]
if not (null bList)
then do
let step = (1.0 :: Float) / fromIntegral (length bList)
mapM_ (processBlock pool step) bList
else sendMsg (SyncVal 1.0)
confUp <- try $ updateConfs zHost zPort pool :: IO (Either IOError ()) confUp <- try $ updateConfs zHost zPort pool :: IO (Either IOError ())
case confUp of case confUp of
Left _e0 -> sendMsg (ShowError "Failed to update unconfirmed transactions") Left _e0 -> sendMsg (ShowError "Failed to update unconfirmed transactions")
Right _ -> do Right _ -> return ()
if sb > zgb_blocks bStatus || sb < 1
then sendMsg (ShowError "Invalid starting block for scan")
else do
let bList = [(sb + 1) .. (zgb_blocks bStatus)]
if not (null bList)
then do
let step = (1.0 :: Float) / fromIntegral (length bList)
mapM_ (processBlock pool step) bList
else sendMsg (SyncVal 1.0)
where where
processBlock :: ConnectionPool -> Float -> Int -> IO () processBlock :: ConnectionPool -> Float -> Int -> IO ()
processBlock pool step bl = do processBlock pool step bl = do
@ -1505,16 +1514,16 @@ scanZebra dbPath zHost zPort net sendMsg = do
Left e2 -> sendMsg (ShowError $ showt e2) Left e2 -> sendMsg (ShowError $ showt e2)
Right hb -> do Right hb -> do
let blockTime = getBlockTime hb let blockTime = getBlockTime hb
mapM_ (processTx zHost zPort blockTime pool (ZcashNetDB net)) $ bi <-
bl_txs $ addTime blk blockTime saveBlock pool $
ZcashBlock
(fromIntegral $ bl_height blk)
(HexStringDB $ bl_hash blk)
(fromIntegral $ bl_confirmations blk)
blockTime
(ZcashNetDB net)
mapM_ (processTx zHost zPort bi pool) $ bl_txs blk
sendMsg (SyncVal step) sendMsg (SyncVal step)
addTime :: BlockResponse -> Int -> BlockResponse
addTime bl t =
BlockResponse
(bl_confirmations bl)
(bl_height bl)
(fromIntegral t)
(bl_txs bl)
sendTransaction :: sendTransaction ::
Config Config
@ -1528,7 +1537,7 @@ sendTransaction ::
-> IO () -> IO ()
sendTransaction config znet accId bl amt ua memo sendMsg = do sendTransaction config znet accId bl amt ua memo sendMsg = do
sendMsg $ ShowModal "Preparing transaction..." sendMsg $ ShowModal "Preparing transaction..."
case parseAddress ua znet of case parseAddressUA ua znet of
Nothing -> sendMsg $ ShowError "Incorrect address" Nothing -> sendMsg $ ShowError "Incorrect address"
Just outUA -> do Just outUA -> do
let dbPath = c_dbPath config let dbPath = c_dbPath config
@ -1606,7 +1615,8 @@ runZenithGUI config = do
else return [] else return []
qr <- qr <-
if not (null addrList) if not (null addrList)
then getQrCode pool Orchard $ entityKey $ head addrList then getQrCode pool OrchardPool $
entityKey $ head addrList
else return Nothing else return Nothing
bal <- bal <-
if not (null accList) if not (null accList)
@ -1635,7 +1645,7 @@ runZenithGUI config = do
(if unconfBal == 0 (if unconfBal == 0
then Nothing then Nothing
else Just unconfBal) else Just unconfBal)
Orchard OrchardPool
qr qr
False False
False False
@ -1673,7 +1683,7 @@ runZenithGUI config = do
Nothing Nothing
False False
False False
None Full
startApp model handleEvent buildUI (params hD) startApp model handleEvent buildUI (params hD)
Left _e -> print "Zebra not available" Left _e -> print "Zebra not available"
where where

View file

@ -6,7 +6,13 @@ import Control.Concurrent.Async (concurrently_, withAsync)
import Control.Exception (throwIO, try) import Control.Exception (throwIO, try)
import Control.Monad (when) import Control.Monad (when)
import Control.Monad.IO.Class (liftIO) import Control.Monad.IO.Class (liftIO)
import Control.Monad.Logger (NoLoggingT, logErrorN, logInfoN, runNoLoggingT) import Control.Monad.Logger
( NoLoggingT
, logErrorN
, logInfoN
, runFileLoggingT
, runNoLoggingT
)
import Data.Aeson import Data.Aeson
import Data.HexString import Data.HexString
import qualified Data.Text as T import qualified Data.Text as T
@ -27,19 +33,23 @@ import ZcashHaskell.Types
import ZcashHaskell.Utils (getBlockTime, makeZebraCall, readZebraTransaction) import ZcashHaskell.Utils (getBlockTime, makeZebraCall, readZebraTransaction)
import Zenith.Core (checkBlockChain, syncWallet) import Zenith.Core (checkBlockChain, syncWallet)
import Zenith.DB import Zenith.DB
( clearWalletData ( ZcashBlock(..)
, ZcashBlockId
, clearWalletData
, clearWalletTransactions , clearWalletTransactions
, getBlock
, getMaxBlock , getMaxBlock
, getMinBirthdayHeight , getMinBirthdayHeight
, getUnconfirmedBlocks , getUnconfirmedBlocks
, getWallets , getWallets
, initDb , initDb
, initPool , initPool
, saveBlock
, saveConfs , saveConfs
, saveTransaction , saveTransaction
, updateWalletSync , updateWalletSync
) )
import Zenith.Types (Config(..), ZcashNetDB(..)) import Zenith.Types (Config(..), HexStringDB(..), ZcashNetDB(..))
import Zenith.Utils (jsonNumber) import Zenith.Utils (jsonNumber)
-- | Function to scan the Zcash blockchain through the Zebra node and populate the Zenith database -- | Function to scan the Zcash blockchain through the Zebra node and populate the Zenith database
@ -57,8 +67,8 @@ rescanZebra host port dbFilePath = do
Right bStatus -> do Right bStatus -> do
let znet = ZcashNetDB $ zgb_net bStatus let znet = ZcashNetDB $ zgb_net bStatus
pool1 <- runNoLoggingT $ initPool dbFilePath pool1 <- runNoLoggingT $ initPool dbFilePath
pool2 <- runNoLoggingT $ initPool dbFilePath {-pool2 <- runNoLoggingT $ initPool dbFilePath-}
pool3 <- runNoLoggingT $ initPool dbFilePath {-pool3 <- runNoLoggingT $ initPool dbFilePath-}
clearWalletTransactions pool1 clearWalletTransactions pool1
clearWalletData pool1 clearWalletData pool1
dbBlock <- getMaxBlock pool1 znet dbBlock <- getMaxBlock pool1 znet
@ -119,28 +129,26 @@ processBlock host port pool pg net b = do
Left e2 -> liftIO $ throwIO $ userError e2 Left e2 -> liftIO $ throwIO $ userError e2
Right hb -> do Right hb -> do
let blockTime = getBlockTime hb let blockTime = getBlockTime hb
mapM_ (processTx host port blockTime pool net) $ bi <-
bl_txs $ addTime blk blockTime saveBlock pool $
ZcashBlock
(fromIntegral $ bl_height blk)
(HexStringDB $ bl_hash blk)
(fromIntegral $ bl_confirmations blk)
blockTime
net
mapM_ (processTx host port bi pool) $ bl_txs blk
liftIO $ tick pg liftIO $ tick pg
where
addTime :: BlockResponse -> Int -> BlockResponse
addTime bl t =
BlockResponse
(bl_confirmations bl)
(bl_height bl)
(fromIntegral t)
(bl_txs bl)
-- | Function to process a raw transaction -- | Function to process a raw transaction
processTx :: processTx ::
T.Text -- ^ Host name for `zebrad` T.Text -- ^ Host name for `zebrad`
-> Int -- ^ Port for `zebrad` -> Int -- ^ Port for `zebrad`
-> Int -- ^ Block time -> ZcashBlockId -- ^ Block ID
-> ConnectionPool -- ^ DB file path -> ConnectionPool -- ^ DB file path
-> ZcashNetDB -- ^ the network
-> HexString -- ^ transaction id -> HexString -- ^ transaction id
-> IO () -> IO ()
processTx host port bt pool net t = do processTx host port bt pool t = do
r <- r <-
liftIO $ liftIO $
makeZebraCall makeZebraCall
@ -156,7 +164,7 @@ processTx host port bt pool net t = do
Just rzt -> do Just rzt -> do
_ <- _ <-
runNoLoggingT $ runNoLoggingT $
saveTransaction pool bt net $ saveTransaction pool bt $
Transaction Transaction
t t
(ztr_blockheight rawTx) (ztr_blockheight rawTx)
@ -211,5 +219,35 @@ clearSync config = do
w <- getWallets pool $ zgb_net chainInfo w <- getWallets pool $ zgb_net chainInfo
liftIO $ mapM_ (updateWalletSync pool 0 . entityKey) w liftIO $ mapM_ (updateWalletSync pool 0 . entityKey) w
w' <- liftIO $ getWallets pool $ zgb_net chainInfo w' <- liftIO $ getWallets pool $ zgb_net chainInfo
r <- mapM (syncWallet config) w' r <- runFileLoggingT "zenith.log" $ mapM (syncWallet config) w'
liftIO $ print r liftIO $ print r
-- | Detect chain re-orgs
checkIntegrity ::
T.Text -- ^ Database path
-> T.Text -- ^ Zebra host
-> Int -- ^ Zebra port
-> Int -- ^ The block to start the check
-> Int -- ^ depth
-> IO Int
checkIntegrity dbP zHost zPort b d =
if b < 1
then return 1
else do
r <-
makeZebraCall
zHost
zPort
"getblock"
[Data.Aeson.String $ T.pack $ show b, jsonNumber 1]
case r of
Left e -> throwIO $ userError e
Right blk -> do
pool <- runNoLoggingT $ initPool dbP
dbBlk <- getBlock pool b
case dbBlk of
Nothing -> throwIO $ userError "Block mismatch, rescan needed"
Just dbBlk' ->
if bl_hash blk == getHex (zcashBlockHash $ entityVal dbBlk')
then return b
else checkIntegrity dbP zHost zPort (b - 5 * d) (d + 1)

View file

@ -104,10 +104,10 @@ data Config = Config
} deriving (Eq, Prelude.Show) } deriving (Eq, Prelude.Show)
data ZcashPool data ZcashPool
= Transparent = TransparentPool
| Sprout | SproutPool
| Sapling | SaplingPool
| Orchard | OrchardPool
deriving (Show, Read, Eq) deriving (Show, Read, Eq)
derivePersistField "ZcashPool" derivePersistField "ZcashPool"
@ -115,18 +115,18 @@ derivePersistField "ZcashPool"
instance ToJSON ZcashPool where instance ToJSON ZcashPool where
toJSON zp = toJSON zp =
case zp of case zp of
Transparent -> Data.Aeson.String "p2pkh" TransparentPool -> Data.Aeson.String "p2pkh"
Sprout -> Data.Aeson.String "sprout" SproutPool -> Data.Aeson.String "sprout"
Sapling -> Data.Aeson.String "sapling" SaplingPool -> Data.Aeson.String "sapling"
Orchard -> Data.Aeson.String "orchard" OrchardPool -> Data.Aeson.String "orchard"
instance FromJSON ZcashPool where instance FromJSON ZcashPool where
parseJSON = parseJSON =
withText "ZcashPool" $ \case withText "ZcashPool" $ \case
"p2pkh" -> return Transparent "p2pkh" -> return TransparentPool
"sprout" -> return Sprout "sprout" -> return SproutPool
"sapling" -> return Sapling "sapling" -> return SaplingPool
"orchard" -> return Orchard "orchard" -> return OrchardPool
_ -> fail "Not a known Zcash pool" _ -> fail "Not a known Zcash pool"
newtype ZenithUuid = ZenithUuid newtype ZenithUuid = ZenithUuid
@ -199,10 +199,10 @@ $(deriveJSON defaultOptions ''ZenithStatus)
derivePersistField "ZenithStatus" derivePersistField "ZenithStatus"
data PrivacyPolicy data PrivacyPolicy
= Full = None
| Medium
| Low | Low
| None | Medium
| Full
deriving (Eq, Show, Read, Ord) deriving (Eq, Show, Read, Ord)
$(deriveJSON defaultOptions ''PrivacyPolicy) $(deriveJSON defaultOptions ''PrivacyPolicy)
@ -298,7 +298,8 @@ instance FromJSON AddressGroup where
Nothing -> return [] Nothing -> return []
Just x -> do Just x -> do
x' <- x .:? "addresses" x' <- x .:? "addresses"
return $ maybe [] (map (ZcashAddress s1 [Transparent] Nothing)) x' return $
maybe [] (map (ZcashAddress s1 [TransparentPool] Nothing)) x'
processSapling k s2 = processSapling k s2 =
case k of case k of
Nothing -> return [] Nothing -> return []
@ -306,7 +307,7 @@ instance FromJSON AddressGroup where
where processOneSapling sx = where processOneSapling sx =
withObject "Sapling" $ \oS -> do withObject "Sapling" $ \oS -> do
oS' <- oS .: "addresses" oS' <- oS .: "addresses"
return $ map (ZcashAddress sx [Sapling] Nothing) oS' return $ map (ZcashAddress sx [SaplingPool] Nothing) oS'
processUnified u = processUnified u =
case u of case u of
Nothing -> return [] Nothing -> return []

View file

@ -3,17 +3,17 @@
module Zenith.Utils where module Zenith.Utils where
import Data.Aeson import Data.Aeson
import Data.Char (isAlphaNum, isSpace)
import Data.Functor (void) import Data.Functor (void)
import Data.Maybe import Data.Maybe
import Data.Ord (clamp) import Data.Ord (clamp)
import Data.Scientific (Scientific(..), scientific) 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.Char (isAlphaNum, isSpace)
import System.Directory import System.Directory
import System.Process (createProcess_, shell) import System.Process (createProcess_, shell)
import Text.Regex.Posix import Text.Regex.Posix
import ZcashHaskell.Orchard (encodeUnifiedAddress, isValidUnifiedAddress) import ZcashHaskell.Orchard (encodeUnifiedAddress, isValidUnifiedAddress, parseAddress)
import ZcashHaskell.Sapling (decodeSaplingAddress, isValidShieldedAddress) import ZcashHaskell.Sapling (decodeSaplingAddress, isValidShieldedAddress)
import ZcashHaskell.Transparent import ZcashHaskell.Transparent
( decodeExchangeAddress ( decodeExchangeAddress
@ -24,12 +24,15 @@ import ZcashHaskell.Types
, TransparentAddress(..) , TransparentAddress(..)
, UnifiedAddress(..) , UnifiedAddress(..)
, ZcashNet(..) , ZcashNet(..)
, ValidAddress(..)
, ExchangeAddress(..)
) )
import Zenith.Types import Zenith.Types
( AddressGroup(..) ( AddressGroup(..)
, UnifiedAddressDB(..) , UnifiedAddressDB(..)
, ZcashAddress(..) , ZcashAddress(..)
, ZcashPool(..) , ZcashPool(..)
, PrivacyPolicy(..)
) )
-- | Helper function to convert numbers into JSON -- | Helper function to convert numbers into JSON
@ -71,9 +74,9 @@ getAddresses ag = agtransparent ag <> agsapling ag <> agunified ag
-- | Helper function to validate potential Zcash addresses -- | Helper function to validate potential Zcash addresses
validateAddress :: T.Text -> Maybe ZcashPool validateAddress :: T.Text -> Maybe ZcashPool
validateAddress txt --(tReg || sReg && isJust chk) || (uReg && isJust chk) validateAddress txt --(tReg || sReg && isJust chk) || (uReg && isJust chk)
| tReg = Just Transparent | tReg = Just TransparentPool
| sReg && chkS = Just Sapling | sReg && chkS = Just SaplingPool
| uReg && chk = Just Orchard | uReg && chk = Just OrchardPool
| otherwise = Nothing | otherwise = Nothing
where where
transparentRegex = "^t1[a-zA-Z0-9]{33}$" :: String transparentRegex = "^t1[a-zA-Z0-9]{33}$" :: String
@ -110,7 +113,7 @@ validBarValue :: Float -> Float
validBarValue = clamp (0, 1) validBarValue = clamp (0, 1)
isRecipientValid :: T.Text -> Bool isRecipientValid :: T.Text -> Bool
isRecipientValid a = isRecipientValid a = do
case isValidUnifiedAddress (E.encodeUtf8 a) of case isValidUnifiedAddress (E.encodeUtf8 a) of
Just _a1 -> True Just _a1 -> True
Nothing -> Nothing ->
@ -122,8 +125,78 @@ isRecipientValid a =
Just _a4 -> True Just _a4 -> True
Nothing -> False) Nothing -> False)
parseAddress :: T.Text -> ZcashNet -> Maybe UnifiedAddress isUnifiedAddressValid :: T.Text -> Bool
parseAddress a znet = isUnifiedAddressValid ua =
case isValidUnifiedAddress (E.encodeUtf8 ua) of
Just _a1 -> True
Nothing -> False
isSaplingAddressValid :: T.Text -> Bool
isSaplingAddressValid sa = isValidShieldedAddress (E.encodeUtf8 sa)
isTransparentAddressValid :: T.Text -> Bool
isTransparentAddressValid ta =
case decodeTransparentAddress (E.encodeUtf8 ta) of
Just _a3 -> True
Nothing -> False
isExchangeAddressValid :: T.Text -> Bool
isExchangeAddressValid xa =
case decodeExchangeAddress (E.encodeUtf8 xa) of
Just _a4 -> True
Nothing -> False
isRecipientValidGUI :: PrivacyPolicy -> T.Text -> Bool
isRecipientValidGUI p a = do
let adr = parseAddress (E.encodeUtf8 a)
case p of
Full -> case adr of
Just a ->
case a of
Unified ua -> True
Sapling sa -> True
_ -> False
Nothing -> False
Medium -> case adr of
Just a ->
case a of
Unified ua -> True
Sapling sa -> True
_ -> False
Nothing -> False
Low -> case adr of
Just a ->
case a of
Unified ua -> True
Sapling sa -> True
Transparent ta -> True
Exchange ea -> True
_ -> False
Nothing -> False
None -> case adr of
Just a ->
case a of
Transparent ta -> True
Exchange ea -> True
_ -> False
Nothing -> False
isZecAddressValid :: T.Text -> Bool
isZecAddressValid a = do
case isValidUnifiedAddress (E.encodeUtf8 a) of
Just _a1 -> True
Nothing ->
isValidShieldedAddress (E.encodeUtf8 a) ||
(case decodeTransparentAddress (E.encodeUtf8 a) of
Just _a3 -> True
Nothing ->
case decodeExchangeAddress (E.encodeUtf8 a) of
Just _a4 -> True
Nothing -> False)
parseAddressUA :: T.Text -> ZcashNet -> Maybe UnifiedAddress
parseAddressUA a znet =
case isValidUnifiedAddress (E.encodeUtf8 a) of case isValidUnifiedAddress (E.encodeUtf8 a) of
Just a1 -> Just a1 Just a1 -> Just a1
Nothing -> Nothing ->
@ -137,16 +210,16 @@ parseAddress a znet =
Nothing -> Nothing Nothing -> Nothing
isValidContent :: String -> Bool isValidContent :: String -> Bool
isValidContent [] = False -- an empty string is invalid isValidContent [] = False -- an empty string is invalid
isValidContent (x:xs) isValidContent (x:xs)
| not (isAlphaNum x ) = False -- string must start with an alphanumeric character | not (isAlphaNum x) = False -- string must start with an alphanumeric character
| otherwise = allValidChars xs -- process the rest of the string | otherwise = allValidChars xs -- process the rest of the string
where where
allValidChars :: String -> Bool allValidChars :: String -> Bool
allValidChars [] = True -- if we got here, string is valid allValidChars [] = True -- if we got here, string is valid
allValidChars (y:ys) allValidChars (y:ys)
| isAlphaNum y || isSpace y = allValidChars ys -- char is valid, continue | isAlphaNum y || isSpace y = allValidChars ys -- char is valid, continue
| otherwise = False -- found an invalid character, return false | otherwise = False -- found an invalid character, return false
isValidString :: T.Text -> Bool isValidString :: T.Text -> Bool
isValidString c = do isValidString c = do
@ -155,10 +228,9 @@ 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
isEmpty [] = True isEmpty [] = True
isEmpty _ = False isEmpty _ = False

View file

@ -123,9 +123,10 @@ sendTx user pwd fromAddy toAddy amount memo = do
if source fromAddy /= ImportedWatchOnly if source fromAddy /= ImportedWatchOnly
then do then do
let privacyPolicy let privacyPolicy
| valAdd == Just Transparent = "AllowRevealedRecipients" | valAdd == Just TransparentPool = "AllowRevealedRecipients"
| isNothing (account fromAddy) && | isNothing (account fromAddy) &&
elem Transparent (pool fromAddy) = "AllowRevealedSenders" elem TransparentPool (pool fromAddy) =
"AllowRevealedSenders"
| otherwise = "AllowRevealedAmounts" | otherwise = "AllowRevealedAmounts"
let pd = let pd =
case memo of case memo of
@ -301,7 +302,7 @@ sendWithUri user pwd fromAddy uri repTo = do
let addType = validateAddress $ T.pack parsedAddress let addType = validateAddress $ T.pack parsedAddress
case addType of case addType of
Nothing -> putStrLn " Invalid address" Nothing -> putStrLn " Invalid address"
Just Transparent -> do Just TransparentPool -> do
putStrLn $ " Address is valid: " ++ parsedAddress putStrLn $ " Address is valid: " ++ parsedAddress
case (readMaybe parsedAmount :: Maybe Double) of case (readMaybe parsedAmount :: Maybe Double) of
Nothing -> putStrLn " Invalid amount." Nothing -> putStrLn " Invalid amount."

View file

@ -1,15 +1,16 @@
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
import Control.Monad (when) import Control.Monad (when)
import Control.Monad.Logger (runNoLoggingT) import Control.Monad.Logger (runFileLoggingT, runNoLoggingT)
import Data.HexString import Data.HexString
import Data.Maybe (fromJust)
import qualified Data.Text.Encoding as E import qualified Data.Text.Encoding as E
import Database.Persist import Database.Persist
import Database.Persist.Sqlite import Database.Persist.Sqlite
import System.Directory import System.Directory
import Test.HUnit import Test.HUnit
import Test.Hspec import Test.Hspec
import ZcashHaskell.Orchard (isValidUnifiedAddress) import ZcashHaskell.Orchard (isValidUnifiedAddress, parseAddress)
import ZcashHaskell.Sapling import ZcashHaskell.Sapling
( decodeSaplingOutputEsk ( decodeSaplingOutputEsk
, encodeSaplingAddress , encodeSaplingAddress
@ -172,15 +173,15 @@ main = do
"6c5d1413c63a9a88db71c3f41dc12cd60197ee742fc75b217215e7144db48bd3" "6c5d1413c63a9a88db71c3f41dc12cd60197ee742fc75b217215e7144db48bd3"
describe "Note selection for Tx" $ do describe "Note selection for Tx" $ do
it "Value less than balance" $ do it "Value less than balance" $ do
pool <- runNoLoggingT $ initPool "zenith.db" pool <- runNoLoggingT $ initPool "/home/rav/Zenith/zenith.db"
res <- selectUnspentNotes pool (toSqlKey 1) 14000000 res <- selectUnspentNotes pool (toSqlKey 1) 14000000
res `shouldNotBe` ([], [], []) res `shouldNotBe` ([], [], [])
it "Value greater than balance" $ do it "Value greater than balance" $ do
pool <- runNoLoggingT $ initPool "zenith.db" pool <- runNoLoggingT $ initPool "/home/rav/Zenith/zenith.db"
let res = selectUnspentNotes pool (toSqlKey 1) 84000000 let res = selectUnspentNotes pool (toSqlKey 1) 84000000
res `shouldThrow` anyIOException res `shouldThrow` anyIOException
it "Fee calculation" $ do it "Fee calculation" $ do
pool <- runNoLoggingT $ initPool "zenith.db" pool <- runNoLoggingT $ initPool "/home/rav/Zenith/zenith.db"
res <- selectUnspentNotes pool (toSqlKey 1) 14000000 res <- selectUnspentNotes pool (toSqlKey 1) 14000000
calculateTxFee res 3 `shouldBe` 20000 calculateTxFee res 3 `shouldBe` 20000
describe "Testing validation" $ do describe "Testing validation" $ do
@ -209,7 +210,7 @@ main = do
(case decodeTransparentAddress (E.encodeUtf8 a) of (case decodeTransparentAddress (E.encodeUtf8 a) of
Just _a3 -> True Just _a3 -> True
Nothing -> Nothing ->
case decodeExchangeAddress (En.encodeUtf8 a) of case decodeExchangeAddress (E.encodeUtf8 a) of
Just _a4 -> True Just _a4 -> True
Nothing -> False)) Nothing -> False))
it "Transparent" $ do it "Transparent" $ do
@ -233,21 +234,115 @@ main = do
a `shouldBe` a `shouldBe`
Just Just
"ztestsapling1tgjr4zppwk4ne8xy6gdq4z2gwq7dmf5jq8z2ctpn8nlmtse0a74fa5z0m8z383gmpgqz6q6duu4" "ztestsapling1tgjr4zppwk4ne8xy6gdq4z2gwq7dmf5jq8z2ctpn8nlmtse0a74fa5z0m8z383gmpgqz6q6duu4"
{-describe "Creating Tx" $ do-} describe "Notes" $ do
{-xit "To Orchard" $ do-} it "Check Orchard notes" $ do
{-let uaRead =-} pool <- runNoLoggingT $ initPool "/home/rav/Zenith/zenith.db"
{-isValidUnifiedAddress-} oNotes <- getWalletUnspentOrchNotes pool (toSqlKey 1)
{-"utest1dl54utt6prjj5e0dnlknwumnxq9hycdjpkfr0sy6e6h522remqee8axe9zax0wsjrwpj76v555pdhvj9rnargpfyycs0vpkapq98xcdhem99gc4wchzn0ggepq3y6nz3a9sscwgqxgsh9vzhcad402y3x9szfregck5gslkya3c79d86xx0l33tpk8gnn7ew9vw37w43zh22u8dgdax"-} oNotes `shouldBe` []
{-case uaRead of-} it "Check Sapling notes" $ do
{-Nothing -> assertFailure "wrong address"-} pool <- runNoLoggingT $ initPool "/home/rav/Zenith/zenith.db"
{-Just ua -> do-} oNotes <- getWalletUnspentSapNotes pool (toSqlKey 4)
{-tx <--} oNotes `shouldBe` []
{-prepareTx-} it "Check transparent notes" $ do
{-"zenith.db"-} pool <- runNoLoggingT $ initPool "/home/rav/Zenith/zenith.db"
{-TestNet-} oNotes <- getWalletUnspentTrNotes pool (toSqlKey 1)
{-(toSqlKey 1)-} oNotes `shouldBe` []
{-2819811-} describe "Creating Tx" $ do
{-0.04-} describe "Full" $ do
{-ua-} it "To Orchard" $ do
{-"sent with Zenith, test"-} let uaRead =
{-tx `shouldBe` Right (hexString "deadbeef")-} parseAddress
"utest1dl54utt6prjj5e0dnlknwumnxq9hycdjpkfr0sy6e6h522remqee8axe9zax0wsjrwpj76v555pdhvj9rnargpfyycs0vpkapq98xcdhem99gc4wchzn0ggepq3y6nz3a9sscwgqxgsh9vzhcad402y3x9szfregck5gslkya3c79d86xx0l33tpk8gnn7ew9vw37w43zh22u8dgdax"
case uaRead of
Nothing -> assertFailure "wrong address"
Just ua -> do
pool <- runNoLoggingT $ initPool "/home/rav/Zenith/zenith.db"
tx <-
runFileLoggingT "zenith.log" $
prepareTxV2
pool
"localhost"
18232
TestNet
(toSqlKey 1)
3001230
0.005
(fromJust uaRead)
"Sending memo to orchard"
Full
case tx of
Left e -> assertFailure $ show e
Right h -> h `shouldNotBe` (hexString "deadbeef")
it "To Sapling" $ do
let uaRead =
parseAddress
"ztestsapling136jp8z89v2jh6kqd5rs4dtvlxym90m43svzdwzxaplyvc5ttzppytpvx80ncllcsqzpmukxjl3y"
case uaRead of
Nothing -> assertFailure "wrong address"
Just ua -> do
pool <- runNoLoggingT $ initPool "/home/rav/Zenith/zenith.db"
tx <-
runFileLoggingT "zenith.log" $
prepareTxV2
pool
"localhost"
18232
TestNet
(toSqlKey 4)
3001230
0.005
(fromJust uaRead)
"Sending memo to sapling"
Full
case tx of
Left e -> assertFailure $ show e
Right h -> h `shouldNotBe` (hexString "deadbeef")
describe "Medium" $ do
xit "To Orchard" $ do
let uaRead =
parseAddress
"utest1dl54utt6prjj5e0dnlknwumnxq9hycdjpkfr0sy6e6h522remqee8axe9zax0wsjrwpj76v555pdhvj9rnargpfyycs0vpkapq98xcdhem99gc4wchzn0ggepq3y6nz3a9sscwgqxgsh9vzhcad402y3x9szfregck5gslkya3c79d86xx0l33tpk8gnn7ew9vw37w43zh22u8dgdax"
case uaRead of
Nothing -> assertFailure "wrong address"
Just ua -> do
pool <- runNoLoggingT $ initPool "/home/rav/Zenith/zenith.db"
tx <-
runFileLoggingT "zenith.log" $
prepareTxV2
pool
"localhost"
18232
TestNet
(toSqlKey 1)
3000789
0.005
(fromJust uaRead)
"Sending memo to orchard"
Medium
case tx of
Left e -> assertFailure $ show e
Right h -> h `shouldNotBe` (hexString "deadbeef")
xit "To Sapling" $ do
let uaRead =
parseAddress
"ztestsapling136jp8z89v2jh6kqd5rs4dtvlxym90m43svzdwzxaplyvc5ttzppytpvx80ncllcsqzpmukxjl3y"
case uaRead of
Nothing -> assertFailure "wrong address"
Just ua -> do
pool <- runNoLoggingT $ initPool "/home/rav/Zenith/zenith.db"
tx <-
runFileLoggingT "zenith.log" $
prepareTxV2
pool
"localhost"
18232
TestNet
(toSqlKey 1)
3000789
0.005
(fromJust uaRead)
"Sending memo to orchard"
Medium
case tx of
Left e -> assertFailure $ show e
Right h -> h `shouldNotBe` (hexString "deadbeef")

@ -1 +1 @@
Subproject commit 7965dc38c48da348f503a52ee10042fffc43f32c Subproject commit 12296026a0ebb9a5afe0904b251c5d31080eab18