zenith/src/Zenith/DB.hs

2048 lines
65 KiB
Haskell
Raw Normal View History

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE TypeApplications #-}
module Zenith.DB where
2024-08-10 07:04:40 -05:00
import Control.Exception (SomeException(..), throwIO, try)
import Control.Monad (when)
2024-08-15 11:17:24 -05:00
import Control.Monad.IO.Class (MonadIO, liftIO)
import Control.Monad.Logger (NoLoggingT, runNoLoggingT)
import qualified Data.ByteString as BS
import Data.HexString
2024-08-12 15:35:00 -05:00
import Data.Int
import Data.List (group, sort)
import Data.Maybe (catMaybes, fromJust, isJust)
import Data.Pool (Pool)
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import Data.Word
import Database.Esqueleto.Experimental
import qualified Database.Persist.Sqlite as PS
import Database.Persist.TH
import Haskoin.Transaction.Common
( OutPoint(..)
, TxIn(..)
, TxOut(..)
, txHashToHex
)
2024-08-10 07:04:40 -05:00
import System.Directory (doesFileExist, getHomeDirectory, removeFile)
import System.FilePath ((</>))
2024-08-15 11:17:24 -05:00
import ZcashHaskell.Orchard
( compareAddress
, getSaplingFromUA
, isValidUnifiedAddress
)
2024-08-07 10:21:04 -05:00
import ZcashHaskell.Transparent (encodeTransparentReceiver)
import ZcashHaskell.Types
( DecodedNote(..)
2024-08-15 11:17:24 -05:00
, ExchangeAddress(..)
, OrchardAction(..)
, OrchardBundle(..)
2024-08-15 11:17:24 -05:00
, OrchardReceiver(..)
, OrchardWitness(..)
2024-08-15 11:17:24 -05:00
, SaplingAddress(..)
, SaplingBundle(..)
2024-08-15 11:17:24 -05:00
, SaplingReceiver(..)
, SaplingWitness(..)
, Scope(..)
, ShieldedOutput(..)
, ShieldedSpend(..)
, ToBytes(..)
, Transaction(..)
2024-08-15 11:17:24 -05:00
, TransparentAddress(..)
, TransparentBundle(..)
, TransparentReceiver(..)
, UnifiedAddress(..)
2024-08-15 11:17:24 -05:00
, ValidAddress(..)
2024-08-07 10:21:04 -05:00
, ZcashNet(..)
)
import Zenith.Types
2024-08-16 13:31:25 -05:00
( AccountBalance(..)
, HexStringDB(..)
, OrchardSpendingKeyDB(..)
, PhraseDB(..)
, RseedDB(..)
, SaplingSpendingKeyDB(..)
, ScopeDB(..)
, TransparentSpendingKeyDB
, UnifiedAddressDB(..)
2024-08-06 13:38:00 -05:00
, ZcashAccountAPI(..)
2024-08-07 10:21:04 -05:00
, ZcashAddressAPI(..)
, ZcashNetDB(..)
2024-08-12 15:35:00 -05:00
, ZcashNoteAPI(..)
2024-06-06 14:10:37 -05:00
, ZcashPool(..)
2024-08-05 12:54:02 -05:00
, ZcashWalletAPI(..)
)
share
[mkPersist sqlSettings, mkMigrate "migrateAll"]
[persistLowerCase|
ZcashWallet
name T.Text
network ZcashNetDB
seedPhrase PhraseDB
birthdayHeight Int
lastSync Int default=0
UniqueWallet name network
deriving Show Eq
ZcashAccount
index Int
walletId ZcashWalletId
name T.Text
orchSpendKey OrchardSpendingKeyDB
sapSpendKey SaplingSpendingKeyDB
tPrivateKey TransparentSpendingKeyDB
UniqueAccount index walletId
UniqueAccName walletId name
deriving Show Eq
WalletAddress
index Int
accId ZcashAccountId
name T.Text
uAddress UnifiedAddressDB
scope ScopeDB
UniqueAddress index scope accId
UniqueAddName accId name
deriving Show Eq
WalletTransaction
txId HexStringDB
accId ZcashAccountId
block Int
conf Int
time Int
UniqueWTx txId accId
deriving Show Eq
UserTx
hex HexStringDB
address WalletAddressId OnDeleteCascade OnUpdateCascade
time Int
amount Int
memo T.Text
UniqueUTx hex address
deriving Show Eq
WalletTrNote
tx WalletTransactionId OnDeleteCascade OnUpdateCascade
accId ZcashAccountId OnDeleteCascade OnUpdateCascade
address WalletAddressId OnDeleteCascade OnUpdateCascade
2024-08-12 15:35:00 -05:00
value Int64
spent Bool
script BS.ByteString
change Bool
2024-08-12 15:35:00 -05:00
position Int
UniqueTNote tx script
deriving Show Eq
WalletTrSpend
tx WalletTransactionId OnDeleteCascade OnUpdateCascade
note WalletTrNoteId OnDeleteCascade OnUpdateCascade
accId ZcashAccountId OnDeleteCascade OnUpdateCascade
2024-08-12 15:35:00 -05:00
value Int64
UniqueTrSpend tx accId
deriving Show Eq
WalletSapNote
tx WalletTransactionId OnDeleteCascade OnUpdateCascade
accId ZcashAccountId OnDeleteCascade OnUpdateCascade
2024-08-12 15:35:00 -05:00
value Int64
recipient BS.ByteString
memo T.Text
spent Bool
nullifier HexStringDB
position Word64
witness HexStringDB
change Bool
witPos ShieldOutputId OnDeleteIgnore OnUpdateIgnore
rseed RseedDB
UniqueSapNote tx nullifier
deriving Show Eq
WalletSapSpend
tx WalletTransactionId OnDeleteCascade OnUpdateCascade
note WalletSapNoteId OnDeleteCascade OnUpdateCascade
accId ZcashAccountId OnDeleteCascade OnUpdateCascade
2024-08-12 15:35:00 -05:00
value Int64
UniqueSapSepnd tx accId
deriving Show Eq
WalletOrchNote
tx WalletTransactionId OnDeleteCascade OnUpdateCascade
accId ZcashAccountId OnDeleteCascade OnUpdateCascade
2024-08-12 15:35:00 -05:00
value Int64
recipient BS.ByteString
memo T.Text
spent Bool
nullifier HexStringDB
2024-08-12 15:35:00 -05:00
position Int64
witness HexStringDB
change Bool
witPos OrchActionId OnDeleteIgnore OnUpdateIgnore
rho BS.ByteString
rseed RseedDB
UniqueOrchNote tx nullifier
deriving Show Eq
WalletOrchSpend
tx WalletTransactionId OnDeleteCascade OnUpdateCascade
note WalletOrchNoteId OnDeleteCascade OnUpdateCascade
accId ZcashAccountId OnDeleteCascade OnUpdateCascade
2024-08-12 15:35:00 -05:00
value Int64
UniqueOrchSpend tx accId
deriving Show Eq
ZcashTransaction
block Int
txId HexStringDB
conf Int
time Int
2024-08-10 07:04:40 -05:00
network ZcashNetDB
UniqueTx block txId network
deriving Show Eq
TransparentNote
tx ZcashTransactionId
2024-08-12 15:35:00 -05:00
value Int64
script BS.ByteString
position Int
UniqueTNPos tx position
deriving Show Eq
TransparentSpend
tx ZcashTransactionId
outPointHash HexStringDB
outPointIndex Word64
script BS.ByteString
seq Word64
position Int
UniqueTSPos tx position
deriving Show Eq
OrchAction
tx ZcashTransactionId
nf HexStringDB
rk HexStringDB
cmx HexStringDB
ephKey HexStringDB
encCipher HexStringDB
outCipher HexStringDB
cv HexStringDB
auth HexStringDB
position Int
UniqueOAPos tx position
deriving Show Eq
ShieldOutput
tx ZcashTransactionId
cv HexStringDB
cmu HexStringDB
ephKey HexStringDB
encCipher HexStringDB
outCipher HexStringDB
proof HexStringDB
position Int
UniqueSOPos tx position
deriving Show Eq
ShieldSpend
tx ZcashTransactionId
cv HexStringDB
anchor HexStringDB
nullifier HexStringDB
rk HexStringDB
proof HexStringDB
authSig HexStringDB
position Int
UniqueSSPos tx position
deriving Show Eq
2024-06-06 14:10:37 -05:00
QrCode
address WalletAddressId OnDeleteCascade OnUpdateCascade
version ZcashPool
bytes BS.ByteString
height Int
width Int
name T.Text
UniqueQr address version
deriving Show Eq
AddressBook
network ZcashNetDB
abdescrip T.Text
abaddress T.Text
UniqueABA abaddress
deriving Show Eq
|]
2024-08-05 12:54:02 -05:00
-- ** Type conversions
-- | @ZcashWallet@
toZcashWalletAPI :: Entity ZcashWallet -> ZcashWalletAPI
toZcashWalletAPI w =
ZcashWalletAPI
(fromIntegral $ fromSqlKey $ entityKey w)
(zcashWalletName $ entityVal w)
(getNet $ zcashWalletNetwork $ entityVal w)
(zcashWalletBirthdayHeight $ entityVal w)
(zcashWalletLastSync $ entityVal w)
2024-08-06 13:38:00 -05:00
-- | @ZcashAccount@
toZcashAccountAPI :: Entity ZcashAccount -> ZcashAccountAPI
toZcashAccountAPI a =
ZcashAccountAPI
(fromIntegral $ fromSqlKey $ entityKey a)
(fromIntegral $ fromSqlKey $ zcashAccountWalletId $ entityVal a)
(zcashAccountName $ entityVal a)
2024-08-07 10:21:04 -05:00
-- | @WalletAddress@
toZcashAddressAPI :: Entity WalletAddress -> ZcashAddressAPI
toZcashAddressAPI a =
ZcashAddressAPI
(fromIntegral $ fromSqlKey $ entityKey a)
(fromIntegral $ fromSqlKey $ walletAddressAccId $ entityVal a)
(walletAddressName $ entityVal a)
(getUA $ walletAddressUAddress $ entityVal a)
(getSaplingFromUA $
TE.encodeUtf8 $ getUA $ walletAddressUAddress $ entityVal a)
(encodeTransparentReceiver
(maybe
TestNet
ua_net
((isValidUnifiedAddress .
TE.encodeUtf8 . getUA . walletAddressUAddress) $
entityVal a)) <$>
(t_rec =<<
(isValidUnifiedAddress . TE.encodeUtf8 . getUA . walletAddressUAddress)
(entityVal a)))
2024-08-12 15:35:00 -05:00
-- | @WalletTrNote@
trToZcashNoteAPI :: ConnectionPool -> Entity WalletTrNote -> IO ZcashNoteAPI
trToZcashNoteAPI pool n = do
t <- getWalletTransaction pool $ walletTrNoteTx $ entityVal n
case t of
Nothing -> throwIO $ userError "Unable to find transaction"
Just t' -> do
return $
ZcashNoteAPI
(getHex $ walletTransactionTxId $ entityVal t') -- tx ID
2024-08-15 11:17:24 -05:00
Zenith.Types.Transparent -- pool
2024-08-12 15:35:00 -05:00
(fromIntegral (walletTrNoteValue (entityVal n)) / 100000000.0) -- zec
(walletTrNoteValue $ entityVal n) -- zats
"" -- memo
(walletTransactionConf (entityVal t') >= 10) -- confirmed
(walletTransactionBlock $ entityVal t') -- blockheight
(walletTransactionTime $ entityVal t') -- blocktime
(walletTrNotePosition $ entityVal n) -- outindex
(walletTrNoteChange $ entityVal n) -- change
-- | @WalletSapNote@
sapToZcashNoteAPI :: ConnectionPool -> Entity WalletSapNote -> IO ZcashNoteAPI
sapToZcashNoteAPI pool n = do
t <- getWalletTransaction pool $ walletSapNoteTx $ entityVal n
oi <- getSaplingOutIndex pool $ walletSapNoteWitPos $ entityVal n
case t of
Nothing -> throwIO $ userError "Unable to find transaction"
Just t' -> do
return $
ZcashNoteAPI
(getHex $ walletTransactionTxId $ entityVal t') -- tx ID
2024-08-15 11:17:24 -05:00
Zenith.Types.Sapling -- pool
2024-08-12 15:35:00 -05:00
(fromIntegral (walletSapNoteValue (entityVal n)) / 100000000.0) -- zec
(walletSapNoteValue $ entityVal n) -- zats
(walletSapNoteMemo $ entityVal n) -- memo
(walletTransactionConf (entityVal t') >= 10) -- confirmed
(walletTransactionBlock $ entityVal t') -- blockheight
(walletTransactionTime $ entityVal t') -- blocktime
oi -- outindex
(walletSapNoteChange $ entityVal n) -- change
-- | @WalletOrchNote@
orchToZcashNoteAPI :: ConnectionPool -> Entity WalletOrchNote -> IO ZcashNoteAPI
orchToZcashNoteAPI pool n = do
t <- getWalletTransaction pool $ walletOrchNoteTx $ entityVal n
oi <- getOrchardOutIndex pool $ walletOrchNoteWitPos $ entityVal n
case t of
Nothing -> throwIO $ userError "Unable to find transaction"
Just t' -> do
return $
ZcashNoteAPI
(getHex $ walletTransactionTxId $ entityVal t') -- tx ID
2024-08-15 11:17:24 -05:00
Orchard
2024-08-12 15:35:00 -05:00
(fromIntegral (walletOrchNoteValue (entityVal n)) / 100000000.0) -- zec
(walletOrchNoteValue $ entityVal n) -- zats
(walletOrchNoteMemo $ entityVal n) -- memo
(walletTransactionConf (entityVal t') >= 10) -- confirmed
(walletTransactionBlock $ entityVal t') -- blockheight
(walletTransactionTime $ entityVal t') -- blocktime
oi -- outindex
(walletOrchNoteChange $ entityVal n) -- change
-- * Database functions
-- | Initializes the database
initDb ::
T.Text -- ^ The database path to check
2024-08-10 07:04:40 -05:00
-> IO (Either String Bool)
initDb dbName = do
2024-08-10 07:04:40 -05:00
j <-
try $ PS.runSqlite dbName $ runMigrationQuiet migrateAll :: IO
(Either SomeException [T.Text])
case j of
2024-08-10 08:17:35 -05:00
Left _e1 -> do
2024-08-10 07:04:40 -05:00
pool <- runNoLoggingT $ initPool dbName
wallets <-
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do select . from $ table @ZcashWallet
accounts <-
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do select . from $ table @ZcashAccount
abook <-
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do select . from $ table @AddressBook
hDir <- getHomeDirectory
let backupDb = hDir </> "Zenith/.backup.db"
checkDbFile <- doesFileExist backupDb
when checkDbFile $ removeFile backupDb
_ <- PS.runSqlite (T.pack backupDb) $ runMigrationQuiet migrateAll
backupPool <- runNoLoggingT $ initPool $ T.pack backupDb
_ <-
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool backupPool $ insertMany_ $ entityVal <$> wallets
_ <-
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool backupPool $ insertMany_ $ entityVal <$> accounts
_ <-
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool backupPool $ insertMany_ $ entityVal <$> abook
2024-08-10 08:17:35 -05:00
clearWalletTransactions pool
clearWalletData pool
2024-08-10 07:04:40 -05:00
m <-
try $ PS.runSqlite dbName $ runMigrationQuiet migrateAll :: IO
(Either SomeException [T.Text])
case m of
Left _e2 -> return $ Left "Failed to migrate data tables"
Right _ -> return $ Right True
Right _ -> return $ Right False
initPool :: T.Text -> NoLoggingT IO ConnectionPool
initPool dbPath = do
let dbInfo = PS.mkSqliteConnectionInfo dbPath
PS.createSqlitePoolFromInfo dbInfo 5
-- | Upgrade the database
upgradeDb ::
T.Text -- ^ database path
-> IO ()
upgradeDb dbName = do
PS.runSqlite dbName $ do runMigrationUnsafe migrateAll
-- | Get existing wallets from database
getWallets :: ConnectionPool -> ZcashNet -> IO [Entity ZcashWallet]
getWallets pool n =
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
select $ do
wallets <- from $ table @ZcashWallet
where_ (wallets ^. ZcashWalletNetwork ==. val (ZcashNetDB n))
pure wallets
2024-08-12 15:35:00 -05:00
getNetwork :: ConnectionPool -> WalletAddressId -> IO ZcashNet
getNetwork pool a = do
n <-
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
selectOne $ do
(wallet :& acc :& addr) <-
from $ table @ZcashWallet `innerJoin` table @ZcashAccount `on`
(\(wallet :& acc) ->
wallet ^. ZcashWalletId ==. acc ^. ZcashAccountWalletId) `innerJoin`
table @WalletAddress `on`
(\(_ :& acc :& addr) ->
acc ^. ZcashAccountId ==. addr ^. WalletAddressAccId)
where_ (addr ^. WalletAddressId ==. val a)
pure $ wallet ^. ZcashWalletNetwork
case n of
Nothing -> throwIO $ userError "Failed to find wallet"
Just (Value n') -> return $ getNet n'
-- | Save a new wallet to the database
saveWallet ::
ConnectionPool -- ^ The database path to use
-> ZcashWallet -- ^ The wallet to add to the database
-> IO (Maybe (Entity ZcashWallet))
saveWallet pool w =
runNoLoggingT $
PS.retryOnBusy $ flip PS.runSqlPool pool $ insertUniqueEntity w
-- | Update the last sync block for the wallet
updateWalletSync :: ConnectionPool -> Int -> ZcashWalletId -> IO ()
updateWalletSync pool b i = do
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
update $ \w -> do
set w [ZcashWalletLastSync =. val b]
where_ $ w ^. ZcashWalletId ==. val i
-- | Returns a list of accounts associated with the given wallet
getAccounts ::
ConnectionPool -- ^ The database path
-> ZcashWalletId -- ^ The wallet ID to check
-> NoLoggingT IO [Entity ZcashAccount]
getAccounts pool w =
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
select $ do
accs <- from $ table @ZcashAccount
where_ (accs ^. ZcashAccountWalletId ==. val w)
pure accs
getAccountById ::
ConnectionPool -> ZcashAccountId -> IO (Maybe (Entity ZcashAccount))
getAccountById pool za = do
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
selectOne $ do
accs <- from $ table @ZcashAccount
where_ (accs ^. ZcashAccountId ==. val za)
pure accs
-- | Returns the largest account index for the given wallet
getMaxAccount ::
ConnectionPool -- ^ The database path
-> ZcashWalletId -- ^ The wallet ID to check
-> IO Int
getMaxAccount pool w = do
a <-
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
selectOne $ do
accs <- from $ table @ZcashAccount
where_ (accs ^. ZcashAccountWalletId ==. val w)
orderBy [desc $ accs ^. ZcashAccountIndex]
pure accs
case a of
Nothing -> return $ -1
Just x -> return $ zcashAccountIndex $ entityVal x
-- | Save a new account to the database
saveAccount ::
ConnectionPool -- ^ The database path
-> ZcashAccount -- ^ The account to add to the database
-> IO (Maybe (Entity ZcashAccount))
saveAccount pool a =
runNoLoggingT $
PS.retryOnBusy $ flip PS.runSqlPool pool $ insertUniqueEntity a
-- | Returns the largest block in storage
getMaxBlock ::
Pool SqlBackend -- ^ The database pool
-> ZcashNetDB
-> IO Int
getMaxBlock pool net = do
b <-
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
selectOne $ do
txs <- from $ table @ZcashTransaction
where_ (txs ^. ZcashTransactionBlock >. val 0)
where_ (txs ^. ZcashTransactionNetwork ==. val net)
orderBy [desc $ txs ^. ZcashTransactionBlock]
pure txs
case b of
Nothing -> return $ -1
Just x -> return $ zcashTransactionBlock $ entityVal x
-- | Returns a list of addresses associated with the given account
getAddresses ::
ConnectionPool -- ^ The database path
-> ZcashAccountId -- ^ The account ID to check
-> NoLoggingT IO [Entity WalletAddress]
getAddresses pool a =
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
select $ do
addrs <- from $ table @WalletAddress
where_ (addrs ^. WalletAddressAccId ==. val a)
where_ (addrs ^. WalletAddressScope ==. val (ScopeDB External))
pure addrs
getAddressById ::
ConnectionPool -> WalletAddressId -> IO (Maybe (Entity WalletAddress))
getAddressById pool a = do
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
selectOne $ do
addr <- from $ table @WalletAddress
where_ (addr ^. WalletAddressId ==. val a)
pure addr
-- | Returns a list of change addresses associated with the given account
getInternalAddresses ::
ConnectionPool -- ^ The database path
-> ZcashAccountId -- ^ The account ID to check
-> NoLoggingT IO [Entity WalletAddress]
getInternalAddresses pool a =
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
select $ do
addrs <- from $ table @WalletAddress
where_ (addrs ^. WalletAddressAccId ==. val a)
where_ (addrs ^. WalletAddressScope ==. val (ScopeDB Internal))
pure addrs
-- | Returns a list of addressess associated with the given wallet
getWalletAddresses ::
ConnectionPool -- ^ The database path
-> ZcashWalletId -- ^ the wallet to search
-> NoLoggingT IO [Entity WalletAddress]
getWalletAddresses pool w = do
accs <- getAccounts pool w
addrs <- mapM (getAddresses pool . entityKey) accs
return $ concat addrs
2024-06-06 14:10:37 -05:00
getExternalAddresses :: ConnectionPool -> IO [Entity WalletAddress]
getExternalAddresses pool = do
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
select $ do
addrs <- from $ table @WalletAddress
where_ $ addrs ^. WalletAddressScope ==. val (ScopeDB External)
return addrs
-- | Returns the largest address index for the given account
getMaxAddress ::
ConnectionPool -- ^ The database path
-> ZcashAccountId -- ^ The account ID to check
-> Scope -- ^ The scope of the address
-> IO Int
getMaxAddress pool aw s = do
a <-
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
selectOne $ do
addrs <- from $ table @WalletAddress
where_ $ addrs ^. WalletAddressAccId ==. val aw
where_ $ addrs ^. WalletAddressScope ==. val (ScopeDB s)
orderBy [desc $ addrs ^. WalletAddressIndex]
pure addrs
case a of
Nothing -> return $ -1
Just x -> return $ walletAddressIndex $ entityVal x
-- | Save a new address to the database
saveAddress ::
ConnectionPool -- ^ the database path
-> WalletAddress -- ^ The wallet to add to the database
-> IO (Maybe (Entity WalletAddress))
saveAddress pool w =
runNoLoggingT $
PS.retryOnBusy $ flip PS.runSqlPool pool $ insertUniqueEntity w
-- | Save a transaction to the data model
saveTransaction ::
ConnectionPool -- ^ the database path
-> Int -- ^ block time
2024-08-10 07:04:40 -05:00
-> ZcashNetDB -- ^ the network
-> Transaction -- ^ The transaction to save
-> NoLoggingT IO (Key ZcashTransaction)
2024-08-10 07:04:40 -05:00
saveTransaction pool t n wt =
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
let ix = [0 ..]
w <-
insert $
2024-08-10 07:04:40 -05:00
ZcashTransaction (tx_height wt) (HexStringDB $ tx_id wt) (tx_conf wt) t n
when (isJust $ tx_transpBundle wt) $ do
_ <-
insertMany_ $
zipWith (curry (storeTxOut w)) ix $
(tb_vout . fromJust . tx_transpBundle) wt
_ <-
insertMany_ $
zipWith (curry (storeTxIn w)) ix $
(tb_vin . fromJust . tx_transpBundle) wt
return ()
when (isJust $ tx_saplingBundle wt) $ do
_ <-
insertMany_ $
zipWith (curry (storeSapSpend w)) ix $
(sbSpends . fromJust . tx_saplingBundle) wt
_ <-
insertMany_ $
zipWith (curry (storeSapOutput w)) ix $
(sbOutputs . fromJust . tx_saplingBundle) wt
return ()
when (isJust $ tx_orchardBundle wt) $
insertMany_ $
zipWith (curry (storeOrchAction w)) ix $
(obActions . fromJust . tx_orchardBundle) wt
return w
where
storeTxOut :: ZcashTransactionId -> (Int, TxOut) -> TransparentNote
storeTxOut wid (i, TxOut v s) = TransparentNote wid (fromIntegral v) s i
storeTxIn :: ZcashTransactionId -> (Int, TxIn) -> TransparentSpend
storeTxIn wid (i, TxIn (OutPoint h k) s sq) =
TransparentSpend
wid
(HexStringDB . fromText $ txHashToHex h)
(fromIntegral k)
s
(fromIntegral sq)
i
storeSapSpend :: ZcashTransactionId -> (Int, ShieldedSpend) -> ShieldSpend
storeSapSpend wid (i, sp) =
ShieldSpend
wid
(HexStringDB $ sp_cv sp)
(HexStringDB $ sp_anchor sp)
(HexStringDB $ sp_nullifier sp)
(HexStringDB $ sp_rk sp)
(HexStringDB $ sp_proof sp)
(HexStringDB $ sp_auth sp)
i
storeSapOutput ::
ZcashTransactionId -> (Int, ShieldedOutput) -> ShieldOutput
storeSapOutput wid (i, so) =
ShieldOutput
wid
(HexStringDB $ s_cv so)
(HexStringDB $ s_cmu so)
(HexStringDB $ s_ephKey so)
(HexStringDB $ s_encCipherText so)
(HexStringDB $ s_outCipherText so)
(HexStringDB $ s_proof so)
i
storeOrchAction :: ZcashTransactionId -> (Int, OrchardAction) -> OrchAction
storeOrchAction wid (i, oa) =
OrchAction
wid
(HexStringDB $ nf oa)
(HexStringDB $ rk oa)
(HexStringDB $ cmx oa)
(HexStringDB $ eph_key oa)
(HexStringDB $ enc_ciphertext oa)
(HexStringDB $ out_ciphertext oa)
(HexStringDB $ cv oa)
(HexStringDB $ auth oa)
i
-- | Get the transactions from a particular block forward
getZcashTransactions ::
ConnectionPool -- ^ The database path
-> Int -- ^ Block
-> ZcashNet -- ^ Network
-> IO [Entity ZcashTransaction]
getZcashTransactions pool b net =
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
select $ do
txs <- from $ table @ZcashTransaction
where_ (txs ^. ZcashTransactionBlock >. val b)
where_ (txs ^. ZcashTransactionNetwork ==. val (ZcashNetDB net))
orderBy [asc $ txs ^. ZcashTransactionBlock]
return txs
2024-06-06 14:10:37 -05:00
-- ** QR codes
-- | Functions to manage the QR codes stored in the database
saveQrCode ::
ConnectionPool -- ^ the connection pool
-> QrCode
-> NoLoggingT IO (Maybe (Entity QrCode))
saveQrCode pool qr =
PS.retryOnBusy $ flip PS.runSqlPool pool $ insertUniqueEntity qr
getQrCodes ::
ConnectionPool -- ^ the connection pool
-> WalletAddressId
-> IO [Entity QrCode]
getQrCodes pool wId =
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
select $ do
qrs <- from $ table @QrCode
where_ $ qrs ^. QrCodeAddress ==. val wId
return qrs
getQrCode :: ConnectionPool -> ZcashPool -> WalletAddressId -> IO (Maybe QrCode)
getQrCode pool zp wId = do
r <-
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
selectOne $ do
qrs <- from $ table @QrCode
where_ $ qrs ^. QrCodeAddress ==. val wId
where_ $ qrs ^. QrCodeVersion ==. val zp
return qrs
return $ entityVal <$> r
-- * Wallet
-- | Get the block of the last transaction known to the wallet
getMaxWalletBlock ::
ConnectionPool -- ^ The database path
-> IO Int
getMaxWalletBlock pool = do
b <-
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
selectOne $ do
txs <- from $ table @WalletTransaction
where_ $ txs ^. WalletTransactionBlock >. val 0
orderBy [desc $ txs ^. WalletTransactionBlock]
return txs
case b of
Nothing -> return $ -1
Just x -> return $ walletTransactionBlock $ entityVal x
getMinBirthdayHeight :: ConnectionPool -> IO Int
getMinBirthdayHeight pool = do
b <-
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
selectOne $ do
w <- from $ table @ZcashWallet
where_ (w ^. ZcashWalletBirthdayHeight >. val 0)
orderBy [asc $ w ^. ZcashWalletBirthdayHeight]
pure w
case b of
Nothing -> return 0
Just x -> return $ zcashWalletBirthdayHeight $ entityVal x
getLastSyncBlock :: ConnectionPool -> ZcashWalletId -> IO Int
getLastSyncBlock pool zw = do
b <-
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
selectOne $ do
w <- from $ table @ZcashWallet
where_ (w ^. ZcashWalletId ==. val zw)
pure w
case b of
Nothing -> throwIO $ userError "Failed to retrieve wallet"
Just x -> return $ zcashWalletLastSync $ entityVal x
-- | Save a @WalletTransaction@
saveWalletTransaction ::
ConnectionPool
-> ZcashAccountId
-> Entity ZcashTransaction
-> IO WalletTransactionId
saveWalletTransaction pool za zt = do
let zT' = entityVal zt
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
t <-
upsert
(WalletTransaction
(zcashTransactionTxId zT')
za
(zcashTransactionBlock zT')
(zcashTransactionConf zT')
(zcashTransactionTime zT'))
[]
return $ entityKey t
-- | Save a @WalletSapNote@
saveWalletSapNote ::
ConnectionPool -- ^ The database path
-> WalletTransactionId -- ^ The index for the transaction that contains the note
-> Integer -- ^ note position
-> SaplingWitness -- ^ the Sapling incremental witness
-> Bool -- ^ change flag
-> ZcashAccountId
-> ShieldOutputId
-> DecodedNote -- The decoded Sapling note
-> IO ()
saveWalletSapNote pool wId pos wit ch za zt dn = do
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
_ <-
upsert
(WalletSapNote
wId
za
(fromIntegral $ a_value dn)
(a_recipient dn)
(T.filter (/= '\NUL') $ TE.decodeUtf8Lenient $ a_memo dn)
False
(HexStringDB $ a_nullifier dn)
(fromIntegral pos)
(HexStringDB $ sapWit wit)
ch
zt
(RseedDB $ a_rseed dn))
[]
return ()
-- | Save a @WalletOrchNote@
saveWalletOrchNote ::
ConnectionPool
-> WalletTransactionId
-> Integer
-> OrchardWitness
-> Bool
-> ZcashAccountId
-> OrchActionId
-> DecodedNote
-> IO ()
saveWalletOrchNote pool wId pos wit ch za zt dn = do
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
_ <-
upsert
(WalletOrchNote
wId
za
(fromIntegral $ a_value dn)
(a_recipient dn)
(T.filter (/= '\NUL') $ TE.decodeUtf8Lenient $ a_memo dn)
False
(HexStringDB $ a_nullifier dn)
(fromIntegral pos)
(HexStringDB $ orchWit wit)
ch
zt
(a_rho dn)
(RseedDB $ a_rseed dn))
[]
return ()
-- | Find the Transparent Notes that match the given transparent receiver
findTransparentNotes ::
ConnectionPool -- ^ The database path
-> Int -- ^ Starting block
-> ZcashNetDB -- ^ Network to use
-> Entity WalletAddress
-> IO ()
findTransparentNotes pool b net t = do
let tReceiver = t_rec =<< readUnifiedAddressDB (entityVal t)
case tReceiver of
Just tR -> do
let s =
BS.concat
[ BS.pack [0x76, 0xA9, 0x14]
, (toBytes . tr_bytes) tR
, BS.pack [0x88, 0xAC]
]
tN <-
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
select $ do
(txs :& tNotes) <-
from $ table @ZcashTransaction `innerJoin` table @TransparentNote `on`
(\(txs :& tNotes) ->
txs ^. ZcashTransactionId ==. tNotes ^. TransparentNoteTx)
where_ (txs ^. ZcashTransactionBlock >. val b)
where_ (txs ^. ZcashTransactionNetwork ==. val net)
where_ (tNotes ^. TransparentNoteScript ==. val s)
pure (txs, tNotes)
mapM_
(saveWalletTrNote
pool
(getScope $ walletAddressScope $ entityVal t)
(walletAddressAccId $ entityVal t)
(entityKey t))
tN
Nothing -> return ()
-- | Add the transparent notes to the wallet
saveWalletTrNote ::
ConnectionPool -- ^ the database path
-> Scope
-> ZcashAccountId
-> WalletAddressId
-> (Entity ZcashTransaction, Entity TransparentNote)
-> IO ()
saveWalletTrNote pool ch za wa (zt, tn) = do
let zT' = entityVal zt
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
t <-
upsert
(WalletTransaction
(zcashTransactionTxId zT')
za
(zcashTransactionBlock zT')
(zcashTransactionConf zT')
(zcashTransactionTime zT'))
[]
insert_ $
WalletTrNote
(entityKey t)
za
wa
(transparentNoteValue $ entityVal tn)
False
(transparentNoteScript $ entityVal tn)
(ch == Internal)
(fromIntegral $ transparentNotePosition $ entityVal tn)
-- | Save a Sapling note to the wallet database
saveSapNote :: ConnectionPool -> WalletSapNote -> IO ()
saveSapNote pool wsn =
runNoLoggingT $ PS.retryOnBusy $ flip PS.runSqlPool pool $ insert_ wsn
-- | Get the shielded outputs from the given blockheight
getShieldedOutputs ::
ConnectionPool -- ^ database path
-> Int -- ^ block
-> ZcashNetDB -- ^ network to use
-> IO [(Entity ZcashTransaction, Entity ShieldOutput)]
getShieldedOutputs pool b net =
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
select $ do
(txs :& sOutputs) <-
from $ table @ZcashTransaction `innerJoin` table @ShieldOutput `on`
(\(txs :& sOutputs) ->
txs ^. ZcashTransactionId ==. sOutputs ^. ShieldOutputTx)
where_ (txs ^. ZcashTransactionBlock >=. val b)
where_ (txs ^. ZcashTransactionNetwork ==. val net)
orderBy
[ asc $ txs ^. ZcashTransactionId
, asc $ sOutputs ^. ShieldOutputPosition
]
pure (txs, sOutputs)
-- | Get the Orchard actions from the given blockheight forward
getOrchardActions ::
ConnectionPool -- ^ database path
-> Int -- ^ block
-> ZcashNetDB -- ^ network to use
-> IO [(Entity ZcashTransaction, Entity OrchAction)]
getOrchardActions pool b net =
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
select $ do
(txs :& oActions) <-
from $ table @ZcashTransaction `innerJoin` table @OrchAction `on`
(\(txs :& oActions) ->
txs ^. ZcashTransactionId ==. oActions ^. OrchActionTx)
where_ (txs ^. ZcashTransactionBlock >=. val b)
where_ (txs ^. ZcashTransactionNetwork ==. val net)
orderBy
[asc $ txs ^. ZcashTransactionId, asc $ oActions ^. OrchActionPosition]
pure (txs, oActions)
2024-08-15 11:17:24 -05:00
findNotesByAddress ::
ConnectionPool -> ValidAddress -> Entity WalletAddress -> IO [ZcashNoteAPI]
findNotesByAddress pool va addr = do
let ua =
isValidUnifiedAddress
((TE.encodeUtf8 . getUA . walletAddressUAddress . entityVal) addr)
case ua of
Just ua' -> do
if compareAddress va ua'
then do
case va of
Unified _ -> getWalletNotes pool addr
ZcashHaskell.Types.Sapling s -> do
n <- getSapNotes pool $ sa_receiver s
mapM (sapToZcashNoteAPI pool) n
ZcashHaskell.Types.Transparent t -> do
n <- getTrNotes pool $ ta_receiver t
mapM (trToZcashNoteAPI pool) n
Exchange e -> do
n <- getTrNotes pool $ ex_address e
mapM (trToZcashNoteAPI pool) n
else return []
Nothing -> return []
getTrNotes :: ConnectionPool -> TransparentReceiver -> IO [Entity WalletTrNote]
getTrNotes pool tr = do
let s =
BS.concat
[ BS.pack [0x76, 0xA9, 0x14]
, (toBytes . tr_bytes) tr
, BS.pack [0x88, 0xAC]
]
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
select $ do
tnotes <- from $ table @WalletTrNote
where_ (tnotes ^. WalletTrNoteScript ==. val s)
pure tnotes
getSapNotes :: ConnectionPool -> SaplingReceiver -> IO [Entity WalletSapNote]
getSapNotes pool sr = do
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
select $ do
snotes <- from $ table @WalletSapNote
where_ (snotes ^. WalletSapNoteRecipient ==. val (getBytes sr))
pure snotes
getOrchNotes :: ConnectionPool -> OrchardReceiver -> IO [Entity WalletOrchNote]
getOrchNotes pool o = do
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
select $ do
onotes <- from $ table @WalletOrchNote
where_ (onotes ^. WalletOrchNoteRecipient ==. val (getBytes o))
pure onotes
2024-08-12 15:35:00 -05:00
getWalletNotes ::
ConnectionPool -- ^ database path
-> Entity WalletAddress
-> IO [ZcashNoteAPI]
getWalletNotes pool w = do
let w' = entityVal w
let tReceiver = t_rec =<< readUnifiedAddressDB w'
let sReceiver = s_rec =<< readUnifiedAddressDB w'
let oReceiver = o_rec =<< readUnifiedAddressDB w'
trNotes <-
case tReceiver of
Nothing -> return []
2024-08-15 11:17:24 -05:00
Just tR -> getTrNotes pool tR
2024-08-12 15:35:00 -05:00
sapNotes <-
case sReceiver of
Nothing -> return []
2024-08-15 11:17:24 -05:00
Just sR -> getSapNotes pool sR
2024-08-12 15:35:00 -05:00
orchNotes <-
case oReceiver of
Nothing -> return []
2024-08-15 11:17:24 -05:00
Just oR -> getOrchNotes pool oR
2024-08-12 15:35:00 -05:00
trNotes' <- mapM (trToZcashNoteAPI pool) trNotes
sapNotes' <- mapM (sapToZcashNoteAPI pool) sapNotes
orchNotes' <- mapM (orchToZcashNoteAPI pool) orchNotes
return $ trNotes' <> sapNotes' <> orchNotes'
-- | Get the transactions belonging to the given address
getWalletTransactions ::
ConnectionPool -- ^ database path
-> Entity WalletAddress
-> NoLoggingT IO ()
getWalletTransactions pool w = do
let w' = entityVal w
chgAddr <- getInternalAddresses pool $ walletAddressAccId $ entityVal w
let ctReceiver = t_rec =<< readUnifiedAddressDB (entityVal $ head chgAddr)
let csReceiver = s_rec =<< readUnifiedAddressDB (entityVal $ head chgAddr)
let coReceiver = o_rec =<< readUnifiedAddressDB (entityVal $ head chgAddr)
let tReceiver = t_rec =<< readUnifiedAddressDB w'
let sReceiver = s_rec =<< readUnifiedAddressDB w'
let oReceiver = o_rec =<< readUnifiedAddressDB w'
trNotes <-
case tReceiver of
Nothing -> return []
2024-08-15 11:17:24 -05:00
Just tR -> liftIO $ getTrNotes pool tR
trChgNotes <-
case ctReceiver of
Nothing -> return []
2024-08-15 11:17:24 -05:00
Just tR -> liftIO $ getTrNotes pool tR
trSpends <-
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
select $ do
trSpends <- from $ table @WalletTrSpend
where_
(trSpends ^. WalletTrSpendNote `in_`
valList (map entityKey (trNotes <> trChgNotes)))
pure trSpends
sapNotes <-
case sReceiver of
Nothing -> return []
2024-08-15 11:17:24 -05:00
Just sR -> liftIO $ getSapNotes pool sR
sapChgNotes <-
case csReceiver of
Nothing -> return []
2024-08-15 11:17:24 -05:00
Just sR -> liftIO $ getSapNotes pool sR
sapSpends <- mapM (getSapSpends . entityKey) (sapNotes <> sapChgNotes)
orchNotes <-
case oReceiver of
Nothing -> return []
2024-08-15 11:17:24 -05:00
Just oR -> liftIO $ getOrchNotes pool oR
orchChgNotes <-
case coReceiver of
Nothing -> return []
2024-08-15 11:17:24 -05:00
Just oR -> liftIO $ getOrchNotes pool oR
orchSpends <- mapM (getOrchSpends . entityKey) (orchNotes <> orchChgNotes)
clearUserTx (entityKey w)
mapM_ addTr trNotes
mapM_ addTr trChgNotes
mapM_ addSap sapNotes
mapM_ addSap sapChgNotes
mapM_ addOrch orchNotes
mapM_ addOrch orchChgNotes
mapM_ subTSpend trSpends
mapM_ subSSpend $ catMaybes sapSpends
mapM_ subOSpend $ catMaybes orchSpends
where
clearUserTx :: WalletAddressId -> NoLoggingT IO ()
clearUserTx waId = do
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
delete $ do
u <- from $ table @UserTx
where_ (u ^. UserTxAddress ==. val waId)
return ()
getSapSpends ::
WalletSapNoteId -> NoLoggingT IO (Maybe (Entity WalletSapSpend))
getSapSpends n = do
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
selectOne $ do
sapSpends <- from $ table @WalletSapSpend
where_ (sapSpends ^. WalletSapSpendNote ==. val n)
pure sapSpends
getOrchSpends ::
WalletOrchNoteId -> NoLoggingT IO (Maybe (Entity WalletOrchSpend))
getOrchSpends n = do
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
selectOne $ do
orchSpends <- from $ table @WalletOrchSpend
where_ (orchSpends ^. WalletOrchSpendNote ==. val n)
pure orchSpends
addTr :: Entity WalletTrNote -> NoLoggingT IO ()
addTr n =
upsertUserTx
(walletTrNoteTx $ entityVal n)
(entityKey w)
(fromIntegral $ walletTrNoteValue $ entityVal n)
""
addSap :: Entity WalletSapNote -> NoLoggingT IO ()
addSap n =
upsertUserTx
(walletSapNoteTx $ entityVal n)
(entityKey w)
(fromIntegral $ walletSapNoteValue $ entityVal n)
(walletSapNoteMemo $ entityVal n)
addOrch :: Entity WalletOrchNote -> NoLoggingT IO ()
addOrch n =
upsertUserTx
(walletOrchNoteTx $ entityVal n)
(entityKey w)
(fromIntegral $ walletOrchNoteValue $ entityVal n)
(walletOrchNoteMemo $ entityVal n)
subTSpend :: Entity WalletTrSpend -> NoLoggingT IO ()
subTSpend n =
upsertUserTx
(walletTrSpendTx $ entityVal n)
(entityKey w)
(-(fromIntegral $ walletTrSpendValue $ entityVal n))
""
subSSpend :: Entity WalletSapSpend -> NoLoggingT IO ()
subSSpend n =
upsertUserTx
(walletSapSpendTx $ entityVal n)
(entityKey w)
(-(fromIntegral $ walletSapSpendValue $ entityVal n))
""
subOSpend :: Entity WalletOrchSpend -> NoLoggingT IO ()
subOSpend n =
upsertUserTx
(walletOrchSpendTx $ entityVal n)
(entityKey w)
(-(fromIntegral $ walletOrchSpendValue $ entityVal n))
""
upsertUserTx ::
WalletTransactionId
-> WalletAddressId
-> Int
-> T.Text
-> NoLoggingT IO ()
upsertUserTx tId wId amt memo = do
tr <-
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
select $ do
tx <- from $ table @WalletTransaction
where_ (tx ^. WalletTransactionId ==. val tId)
pure tx
existingUtx <-
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
selectOne $ do
ut <- from $ table @UserTx
where_
(ut ^. UserTxHex ==.
val (walletTransactionTxId $ entityVal $ head tr))
where_ (ut ^. UserTxAddress ==. val wId)
pure ut
case existingUtx of
Nothing -> do
_ <-
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
upsert
(UserTx
(walletTransactionTxId $ entityVal $ head tr)
wId
(walletTransactionTime $ entityVal $ head tr)
amt
memo)
[]
return ()
Just uTx -> do
_ <-
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
update $ \t -> do
set
t
[ UserTxAmount +=. val amt
, UserTxMemo =.
val (memo <> " " <> userTxMemo (entityVal uTx))
]
where_ (t ^. UserTxId ==. val (entityKey uTx))
return ()
2024-08-12 15:35:00 -05:00
getWalletTransaction ::
ConnectionPool
-> WalletTransactionId
-> IO (Maybe (Entity WalletTransaction))
getWalletTransaction pool i =
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
selectOne $ do
trs <- from $ table @WalletTransaction
where_ (trs ^. WalletTransactionId ==. val i)
pure trs
getUserTx :: ConnectionPool -> WalletAddressId -> IO [Entity UserTx]
getUserTx pool aId = do
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
select $ do
uTxs <- from $ table @UserTx
where_ (uTxs ^. UserTxAddress ==. val aId)
orderBy [asc $ uTxs ^. UserTxTime]
return uTxs
-- | Get wallet transparent notes by account
getWalletTrNotes :: ConnectionPool -> ZcashAccountId -> IO [Entity WalletTrNote]
getWalletTrNotes pool za = do
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
select $ do
n <- from $ table @WalletTrNote
where_ (n ^. WalletTrNoteAccId ==. val za)
pure n
-- | find Transparent spends
findTransparentSpends :: ConnectionPool -> ZcashAccountId -> IO ()
findTransparentSpends pool za = do
notes <- getWalletTrNotes pool za
mapM_ findOneTrSpend notes
where
findOneTrSpend :: Entity WalletTrNote -> IO ()
findOneTrSpend n = do
mReverseTxId <-
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
selectOne $ do
wtx <- from $ table @WalletTransaction
where_
(wtx ^. WalletTransactionId ==. val (walletTrNoteTx $ entityVal n))
pure $ wtx ^. WalletTransactionTxId
case mReverseTxId of
Nothing -> throwIO $ userError "failed to get tx ID"
Just (Value reverseTxId) -> do
let flipTxId =
HexStringDB $
HexString $ BS.reverse $ toBytes $ getHex reverseTxId
s <-
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
select $ do
(tx :& trSpends) <-
from $
table @ZcashTransaction `innerJoin` table @TransparentSpend `on`
(\(tx :& trSpends) ->
tx ^. ZcashTransactionId ==. trSpends ^. TransparentSpendTx)
where_
(trSpends ^. TransparentSpendOutPointHash ==. val flipTxId)
where_
(trSpends ^. TransparentSpendOutPointIndex ==.
2024-08-12 15:35:00 -05:00
val (fromIntegral $ walletTrNotePosition $ entityVal n))
pure (tx, trSpends)
if null s
then return ()
else do
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
_ <-
update $ \w -> do
set w [WalletTrNoteSpent =. val True]
where_ $ w ^. WalletTrNoteId ==. val (entityKey n)
t' <- upsertWalTx (entityVal $ fst $ head s) za
_ <-
upsert
(WalletTrSpend
(entityKey t')
(entityKey n)
za
(walletTrNoteValue $ entityVal n))
[]
return ()
getWalletSapNotes ::
ConnectionPool -> ZcashAccountId -> IO [Entity WalletSapNote]
getWalletSapNotes pool za = do
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
select $ do
n <- from $ table @WalletSapNote
where_ (n ^. WalletSapNoteAccId ==. val za)
pure n
-- | Sapling DAG-aware spend tracking
findSapSpends ::
ConnectionPool -> ZcashAccountId -> [Entity WalletSapNote] -> IO ()
findSapSpends _ _ [] = return ()
findSapSpends pool za (n:notes) = do
s <-
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
select $ do
(tx :& sapSpends) <-
from $ table @ZcashTransaction `innerJoin` table @ShieldSpend `on`
(\(tx :& sapSpends) ->
tx ^. ZcashTransactionId ==. sapSpends ^. ShieldSpendTx)
where_
(sapSpends ^. ShieldSpendNullifier ==.
val (walletSapNoteNullifier (entityVal n)))
pure (tx, sapSpends)
if null s
then findSapSpends pool za notes
else do
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
_ <-
update $ \w -> do
set w [WalletSapNoteSpent =. val True]
where_ $ w ^. WalletSapNoteId ==. val (entityKey n)
t' <- upsertWalTx (entityVal $ fst $ head s) za
_ <-
upsert
(WalletSapSpend
(entityKey t')
(entityKey n)
za
(walletSapNoteValue $ entityVal n))
[]
return ()
findSapSpends pool za notes
getWalletOrchNotes ::
ConnectionPool -> ZcashAccountId -> IO [Entity WalletOrchNote]
getWalletOrchNotes pool za = do
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
select $ do
n <- from $ table @WalletOrchNote
where_ (n ^. WalletOrchNoteAccId ==. val za)
pure n
getUnspentSapNotes :: ConnectionPool -> IO [Entity WalletSapNote]
getUnspentSapNotes pool = do
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
select $ do
n <- from $ table @WalletSapNote
where_ (n ^. WalletSapNoteSpent ==. val False)
pure n
getSaplingCmus :: Pool SqlBackend -> ShieldOutputId -> IO [Value HexStringDB]
getSaplingCmus pool zt = do
PS.runSqlPool
(select $ do
n <- from $ table @ShieldOutput
where_ (n ^. ShieldOutputId >. val zt)
orderBy [asc $ n ^. ShieldOutputId]
pure $ n ^. ShieldOutputCmu)
pool
getMaxSaplingNote :: Pool SqlBackend -> IO ShieldOutputId
getMaxSaplingNote pool = do
flip PS.runSqlPool pool $ do
x <-
selectOne $ do
n <- from $ table @ShieldOutput
where_ (n ^. ShieldOutputId >. val (toSqlKey 0))
orderBy [desc $ n ^. ShieldOutputId]
pure (n ^. ShieldOutputId)
case x of
Nothing -> return $ toSqlKey 0
Just (Value y) -> return y
updateSapNoteRecord ::
Pool SqlBackend
-> WalletSapNoteId
-> SaplingWitness
-> ShieldOutputId
-> IO ()
updateSapNoteRecord pool n w o = do
flip PS.runSqlPool pool $ do
update $ \x -> do
set
x
[ WalletSapNoteWitness =. val (HexStringDB $ sapWit w)
, WalletSapNoteWitPos =. val o
]
where_ (x ^. WalletSapNoteId ==. val n)
getUnspentOrchNotes :: ConnectionPool -> IO [Entity WalletOrchNote]
getUnspentOrchNotes pool = do
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
select $ do
n <- from $ table @WalletOrchNote
where_ (n ^. WalletOrchNoteSpent ==. val False)
pure n
getOrchardCmxs :: Pool SqlBackend -> OrchActionId -> IO [Value HexStringDB]
getOrchardCmxs pool zt = do
PS.runSqlPool
(select $ do
n <- from $ table @OrchAction
where_ (n ^. OrchActionId >. val zt)
orderBy [asc $ n ^. OrchActionId]
pure $ n ^. OrchActionCmx)
pool
getMaxOrchardNote :: Pool SqlBackend -> IO OrchActionId
getMaxOrchardNote pool = do
flip PS.runSqlPool pool $ do
x <-
selectOne $ do
n <- from $ table @OrchAction
where_ (n ^. OrchActionId >. val (toSqlKey 0))
orderBy [desc $ n ^. OrchActionId]
pure (n ^. OrchActionId)
case x of
Nothing -> return $ toSqlKey 0
Just (Value y) -> return y
updateOrchNoteRecord ::
Pool SqlBackend
-> WalletOrchNoteId
-> OrchardWitness
-> OrchActionId
-> IO ()
updateOrchNoteRecord pool n w o = do
flip PS.runSqlPool pool $ do
update $ \x -> do
set
x
[ WalletOrchNoteWitness =. val (HexStringDB $ orchWit w)
, WalletOrchNoteWitPos =. val o
]
where_ (x ^. WalletOrchNoteId ==. val n)
findOrchSpends ::
ConnectionPool -> ZcashAccountId -> [Entity WalletOrchNote] -> IO ()
findOrchSpends _ _ [] = return ()
findOrchSpends pool za (n:notes) = do
s <-
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
select $ do
(tx :& orchSpends) <-
from $ table @ZcashTransaction `innerJoin` table @OrchAction `on`
(\(tx :& orchSpends) ->
tx ^. ZcashTransactionId ==. orchSpends ^. OrchActionTx)
where_
(orchSpends ^. OrchActionNf ==.
val (walletOrchNoteNullifier (entityVal n)))
pure (tx, orchSpends)
if null s
then findOrchSpends pool za notes
else do
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
_ <-
update $ \w -> do
set w [WalletOrchNoteSpent =. val True]
where_ $ w ^. WalletOrchNoteId ==. val (entityKey n)
t' <- upsertWalTx (entityVal $ fst $ head s) za
_ <-
upsert
(WalletOrchSpend
(entityKey t')
(entityKey n)
za
(walletOrchNoteValue $ entityVal n))
[]
return ()
findOrchSpends pool za notes
upsertWalTx ::
MonadIO m
=> ZcashTransaction
-> ZcashAccountId
-> SqlPersistT m (Entity WalletTransaction)
upsertWalTx zt za =
upsert
(WalletTransaction
(zcashTransactionTxId zt)
za
(zcashTransactionBlock zt)
(zcashTransactionConf zt)
(zcashTransactionTime zt))
[]
2024-08-12 15:35:00 -05:00
getSaplingOutIndex :: ConnectionPool -> ShieldOutputId -> IO Int
getSaplingOutIndex pool i = do
o <-
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
selectOne $ do
sout <- from $ table @ShieldOutput
where_ (sout ^. ShieldOutputId ==. val i)
pure $ sout ^. ShieldOutputPosition
case o of
Nothing -> throwIO $ userError "couldn't find shielded output"
Just (Value o') -> return o'
getOrchardOutIndex :: ConnectionPool -> OrchActionId -> IO Int
getOrchardOutIndex pool i = do
o <-
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
selectOne $ do
sout <- from $ table @OrchAction
where_ (sout ^. OrchActionId ==. val i)
pure $ sout ^. OrchActionPosition
case o of
Nothing -> throwIO $ userError "couldn't find orchard action"
Just (Value o') -> return o'
getBalance :: ConnectionPool -> ZcashAccountId -> IO Integer
getBalance pool za = do
trNotes <- getWalletUnspentTrNotes pool za
let tAmts = map (walletTrNoteValue . entityVal) trNotes
let tBal = sum tAmts
sapNotes <- getWalletUnspentSapNotes pool za
let sAmts = map (walletSapNoteValue . entityVal) sapNotes
let sBal = sum sAmts
orchNotes <- getWalletUnspentOrchNotes pool za
let oAmts = map (walletOrchNoteValue . entityVal) orchNotes
let oBal = sum oAmts
return . fromIntegral $ tBal + sBal + oBal
getTransparentBalance :: ConnectionPool -> ZcashAccountId -> IO Integer
getTransparentBalance pool za = do
trNotes <- getWalletUnspentTrNotes pool za
let tAmts = map (walletTrNoteValue . entityVal) trNotes
return . fromIntegral $ sum tAmts
getShieldedBalance :: ConnectionPool -> ZcashAccountId -> IO Integer
getShieldedBalance pool za = do
sapNotes <- getWalletUnspentSapNotes pool za
let sAmts = map (walletSapNoteValue . entityVal) sapNotes
let sBal = sum sAmts
orchNotes <- getWalletUnspentOrchNotes pool za
let oAmts = map (walletOrchNoteValue . entityVal) orchNotes
let oBal = sum oAmts
return . fromIntegral $ sBal + oBal
getUnconfirmedBalance :: ConnectionPool -> ZcashAccountId -> IO Integer
getUnconfirmedBalance pool za = do
trNotes <- getWalletUnspentUnconfirmedTrNotes pool za
let tAmts = map (walletTrNoteValue . entityVal) trNotes
let tBal = sum tAmts
sapNotes <- getWalletUnspentUnconfirmedSapNotes pool za
let sAmts = map (walletSapNoteValue . entityVal) sapNotes
let sBal = sum sAmts
orchNotes <- getWalletUnspentUnconfirmedOrchNotes pool za
let oAmts = map (walletOrchNoteValue . entityVal) orchNotes
let oBal = sum oAmts
return . fromIntegral $ tBal + sBal + oBal
2024-08-16 13:31:25 -05:00
getPoolBalance :: ConnectionPool -> ZcashAccountId -> IO AccountBalance
getPoolBalance pool za = do
trNotes <- getWalletUnspentTrNotes pool za
let tAmts = map (walletTrNoteValue . entityVal) trNotes
let tBal = sum tAmts
sapNotes <- getWalletUnspentSapNotes pool za
let sAmts = map (walletSapNoteValue . entityVal) sapNotes
let sBal = sum sAmts
orchNotes <- getWalletUnspentOrchNotes pool za
let oAmts = map (walletOrchNoteValue . entityVal) orchNotes
let oBal = sum oAmts
return $ AccountBalance tBal sBal oBal
getUnconfPoolBalance :: ConnectionPool -> ZcashAccountId -> IO AccountBalance
getUnconfPoolBalance pool za = do
trNotes <- getWalletUnspentUnconfirmedTrNotes pool za
let tAmts = map (walletTrNoteValue . entityVal) trNotes
let tBal = sum tAmts
sapNotes <- getWalletUnspentUnconfirmedSapNotes pool za
let sAmts = map (walletSapNoteValue . entityVal) sapNotes
let sBal = sum sAmts
orchNotes <- getWalletUnspentUnconfirmedOrchNotes pool za
let oAmts = map (walletOrchNoteValue . entityVal) orchNotes
let oBal = sum oAmts
return $ AccountBalance tBal sBal oBal
clearWalletTransactions :: ConnectionPool -> IO ()
clearWalletTransactions pool = do
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
delete $ do
_ <- from $ table @UserTx
return ()
delete $ do
_ <- from $ table @WalletOrchSpend
return ()
delete $ do
_ <- from $ table @WalletOrchNote
return ()
delete $ do
_ <- from $ table @WalletSapSpend
return ()
delete $ do
_ <- from $ table @WalletSapNote
return ()
delete $ do
_ <- from $ table @WalletTrNote
return ()
delete $ do
_ <- from $ table @WalletTrSpend
return ()
delete $ do
_ <- from $ table @WalletTransaction
return ()
2024-08-10 07:04:40 -05:00
update $ \w -> do
set w [ZcashWalletLastSync =. val 0]
clearWalletData :: ConnectionPool -> IO ()
clearWalletData pool = do
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
delete $ do
_ <- from $ table @TransparentNote
return ()
delete $ do
_ <- from $ table @TransparentSpend
return ()
delete $ do
_ <- from $ table @OrchAction
return ()
delete $ do
_ <- from $ table @ShieldOutput
return ()
delete $ do
_ <- from $ table @ShieldSpend
return ()
delete $ do
_ <- from $ table @ZcashTransaction
return ()
getWalletUnspentTrNotes ::
ConnectionPool -> ZcashAccountId -> IO [Entity WalletTrNote]
getWalletUnspentTrNotes pool za = do
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
select $ do
(txs :& tNotes) <-
from $ table @WalletTransaction `innerJoin` table @WalletTrNote `on`
(\(txs :& tNotes) ->
txs ^. WalletTransactionId ==. tNotes ^. WalletTrNoteTx)
where_ (tNotes ^. WalletTrNoteAccId ==. val za)
where_ (tNotes ^. WalletTrNoteSpent ==. val False)
where_
((tNotes ^. WalletTrNoteChange ==. val True &&. txs ^.
WalletTransactionConf >=.
val 3) ||.
(tNotes ^. WalletTrNoteChange ==. val False &&. txs ^.
WalletTransactionConf >=.
val 10))
pure tNotes
getWalletUnspentUnconfirmedTrNotes ::
ConnectionPool -> ZcashAccountId -> IO [Entity WalletTrNote]
getWalletUnspentUnconfirmedTrNotes pool za = do
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
select $ do
(txs :& tNotes) <-
from $ table @WalletTransaction `innerJoin` table @WalletTrNote `on`
(\(txs :& tNotes) ->
txs ^. WalletTransactionId ==. tNotes ^. WalletTrNoteTx)
where_ (tNotes ^. WalletTrNoteAccId ==. val za)
where_ (tNotes ^. WalletTrNoteSpent ==. val False)
where_
((tNotes ^. WalletTrNoteChange ==. val True &&. txs ^.
WalletTransactionConf <.
val 3) ||.
(tNotes ^. WalletTrNoteChange ==. val False &&. txs ^.
WalletTransactionConf <.
val 10))
pure tNotes
getWalletUnspentSapNotes ::
ConnectionPool -> ZcashAccountId -> IO [Entity WalletSapNote]
getWalletUnspentSapNotes pool za = do
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
select $ do
(txs :& sNotes) <-
from $ table @WalletTransaction `innerJoin` table @WalletSapNote `on`
(\(txs :& sNotes) ->
txs ^. WalletTransactionId ==. sNotes ^. WalletSapNoteTx)
where_ (sNotes ^. WalletSapNoteAccId ==. val za)
where_ (sNotes ^. WalletSapNoteSpent ==. val False)
where_
((sNotes ^. WalletSapNoteChange ==. val True &&. txs ^.
WalletTransactionConf >=.
val 3) ||.
(sNotes ^. WalletSapNoteChange ==. val False &&. txs ^.
WalletTransactionConf >=.
val 10))
pure sNotes
getWalletUnspentUnconfirmedSapNotes ::
ConnectionPool -> ZcashAccountId -> IO [Entity WalletSapNote]
getWalletUnspentUnconfirmedSapNotes pool za = do
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
select $ do
(txs :& sNotes) <-
from $ table @WalletTransaction `innerJoin` table @WalletSapNote `on`
(\(txs :& sNotes) ->
txs ^. WalletTransactionId ==. sNotes ^. WalletSapNoteTx)
where_ (sNotes ^. WalletSapNoteAccId ==. val za)
where_ (sNotes ^. WalletSapNoteSpent ==. val False)
where_
((sNotes ^. WalletSapNoteChange ==. val True &&. txs ^.
WalletTransactionConf <.
val 3) ||.
(sNotes ^. WalletSapNoteChange ==. val False &&. txs ^.
WalletTransactionConf <.
val 10))
pure sNotes
getWalletUnspentOrchNotes ::
ConnectionPool -> ZcashAccountId -> IO [Entity WalletOrchNote]
getWalletUnspentOrchNotes pool za = do
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
select $ do
(txs :& oNotes) <-
from $ table @WalletTransaction `innerJoin` table @WalletOrchNote `on`
(\(txs :& oNotes) ->
txs ^. WalletTransactionId ==. oNotes ^. WalletOrchNoteTx)
where_ (oNotes ^. WalletOrchNoteAccId ==. val za)
where_ (oNotes ^. WalletOrchNoteSpent ==. val False)
where_
((oNotes ^. WalletOrchNoteChange ==. val True &&. txs ^.
WalletTransactionConf >=.
val 3) ||.
(oNotes ^. WalletOrchNoteChange ==. val False &&. txs ^.
WalletTransactionConf >=.
val 10))
pure oNotes
getWalletUnspentUnconfirmedOrchNotes ::
ConnectionPool -> ZcashAccountId -> IO [Entity WalletOrchNote]
getWalletUnspentUnconfirmedOrchNotes pool za = do
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
select $ do
(txs :& oNotes) <-
from $ table @WalletTransaction `innerJoin` table @WalletOrchNote `on`
(\(txs :& oNotes) ->
txs ^. WalletTransactionId ==. oNotes ^. WalletOrchNoteTx)
where_ (oNotes ^. WalletOrchNoteAccId ==. val za)
where_ (oNotes ^. WalletOrchNoteSpent ==. val False)
where_
((oNotes ^. WalletOrchNoteChange ==. val True &&. txs ^.
WalletTransactionConf <.
val 3) ||.
(oNotes ^. WalletOrchNoteChange ==. val False &&. txs ^.
WalletTransactionConf <.
val 10))
pure oNotes
selectUnspentNotes ::
ConnectionPool
-> ZcashAccountId
-> Integer
-> IO ([Entity WalletTrNote], [Entity WalletSapNote], [Entity WalletOrchNote])
selectUnspentNotes pool za amt = do
trNotes <- getWalletUnspentTrNotes pool za
let (a1, tList) = checkTransparent (fromIntegral amt) trNotes
if a1 > 0
then do
sapNotes <- getWalletUnspentSapNotes pool za
let (a2, sList) = checkSapling a1 sapNotes
if a2 > 0
then do
orchNotes <- getWalletUnspentOrchNotes pool za
let (a3, oList) = checkOrchard a2 orchNotes
if a3 > 0
then throwIO $ userError "Not enough funds"
else return (tList, sList, oList)
else return (tList, sList, [])
else return (tList, [], [])
where
checkTransparent ::
2024-08-12 15:35:00 -05:00
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 ::
2024-08-12 15:35:00 -05:00
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 ::
2024-08-12 15:35:00 -05:00
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 ::
ConnectionPool -> WalletTransactionId -> IO (Maybe (Value HexStringDB))
getWalletTxId pool wId = do
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
selectOne $ do
wtx <- from $ table @WalletTransaction
where_ (wtx ^. WalletTransactionId ==. val wId)
pure $ wtx ^. WalletTransactionTxId
2024-07-10 10:52:04 -05:00
getUnconfirmedBlocks :: ConnectionPool -> IO [Int]
getUnconfirmedBlocks pool = do
r <-
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
select $ do
wtx <- from $ table @WalletTransaction
where_ (wtx ^. WalletTransactionConf <=. val 10)
pure $ wtx ^. WalletTransactionBlock
return $ map (\(Value i) -> i) r
saveConfs :: ConnectionPool -> Int -> Int -> IO ()
saveConfs pool b c = do
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
update $ \t -> do
set t [WalletTransactionConf =. val c]
where_ $ t ^. WalletTransactionBlock ==. val b
-- | Helper function to extract a Unified Address from the database
readUnifiedAddressDB :: WalletAddress -> Maybe UnifiedAddress
readUnifiedAddressDB =
isValidUnifiedAddress . TE.encodeUtf8 . getUA . walletAddressUAddress
-- | Get list of external zcash addresses from database
getAdrBook :: ConnectionPool -> ZcashNet -> IO [Entity AddressBook]
getAdrBook pool n =
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
select $ do
adrbook <- from $ table @AddressBook
where_ (adrbook ^. AddressBookNetwork ==. val (ZcashNetDB n))
pure adrbook
-- | Save a new address into AddressBook
saveAdrsInAdrBook ::
ConnectionPool -- ^ The database path to use
-> AddressBook -- ^ The address to add to the database
-> IO (Maybe (Entity AddressBook))
saveAdrsInAdrBook pool a =
runNoLoggingT $
PS.retryOnBusy $ flip PS.runSqlPool pool $ insertUniqueEntity a
-- | Update an existing address into AddressBook
updateAdrsInAdrBook :: ConnectionPool -> T.Text -> T.Text -> T.Text -> IO ()
updateAdrsInAdrBook pool d a ia = do
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
update $ \ab -> do
set ab [AddressBookAbdescrip =. val d, AddressBookAbaddress =. val a]
where_ $ ab ^. AddressBookAbaddress ==. val ia
-- | Get one AddrssBook record using the Address as a key
-- getABookRec :: ConnectionPool -> T.Tex t -> IO (Maybe (Entity AddressBook))
-- getABookRec pool a = do
-- runNoLoggingT $
-- PS.retryOnBusy $
-- flip PS.runSqlPool pool $
-- select $ do
-- adrbook <- from $ table @AddressBook
-- where_ ((adrbook ^. AddressBookAbaddress) ==. val a)
-- return adrbook
-- | delete an existing address from AddressBook
deleteAdrsFromAB :: ConnectionPool -> T.Text -> IO ()
deleteAdrsFromAB pool ia = do
runNoLoggingT $
PS.retryOnBusy $
flip PS.runSqlPool pool $ do
delete $ do
ab <- from $ table @AddressBook
2024-07-10 15:45:10 -05:00
where_ (ab ^. AddressBookAbaddress ==. val ia)
rmdups :: Ord a => [a] -> [a]
rmdups = map head . group . sort