haskoin-store-1.0.0: src/Haskoin/Store/Database/Writer.hs
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE NoFieldSelectors #-}
module Haskoin.Store.Database.Writer (WriterT, runWriter) where
import Control.Monad (join)
import Control.Monad.Reader (ReaderT (..))
import Control.Monad.Reader qualified as R
import Data.HashMap.Strict (HashMap)
import Data.HashMap.Strict qualified as M
import Data.HashTable.IO qualified as H
import Data.IntMap.Strict qualified 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,
Ctx,
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
{ reader :: !DatabaseReader,
memory :: !Memory
}
type WriterT = ReaderT Writer
instance (MonadIO m) => StoreReadBase (WriterT m) where
getNetwork = R.asks (.reader.net)
getCtx = R.asks (.reader.ctx)
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
{ net :: !Network,
ctx :: !Ctx,
best :: !BestRef,
blockTable :: !BlockTable,
heightTable :: !HeightTable,
txTable :: !TxTable,
unspentTable :: !UnspentTable,
balanceTable :: !BalanceTable,
addressTable :: !AddrTxTable,
outputTable :: !AddrOutTable,
mempoolTable :: !MempoolTable
}
instance (MonadIO m) => StoreWrite (WriterT m) where
setBest h =
ReaderT $ \Writer {memory = Memory {..}} ->
liftIO $ writeIORef best (Just (Just h))
insertBlock b =
ReaderT $ \Writer {memory = Memory {..}} ->
liftIO $ H.insert blockTable (headerHash b.header) (Just b)
setBlocksAtHeight h g =
ReaderT $ \Writer {memory = Memory {..}} ->
liftIO $ H.insert heightTable g h
insertTx t =
ReaderT $ \Writer {memory = Memory {..}} ->
liftIO $ H.insert txTable (txHash t.tx) (Just t)
insertAddrTx a t =
ReaderT $ \Writer {memory = Memory {..}} ->
liftIO $ H.insert addressTable (a, t) (Just ())
deleteAddrTx a t =
ReaderT $ \Writer {memory = Memory {..}} ->
liftIO $ H.insert addressTable (a, t) Nothing
insertAddrUnspent a u =
ReaderT $ \Writer {memory = Memory {..}} ->
liftIO $ H.insert outputTable k (Just v)
where
k = (a, u.block, u.outpoint)
v = OutVal {value = u.value, script = u.script}
deleteAddrUnspent a u =
ReaderT $ \Writer {memory = Memory {..}} ->
liftIO $ H.insert outputTable k Nothing
where
k = (a, u.block, u.outpoint)
addToMempool x t =
ReaderT $ \Writer {memory = Memory {..}} ->
liftIO $ H.insert mempoolTable x t
deleteFromMempool x =
ReaderT $ \Writer {memory = Memory {..}} ->
liftIO $ H.delete mempoolTable x
setBalance b =
ReaderT $ \Writer {memory = Memory {..}} ->
liftIO $ H.insert balanceTable b.address (Just b)
insertUnspent u =
ReaderT $ \Writer {memory = Memory {..}} ->
liftIO $ H.insert unspentTable (fst (unspentToVal u)) (Just u)
deleteUnspent p =
ReaderT $ \Writer {memory = Memory {..}} ->
liftIO $ H.insert unspentTable p Nothing
getLayered ::
(MonadIO m) =>
(Memory -> IO (Maybe a)) ->
DatabaseReaderT m a ->
WriterT m a
getLayered f g =
ReaderT $ \Writer {reader = db, memory = s} ->
liftIO (f s) >>= \case
Just x -> return x
Nothing -> runReaderT g db
runWriter ::
(MonadIO m) =>
Network ->
Ctx ->
DatabaseReader ->
WriterT m a ->
m a
runWriter net ctx bdb@DatabaseReader {db} f = do
mempool <- runReaderT getMempool bdb
hm <- newMemory net ctx mempool
x <- R.runReaderT f Writer {reader = bdb, memory = hm}
ops <- hashMapOps db hm
writeBatch db ops
return x
hashMapOps :: (MonadIO m) => DB -> Memory -> m [BatchOp]
hashMapOps db Memory {..} =
mconcat
<$> sequence
[ bestBlockOp best,
blockHashOps db blockTable,
blockHeightOps db heightTable,
txOps db txTable,
balOps db balanceTable,
addrTxOps db addressTable,
addrOutOps db outputTable,
mempoolOp mempoolTable,
unspentOps db unspentTable
]
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
{ address = a,
block = b,
outpoint = p
}
)
l
f (a, b, p) Nothing =
deleteOpCF
(addrOutCF db)
AddrOutKey
{ address = a,
block = b,
outpoint = 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)
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 {memory = Memory {..}} ->
liftIO $ map swap <$> H.toList mempoolTable
newMemory ::
(MonadIO m) =>
Network ->
Ctx ->
[(UnixTime, TxHash)] ->
m Memory
newMemory net ctx mempool = do
best <- newIORef Nothing
blockTable <- liftIO H.new
heightTable <- liftIO H.new
txTable <- liftIO H.new
unspentTable <- liftIO H.new
balanceTable <- liftIO H.new
addressTable <- liftIO H.new
outputTable <- liftIO H.new
mempoolTable <- liftIO $ H.fromList (map swap mempool)
return Memory {..}
getNetworkH :: (Monad m) => Memory -> m Network
getNetworkH Memory {net} = return net
getCtxH :: (Monad m) => Memory -> m Ctx
getCtxH Memory {ctx} = return ctx
getBestBlockH :: Memory -> IO (Maybe (Maybe BlockHash))
getBestBlockH = readIORef . (.best)
getBlocksAtHeightH :: BlockHeight -> Memory -> IO (Maybe [BlockHash])
getBlocksAtHeightH h s = H.lookup s.heightTable h
getBlockH :: BlockHash -> Memory -> IO (Maybe (Maybe BlockData))
getBlockH h s = H.lookup s.blockTable h
getTxDataH :: TxHash -> Memory -> IO (Maybe (Maybe TxData))
getTxDataH t s = H.lookup s.txTable t
getSpenderH :: OutPoint -> Memory -> IO (Maybe (Maybe Spender))
getSpenderH op s = do
fmap (f =<<) <$> getTxDataH op.hash s
where
f = IntMap.lookup (fromIntegral op.index) . (.spenders)
getBalanceH :: Address -> Memory -> IO (Maybe (Maybe Balance))
getBalanceH a s = H.lookup s.balanceTable a
getMempoolH :: Memory -> IO [(UnixTime, TxHash)]
getMempoolH Memory {..} = sortOn Down . map swap <$> H.toList mempoolTable
getUnspentH :: OutPoint -> Memory -> IO (Maybe (Maybe Unspent))
getUnspentH p Memory {..} = H.lookup unspentTable p