{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE ScopedTypeVariables #-} {-# OPTIONS_GHC -Wno-orphans #-} module Network.Haskoin.Store.Data.HashMap where import Conduit import Control.Monad import qualified Data.ByteString.Short as B.Short import Data.Function import Data.HashMap.Strict (HashMap) import qualified Data.HashMap.Strict as M import Data.IntMap.Strict (IntMap) import qualified Data.IntMap.Strict as I import Data.List import Data.Maybe import Haskoin import Network.Haskoin.Store.Data import Network.Haskoin.Store.Data.KeyValue import UnliftIO type UnspentMap = HashMap TxHash (IntMap Unspent) type BalanceMap = (HashMap Address Balance, [Address]) data HashMapDB = HashMapDB { hBest :: !(Maybe BlockHash) , hBlock :: !(HashMap BlockHash BlockData) , hHeight :: !(HashMap BlockHeight [BlockHash]) , hTx :: !(HashMap TxHash TxData) , hSpender :: !(HashMap TxHash (IntMap (Maybe Spender))) , hUnspent :: !(HashMap TxHash (IntMap (Maybe Unspent))) , hBalance :: !(HashMap Address (Maybe BalVal)) , hAddrTx :: !(HashMap Address (HashMap BlockRef (HashMap TxHash Bool))) , hAddrOut :: !(HashMap Address (HashMap BlockRef (HashMap OutPoint (Maybe OutVal)))) , hMempool :: !(HashMap PreciseUnixTime (HashMap TxHash Bool)) , hInit :: !Bool } deriving (Eq, Show) emptyHashMapDB :: HashMapDB emptyHashMapDB = HashMapDB { 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.empty , hInit = False } isInitializedH :: HashMapDB -> Either InitException Bool isInitializedH = Right . hInit getBestBlockH :: HashMapDB -> Maybe BlockHash getBestBlockH = hBest getBlocksAtHeightH :: HashMapDB -> BlockHeight -> [BlockHash] getBlocksAtHeightH db h = M.lookupDefault [] h (hHeight db) getBlockH :: HashMapDB -> BlockHash -> Maybe BlockData getBlockH db h = M.lookup h (hBlock db) getTxDataH :: HashMapDB -> TxHash -> Maybe TxData getTxDataH db t = M.lookup t (hTx db) getSpenderH :: HashMapDB -> OutPoint -> Maybe (Maybe Spender) getSpenderH db op = do m <- M.lookup (outPointHash op) (hSpender db) I.lookup (fromIntegral (outPointIndex op)) m getSpendersH :: HashMapDB -> TxHash -> IntMap (Maybe Spender) getSpendersH db t = M.lookupDefault I.empty t (hSpender db) getBalanceH :: HashMapDB -> Address -> Maybe (Maybe Balance) getBalanceH db a = fmap f <$> M.lookup a (hBalance db) where f b = Balance { balanceAddress = a , balanceAmount = balValAmount b , balanceZero = balValZero b , balanceCount = balValCount b } getMempoolH :: HashMapDB -> HashMap PreciseUnixTime (HashMap TxHash Bool) getMempoolH = hMempool getAddressTxsH :: HashMapDB -> Address -> [Maybe AddressTx] getAddressTxsH db a = concatMap (uncurry f) . M.toList $ M.lookupDefault M.empty a (hAddrTx db) where f b hm = map (uncurry (g b)) $ M.toList hm g b h True = Just AddressTx {addressTxAddress = a, addressTxBlock = b, addressTxHash = h} g _ _ False = Nothing getAddressUnspentsH :: HashMapDB -> Address -> [Maybe Unspent] getAddressUnspentsH db a = concatMap (uncurry f) . M.toList $ M.lookupDefault M.empty a (hAddrOut db) where f b hm = map (uncurry (g b)) $ M.toList hm g b p (Just u) = Just Unspent { unspentBlock = b , unspentAmount = outValAmount u , unspentScript = B.Short.toShort (outValScript u) , unspentPoint = p } g _ _ Nothing = Nothing setInitH :: HashMapDB -> HashMapDB setInitH db = db {hInit = True} setBestH :: BlockHash -> HashMapDB -> HashMapDB setBestH h db = db {hBest = Just h} insertBlockH :: BlockData -> HashMapDB -> HashMapDB insertBlockH bd db = db {hBlock = M.insert (headerHash (blockDataHeader bd)) bd (hBlock db)} insertAtHeightH :: BlockHash -> BlockHeight -> HashMapDB -> HashMapDB insertAtHeightH h g db = db {hHeight = M.insertWith f g [h] (hHeight db)} where f xs ys = nub $ xs <> ys insertTxH :: TxData -> HashMapDB -> HashMapDB insertTxH tx db = db {hTx = M.insert (txHash (txData tx)) tx (hTx db)} insertSpenderH :: OutPoint -> Spender -> HashMapDB -> HashMapDB insertSpenderH op s db = db { hSpender = M.insertWith (<>) (outPointHash op) (I.singleton (fromIntegral (outPointIndex op)) (Just s)) (hSpender db) } deleteSpenderH :: OutPoint -> HashMapDB -> HashMapDB deleteSpenderH op db = db { hSpender = M.insertWith (<>) (outPointHash op) (I.singleton (fromIntegral (outPointIndex op)) Nothing) (hSpender db) } setBalanceH :: Balance -> HashMapDB -> HashMapDB setBalanceH b db = db {hBalance = M.insert (balanceAddress b) x (hBalance db)} where x | balanceCount b == 0 = Nothing | otherwise = Just BalVal { balValAmount = balanceAmount b , balValZero = balanceZero b , balValCount = balanceCount b } insertAddrTxH :: AddressTx -> HashMapDB -> HashMapDB insertAddrTxH a db = let s = M.singleton (addressTxAddress a) (M.singleton (addressTxBlock a) (M.singleton (addressTxHash a) True)) in db {hAddrTx = M.unionWith (M.unionWith M.union) s (hAddrTx db)} removeAddrTxH :: AddressTx -> HashMapDB -> HashMapDB removeAddrTxH a db = let s = M.singleton (addressTxAddress a) (M.singleton (addressTxBlock a) (M.singleton (addressTxHash a) False)) in db {hAddrTx = M.unionWith (M.unionWith M.union) s (hAddrTx db)} insertAddrUnspentH :: Address -> Unspent -> HashMapDB -> HashMapDB insertAddrUnspentH a u db = let uns = OutVal { outValAmount = unspentAmount u , outValScript = B.Short.fromShort (unspentScript u) } s = M.singleton a (M.singleton (unspentBlock u) (M.singleton (unspentPoint u) (Just uns))) in db {hAddrOut = M.unionWith (M.unionWith M.union) s (hAddrOut db)} removeAddrUnspentH :: Address -> Unspent -> HashMapDB -> HashMapDB removeAddrUnspentH a u db = let s = M.singleton a (M.singleton (unspentBlock u) (M.singleton (unspentPoint u) Nothing)) in db {hAddrOut = M.unionWith (M.unionWith M.union) s (hAddrOut db)} insertMempoolTxH :: TxHash -> PreciseUnixTime -> HashMapDB -> HashMapDB insertMempoolTxH h u db = let s = M.singleton u (M.singleton h True) in db {hMempool = M.unionWith M.union s (hMempool db)} deleteMempoolTxH :: TxHash -> PreciseUnixTime -> HashMapDB -> HashMapDB deleteMempoolTxH h u db = let s = M.singleton u (M.singleton h False) in db {hMempool = M.unionWith M.union s (hMempool db)} getUnspentH :: HashMapDB -> OutPoint -> Maybe (Maybe Unspent) getUnspentH db op = do m <- M.lookup (outPointHash op) (hUnspent db) I.lookup (fromIntegral (outPointIndex op)) m addUnspentH :: Unspent -> HashMapDB -> HashMapDB addUnspentH u db = db { hUnspent = M.insertWith (<>) (outPointHash (unspentPoint u)) (I.singleton (fromIntegral (outPointIndex (unspentPoint u))) (Just u)) (hUnspent db) } delUnspentH :: OutPoint -> HashMapDB -> HashMapDB delUnspentH op db = db { hUnspent = M.insertWith (<>) (outPointHash op) (I.singleton (fromIntegral (outPointIndex op)) Nothing) (hUnspent db) } instance Applicative m => StoreRead HashMapDB m where isInitialized = pure . isInitializedH getBestBlock = pure . getBestBlockH getBlocksAtHeight db = pure . getBlocksAtHeightH db getBlock db = pure . getBlockH db getTxData db = pure . getTxDataH db getSpenders db = pure . I.map fromJust . I.filter isJust . getSpendersH db getSpender db = pure . join . getSpenderH db instance Applicative m => BalanceRead HashMapDB m where getBalance db = pure . join . getBalanceH db instance Applicative m => UnspentRead HashMapDB m where getUnspent db = pure . join . getUnspentH db instance Monad m => StoreStream HashMapDB m where getMempool db = let ls = M.toList . M.map (M.keys . M.filter id) $ getMempoolH db in yieldMany [(u, h) | (u, hs) <- ls, h <- hs] getAddressTxs db = yieldMany . sort . catMaybes . getAddressTxsH db getAddressUnspents db = yieldMany . sort . catMaybes . getAddressUnspentsH db instance MonadIO m => StoreRead (TVar HashMapDB) m where isInitialized v = readTVarIO v >>= isInitialized getBestBlock v = readTVarIO v >>= getBestBlock getBlocksAtHeight v h = readTVarIO v >>= \db -> getBlocksAtHeight db h getBlock v b = readTVarIO v >>= \db -> getBlock db b getTxData v t = readTVarIO v >>= \db -> getTxData db t getSpender v t = readTVarIO v >>= \db -> getSpender db t getSpenders v t = readTVarIO v >>= \db -> getSpenders db t instance MonadIO m => BalanceRead (TVar HashMapDB) m where getBalance v a = readTVarIO v >>= \db -> getBalance db a instance MonadIO m => UnspentRead (TVar HashMapDB) m where getUnspent v op = readTVarIO v >>= \db -> getUnspent db op instance MonadIO m => BalanceWrite (TVar HashMapDB) m where setBalance v b = atomically $ modifyTVar v (setBalanceH b) instance MonadIO m => StoreStream (TVar HashMapDB) m where getMempool v = readTVarIO v >>= getMempool getAddressTxs v a = readTVarIO v >>= \db -> getAddressTxs db a getAddressUnspents v a = readTVarIO v >>= \db -> getAddressUnspents db a instance StoreWrite ((HashMapDB -> HashMapDB) -> m ()) m where setInit f = f setInitH setBest f = f . setBestH insertBlock f = f . insertBlockH insertAtHeight f h = f . insertAtHeightH h insertTx f = f . insertTxH insertSpender f p = f . insertSpenderH p deleteSpender f = f . deleteSpenderH insertAddrTx f = f . insertAddrTxH removeAddrTx f = f . removeAddrTxH insertAddrUnspent f a = f . insertAddrUnspentH a removeAddrUnspent f a = f . removeAddrUnspentH a insertMempoolTx f h = f . insertMempoolTxH h deleteMempoolTx f h = f . deleteMempoolTxH h instance Applicative m => UnspentWrite ((HashMapDB -> HashMapDB) -> m ()) m where addUnspent f = f . addUnspentH delUnspent f = f . delUnspentH instance MonadIO m => StoreWrite (TVar HashMapDB) m where setInit v = atomically $ setInit (modifyTVar v) setBest v = atomically . setBest (modifyTVar v) insertBlock v = atomically . insertBlock (modifyTVar v) insertAtHeight v h = atomically . insertAtHeight (modifyTVar v) h insertTx v = atomically . insertTx (modifyTVar v) insertSpender v p = atomically . insertSpender (modifyTVar v) p deleteSpender v = atomically . deleteSpender (modifyTVar v) insertAddrTx v = atomically . insertAddrTx (modifyTVar v) removeAddrTx v = atomically . removeAddrTx (modifyTVar v) insertAddrUnspent v a = atomically . insertAddrUnspent (modifyTVar v) a removeAddrUnspent v a = atomically . removeAddrUnspent (modifyTVar v) a insertMempoolTx v h = atomically . insertMempoolTx (modifyTVar v) h deleteMempoolTx v h = atomically . deleteMempoolTx (modifyTVar v) h instance MonadIO m => UnspentWrite (TVar HashMapDB) m where addUnspent v = atomically . addUnspent (modifyTVar v) delUnspent v = atomically . delUnspent (modifyTVar v) instance Applicative m => UnspentRead UnspentMap m where getUnspent um op = pure $ do m <- M.lookup (outPointHash op) um I.lookup (fromIntegral (outPointIndex op)) m instance MonadIO m => UnspentRead (TVar UnspentMap) m where getUnspent v op = readTVarIO v >>= \um -> getUnspent um op instance Applicative m => UnspentWrite ((UnspentMap -> UnspentMap) -> m ()) m where addUnspent f u = f $ M.insertWith (<>) (outPointHash (unspentPoint u)) (I.singleton (fromIntegral (outPointIndex (unspentPoint u))) u) delUnspent f op = f $ M.update g (outPointHash op) where g m = let n = I.delete (fromIntegral (outPointIndex op)) m in if I.null n then Nothing else Just n pruneUnspent f = f $ \um -> if M.size um > 2000 * 1000 then let g is = unspentBlock (head (I.elems is)) ls = sortBy (compare `on` (g . snd)) (filter (not . I.null . snd) (M.toList um)) in M.fromList (drop (1000 * 1000) ls) else um instance MonadIO m => UnspentWrite (TVar UnspentMap) m where addUnspent v = atomically . addUnspent (modifyTVar v) delUnspent v = atomically . delUnspent (modifyTVar v) pruneUnspent = atomically . pruneUnspent . modifyTVar instance Applicative m => BalanceRead BalanceMap m where getBalance m a = pure $ M.lookup a (fst m) instance Applicative m => BalanceWrite ((BalanceMap -> BalanceMap) -> m ()) m where setBalance f b = f $ \(m, s) -> if balanceCount b == 0 then let m' = M.delete (balanceAddress b) m in (m', s) else let m' = M.insert (balanceAddress b) b m s' = balanceAddress b : s in (m', s') pruneBalance f = f $ \(m, s) -> if length s > 2000 * 1000 then let s' = take (1000 * 1000) s m' = M.fromList (mapMaybe (g m) s') in (m', s') else (m, s) where g m a = case M.lookup a m of Nothing -> Nothing Just b -> Just (a, b) instance MonadIO m => BalanceWrite (TVar BalanceMap) m where setBalance v = atomically . setBalance (modifyTVar v) pruneBalance = atomically . pruneBalance . modifyTVar instance MonadIO m => BalanceRead (TVar BalanceMap) m where getBalance v a = readTVarIO v >>= \m -> getBalance m a