diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,16 @@
 The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
 and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
 
+## 0.23.4
+### Added
+- Add extra debug logging for cache code.
+
+### Fixed
+- Fix a bug with xpub growing algorithm.
+
+### Changed
+- Use locks instead of transactions to update cache.
+
 ## 0.23.3
 ### Fixed
 - Reduce contention when many instances of Haskoin Store share a cache.
diff --git a/haskoin-store.cabal b/haskoin-store.cabal
--- a/haskoin-store.cabal
+++ b/haskoin-store.cabal
@@ -4,10 +4,10 @@
 --
 -- see: https://github.com/sol/hpack
 --
--- hash: ab4c33eb49a6c0ff991802e63a9a5ef7bf56cf2cb8c2b73b518b7ae7658c2f97
+-- hash: 1e674b620cac18d9bc1185ecf77f9c600b3b0c953483d8f0e4fe18fe4d3fe02e
 
 name:           haskoin-store
-version:        0.23.3
+version:        0.23.4
 synopsis:       Storage and index for Bitcoin and Bitcoin Cash
 description:    Store and index Bitcoin or Bitcoin Cash blocks, transactions, balances and unspent outputs.
                 All data is available via REST API in JSON or binary format.
diff --git a/src/Haskoin/Store/Cache.hs b/src/Haskoin/Store/Cache.hs
--- a/src/Haskoin/Store/Cache.hs
+++ b/src/Haskoin/Store/Cache.hs
@@ -24,9 +24,9 @@
     ) where
 
 import           Control.DeepSeq           (NFData)
-import           Control.Monad             (forM, forever, void)
-import           Control.Monad.Logger      (MonadLoggerIO, logErrorS, logInfoS,
-                                            logWarnS)
+import           Control.Monad             (forM, forM_, forever, void)
+import           Control.Monad.Logger      (MonadLoggerIO, logDebugS, logErrorS,
+                                            logInfoS, logWarnS)
 import           Control.Monad.Reader      (ReaderT (..), asks)
 import           Control.Monad.Trans       (lift)
 import           Control.Monad.Trans.Maybe (MaybeT (..), runMaybeT)
@@ -37,19 +37,19 @@
 import           Data.HashMap.Strict       (HashMap)
 import qualified Data.HashMap.Strict       as HashMap
 import qualified Data.IntMap.Strict        as IntMap
-import           Data.List                 (nub, sort)
+import           Data.List                 (nub, sort, (\\))
 import qualified Data.Map.Strict           as Map
 import           Data.Maybe                (catMaybes, mapMaybe)
 import           Data.Serialize            (decode, encode)
 import           Data.Serialize            (Serialize)
 import           Data.String.Conversions   (cs)
+import           Data.Text                 (Text)
 import           Data.Time.Clock.System    (getSystemTime, systemSeconds)
 import           Data.Word                 (Word64)
-import           Database.Redis            (Connection, Queued, Redis, RedisCtx,
-                                            RedisTx, Reply, TxResult (..),
+import           Database.Redis            (Connection, Redis, RedisCtx, Reply,
                                             checkedConnect, defaultConnectInfo,
-                                            hgetall, parseConnectInfo, watch,
-                                            zadd, zrangeWithscores,
+                                            hgetall, parseConnectInfo, zadd,
+                                            zrangeWithscores,
                                             zrangebyscoreWithscoresLimit, zrem)
 import qualified Database.Redis            as Redis
 import           GHC.Generics              (Generic)
@@ -62,7 +62,7 @@
                                             eitherToMaybe, headerHash,
                                             pathToList, scriptToAddressBS,
                                             txHash, txHashToHex, xPubAddr,
-                                            xPubCompatWitnessAddr,
+                                            xPubCompatWitnessAddr, xPubExport,
                                             xPubWitnessAddr)
 import           Haskoin.Node              (Chain, chainGetAncestor,
                                             chainGetBlock, chainGetSplitBlock)
@@ -77,8 +77,10 @@
                                             xPubBalsTxs, xPubBalsUnspents,
                                             xPubTxs)
 import           NQE                       (Inbox, Mailbox, receive)
+import           System.Random             (randomIO, randomRIO)
 import           UnliftIO                  (Exception, MonadIO, MonadUnliftIO,
-                                            liftIO, throwIO)
+                                            bracket, liftIO, throwIO)
+import           UnliftIO.Concurrent       (threadDelay)
 
 runRedis :: MonadIO m => Redis (Either Reply a) -> CacheT m a
 runRedis action =
@@ -87,23 +89,6 @@
             Right x -> return x
             Left e -> throwIO (RedisError e)
 
