haskoin-store-0.65.0: src/Haskoin/Store/Database/Writer.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
module Haskoin.Store.Database.Writer (WriterT, runWriter) where
import Control.Monad (join)
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 qualified Data.HashTable.IO as H
import qualified Data.IntMap.Strict as IntMap
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
( IORef,
MonadIO,
TVar,
atomically,
liftIO,
modifyIORef,
modifyTVar,
newIORef,
newTVarIO,
readIORef,
readTVarIO,
writeIORef,
)
data Writer = Writer
{ getReader :: !DatabaseReader,
getState :: !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
type NetRef = IORef (Maybe Network)
type BestRef = IORef (Maybe (Maybe BlockHash))
type BlockTable = H.BasicHashTable BlockHash (Maybe BlockData)
type HeightTable = H.BasicHashTable BlockHeight [BlockHash]
type TxTable = H.BasicHashTable TxHash (Maybe TxData)
type UnspentTable = H.BasicHashTable OutPoint (Maybe Unspent)
type BalanceTable = H.BasicHashTable Address (Maybe Balance)
type AddrTxTable = H.BasicHashTable (Address, TxRef) (Maybe ())
type AddrOutTable = H.BasicHashTable (Address, BlockRef, OutPoint) (Maybe OutVal)
type MempoolTable = H.BasicHashTable TxHash UnixTime
data Memory = Memory
{ hNet :: !NetRef,
hBest :: !BestRef,
hBlock :: !BlockTable,
hHeight :: !HeightTable,
hTx :: !TxTable,
hUnspent :: !UnspentTable,
hBalance :: !BalanceTable,
hAddrTx :: !AddrTxTable,
hAddrOut :: !AddrOutTable,
hMempool :: !MempoolTable
}
instance MonadIO m => StoreWrite (WriterT m) where
setBest h =
ReaderT $ \Writer {getState = s} ->
liftIO $ writeIORef (hBest s) (Just (Just h))
insertBlock b =
ReaderT $ \Writer {getState = s} ->
liftIO $ H.insert (hBlock s) (headerHash (blockDataHeader b)) (Just b)
setBlocksAtHeight h g =
ReaderT $ \Writer {getState = s} ->
liftIO $ H.insert (hHeight s) g h
insertTx t =
ReaderT $ \Writer {getState = s} ->
liftIO $ H.insert (hTx s) (txHash (txData t)) (Just t)
insertAddrTx a t =
ReaderT $ \Writer {getState = s} ->
liftIO $ H.insert (hAddrTx s) (a, t) (Just ())
deleteAddrTx a t =
ReaderT $ \Writer {getState = s} ->
liftIO $ H.insert (hAddrTx s) (a, t) Nothing
insertAddrUnspent a u =
ReaderT $ \Writer {getState = s} ->
liftIO $ H.insert (hAddrOut s) k (Just v)
where
k = (a, unspentBlock u, unspentPoint u)
v = OutVal {outValAmount = unspentAmount u, outValScript = unspentScript u}
deleteAddrUnspent a u =
ReaderT $ \Writer {getState = s} ->
liftIO $ H.insert (hAddrOut s) k Nothing
where
k = (a, unspentBlock u, unspentPoint u)
addToMempool x t =
ReaderT $ \Writer {getState = s} ->
liftIO $ H.insert (hMempool s) x t
deleteFromMempool x =
ReaderT $ \Writer {getState = s} ->
liftIO $ H.delete (hMempool s) x
setBalance b =
ReaderT $ \Writer {getState = s} ->
liftIO $ H.insert (hBalance s) (balanceAddress b) (Just b)
insertUnspent u =
ReaderT $ \Writer {getState = s} ->
liftIO $ H.insert (hUnspent s) (fst (unspentToVal u)) (Just u)
deleteUnspent p =
ReaderT $ \Writer {getState = s} ->
liftIO $ H.insert (hUnspent s) p Nothing
getLayered ::
MonadIO m =>
(Memory -> IO (Maybe a)) ->
DatabaseReaderT m a ->
WriterT m a
getLayered f g =
ReaderT $ \Writer {getReader = db, getState = s} ->
liftIO (f s) >>= \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
mempool <- runReaderT getMempool bdb
hm <- newMemory mempool
x <- R.runReaderT f Writer {getReader = bdb, getState = hm}
ops <- hashMapOps db hm
writeBatch db ops
return x
hashMapOps :: MonadIO m => DB -> Memory -> m [BatchOp]
hashMapOps db mem =
mconcat
<$> sequence
[ bestBlockOp (hBest mem),
blockHashOps db (hBlock mem),
blockHeightOps db (hHeight mem),
txOps db (hTx mem),
balOps db (hBalance mem),
addrTxOps db (hAddrTx mem),
addrOutOps db (hAddrOut mem),
mempoolOp (hMempool mem),
unspentOps db (hUnspent mem)
]
bestBlockOp :: MonadIO m => BestRef -> m [BatchOp]
bestBlockOp r =
readIORef r >>= \case
Nothing -> return []
Just Nothing -> return [deleteOp BestKey]
Just (Just b) -> return [insertOp BestKey b]
blockHashOps :: MonadIO m => DB -> BlockTable -> m [BatchOp]
blockHashOps db t = map (uncurry f) <$> liftIO (H.toList t)
where
f k (Just d) = insertOpCF (blockCF db) (BlockKey k) d
f k Nothing = deleteOpCF (blockCF db) (BlockKey k)
blockHeightOps :: MonadIO m => DB -> HeightTable -> m [BatchOp]
blockHeightOps db t = map (uncurry f) <$> liftIO (H.toList t)
where
f = insertOpCF (heightCF db) . HeightKey
txOps :: MonadIO m => DB -> TxTable -> m [BatchOp]
txOps db t = map (uncurry f) <$> liftIO (H.toList t)
where
f k (Just t) = insertOpCF (txCF db) (TxKey k) t
f k Nothing = deleteOpCF (txCF db) (TxKey k)
balOps :: MonadIO m => DB -> BalanceTable -> m [BatchOp]
balOps db t = map (uncurry f) <$> liftIO (H.toList t)
where
f a (Just b) = insertOpCF (balanceCF db) (BalKey a) (balanceToVal b)
f a Nothing = deleteOpCF (balanceCF db) (BalKey a)
addrTxOps :: MonadIO m => DB -> AddrTxTable -> m [BatchOp]
addrTxOps db t = map (uncurry f) <$> liftIO (H.toList t)
where
f (a, t) (Just ()) = insertOpCF (addrTxCF db) (AddrTxKey a t) ()
f (a, t) Nothing = deleteOpCF (addrTxCF db) (AddrTxKey a t)
addrOutOps :: MonadIO m => DB -> AddrOutTable -> m [BatchOp]
addrOutOps db t = map (uncurry f) <$> liftIO (H.toList t)
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 :: MonadIO m => MempoolTable -> m [BatchOp]
mempoolOp t =
return . insertOp MemKey . sortOn Down . map swap <$> liftIO (H.toList t)
unspentOps :: MonadIO m => DB -> UnspentTable -> m [BatchOp]
unspentOps db t = map (uncurry f) <$> liftIO (H.toList t)
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 (liftIO . readIORef . 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 = s} ->
liftIO $ map swap <$> H.toList (hMempool s)
newMemory :: MonadIO m => [(UnixTime, TxHash)] -> m Memory
newMemory mempool = do
hNet <- newIORef Nothing
hBest <- newIORef Nothing
hBlock <- liftIO H.new
hHeight <- liftIO H.new
hTx <- liftIO H.new
hUnspent <- liftIO H.new
hBalance <- liftIO H.new
hAddrTx <- liftIO H.new
hAddrOut <- liftIO H.new
hMempool <- liftIO $ H.fromList (map swap mempool)
return Memory {..}
getBestBlockH :: Memory -> IO (Maybe (Maybe BlockHash))
getBestBlockH = readIORef . hBest
getBlocksAtHeightH :: BlockHeight -> Memory -> IO (Maybe [BlockHash])
getBlocksAtHeightH h s = H.lookup (hHeight s) h
getBlockH :: BlockHash -> Memory -> IO (Maybe (Maybe BlockData))
getBlockH h s = H.lookup (hBlock s) h
getTxDataH :: TxHash -> Memory -> IO (Maybe (Maybe TxData))
getTxDataH t s = H.lookup (hTx s) t
getSpenderH :: OutPoint -> Memory -> IO (Maybe (Maybe Spender))
getSpenderH op s = do
fmap (join . fmap f) <$> getTxDataH (outPointHash op) s
where
f = IntMap.lookup (fromIntegral (outPointIndex op)) . txDataSpenders
getBalanceH :: Address -> Memory -> IO (Maybe (Maybe Balance))
getBalanceH a s = H.lookup (hBalance s) a
getMempoolH :: Memory -> IO [(UnixTime, TxHash)]
getMempoolH s = sortOn Down . map swap <$> H.toList (hMempool s)
getUnspentH :: OutPoint -> Memory -> IO (Maybe (Maybe Unspent))
getUnspentH p s = H.lookup (hUnspent s) p