packages feed

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)}