-runRedisTx ::
-       MonadIO m
-    => Redis (Either Reply b)
-    -> (b -> RedisTx (Queued a))
-    -> CacheT m (Maybe a)
-runRedisTx pre action =
-    asks cacheConn >>= \conn ->
-        liftIO (Redis.runRedis conn go) >>= \case
-            TxSuccess x -> return (Just x)
-            TxAborted -> return Nothing
-            TxError e -> throwIO (RedisTxError e)
-  where
-    go =
-        pre >>= \case
-            Left e -> throwIO (RedisError e)
-            Right y -> Redis.multiExec (action y)
-
 data CacheConfig =
     CacheConfig
         { cacheConn  :: !Connection
@@ -130,7 +115,8 @@
                      Right r -> return r
     liftIO (checkedConnect conninfo)
 
-instance (MonadLoggerIO m, StoreRead m) => StoreRead (CacheT m) where
+instance (MonadUnliftIO m, MonadLoggerIO m, StoreRead m) =>
+         StoreRead (CacheT m) where
     getNetwork = lift getNetwork
     getBestBlock = lift getBestBlock
     getBlocksAtHeight = lift . getBlocksAtHeight
@@ -167,31 +153,46 @@
 utxoPfx = "u"
 
 getXPubTxs ::
-       (MonadLoggerIO m, StoreRead m)
+       (MonadUnliftIO m, MonadLoggerIO m, StoreRead m)
     => XPubSpec
     -> Maybe BlockRef
     -> Offset
     -> Maybe Limit
     -> CacheT m [BlockTx]
-getXPubTxs xpub start offset limit =
+getXPubTxs xpub start offset limit = do
+    xpubtxt <- xpubText xpub
+    $(logDebugS) "Cache" $ "Getting xpub txs for " <> xpubtxt
     isXPubCached xpub >>= \case
         True -> do
             txs <- cacheGetXPubTxs xpub start offset limit
+            $(logDebugS) "Cache" $
+                "Returning " <> cs (show (length txs)) <>
+                " transactions for cached xpub: " <>
+                xpubtxt
             return txs
         False -> do
+            $(logDebugS) "Cache" $ "Caching new xpub " <> xpubtxt
             newXPubC xpub >>= \(bals, t) ->
                 if t
-                    then cacheGetXPubTxs xpub start offset limit
-                    else xPubBalsTxs bals start offset limit
+                    then do
+                        $(logDebugS) "Cache" $
+                            "Successfully cached xpub " <> xpubtxt
+                        cacheGetXPubTxs xpub start offset limit
+                    else do
+                        $(logDebugS) "Cache" $
+                            "Using DB to return txs for xpub " <> xpubtxt
+                        xPubBalsTxs bals start offset limit
 
 getXPubUnspents ::
-       (MonadLoggerIO m, StoreRead m)
+       (MonadUnliftIO m, MonadLoggerIO m, StoreRead m)
     => XPubSpec
     -> Maybe BlockRef
     -> Offset
     -> Maybe Limit
     -> CacheT m [XPubUnspent]
-getXPubUnspents xpub start offset limit =
+getXPubUnspents xpub start offset limit = do
+    xpubtxt <- xpubText xpub
+    $(logDebugS) "Cache" $ "Getting utxo for xpub " <> xpubtxt
     isXPubCached xpub >>= \case
         True -> do
             bals <- cacheGetXPubBalances xpub
@@ -199,8 +200,14 @@
         False -> do
             newXPubC xpub >>= \(bals, t) ->
                 if t
-                    then process bals
-                    else xPubBalsUnspents bals start offset limit
+                    then do
+                        $(logDebugS) "Cache" $
+                            "Successfully cached xpub " <> xpubtxt
+                        process bals
+                    else do
+                        $(logDebugS) "Cache" $
+                            "Using DB to return utxo for xpub " <> xpubtxt
+                        xPubBalsUnspents bals start offset limit
   where
     process bals = do
         ops <- map snd <$> cacheGetXPubUnspents xpub start offset limit
@@ -221,18 +228,29 @@
                 mapMaybe
                     (\(a, u) -> (\p -> XPubUnspent p u) <$> Map.lookup a addrmap)
                     addrutxo
+        xpubtxt <- xpubText xpub
+        $(logDebugS) "Cache" $
+            "Returning " <> cs (show (length xpubutxo)) <>
+            " utxos for cached xpub: " <>
+            xpubtxt
         return xpubutxo
 
 getXPubBalances ::
-       (MonadLoggerIO m, StoreRead m)
+       (MonadUnliftIO m, MonadLoggerIO m, StoreRead m)
     => XPubSpec
     -> CacheT m [XPubBal]
-getXPubBalances xpub =
+getXPubBalances xpub = do
+    xpubtxt <- xpubText xpub
+    $(logDebugS) "Cache" $ "Getting balances for xpub " <> xpubtxt
     isXPubCached xpub >>= \case
         True -> do
             bals <- cacheGetXPubBalances xpub
+            $(logDebugS) "Cache" $
+                "Returning " <> cs (show (length bals)) <> " balances for xpub " <>
+                xpubtxt
             return bals
         False -> do
+            $(logDebugS) "Cache" $ "Caching balances for xpub " <> xpubtxt
             fst <$> newXPubC xpub
 
 isXPubCached :: MonadIO m => XPubSpec -> CacheT m Bool
@@ -241,13 +259,6 @@
 redisIsXPubCached :: RedisCtx m f => XPubSpec -> m (f Bool)
 redisIsXPubCached xpub = Redis.exists (balancesPfx <> encode xpub)
 
-redisWatchXPubCached :: [XPubSpec] -> Redis (Either Reply [Bool])
-redisWatchXPubCached [] = return (pure [])
-redisWatchXPubCached xpubs = do
-    w <- watch $ map ((balancesPfx <>) . encode) xpubs
-    ys <- sequence <$> forM xpubs redisIsXPubCached
-    return $ w >> ys
-
 cacheGetXPubBalances :: MonadIO m => XPubSpec -> CacheT m [XPubBal]
 cacheGetXPubBalances xpub = do
     bals <- runRedis $ redisGetXPubBalances xpub
@@ -416,6 +427,9 @@
 mempoolSetKey :: ByteString
 mempoolSetKey = "mempool"
 
+deleteLockKey :: ByteString
+deleteLockKey = "deleted"
+
 addrPfx :: ByteString
 addrPfx = "a"
 
@@ -444,39 +458,76 @@
             x <- receive inbox
             cacheWriterReact x
 
-pruneDB :: (MonadLoggerIO m, StoreRead m) => CacheT m Integer
-pruneDB = do
-    x <- asks cacheMax
-    s <- runRedis Redis.dbsize
-    if s > x
-        then flush (s - x)
-        else return 0
+lockIt :: MonadIO m => CacheT m Bool
+lockIt = do
+    r <- liftIO randomIO
+    mi <-
+        runRedis $ do
+            let opts =
+                    Redis.SetOpts
+                        { Redis.setSeconds = Just 30
+                        , Redis.setMilliseconds = Nothing
+                        , Redis.setCondition = Just Redis.Nx
+                        }
+            a <- Redis.setOpts deleteLockKey (cs (show (r :: Integer))) opts
+            b <- Redis.get deleteLockKey
+            return $ a >> fmap (read . cs) <$> b
+    case mi of
+        Just i
+            | i == r -> return True
+        _ -> return False
+
+
+unlockIt :: MonadIO m => CacheT m ()
+unlockIt = runRedis (Redis.del [deleteLockKey]) >> return ()
+
+withLock :: MonadUnliftIO m => CacheT m a -> CacheT m (Maybe a)
+withLock f =
+    bracket lockIt (const unlockIt) $ \case
+        True -> Just <$> f
+        False -> return Nothing
+
+withLockWait :: MonadUnliftIO m => CacheT m a -> CacheT m a
+withLockWait f =
+    withLock f >>= \case
+        Just x -> return x
+        Nothing -> do
+            r <- liftIO $ randomRIO (500, 10000)
+            threadDelay r
+            withLockWait f
+
+pruneDB :: (MonadUnliftIO m, MonadLoggerIO m, StoreRead m) => CacheT m Integer
+pruneDB =
+    withLock go >>= \case
+        Nothing -> return 0
+        Just n -> return n
   where
-    flush n =
+    go = do
+        x <- asks cacheMax
+        s <- runRedis Redis.dbsize
+        if s > x
+            then flush (s - x)
+            else return 0
+    flush n = do
         case min 1000 (n `div` 64) of
             0 -> return 0
             x -> do
                 ks <-
                     fmap (map fst) . runRedis $
                     getFromSortedSet maxKey Nothing 0 (Just x)
+                $(logDebugS) "Cache" $
+                    "Pruning " <> cs (show (length ks)) <> " old xpubs"
                 delXPubKeys ks
 
-touchKeys :: MonadIO m => [XPubSpec] -> CacheT m Integer
+touchKeys :: MonadIO m => [XPubSpec] -> CacheT m ()
 touchKeys xpubs = do
     now <- systemSeconds <$> liftIO getSystemTime
-    m <-
-        runRedisTx (redisWatchXPubCached xpubs) $ \ys -> do
-            let xpubs' = map fst . filter snd $ zip xpubs ys
-            redisTouchKeys now xpubs'
-    case m of
-        Nothing -> touchKeys xpubs
-        Just x  -> return x
+    runRedis $ redisTouchKeys now xpubs
 
-redisTouchKeys ::
-       (Applicative f, RedisCtx m f, Real a) => a -> [XPubSpec] -> m (f Integer)
-redisTouchKeys _ [] = return (pure 0)
+redisTouchKeys :: (Monad f, RedisCtx m f, Real a) => a -> [XPubSpec] -> m (f ())
+redisTouchKeys _ [] = return $ return ()
 redisTouchKeys now xpubs =
-    Redis.zadd maxKey $ map ((realToFrac now, ) . encode) xpubs
+    void <$> Redis.zadd maxKey (map ((realToFrac now, ) . encode) xpubs)
 
 cacheWriterReact ::
        (MonadUnliftIO m, MonadLoggerIO m, StoreRead m)
@@ -490,37 +541,54 @@
 lenNotNull bals = length $ filter (not . nullBalance . xPubBal) bals
 
 newXPubC ::
-       (MonadLoggerIO m, StoreRead m)
+       (MonadUnliftIO m, MonadLoggerIO m, StoreRead m)
     => XPubSpec
     -> CacheT m ([XPubBal], Bool)
-newXPubC xpub = do
-    bals <- lift $ xPubBals xpub
-    x <- asks cacheMin
-    let n = lenNotNull bals
-    if x <= n
-        then do
-            go bals
-            return (bals, True)
-        else do
-            return (bals, False)
+newXPubC xpub =
+    withLockWait $ do
+        bals <- lift $ xPubBals xpub
+        x <- asks cacheMin
+        xpubtxt <- xpubText xpub
+        let n = lenNotNull bals
+        if x <= n
+            then do
+                go bals
+                return (bals, True)
+            else do
+                $(logDebugS) "Cache" $
+                    "Not caching xpub with " <> cs (show n) <>
+                    " used addresses: " <>
+                    xpubtxt
+                return (bals, False)
   where
     op XPubUnspent {xPubUnspent = u} = (unspentPoint u, unspentBlock u)
     go bals = do
+        xpubtxt <- xpubText xpub
+        $(logDebugS) "Cache" $
+            "Caching xpub with " <> cs (show (length bals)) <>
+            " addresses (used: " <>
+            cs (show (lenNotNull bals)) <>
+            "): " <>
+            xpubtxt
         utxo <- lift $ xPubUnspents xpub Nothing 0 Nothing
+        $(logDebugS) "Cache" $
+            "Caching xpub with " <> cs (show (length utxo)) <> " utxos: " <>
+            xpubtxt
         xtxs <- lift $ xPubTxs xpub Nothing 0 Nothing
+        $(logDebugS) "Cache" $
+            "Caching xpub with " <> cs (show (length xtxs)) <> " txs: " <>
+            xpubtxt
         now <- systemSeconds <$> liftIO getSystemTime
-        runRedisTx (redisWatchXPubCached [xpub]) $ \ts ->
-            if and ts
-                then return (pure ())
-                else do
-                    x <- redisAddXPubBalances xpub bals
-                    y <- redisAddXPubUnspents xpub (map op utxo)
-                    z <- redisAddXPubTxs xpub xtxs
-                    t <- redisTouchKeys now [xpub]
-                    return $ x >> y >> z >> t >> pure ()
+        runRedis $ do
+            b <- redisTouchKeys now [xpub]
+            c <- redisAddXPubBalances xpub bals
+            d <- redisAddXPubUnspents xpub (map op utxo)
+            e <- redisAddXPubTxs xpub xtxs
+            return $ b >> c >> d >> e >> return ()
 
-newBlockC :: (MonadLoggerIO m, StoreRead m) => CacheT m ()
+newBlockC :: (MonadUnliftIO m, MonadLoggerIO m, StoreRead m) => CacheT m ()
 newBlockC =
+    withLockWait $
     lift getBestBlock >>= \case
         Nothing -> $(logErrorS) "Cache" "Best block not set yet"
         Just newhead -> do
@@ -578,17 +646,17 @@
                 importBlockC (headerHash (nodeHeader a))
                 newBlockC
 
-newTxC :: (MonadLoggerIO m, StoreRead m) => TxHash -> CacheT m ()
+newTxC :: (MonadUnliftIO m, MonadLoggerIO m, StoreRead m) => TxHash -> CacheT m ()
 newTxC th =
+    withLockWait $
     lift (getTxData th) >>= \case
-        Just txd ->
-            importMultiTxC [txd] >>= \case
-                False -> newTxC th
-                True -> return ()
+        Just txd -> do
+            $(logDebugS) "Cache" $ "Importing transaction: " <> txHashToHex th
+            importMultiTxC [txd]
         Nothing ->
             $(logErrorS) "Cache" $ "Transaction not found: " <> txHashToHex th
 
-importBlockC :: (StoreRead m, MonadLoggerIO m) => BlockHash -> CacheT m ()
+importBlockC :: (MonadUnliftIO m, StoreRead m, MonadLoggerIO m) => BlockHash -> CacheT m ()
 importBlockC bh =
     lift (getBlock bh) >>= \case
         Nothing -> do
@@ -596,17 +664,19 @@
             throwIO . LogicError . cs $
                 "Could not get block: " <> blockHashToHex bh
         Just bd -> do
-            $(logInfoS) "Cache" $ "Importing block: " <> blockHashToHex bh
             go bd
   where
     go bd = do
         let ths = blockDataTxs bd
         tds <- sortTxData . catMaybes <$> mapM (lift . getTxData) ths
-        importMultiTxC tds >>= \case
-            False -> return ()
-            True -> cacheSetHead bh
+        $(logInfoS) "Cache" $
+            "Importing " <> cs (show (length tds)) <>
+            " transactions from block " <>
+            blockHashToHex bh
+        importMultiTxC tds
+        cacheSetHead bh
 
-removeHeadC :: (StoreRead m, MonadLoggerIO m) => CacheT m ()
+removeHeadC :: (StoreRead m, MonadUnliftIO m, MonadLoggerIO m) => CacheT m ()
 removeHeadC =
     void . runMaybeT $ do
         bh <- MaybeT cacheGetHead
@@ -616,45 +686,56 @@
                 sortTxData . catMaybes <$>
                 mapM (lift . getTxData) (blockDataTxs bd)
             $(logWarnS) "Cache" $ "Reverting head: " <> blockHashToHex bh
-            importMultiTxC tds >>= \case
-                True -> cacheSetHead (prevBlock (blockDataHeader bd))
-                False -> return ()
+            importMultiTxC tds
+            cacheSetHead (prevBlock (blockDataHeader bd))
 
-importMultiTxC :: (StoreRead m, MonadLoggerIO m) => [TxData] -> CacheT m Bool
+importMultiTxC ::
+       (MonadUnliftIO m, StoreRead m, MonadLoggerIO m)
+    => [TxData]
+    -> CacheT m ()
 importMultiTxC txs = do
-    addrmap <-
-        HashMap.fromList . catMaybes . zipWith (\a -> fmap (a, )) alladdrs <$>
-        cacheGetAddrsInfo alladdrs
+    forM_ (zip [(1 :: Int) ..] txs) $ \(i, tx) ->
+        $(logDebugS) "Cache" $
+        "Import tx " <> cs (show i) <> "/" <> cs (show (length txs)) <> ": " <>
+        txHashToHex (txHash (txData tx))
+    addrmap <- getaddrmap
     let addrs = HashMap.keys addrmap
-    balmap <-
-        HashMap.fromList . zipWith (,) addrs <$> mapM (lift . getBalance) addrs
-    unspentmap <-
-        HashMap.fromList . catMaybes . zipWith (\p -> fmap (p, )) allops <$>
-        lift (mapM getUnspent allops)
+    balmap <- getbalances addrs
+    unspentmap <- getunspents
     gap <- getMaxGap
     now <- systemSeconds <$> liftIO getSystemTime
-    ms <-
-        runRedisTx (w (allxpubs addrmap)) $ \bals -> do
-            let xmap = HashMap.filter (not . null) (HashMap.fromList bals)
-                addrmap' = faddrmap (HashMap.keysSet xmap) addrmap
-            x <-
-                if not (HashMap.null xmap)
-                    then do
-                        x <- redisImportMultiTx addrmap' unspentmap txs
-                        y <- redisUpdateBalances addrmap' balmap
-                        z <- redisTouchKeys now (allxpubs addrmap')
-                        return $ x >> y >> z >> return ()
-                    else return (pure ())
-            return $ x >> return (getNewAddrs gap xmap (HashMap.elems addrmap'))
-    case ms of
-        Nothing     -> return False
-        Just addrs' -> cacheAddAddresses addrs' >> return True
+    let xpubs = allxpubs addrmap
+    forM_ (zip [(1 :: Int) ..] xpubs) $ \(i, xpub) -> do
+        xpubtxt <- xpubText xpub
+        $(logDebugS) "Cache" $
+            "Affected xpub " <> cs (show i) <> "/" <> cs (show (length xpubs)) <>
+            ": " <>
+            xpubtxt
+    bals <- getxbals xpubs
+    let xmap = HashMap.filter (not . null) (HashMap.fromList bals)
+        addrs' = getNewAddrs gap xmap (HashMap.elems addrmap)
+    runRedis $
+        if not (HashMap.null xmap)
+            then do
+                x <- redisImportMultiTx addrmap unspentmap txs
+                y <- redisUpdateBalances addrmap balmap
+                z <- redisTouchKeys now xpubs
+                return $ x >> y >> z >> return ()
+            else return (return ())
+    cacheAddAddresses addrs'
   where
-    w xpubs = do
-        bals <- watchRedisXPubBalances xpubs
-        addrs <- watchRedisXPubAddrs alladdrs
-        return $ addrs >> bals
-    faddrmap xpubs = HashMap.filter ((`elem` xpubs) . addressXPubSpec)
+    getaddrmap =
+        HashMap.fromList . catMaybes . zipWith (\a -> fmap (a, )) alladdrs <$>
+        cacheGetAddrsInfo alladdrs
+    getunspents =
+        HashMap.fromList . catMaybes . zipWith (\p -> fmap (p, )) allops <$>
+        lift (mapM getUnspent allops)
+    getbalances addrs =
+        HashMap.fromList . zipWith (,) addrs <$> mapM (lift . getBalance) addrs
+    getxbals xpubs =
+        runRedis . fmap sequence . forM xpubs $ \xpub -> do
+            bs <- redisGetXPubBalances xpub
+            return $ (xpub, ) <$> bs
     allops = map snd $ concatMap txInputs txs <> concatMap txOutputs txs
     alladdrs = nub . map fst $ concatMap txInputs txs <> concatMap txOutputs txs
     allxpubs addrmap = nub . map addressXPubSpec $ HashMap.elems addrmap
@@ -702,8 +783,8 @@
                 y <- redisRemFromMempool (txHash (txData tx))
                 return $ sequence x >> y >> return ()
             else do
-                tops <- mapM (uncurry (addtx tx)) (txaddrops tx)
-                mem <-
+                a <- sequence <$> mapM (uncurry (addtx tx)) (txaddrops tx)
+                b <-
                     case txDataBlock tx of
                         b@MemRef {} ->
                             redisAddToMempool
@@ -712,7 +793,7 @@
                                     , blockTxBlock = b
                                     }
                         _ -> redisRemFromMempool (txHash (txData tx))
-                return $ sequence tops >> mem >> return ()
+                return $ a >> b >> return ()
     txaddrops td = spnts td <> utxos td
     spnts td = txInputs td
     utxos td = txOutputs td
@@ -733,41 +814,34 @@
         XPubBal {xPubBalPath = addressXPubPath ainfo, xPubBal = bal}
 
 cacheAddAddresses ::
-       (StoreRead m, MonadLoggerIO m)
+       (StoreRead m, MonadUnliftIO m, MonadLoggerIO m)
     => [(Address, AddressXPub)]
     -> CacheT m ()
 cacheAddAddresses [] = return ()
 cacheAddAddresses addrs = do
+    $(logDebugS) "Cache" $
+        "Adding " <> cs (show (length addrs)) <> " new generated addresses"
+    balmap <- HashMap.fromListWith (<>) <$> mapM (uncurry getbal) addrs
+    utxomap <- HashMap.fromListWith (<>) <$> mapM (uncurry getutxo) addrs
+    txmap <- HashMap.fromListWith (<>) <$> mapM (uncurry gettxmap) addrs
+    runRedis $ do
+        a <- forM (HashMap.toList balmap) (uncurry redisAddXPubBalances)
+        b <- forM (HashMap.toList utxomap) (uncurry redisAddXPubUnspents)
+        c <- forM (HashMap.toList txmap) (uncurry redisAddXPubTxs)
+        return $ sequence a >> sequence b >> sequence c >> return ()
+    let xpubs = nub (map addressXPubSpec (Map.elems amap))
+    xmap <- getbals xpubs
     gap <- getMaxGap
-    balmap' <- HashMap.fromListWith (<>) <$> mapM (uncurry getbal) addrs
-    utxomap' <- HashMap.fromListWith (<>) <$> mapM (uncurry getutxo) addrs
-    txmap' <- HashMap.fromListWith (<>) <$> mapM (uncurry gettxmap) addrs
-    let xpubs' = nub (map addressXPubSpec (Map.elems amap))
-    ms <-
-        runRedisTx (watchRedisXPubBalances xpubs') $ \bals -> do
-            let xmap = HashMap.filter (not . null) (HashMap.fromList bals)
-                xpubs = HashMap.keysSet xmap
-                balmap = HashMap.filterWithKey (\k _ -> k `elem` xpubs) balmap'
-                utxomap =
-                    HashMap.filterWithKey (\k _ -> k `elem` xpubs) utxomap'
-                txmap = HashMap.filterWithKey (\k _ -> k `elem` xpubs) txmap'
-                notnulls = getnotnull balmap
-            x' <-
-                forM (HashMap.toList balmap) $ \(x, bs) ->
-                    redisAddXPubBalances x bs
-            y' <-
-                forM (HashMap.toList utxomap) $ \(x, us) ->
-                    redisAddXPubUnspents x us
-            z' <- forM (HashMap.toList txmap) $ \(x, ts) -> redisAddXPubTxs x ts
-            return $ do
-                _ <- sequence x'
-                _ <- sequence y'
-                _ <- sequence z'
-                return $ getNewAddrs gap xmap notnulls
-    case ms of
-        Nothing     -> cacheAddAddresses addrs
-        Just addrs' -> cacheAddAddresses addrs'
+    let notnulls = getnotnull balmap
+        addrs' = getNewAddrs gap xmap notnulls
+    cacheAddAddresses addrs'
   where
+    getbals xpubs =
+        runRedis $ do
+            bs <- sequence <$> forM xpubs redisGetXPubBalances
+            return $
+                HashMap.filter (not . null) . HashMap.fromList . zip xpubs <$>
+                bs
     amap = Map.fromList addrs
     getnotnull =
         let f xpub =
@@ -805,14 +879,17 @@
             Nothing   -> []
             Just bals -> addrsToAdd gap bals a
 
-syncMempoolC :: (MonadLoggerIO m, StoreRead m) => CacheT m ()
+syncMempoolC :: (MonadUnliftIO m, MonadLoggerIO m, StoreRead m) => CacheT m ()
 syncMempoolC = do
     nodepool <- map blockTxHash <$> lift getMempool
     cachepool <- map blockTxHash <$> cacheGetMempool
-    txs <- catMaybes <$> mapM (lift . getTxData) (nodepool <> cachepool)
-    importMultiTxC txs >>= \case
-        True -> return ()
-        False -> syncMempoolC
+    txs <- catMaybes <$> mapM (lift . getTxData) (nodepool \\ cachepool)
+    if null txs
+        then $(logDebugS) "Cache" "Cache mempool in sync"
+        else do
+            $(logDebugS) "Cache" $
+                "Importing " <> cs (show (length txs)) <> " mempool transactions"
+            importMultiTxC txs
 
 cacheGetMempool :: MonadIO m => CacheT m [BlockTx]
 cacheGetMempool = runRedis redisGetMempool
@@ -820,8 +897,10 @@
 cacheGetHead :: MonadIO m => CacheT m (Maybe BlockHash)
 cacheGetHead = runRedis redisGetHead
 
-cacheSetHead :: MonadIO m => BlockHash -> CacheT m ()
-cacheSetHead bh = runRedis (redisSetHead bh) >> return ()
+cacheSetHead :: (MonadLoggerIO m, StoreRead m) => BlockHash -> CacheT m ()
+cacheSetHead bh = do
+    $(logDebugS) "Cache" $ "Cache head set to: " <> blockHashToHex bh
+    runRedis (redisSetHead bh) >> return ()
 
 cacheGetAddrsInfo ::
        MonadIO m => [Address] -> CacheT m [Maybe AddressXPub]
@@ -836,37 +915,24 @@
 redisRemFromMempool :: RedisCtx m f => TxHash -> m (f Integer)
 redisRemFromMempool th = zrem mempoolSetKey [encode th]
 
-redisSetAddrInfo :: RedisCtx m f => Address -> AddressXPub -> m (f Redis.Status)
-redisSetAddrInfo a i = Redis.set (addrPfx <> encode a) (encode i)
+redisSetAddrInfo ::
+       (Functor f, RedisCtx m f) => Address -> AddressXPub -> m (f ())
+redisSetAddrInfo a i = void <$> Redis.set (addrPfx <> encode a) (encode i)
 
-delXPubKeys :: MonadIO m => [XPubSpec] -> CacheT m Integer
+delXPubKeys ::
+       (MonadUnliftIO m, MonadLoggerIO m, StoreRead m)
+    => [XPubSpec]
+    -> CacheT m Integer
+delXPubKeys [] = return 0
 delXPubKeys xpubs = do
-    ms <-
-        runRedisTx (watchRedisXPubBalances xpubs) $ \xbals -> do
-            xs <- forM xbals $ uncurry redisDelXPubKeys
-            return $ sequence xs
-    case ms of
-        Nothing -> delXPubKeys xpubs
-        Just is -> return $ sum is
-
-watchRedisXPubBalances ::
-       [XPubSpec] -> Redis (Either Reply [(XPubSpec, [XPubBal])])
-watchRedisXPubBalances [] = return (pure [])
-watchRedisXPubBalances xpubs = do
-    watch $ map (\xpub -> balancesPfx <> encode xpub) xpubs
-    bals' <- mapM redisGetXPubBalances xpubs
-    return $ do
-        bals <- sequence bals'
-        return $ zip xpubs bals
-
-watchRedisXPubAddrs :: [Address] -> Redis (Either Reply [Maybe AddressXPub])
-watchRedisXPubAddrs [] = return (pure [])
-watchRedisXPubAddrs as = do
-    x <- watch $ map (\a -> addrPfx <> encode a) as
-    i <- redisGetAddrsInfo as
-    return $ x >> i
-
-
+    forM_ xpubs $ \x -> do
+        xtxt <- xpubText x
+        $(logDebugS) "Cache" $ "Deleting xpub: " <> xtxt
+    xbals <-
+        runRedis . fmap sequence . forM xpubs $ \xpub -> do
+            bs <- redisGetXPubBalances xpub
+            return $ (xpub, ) <$> bs
+    runRedis $ fmap sum . sequence <$> forM xbals (uncurry redisDelXPubKeys)
 
 redisDelXPubKeys ::
        (Monad f, RedisCtx m f) => XPubSpec -> [XPubBal] -> m (f Integer)
@@ -874,9 +940,9 @@
   where
     go addrs = do
         addrcount <-
-            if null addrs
-                then return (pure 0)
-                else Redis.del (map ((addrPfx <>) . encode) addrs)
+            case addrs of
+                [] -> return (pure 0)
+                _  -> Redis.del (map ((addrPfx <>) . encode) addrs)
         txsetcount <- Redis.del [txSetPfx <> encode xpub]
         utxocount <- Redis.del [utxoPfx <> encode xpub]
         balcount <- Redis.del [balancesPfx <> encode xpub]
@@ -1004,3 +1070,8 @@
         return $ map (uncurry f) ys
   where
     f t s = BlockTx {blockTxBlock = scoreBlockRef s, blockTxHash = t}
+
+xpubText :: (MonadUnliftIO m, MonadLoggerIO m, StoreRead m) => XPubSpec -> CacheT m Text
+xpubText xpub = do
+    net <- getNetwork
+    return . cs $ xPubExport net (xPubSpecKey xpub)
diff --git a/src/Haskoin/Store/Web.hs b/src/Haskoin/Store/Web.hs
--- a/src/Haskoin/Store/Web.hs
+++ b/src/Haskoin/Store/Web.hs
@@ -224,7 +224,8 @@
     bdb <- asks (storeDB . webStore)
     lift $ withDatabaseReader bdb f
 
-instance MonadLoggerIO m => StoreRead (ReaderT WebConfig m) where
+instance (MonadUnliftIO m, MonadLoggerIO m) =>
+         StoreRead (ReaderT WebConfig m) where
     getMaxGap = runInWebReader getMaxGap getMaxGap
     getInitialGap = runInWebReader getInitialGap getInitialGap
     getNetwork = runInWebReader getNetwork getNetwork
@@ -250,8 +251,7 @@
             (getAddressesUnspents addrs start limit)
     getOrphans = runInWebReader getOrphans getOrphans
     xPubBals xpub = runInWebReader (xPubBals xpub) (xPubBals xpub)
-    xPubSummary xpub =
-        runInWebReader (xPubSummary xpub) (xPubSummary xpub)
+    xPubSummary xpub = runInWebReader (xPubSummary xpub) (xPubSummary xpub)
     xPubUnspents xpub start offset limit =
         runInWebReader
             (xPubUnspents xpub start offset limit)
@@ -261,7 +261,7 @@
             (xPubTxs xpub start offset limit)
             (xPubTxs xpub start offset limit)
 
-instance MonadLoggerIO m => StoreRead (WebT m) where
+instance (MonadUnliftIO m, MonadLoggerIO m) => StoreRead (WebT m) where
     getNetwork = lift getNetwork
     getBestBlock = lift getBestBlock
     getBlocksAtHeight = lift . getBlocksAtHeight
@@ -335,7 +335,7 @@
     S.raw (serialAnyNet net proto x)
 
 scottyBestBlock ::
-       (MonadLoggerIO m, MonadIO m) => Bool -> WebT m ()
+       (MonadUnliftIO m, MonadLoggerIO m, MonadIO m) => Bool -> WebT m ()
 scottyBestBlock raw = do
     limits <- lift $ asks webMaxLimits
     setHeaders
@@ -355,7 +355,7 @@
             rawBlock b >>= protoSerial proto
         else protoSerial proto (pruneTx n b)
 
-scottyBlock :: MonadLoggerIO m => Bool -> WebT m ()
+scottyBlock :: (MonadUnliftIO m, MonadLoggerIO m) => Bool -> WebT m ()
 scottyBlock raw = do
     limits <- lift $ asks webMaxLimits
     setHeaders
@@ -372,8 +372,7 @@
             rawBlock b >>= protoSerial proto
         else protoSerial proto (pruneTx n b)
 
-scottyBlockHeight ::
-       MonadLoggerIO m => Bool -> WebT m ()
+scottyBlockHeight :: (MonadUnliftIO m, MonadLoggerIO m) => Bool -> WebT m ()
 scottyBlockHeight raw = do
     limits <- lift $ asks webMaxLimits
     setHeaders
@@ -392,7 +391,7 @@
             let blocks' = map (pruneTx n) blocks
             protoSerial proto blocks'
 
-scottyBlockTime :: MonadLoggerIO m => Bool -> WebT m ()
+scottyBlockTime :: (MonadUnliftIO m, MonadLoggerIO m) => Bool -> WebT m ()
 scottyBlockTime raw = do
     limits <- lift $ asks webMaxLimits
     setHeaders
@@ -409,7 +408,7 @@
                      Just <$> rawBlock d
         else maybeSerial proto m
 
-scottyBlockHeights :: MonadLoggerIO m => WebT m ()
+scottyBlockHeights :: (MonadUnliftIO m, MonadLoggerIO m) => WebT m ()
 scottyBlockHeights = do
     setHeaders
     heights <- S.param "heights"
@@ -419,7 +418,7 @@
     blocks <- map (pruneTx n) . catMaybes <$> mapM getBlock bhs
     protoSerial proto blocks
 
-scottyBlockLatest :: MonadLoggerIO m => WebT m ()
+scottyBlockLatest :: (MonadUnliftIO m, MonadLoggerIO m) => WebT m ()
 scottyBlockLatest = do
     setHeaders
     n <- parseNoTx
@@ -443,7 +442,7 @@
                         else (b' :) <$> go i' n prev
 
 
-scottyBlocks :: MonadLoggerIO m => WebT m ()
+scottyBlocks :: (MonadUnliftIO m, MonadLoggerIO m) => WebT m ()
 scottyBlocks = do
     setHeaders
     bhs <- map (\(MyBlockHash h) -> h) <$> S.param "blocks"
@@ -452,14 +451,14 @@
     bks <- map (pruneTx n) . catMaybes <$> mapM getBlock (nub bhs)
     protoSerial proto bks
 
-scottyMempool :: MonadLoggerIO m => WebT m ()
+scottyMempool :: (MonadUnliftIO m, MonadLoggerIO m) => WebT m ()
 scottyMempool = do
     setHeaders
     proto <- setupBin
     txs <- map blockTxHash <$> getMempool
     protoSerial proto txs
 
-scottyTransaction :: MonadLoggerIO m => WebT m ()
+scottyTransaction :: (MonadUnliftIO m, MonadLoggerIO m) => WebT m ()
 scottyTransaction = do
     setHeaders
     MyTxHash txid <- S.param "txid"
@@ -467,7 +466,7 @@
     res <- getTransaction txid
     maybeSerialNet proto res
 
-scottyRawTransaction :: MonadLoggerIO m => WebT m ()
+scottyRawTransaction :: (MonadUnliftIO m, MonadLoggerIO m) => WebT m ()
 scottyRawTransaction = do
     setHeaders
     MyTxHash txid <- S.param "txid"
@@ -475,7 +474,7 @@
     res <- fmap transactionData <$> getTransaction txid
     maybeSerial proto res
 
-scottyTxAfterHeight :: MonadLoggerIO m => WebT m ()
+scottyTxAfterHeight :: (MonadUnliftIO m, MonadLoggerIO m) => WebT m ()
 scottyTxAfterHeight = do
     setHeaders
     MyTxHash txid <- S.param "txid"
@@ -484,7 +483,7 @@
     res <- cbAfterHeight 10000 height txid
     protoSerial proto res
 
-scottyTransactions :: MonadLoggerIO m => WebT m ()
+scottyTransactions :: (MonadUnliftIO m, MonadLoggerIO m) => WebT m ()
 scottyTransactions = do
     setHeaders
     txids <- map (\(MyTxHash h) -> h) <$> S.param "txids"
@@ -492,7 +491,7 @@
     txs <- catMaybes <$> mapM getTransaction (nub txids)
     protoSerialNet proto txs
 
-scottyBlockTransactions :: MonadLoggerIO m => WebT m ()
+scottyBlockTransactions :: (MonadUnliftIO m, MonadLoggerIO m) => WebT m ()
 scottyBlockTransactions = do
     limits <- lift $ asks webMaxLimits
     setHeaders
@@ -507,7 +506,7 @@
         Nothing -> S.raise ThingNotFound
 
 scottyRawTransactions ::
-       MonadLoggerIO m => WebT m ()
+       (MonadUnliftIO m, MonadLoggerIO m) => WebT m ()
 scottyRawTransactions = do
     setHeaders
     txids <- map (\(MyTxHash h) -> h) <$> S.param "txids"
@@ -523,7 +522,7 @@
     return Block {blockHeader = h, blockTxns = txs}
 
 scottyRawBlockTransactions ::
-       MonadLoggerIO m => WebT m ()
+       (MonadUnliftIO m, MonadLoggerIO m) => WebT m ()
 scottyRawBlockTransactions = do
     limits <- lift $ asks webMaxLimits
     setHeaders
@@ -537,7 +536,7 @@
             protoSerial proto txs
         Nothing -> S.raise ThingNotFound
 
-scottyAddressTxs :: MonadLoggerIO m => Bool -> WebT m ()
+scottyAddressTxs :: (MonadUnliftIO m, MonadLoggerIO m) => Bool -> WebT m ()
 scottyAddressTxs full = do
     setHeaders
     a <- parseAddress
@@ -552,7 +551,7 @@
             getAddressTxsLimit o l s a >>= protoSerial proto
 
 scottyAddressesTxs ::
-       MonadLoggerIO m => Bool -> WebT m ()
+       (MonadUnliftIO m, MonadLoggerIO m) => Bool -> WebT m ()
 scottyAddressesTxs full = do
     setHeaders
     as <- parseAddresses
@@ -563,7 +562,7 @@
         then getAddressesTxsFull l s as >>= protoSerialNet proto
         else getAddressesTxsLimit l s as >>= protoSerial proto
 
-scottyAddressUnspent :: MonadLoggerIO m => WebT m ()
+scottyAddressUnspent :: (MonadUnliftIO m, MonadLoggerIO m) => WebT m ()
 scottyAddressUnspent = do
     setHeaders
     a <- parseAddress
@@ -574,7 +573,7 @@
     uns <- getAddressUnspentsLimit o l s a
     protoSerial proto uns
 
-scottyAddressesUnspent :: MonadLoggerIO m => WebT m ()
+scottyAddressesUnspent :: (MonadUnliftIO m, MonadLoggerIO m) => WebT m ()
 scottyAddressesUnspent = do
     setHeaders
     as <- parseAddresses
@@ -584,7 +583,7 @@
     uns <- getAddressesUnspentsLimit l s as
     protoSerial proto uns
 
-scottyAddressBalance :: MonadLoggerIO m => WebT m ()
+scottyAddressBalance :: (MonadUnliftIO m, MonadLoggerIO m) => WebT m ()
 scottyAddressBalance = do
     setHeaders
     a <- parseAddress
@@ -592,7 +591,7 @@
     res <- getBalance a
     protoSerialNet proto res
 
-scottyAddressesBalances :: MonadLoggerIO m => WebT m ()
+scottyAddressesBalances :: (MonadUnliftIO m, MonadLoggerIO m) => WebT m ()
 scottyAddressesBalances = do
     setHeaders
     as <- parseAddresses
@@ -600,7 +599,7 @@
     res <- getBalances as
     protoSerialNet proto res
 
-scottyXpubBalances :: MonadLoggerIO m => WebT m ()
+scottyXpubBalances :: (MonadUnliftIO m, MonadLoggerIO m) => WebT m ()
 scottyXpubBalances = do
     setHeaders
     nocache <- parseNoCache
@@ -613,7 +612,7 @@
             else xPubBals xpub
     protoSerialNet proto res
 
-scottyXpubTxs :: MonadLoggerIO m => Bool -> WebT m ()
+scottyXpubTxs :: (MonadUnliftIO m, MonadLoggerIO m) => Bool -> WebT m ()
 scottyXpubTxs full = do
     setHeaders
     nocache <- parseNoCache
@@ -632,7 +631,7 @@
             protoSerialNet proto txs'
         else protoSerial proto txs
 
-scottyXpubEvict :: MonadLoggerIO m => WebT m ()
+scottyXpubEvict :: (MonadUnliftIO m, MonadLoggerIO m) => WebT m ()
 scottyXpubEvict =
     lift (asks (storeCache . webStore)) >>= \case
         Nothing -> S.raise ThingNotFound
@@ -640,11 +639,11 @@
             setHeaders
             xpub <- parseXpub
             proto <- setupBin
-            n <- withCache cache (delXPubKeys [xpub])
+            n <- lift $ withCache cache (delXPubKeys [xpub])
             protoSerial proto (GenericResult (n > 0))
 
 
-scottyXpubUnspents :: MonadLoggerIO m => WebT m ()
+scottyXpubUnspents :: (MonadUnliftIO m, MonadLoggerIO m) => WebT m ()
 scottyXpubUnspents = do
     setHeaders
     nocache <- parseNoCache
@@ -658,7 +657,7 @@
             else xPubUnspents xpub start 0 limit
     protoSerial proto uns
 
-scottyXpubSummary :: MonadLoggerIO m => WebT m ()
+scottyXpubSummary :: (MonadUnliftIO m, MonadLoggerIO m) => WebT m ()
 scottyXpubSummary = do
     setHeaders
     nocache <- parseNoCache
@@ -809,7 +808,7 @@
         S.get "/health" scottyHealth
         S.notFound $ S.raise ThingNotFound
 
-getStart :: MonadLoggerIO m => WebT m (Maybe BlockRef)
+getStart :: (MonadUnliftIO m, MonadLoggerIO m) => WebT m (Maybe BlockRef)
 getStart =
     runMaybeT $ do
         s <- MaybeT $ (Just <$> S.param "height") `S.rescue` const (return Nothing)
