haskoin-store-0.60.0: src/Haskoin/Store/Database/Writer.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
module Haskoin.Store.Database.Writer (WriterT , runWriter) where
import Control.Monad.Reader (ReaderT (..))
import qualified Control.Monad.Reader as R
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as M
import Data.List (sortOn)
import Data.Ord (Down (..))
import Data.Tuple (swap)
import Database.RocksDB (BatchOp, DB)
import Database.RocksDB.Query (deleteOp, deleteOpCF, insertOp,
insertOpCF, writeBatch)
import Haskoin (Address, BlockHash, BlockHeight,
Network, OutPoint (..), TxHash,
headerHash, txHash)
import Haskoin.Store.Common
import Haskoin.Store.Data
import Haskoin.Store.Database.Reader
import Haskoin.Store.Database.Types
import UnliftIO (MonadIO, TVar, atomically,
liftIO, modifyTVar, newTVarIO,
readTVarIO)
data Writer = Writer { getReader :: !DatabaseReader
, getState :: !(TVar Memory) }
type WriterT = ReaderT Writer
instance MonadIO m => StoreReadBase (WriterT m) where
getNetwork = getNetworkI
getBestBlock = getBestBlockI
getBlocksAtHeight = getBlocksAtHeightI
getBlock = getBlockI
getTxData = getTxDataI
getSpender = getSpenderI
getUnspent = getUnspentI
getBalance = getBalanceI
getMempool = getMempoolI
data Memory = Memory
{ hNet
:: !(Maybe Network)
, hBest
:: !(Maybe (Maybe BlockHash))
, hBlock
:: !(HashMap BlockHash (Maybe BlockData))
, hHeight
:: !(HashMap BlockHeight [BlockHash])
, hTx
:: !(HashMap TxHash (Maybe TxData))
, hSpender
:: !(HashMap OutPoint (Maybe Spender))
, hUnspent
:: !(HashMap OutPoint (Maybe Unspent))
, hBalance
:: !(HashMap Address (Maybe Balance))
, hAddrTx
:: !(HashMap (Address, TxRef) (Maybe ()))
, hAddrOut
:: !(HashMap (Address, BlockRef, OutPoint) (Maybe OutVal))
, hMempool
:: !(HashMap TxHash UnixTime)
} deriving (Eq, Show)
instance MonadIO m => StoreWrite (WriterT m) where
setBest h =
ReaderT $ \Writer { getState = s } ->
liftIO . atomically . modifyTVar s $
setBestH h
insertBlock b =
ReaderT $ \Writer { getState = s } ->
liftIO . atomically . modifyTVar s $
insertBlockH b
setBlocksAtHeight h g =
ReaderT $ \Writer { getState = s } ->
liftIO . atomically . modifyTVar s $
setBlocksAtHeightH h g
insertTx t =
ReaderT $ \Writer { getState = s } ->
liftIO . atomically . modifyTVar s $
insertTxH t
insertSpender p s' =
ReaderT $ \Writer { getState = s } ->
liftIO . atomically . modifyTVar s $
insertSpenderH p s'
deleteSpender p =
ReaderT $ \Writer { getState = s } ->
liftIO . atomically . modifyTVar s $
deleteSpenderH p
insertAddrTx a t =
ReaderT $ \Writer { getState = s } ->
liftIO . atomically . modifyTVar s $
insertAddrTxH a t
deleteAddrTx a t =
ReaderT $ \Writer { getState = s } ->
liftIO . atomically . modifyTVar s $
deleteAddrTxH a t
insertAddrUnspent a u =
ReaderT $ \Writer { getState = s } ->
liftIO . atomically . modifyTVar s $
insertAddrUnspentH a u
deleteAddrUnspent a u =
ReaderT $ \Writer { getState = s } ->
liftIO . atomically . modifyTVar s $
deleteAddrUnspentH a u
addToMempool x t =
ReaderT $ \Writer { getState = s } ->
liftIO . atomically . modifyTVar s $
addToMempoolH x t
deleteFromMempool x =
ReaderT $ \Writer { getState = s } ->
liftIO . atomically . modifyTVar s $
deleteFromMempoolH x
setBalance b =
ReaderT $ \Writer { getState = s } ->
liftIO . atomically . modifyTVar s $
setBalanceH b
insertUnspent h =
ReaderT $ \Writer { getState = s } ->
liftIO . atomically . modifyTVar s $
insertUnspentH h
deleteUnspent p =
ReaderT $ \Writer { getState = s } ->
liftIO . atomically . modifyTVar s $
deleteUnspentH p
getLayered :: MonadIO m
=> (Memory -> Maybe a)
-> DatabaseReaderT m a
-> WriterT m a
getLayered f g =
ReaderT $ \Writer { getReader = db, getState = tmem } ->
f <$> readTVarIO tmem >>= \case
Just x -> return x
Nothing -> runReaderT g db
runWriter :: MonadIO m
=> DatabaseReader
-> WriterT m a
-> m a
runWriter bdb@DatabaseReader{ databaseHandle = db } f = do
mem <- runReaderT getMempool bdb
hm <- newTVarIO (newMemory mem)
x <- R.runReaderT f Writer { getReader = bdb, getState = hm }
mem' <- readTVarIO hm
let ops = hashMapOps db mem'
writeBatch db ops
return x
hashMapOps :: DB -> Memory -> [BatchOp]
hashMapOps db mem =
bestBlockOp (hBest mem) <>
blockHashOps db (hBlock mem) <>
blockHeightOps db (hHeight mem) <>
txOps db (hTx mem) <>
spenderOps db (hSpender mem) <>
balOps db (hBalance mem) <>
addrTxOps db (hAddrTx mem) <>
addrOutOps db (hAddrOut mem) <>
mempoolOp (hMempool mem) <>
unspentOps db (hUnspent mem)
bestBlockOp :: Maybe (Maybe BlockHash) -> [BatchOp]
bestBlockOp Nothing = []
bestBlockOp (Just Nothing) = [deleteOp BestKey]
bestBlockOp (Just (Just b)) = [insertOp BestKey b]
blockHashOps :: DB -> HashMap BlockHash (Maybe BlockData) -> [BatchOp]
blockHashOps db = map (uncurry f) . M.toList
where
f k (Just d) = insertOpCF (blockCF db) (BlockKey k) d
f k Nothing = deleteOpCF (blockCF db) (BlockKey k)
blockHeightOps :: DB -> HashMap BlockHeight [BlockHash] -> [BatchOp]
blockHeightOps db = map (uncurry f) . M.toList
where
f = insertOpCF (heightCF db) . HeightKey
txOps :: DB -> HashMap TxHash (Maybe TxData) -> [BatchOp]
txOps db = map (uncurry f) . M.toList
where
f k (Just t) = insertOpCF (txCF db) (TxKey k) t
f k Nothing = deleteOpCF (txCF db) (TxKey k)
spenderOps :: DB -> HashMap OutPoint (Maybe Spender) -> [BatchOp]
spenderOps db = map (uncurry f) . M.toList
where
f o (Just s) = insertOpCF (spenderCF db) (SpenderKey o) s
f o Nothing = deleteOpCF (spenderCF db) (SpenderKey o)
balOps :: DB -> HashMap Address (Maybe Balance) -> [BatchOp]
balOps db = map (uncurry f) . M.toList
where
f a (Just b) = insertOpCF (balanceCF db) (BalKey a) (balanceToVal b)
f a Nothing = deleteOpCF (balanceCF db) (BalKey a)
addrTxOps :: DB -> HashMap (Address, TxRef) (Maybe ()) -> [BatchOp]
addrTxOps db = map (uncurry f) . M.toList
where
f (a, t) (Just ()) = insertOpCF (addrTxCF db) (AddrTxKey a t) ()
f (a, t) Nothing = deleteOpCF (addrTxCF db) (AddrTxKey a t)
addrOutOps :: DB
-> HashMap (Address, BlockRef, OutPoint) (Maybe OutVal)
-> [BatchOp]
addrOutOps db = map (uncurry f) . M.toList
where
f (a, b, p) (Just l) =
insertOpCF
(addrOutCF db)
(AddrOutKey { addrOutKeyA = a
, addrOutKeyB = b
, addrOutKeyP = p
}
)
l
f (a, b, p) Nothing =
deleteOpCF
(addrOutCF db)
AddrOutKey { addrOutKeyA = a
, addrOutKeyB = b
, addrOutKeyP = p
}
mempoolOp :: HashMap TxHash UnixTime -> [BatchOp]
mempoolOp = return . insertOp MemKey . sortOn Down . map swap . M.toList
unspentOps :: DB -> HashMap OutPoint (Maybe Unspent) -> [BatchOp]
unspentOps db = map (uncurry f) . M.toList
where
f p (Just u) =
insertOpCF (unspentCF db) (UnspentKey p) (snd (unspentToVal u))
f p Nothing =
deleteOpCF (unspentCF db) (UnspentKey p)
getNetworkI :: MonadIO m => WriterT m Network
getNetworkI = getLayered hNet getNetwork
getBestBlockI :: MonadIO m => WriterT m (Maybe BlockHash)
getBestBlockI = getLayered getBestBlockH getBestBlock
getBlocksAtHeightI :: MonadIO m => BlockHeight -> WriterT m [BlockHash]
getBlocksAtHeightI bh =
getLayered (getBlocksAtHeightH bh) (getBlocksAtHeight bh)
getBlockI :: MonadIO m => BlockHash -> WriterT m (Maybe BlockData)
getBlockI bh = getLayered (getBlockH bh) (getBlock bh)
getTxDataI :: MonadIO m => TxHash -> WriterT m (Maybe TxData)
getTxDataI th = getLayered (getTxDataH th) (getTxData th)
getSpenderI :: MonadIO m => OutPoint -> WriterT m (Maybe Spender)
getSpenderI op = getLayered (getSpenderH op) (getSpender op)
getBalanceI :: MonadIO m => Address -> WriterT m (Maybe Balance)
getBalanceI a = getLayered (getBalanceH a) (getBalance a)
getUnspentI :: MonadIO m => OutPoint -> WriterT m (Maybe Unspent)
getUnspentI op = getLayered (getUnspentH op) (getUnspent op)
getMempoolI :: MonadIO m => WriterT m [(UnixTime, TxHash)]
getMempoolI =
ReaderT $ \Writer { getState = tmem } ->
getMempoolH <$> readTVarIO tmem
newMemory :: [(UnixTime, TxHash)] -> Memory
newMemory mem =
Memory { hNet = Nothing
, hBest = Nothing
, hBlock = M.empty
, hHeight = M.empty
, hTx = M.empty
, hSpender = M.empty
, hUnspent = M.empty
, hBalance = M.empty
, hAddrTx = M.empty
, hAddrOut = M.empty
, hMempool = M.fromList (map swap mem)
}
getBestBlockH :: Memory -> Maybe (Maybe BlockHash)
getBestBlockH = hBest
getBlocksAtHeightH :: BlockHeight -> Memory -> Maybe [BlockHash]
getBlocksAtHeightH h = M.lookup h . hHeight
getBlockH :: BlockHash -> Memory -> Maybe (Maybe BlockData)
getBlockH h = M.lookup h . hBlock
getTxDataH :: TxHash -> Memory -> Maybe (Maybe TxData)
getTxDataH t = M.lookup t . hTx
getSpenderH :: OutPoint -> Memory -> Maybe (Maybe Spender)
getSpenderH op db = M.lookup op (hSpender db)
getBalanceH :: Address -> Memory -> Maybe (Maybe Balance)
getBalanceH a = M.lookup a . hBalance
getMempoolH :: Memory -> [(UnixTime, TxHash)]
getMempoolH = sortOn Down . map swap . M.toList . hMempool
setBestH :: BlockHash -> Memory -> Memory
setBestH h db = db {hBest = Just (Just h)}
insertBlockH :: BlockData -> Memory -> Memory
insertBlockH bd db =
db { hBlock = M.insert
(headerHash (blockDataHeader bd))
(Just bd)
(hBlock db)
}
setBlocksAtHeightH :: [BlockHash] -> BlockHeight -> Memory -> Memory
setBlocksAtHeightH hs g db =
db {hHeight = M.insert g hs (hHeight db)}
insertTxH :: TxData -> Memory -> Memory
insertTxH tx db =
db {hTx = M.insert (txHash (txData tx)) (Just tx) (hTx db)}
insertSpenderH :: OutPoint -> Spender -> Memory -> Memory
insertSpenderH op s db =
db { hSpender = M.insert op (Just s) (hSpender db) }
deleteSpenderH :: OutPoint -> Memory -> Memory
deleteSpenderH op db =
db { hSpender = M.insert op Nothing (hSpender db) }
setBalanceH :: Balance -> Memory -> Memory
setBalanceH bal db =
db {hBalance = M.insert (balanceAddress bal) (Just bal) (hBalance db)}
insertAddrTxH :: Address -> TxRef -> Memory -> Memory
insertAddrTxH a tr db =
db { hAddrTx = M.insert (a, tr) (Just ()) (hAddrTx db) }
deleteAddrTxH :: Address -> TxRef -> Memory -> Memory
deleteAddrTxH a tr db =
db { hAddrTx = M.insert (a, tr) Nothing (hAddrTx db) }
insertAddrUnspentH :: Address -> Unspent -> Memory -> Memory
insertAddrUnspentH a u db =
let k = (a, unspentBlock u, unspentPoint u)
v = OutVal { outValAmount = unspentAmount u
, outValScript = unspentScript u
}
in db { hAddrOut = M.insert k (Just v) (hAddrOut db) }
deleteAddrUnspentH :: Address -> Unspent -> Memory -> Memory
deleteAddrUnspentH a u db =
let k = (a, unspentBlock u, unspentPoint u)
in db { hAddrOut = M.insert k Nothing (hAddrOut db) }
addToMempoolH :: TxHash -> UnixTime -> Memory -> Memory
addToMempoolH h t db =
db { hMempool = M.insert h t (hMempool db) }
deleteFromMempoolH :: TxHash -> Memory -> Memory
deleteFromMempoolH h db =
db { hMempool = M.delete h (hMempool db) }
getUnspentH :: OutPoint -> Memory -> Maybe (Maybe Unspent)
getUnspentH op db = M.lookup op (hUnspent db)
insertUnspentH :: Unspent -> Memory -> Memory
insertUnspentH u db =
let k = fst (unspentToVal u)
in db { hUnspent = M.insert k (Just u) (hUnspent db) }
deleteUnspentH :: OutPoint -> Memory -> Memory
deleteUnspentH op db =
db { hUnspent = M.insert op Nothing (hUnspent db) }