haskoin-store-0.33.0: src/Haskoin/Store/Database/Writer.hs
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
module Haskoin.Store.Database.Writer
( WriterT
, MemoryTx
, runWriter
, runTx
) where
import Control.Applicative ((<|>))
import Control.DeepSeq (NFData)
import Control.Monad.Reader (ReaderT (..))
import qualified Control.Monad.Reader as R
import Control.Monad.Trans.Maybe (MaybeT (..), runMaybeT)
import qualified Data.ByteString.Short as B.Short
import Data.Hashable (Hashable)
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as M
import Data.Maybe (maybeToList)
import Database.RocksDB (BatchOp)
import Database.RocksDB.Query (deleteOp, insertOp, writeBatch)
import GHC.Generics (Generic)
import Haskoin (Address, BlockHash, BlockHeight,
Network, OutPoint (..), TxHash,
headerHash, txHash)
import Haskoin.Store.Common
import Haskoin.Store.Data (Balance (..), BlockData (..),
BlockRef (..), Spender,
TxData (..), TxRef (..),
Unspent (..))
import Haskoin.Store.Database.Reader
import Haskoin.Store.Database.Types
import UnliftIO (MonadIO, STM, TVar, atomically,
modifyTVar, newTVarIO, readTVar,
readTVarIO)
data Dirty a = Modified a | Deleted
deriving (Eq, Show, Generic, Hashable, NFData)
instance Functor Dirty where
fmap _ Deleted = Deleted
fmap f (Modified a) = Modified (f a)
data Writer = Writer { getReader :: !DatabaseReader
, getState :: !(TVar Memory) }
type MemoryTx = ReaderT (TVar Memory) STM
type WriterT = ReaderT Writer
instance MonadIO m => StoreReadBase (WriterT m) where
getNetwork =
R.ask >>= getNetworkI
getBestBlock =
R.ask >>= getBestBlockI
getBlocksAtHeight h =
R.ask >>= getBlocksAtHeightI h
getBlock b =
R.ask >>= getBlockI b
getTxData t =
R.ask >>= getTxDataI t
getSpender p =
R.ask >>= getSpenderI p
getUnspent a =
R.ask >>= getUnspentI a
getBalance a =
R.ask >>= getBalanceI a
getMempool =
R.ask >>= getMempoolI
data Memory = Memory
{ hNet
:: !Network
, hBest
:: !(Maybe BlockHash)
, hBlock
:: !(HashMap BlockHash BlockData)
, hHeight
:: !(HashMap BlockHeight [BlockHash])
, hTx
:: !(HashMap TxHash TxData)
, hSpender
:: !(HashMap OutPoint (Dirty Spender))
, hUnspent
:: !(HashMap OutPoint (Dirty UnspentVal))
, hBalance
:: !(HashMap Address BalVal)
, hAddrTx
:: !(HashMap (Address, TxRef) (Dirty ()))
, hAddrOut
:: !(HashMap (Address, BlockRef, OutPoint) (Dirty OutVal))
, hMempool
:: !(Maybe [TxRef])
} deriving (Eq, Show)
instance StoreWrite MemoryTx where
setBest h =
ReaderT $ \v -> modifyTVar v $
setBestH h
insertBlock b =
ReaderT $ \v -> modifyTVar v $
insertBlockH b
setBlocksAtHeight h g =
ReaderT $ \v -> modifyTVar v $
setBlocksAtHeightH h g
insertTx t =
ReaderT $ \v -> modifyTVar v $
insertTxH t
insertSpender p s =
ReaderT $ \v -> modifyTVar v $
insertSpenderH p s
deleteSpender p =
ReaderT $ \v -> modifyTVar v $
deleteSpenderH p
insertAddrTx a t =
ReaderT $ \v -> modifyTVar v $
insertAddrTxH a t
deleteAddrTx a t =
ReaderT $ \v -> modifyTVar v $
deleteAddrTxH a t
insertAddrUnspent a u =
ReaderT $ \v -> modifyTVar v $
insertAddrUnspentH a u
deleteAddrUnspent a u =
ReaderT $ \v -> modifyTVar v $
deleteAddrUnspentH a u
setMempool xs =
ReaderT $ \v -> modifyTVar v $
setMempoolH xs
setBalance b =
ReaderT $ \v -> modifyTVar v $
setBalanceH b
insertUnspent h =
ReaderT $ \v -> modifyTVar v $
insertUnspentH h
deleteUnspent p =
ReaderT $ \v -> modifyTVar v $
deleteUnspentH p
instance StoreReadBase MemoryTx where
getNetwork =
ReaderT $ fmap hNet . readTVar
getBestBlock =
ReaderT $ \v -> getBestH <$> readTVar v >>= \case
Nothing -> error "Best block not set in STM"
Just b -> return (Just b)
getBlocksAtHeight h =
ReaderT $ \v -> getBlocksAtHeightH h <$> readTVar v >>= \case
Nothing -> error "Blocks at height not set in STM"
Just hs -> return hs
getBlock h =
ReaderT $ \v -> getBlockH h <$> readTVar v >>= \case
Nothing -> error "Block not set in STM"
Just b -> return (Just b)
getTxData t =
ReaderT $ \v -> getTxDataH t <$> readTVar v >>= \case
Nothing -> error "Tx data not set in STM"
Just d -> return (Just d)
getSpender op =
ReaderT $ \v -> do
m <- getSpenderH op <$> readTVar v
case m of
Just (Modified s) -> return (Just s)
Just Deleted -> return Nothing
Nothing -> return Nothing
getUnspent op =
ReaderT $ \v -> do
m <- getUnspentH op <$> readTVar v
case m of
Just (Modified u) -> return (Just (valToUnspent op u))
Just Deleted -> return Nothing
Nothing -> return Nothing
getBalance a =
ReaderT $ \v -> getBalanceH a <$> readTVar v >>= \case
Just b -> return $ Just (valToBalance a b)
Nothing -> error "Balance not set in STM"
getMempool =
ReaderT $ \v -> getMempoolH <$> readTVar v >>= \case
Just mp -> return mp
Nothing -> error "Mempool not set in STM"
runWriter
:: MonadIO m
=> DatabaseReader
-> WriterT m a
-> m a
runWriter bdb@DatabaseReader{databaseHandle = db, databaseNetwork = net} f = do
hm <- newTVarIO (emptyMemory net)
x <- R.runReaderT f Writer {getReader = bdb, getState = hm}
ops <- hashMapOps <$> readTVarIO hm
writeBatch db ops
return x
hashMapOps :: Memory -> [BatchOp]
hashMapOps db =
bestBlockOp (hBest db) <>
blockHashOps (hBlock db) <>
blockHeightOps (hHeight db) <>
txOps (hTx db) <>
spenderOps (hSpender db) <>
balOps (hBalance db) <>
addrTxOps (hAddrTx db) <>
addrOutOps (hAddrOut db) <>
maybeToList (mempoolOp <$> hMempool db) <>
unspentOps (hUnspent db)
bestBlockOp :: Maybe BlockHash -> [BatchOp]
bestBlockOp Nothing = []
bestBlockOp (Just b) = [insertOp BestKey b]
blockHashOps :: HashMap BlockHash BlockData -> [BatchOp]
blockHashOps = map (uncurry f) . M.toList
where
f = insertOp . BlockKey
blockHeightOps :: HashMap BlockHeight [BlockHash] -> [BatchOp]
blockHeightOps = map (uncurry f) . M.toList
where
f = insertOp . HeightKey
txOps :: HashMap TxHash TxData -> [BatchOp]
txOps = map (uncurry f) . M.toList
where
f = insertOp . TxKey
spenderOps :: HashMap OutPoint (Dirty Spender)
-> [BatchOp]
spenderOps = map (uncurry f) . M.toList
where
f o (Modified s) =
insertOp (SpenderKey o) s
f o Deleted =
deleteOp (SpenderKey o)
balOps :: HashMap Address BalVal -> [BatchOp]
balOps = map (uncurry f) . M.toList
where
f = insertOp . BalKey
addrTxOps :: HashMap (Address, TxRef) (Dirty ()) -> [BatchOp]
addrTxOps = map (uncurry f) . M.toList
where
f (a, t) (Modified ()) = insertOp (AddrTxKey a t) ()
f (a, t) Deleted = deleteOp (AddrTxKey a t)
addrOutOps
:: HashMap (Address, BlockRef, OutPoint) (Dirty OutVal) -> [BatchOp]
addrOutOps = map (uncurry f) . M.toList
where
f (a, b, p) (Modified l) =
insertOp
(AddrOutKey { addrOutKeyA = a
, addrOutKeyB = b
, addrOutKeyP = p })
l
f (a, b, p) Deleted =
deleteOp AddrOutKey { addrOutKeyA = a
, addrOutKeyB = b
, addrOutKeyP = p }
mempoolOp :: [TxRef] -> BatchOp
mempoolOp =
insertOp MemKey .
map (\TxRef { txRefBlock = MemRef t
, txRefHash = h } -> (t, h))
unspentOps :: HashMap OutPoint (Dirty UnspentVal)
-> [BatchOp]
unspentOps = map (uncurry f) . M.toList
where
f p (Modified u) =
insertOp (UnspentKey p) u
f p Deleted =
deleteOp (UnspentKey p)
getNetworkI :: MonadIO m => Writer -> m Network
getNetworkI Writer {getState = hm} =
hNet <$> readTVarIO hm
getBestBlockI :: MonadIO m => Writer -> m (Maybe BlockHash)
getBestBlockI Writer {getState = hm, getReader = db} =
runMaybeT $ MaybeT f <|> MaybeT g
where
f = getBestBlockH <$> readTVarIO hm
g = withDatabaseReader db getBestBlock
getBlocksAtHeightI :: MonadIO m
=> BlockHeight
-> Writer
-> m [BlockHash]
getBlocksAtHeightI bh Writer {getState = hm, getReader = db} =
getBlocksAtHeightH bh <$> readTVarIO hm >>= \case
Just bs -> return bs
Nothing -> withDatabaseReader db $ getBlocksAtHeight bh
getBlockI :: MonadIO m
=> BlockHash
-> Writer
-> m (Maybe BlockData)
getBlockI bh Writer {getReader = db, getState = hm} =
runMaybeT $ MaybeT f <|> MaybeT g
where
f = getBlockH bh <$> readTVarIO hm
g = withDatabaseReader db $ getBlock bh
getTxDataI :: MonadIO m
=> TxHash
-> Writer
-> m (Maybe TxData)
getTxDataI th Writer {getReader = db, getState = hm} =
runMaybeT $ MaybeT f <|> MaybeT g
where
f = getTxDataH th <$> readTVarIO hm
g = withDatabaseReader db $ getTxData th
getSpenderI :: MonadIO m => OutPoint -> Writer -> m (Maybe Spender)
getSpenderI op Writer {getReader = db, getState = hm} =
getSpenderH op <$> readTVarIO hm >>= \case
Just (Modified s) -> return (Just s)
Just Deleted -> return Nothing
Nothing -> withDatabaseReader db (getSpender op)
getBalanceI :: MonadIO m => Address -> Writer -> m (Maybe Balance)
getBalanceI a Writer {getReader = db, getState = hm} =
getBalanceH a <$> readTVarIO hm >>= \case
Just b -> return $ Just (valToBalance a b)
Nothing -> withDatabaseReader db $ getBalance a
getUnspentI :: MonadIO m
=> OutPoint
-> Writer
-> m (Maybe Unspent)
getUnspentI op Writer {getReader = db, getState = hm} =
getUnspentH op <$> readTVarIO hm >>= \case
Just (Modified u) -> return (Just (valToUnspent op u))
Just Deleted -> return Nothing
Nothing -> withDatabaseReader db (getUnspent op)
getMempoolI :: MonadIO m => Writer -> m [TxRef]
getMempoolI Writer {getState = hm, getReader = db} =
getMempoolH <$> readTVarIO hm >>= \case
Just xs -> return xs
Nothing -> withDatabaseReader db getMempool
runTx :: MonadIO m => MemoryTx a -> WriterT m a
runTx f = ReaderT $ atomically . runReaderT f . getState
emptyMemory :: Network -> Memory
emptyMemory net =
Memory { hNet = net
, 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 = Nothing
}
getBestBlockH :: Memory -> Maybe BlockHash
getBestBlockH = hBest
getBlocksAtHeightH :: BlockHeight -> Memory -> Maybe [BlockHash]
getBlocksAtHeightH h = M.lookup h . hHeight
getBlockH :: BlockHash -> Memory -> Maybe BlockData
getBlockH h = M.lookup h . hBlock
getTxDataH :: TxHash -> Memory -> Maybe TxData
getTxDataH t = M.lookup t . hTx
getSpenderH :: OutPoint -> Memory -> Maybe (Dirty Spender)
getSpenderH op db = M.lookup op (hSpender db)
getBalanceH :: Address -> Memory -> Maybe BalVal
getBalanceH a = M.lookup a . hBalance
getMempoolH :: Memory -> Maybe [TxRef]
getMempoolH = hMempool
getBestH :: Memory -> Maybe BlockHash
getBestH = hBest
setBestH :: BlockHash -> Memory -> Memory
setBestH h db = db {hBest = Just h}
insertBlockH :: BlockData -> Memory -> Memory
insertBlockH bd db =
db { hBlock =
M.insert
(headerHash (blockDataHeader bd))
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)) tx (hTx db)}
insertSpenderH :: OutPoint -> Spender -> Memory -> Memory
insertSpenderH op s db =
db { hSpender = M.insert op (Modified s) (hSpender db) }
deleteSpenderH :: OutPoint -> Memory -> Memory
deleteSpenderH op db =
db { hSpender = M.insert op Deleted (hSpender db) }
setBalanceH :: Balance -> Memory -> Memory
setBalanceH bal db =
db {hBalance = M.insert (balanceAddress bal) b (hBalance db)}
where
b = balanceToVal bal
insertAddrTxH :: Address -> TxRef -> Memory -> Memory
insertAddrTxH a tr db =
db { hAddrTx = M.insert (a, tr) (Modified ()) (hAddrTx db) }
deleteAddrTxH :: Address -> TxRef -> Memory -> Memory
deleteAddrTxH a tr db =
db { hAddrTx = M.insert (a, tr) Deleted (hAddrTx db) }
insertAddrUnspentH :: Address -> Unspent -> Memory -> Memory
insertAddrUnspentH a u db =
let k = (a, unspentBlock u, unspentPoint u)
v = OutVal { outValAmount = unspentAmount u
, outValScript = B.Short.fromShort (unspentScript u) }
in db { hAddrOut = M.insert k (Modified 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 Deleted (hAddrOut db) }
setMempoolH :: [TxRef] -> Memory -> Memory
setMempoolH xs db = db { hMempool = Just xs }
getUnspentH :: OutPoint -> Memory -> Maybe (Dirty UnspentVal)
getUnspentH op db = M.lookup op (hUnspent db)
insertUnspentH :: Unspent -> Memory -> Memory
insertUnspentH u db =
let (k, v) = unspentToVal u
in db { hUnspent = M.insert k (Modified v) (hUnspent db) }
deleteUnspentH :: OutPoint -> Memory -> Memory
deleteUnspentH op db =
db { hUnspent = M.insert op Deleted (hUnspent db) }