haskoin-store-0.64.1: 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)}