mirror of https://github.com/voidlizard/hbs2
429 lines
13 KiB
Haskell
429 lines
13 KiB
Haskell
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
|
{-# Language TemplateHaskell #-}
|
|
{-# Language UndecidableInstances #-}
|
|
{-# Language MultiWayIf #-}
|
|
module PeerTypes where
|
|
|
|
import HBS2.Actors.Peer
|
|
import HBS2.Clock
|
|
import HBS2.Defaults
|
|
import HBS2.Events
|
|
import HBS2.Hash
|
|
import HBS2.Net.Messaging.UDP (UDP)
|
|
import HBS2.Net.Proto
|
|
import HBS2.Net.Proto.Peer
|
|
import HBS2.Net.Proto.BlockInfo
|
|
import HBS2.Net.Proto.Definition
|
|
import HBS2.Net.Proto.Sessions
|
|
import HBS2.Prelude.Plated
|
|
import HBS2.Storage
|
|
import HBS2.Net.PeerLocator
|
|
import HBS2.System.Logger.Simple
|
|
|
|
import PeerInfo
|
|
|
|
import Data.Foldable (for_)
|
|
import Control.Concurrent.Async
|
|
import Control.Concurrent.STM
|
|
import Control.Monad.Reader
|
|
import Data.ByteString.Lazy (ByteString)
|
|
import Data.Cache (Cache)
|
|
import Data.Cache qualified as Cache
|
|
import Data.HashMap.Strict (HashMap)
|
|
import Data.HashMap.Strict qualified as HashMap
|
|
import Data.Maybe
|
|
import Lens.Micro.Platform
|
|
import Data.Hashable
|
|
import Type.Reflection
|
|
import Numeric (showGFloat)
|
|
|
|
|
|
type MyPeer e = ( Eq (Peer e)
|
|
, Hashable (Peer e)
|
|
, Pretty (Peer e)
|
|
, HasPeer e
|
|
, Block ByteString ~ ByteString
|
|
)
|
|
|
|
data DownloadReq e
|
|
|
|
data DownloadAsap e
|
|
|
|
data instance EventKey e (DownloadReq e) =
|
|
DownloadReqKey
|
|
deriving (Generic,Typeable,Eq)
|
|
|
|
instance Typeable (DownloadReq e) => Hashable (EventKey e (DownloadReq e)) where
|
|
hashWithSalt salt _ = hashWithSalt salt (someTypeRep p)
|
|
where
|
|
p = Proxy @DownloadReq
|
|
|
|
newtype instance Event e (DownloadReq e) =
|
|
DownloadReqData (Hash HbSync)
|
|
deriving (Typeable)
|
|
|
|
instance EventType ( Event e (DownloadReq e) ) where
|
|
isPersistent = True
|
|
|
|
instance Expires (EventKey e (DownloadReq e)) where
|
|
expiresIn = const Nothing
|
|
|
|
|
|
|
|
type DownloadFromPeerStuff e m = ( MyPeer e
|
|
, MonadIO m
|
|
, Request e (BlockInfo e) m
|
|
, Request e (BlockChunks e) m
|
|
, MonadReader (PeerEnv e ) m
|
|
, PeerMessaging e
|
|
, HasProtocol e (BlockInfo e)
|
|
, EventListener e (BlockInfo e) m
|
|
, EventListener e (BlockChunks e) m
|
|
, Sessions e (BlockChunks e) m
|
|
, Sessions e (PeerInfo e) m
|
|
, Block ByteString ~ ByteString
|
|
, HasStorage m
|
|
)
|
|
|
|
|
|
calcBursts :: forall a . Integral a => a -> [a] -> [(a,a)]
|
|
calcBursts bu pieces = go seed
|
|
where
|
|
seed = fmap (,1) pieces
|
|
|
|
go ( (n1,s1) : (n2,s2) : xs )
|
|
| (s1 + s2) <= bu = go ((n1, s1+s2) : xs)
|
|
| otherwise = (n1,s1) : go ( (n2,s2) : xs)
|
|
|
|
go [x] = [x]
|
|
go [] = []
|
|
|
|
data BlockDownload =
|
|
BlockDownload
|
|
{ _sBlockHash :: Hash HbSync
|
|
, _sBlockSize :: Size
|
|
, _sBlockChunkSize :: ChunkSize
|
|
, _sBlockChunks :: TQueue (ChunkNum, ByteString)
|
|
}
|
|
deriving stock (Typeable)
|
|
|
|
makeLenses 'BlockDownload
|
|
|
|
newBlockDownload :: MonadIO m => Hash HbSync -> m BlockDownload
|
|
newBlockDownload h = do
|
|
BlockDownload h 0 0 <$> liftIO newTQueueIO
|
|
|
|
|
|
type instance SessionData e (BlockChunks e) = BlockDownload
|
|
|
|
newtype instance SessionKey e (BlockChunks e) =
|
|
DownloadSessionKey (Peer e, Cookie e)
|
|
deriving stock (Generic,Typeable)
|
|
|
|
deriving newtype instance Hashable (SessionKey UDP (BlockChunks UDP))
|
|
deriving stock instance Eq (SessionKey UDP (BlockChunks UDP))
|
|
|
|
data BlockState =
|
|
BlockState
|
|
{ _bsStart :: TimeSpec
|
|
, _bsReqSizeTimes :: TVar Int
|
|
, _bsLastSeen :: TVar TimeSpec
|
|
, _bsHasSize :: TVar Bool
|
|
}
|
|
|
|
makeLenses 'BlockState
|
|
|
|
|
|
data PeerTask e = DoDownload
|
|
|
|
data PeerThread e =
|
|
PeerThread
|
|
{ _peerThreadAsync :: Async ()
|
|
, _peerThreadMailbox :: TQueue (PeerTask e)
|
|
}
|
|
|
|
makeLenses 'PeerThread
|
|
|
|
data DownloadEnv e =
|
|
DownloadEnv
|
|
{ _downloadQ :: TQueue (Hash HbSync)
|
|
, _peerBusy :: TVar (HashMap (Peer e) ())
|
|
, _blockPeers :: TVar (HashMap (Hash HbSync) (HashMap (Peer e) Integer) )
|
|
, _blockWip :: Cache (Hash HbSync) ()
|
|
, _blockState :: TVar (HashMap (Hash HbSync) BlockState)
|
|
, _blockInQ :: TVar (HashMap (Hash HbSync) ())
|
|
, _peerThreads :: TVar (HashMap (Peer e) (PeerThread e))
|
|
, _peerPostponed :: TVar (HashMap (Hash HbSync) ())
|
|
, _blockStored :: Cache (Hash HbSync) ()
|
|
, _blockBanned :: Cache (Hash HbSync, Peer e) ()
|
|
}
|
|
|
|
makeLenses 'DownloadEnv
|
|
|
|
|
|
newDownloadEnv :: (MonadIO m, MyPeer e) => m (DownloadEnv e)
|
|
newDownloadEnv = liftIO do
|
|
DownloadEnv <$> newTQueueIO
|
|
<*> newTVarIO mempty
|
|
<*> newTVarIO mempty
|
|
<*> Cache.newCache (Just defBlockWipTimeout)
|
|
<*> newTVarIO mempty
|
|
<*> newTVarIO mempty
|
|
<*> newTVarIO mempty
|
|
<*> newTVarIO mempty
|
|
<*> Cache.newCache (Just defBlockWipTimeout)
|
|
<*> Cache.newCache (Just defBlockBanTime)
|
|
|
|
newtype BlockDownloadM e m a =
|
|
BlockDownloadM { fromBlockDownloadM :: ReaderT (DownloadEnv e) m a }
|
|
deriving newtype ( Functor
|
|
, Applicative
|
|
, Monad
|
|
, MonadIO
|
|
, MonadReader (DownloadEnv e)
|
|
, MonadTrans
|
|
)
|
|
|
|
runDownloadM :: (MyPeer e, MonadIO m) => BlockDownloadM e m a -> m a
|
|
runDownloadM m = runReaderT ( fromBlockDownloadM m ) =<< newDownloadEnv
|
|
|
|
withDownload :: (MyPeer e, HasPeerLocator e m, MonadIO m) => DownloadEnv e -> BlockDownloadM e m a -> m a
|
|
withDownload e m = runReaderT ( fromBlockDownloadM m ) e
|
|
|
|
setBlockState :: MonadIO m => Hash HbSync -> BlockState -> BlockDownloadM e m ()
|
|
setBlockState h s = do
|
|
sh <- asks (view blockState)
|
|
liftIO $ atomically $ modifyTVar' sh (HashMap.insert h s)
|
|
|
|
setBlockHasSize :: MonadIO m => Hash HbSync -> BlockDownloadM e m ()
|
|
setBlockHasSize h = do
|
|
blk <- fetchBlockState h
|
|
liftIO $ atomically $ writeTVar (view bsHasSize blk) True
|
|
|
|
fetchBlockState :: MonadIO m => Hash HbSync -> BlockDownloadM e m BlockState
|
|
fetchBlockState h = do
|
|
sh <- asks (view blockState)
|
|
liftIO do
|
|
now <- getTime MonotonicCoarse
|
|
tvlast <- newTVarIO now
|
|
tvreq <- newTVarIO 0
|
|
tvsz <- newTVarIO False
|
|
let defState = BlockState now tvreq tvlast tvsz
|
|
atomically $ stateTVar sh $ \hm -> case HashMap.lookup h hm of
|
|
Nothing -> (defState, HashMap.insert h defState hm)
|
|
Just x -> (x, hm)
|
|
|
|
banBlock :: (MyPeer e, MonadIO m) => Peer e -> Hash HbSync -> BlockDownloadM e m ()
|
|
banBlock p h = do
|
|
banned <- asks (view blockBanned)
|
|
liftIO $ Cache.insert banned (h,p) ()
|
|
|
|
isBanned :: (MyPeer e, MonadIO m) => Peer e -> Hash HbSync -> BlockDownloadM e m Bool
|
|
isBanned p h = do
|
|
banned <- asks (view blockBanned)
|
|
liftIO $ Cache.lookup banned (h,p) <&> isJust
|
|
|
|
delBlockState :: MonadIO m => Hash HbSync -> BlockDownloadM e m ()
|
|
delBlockState h = do
|
|
sh <- asks (view blockState)
|
|
liftIO $ atomically $ modifyTVar' sh (HashMap.delete h)
|
|
|
|
incBlockSizeReqCount :: MonadIO m => Hash HbSync -> BlockDownloadM e m ()
|
|
incBlockSizeReqCount h = do
|
|
blk <- fetchBlockState h
|
|
now <- liftIO $ getTime MonotonicCoarse
|
|
seen <- liftIO $ readTVarIO (view bsLastSeen blk)
|
|
let elapsed = realToFrac (toNanoSecs (now - seen)) / 1e9
|
|
noSize <- liftIO $ readTVarIO (view bsHasSize blk) <&> not
|
|
|
|
when (elapsed > 1.0 && noSize) do
|
|
liftIO $ atomically $ do
|
|
writeTVar (view bsLastSeen blk) now
|
|
modifyTVar (view bsReqSizeTimes blk) succ
|
|
|
|
isBlockHereCached :: forall e m . ( MyPeer e
|
|
, MonadIO m
|
|
, HasStorage m
|
|
)
|
|
=> Hash HbSync -> BlockDownloadM e m Bool
|
|
|
|
isBlockHereCached h = do
|
|
szcache <- asks (view blockStored)
|
|
sto <- lift getStorage
|
|
|
|
cached <- liftIO $ Cache.lookup szcache h
|
|
|
|
case cached of
|
|
Just{} -> pure True
|
|
Nothing -> liftIO do
|
|
blk <- hasBlock sto h <&> isJust
|
|
when blk $ Cache.insert szcache h ()
|
|
pure blk
|
|
|
|
checkForDownload :: forall e m . ( MyPeer e
|
|
, MonadIO m
|
|
, HasPeerLocator e (BlockDownloadM e m)
|
|
, HasStorage m -- (BlockDownloadM e m)
|
|
)
|
|
=> ByteString -> BlockDownloadM e m ()
|
|
|
|
checkForDownload lbs = do
|
|
pure ()
|
|
|
|
addDownload :: forall e m . ( MyPeer e
|
|
, MonadIO m
|
|
, HasPeerLocator e (BlockDownloadM e m)
|
|
, HasStorage m -- (BlockDownloadM e m)
|
|
, Block ByteString ~ ByteString
|
|
)
|
|
=> Hash HbSync -> BlockDownloadM e m ()
|
|
|
|
addDownload h = do
|
|
|
|
po <- asks (view peerPostponed)
|
|
|
|
tinq <- asks (view blockInQ)
|
|
|
|
doAdd <- do liftIO $ atomically $ stateTVar tinq
|
|
\hm -> case HashMap.lookup h hm of
|
|
Nothing -> (True, HashMap.insert h () hm)
|
|
Just{} -> (False, HashMap.insert h () hm)
|
|
|
|
notHere <- isBlockHereCached h <&> not
|
|
|
|
notPostponed <- liftIO $ readTVarIO po <&> isNothing . HashMap.lookup h
|
|
|
|
when (doAdd && notPostponed && notHere) do
|
|
|
|
q <- asks (view downloadQ)
|
|
wip <- asks (view blockWip)
|
|
|
|
liftIO do
|
|
atomically $ do
|
|
modifyTVar tinq $ HashMap.insert h ()
|
|
writeTQueue q h
|
|
|
|
Cache.insert wip h ()
|
|
|
|
-- | False -> do -- not hasSize -> do
|
|
|
|
-- po <- asks (view peerPostponed)
|
|
-- liftIO $ atomically $ do
|
|
-- modifyTVar po $ HashMap.insert h ()
|
|
|
|
-- trace $ "postpone block" <+> pretty h <+> pretty brt
|
|
-- <+> "here:" <+> pretty (not missed)
|
|
|
|
-- | otherwise -> do
|
|
-- -- TODO: counter-on-this-situation
|
|
-- none
|
|
|
|
returnPostponed :: forall e m . ( MyPeer e
|
|
, MonadIO m
|
|
, HasStorage m
|
|
, HasPeerLocator e (BlockDownloadM e m)
|
|
)
|
|
=> Hash HbSync -> BlockDownloadM e m ()
|
|
|
|
returnPostponed h = do
|
|
tinq <- asks (view blockInQ)
|
|
-- TODO: atomic-operations
|
|
delFromPostponed h
|
|
delBlockState h
|
|
liftIO $ atomically $ modifyTVar' tinq (HashMap.delete h)
|
|
addDownload h
|
|
|
|
delFromPostponed :: MonadIO m => Hash HbSync -> BlockDownloadM e m ()
|
|
delFromPostponed h = do
|
|
po <- asks (view peerPostponed)
|
|
liftIO $ atomically $ do
|
|
modifyTVar' po (HashMap.delete h)
|
|
|
|
removeFromWip :: (MyPeer e, MonadIO m) => Hash HbSync -> BlockDownloadM e m ()
|
|
removeFromWip h = do
|
|
wip <- asks (view blockWip)
|
|
st <- asks (view blockState)
|
|
sz <- asks (view blockPeers)
|
|
tinq <- asks (view blockInQ)
|
|
po <- asks (view peerPostponed)
|
|
ba <- asks (view blockBanned)
|
|
|
|
liftIO $ Cache.delete wip h
|
|
liftIO $ Cache.filterWithKey (\(hx,_) _ -> hx /= h) ba
|
|
|
|
liftIO $ atomically $ do
|
|
modifyTVar' st (HashMap.delete h)
|
|
modifyTVar' sz (HashMap.delete h)
|
|
modifyTVar' tinq (HashMap.delete h)
|
|
modifyTVar' po (HashMap.delete h)
|
|
|
|
hasPeerThread :: (MyPeer e, MonadIO m) => Peer e -> BlockDownloadM e m Bool
|
|
hasPeerThread p = do
|
|
threads <- asks (view peerThreads)
|
|
liftIO $ readTVarIO threads <&> HashMap.member p
|
|
|
|
|
|
delPeerThread :: (MyPeer e, MonadIO m) => Peer e -> BlockDownloadM e m ()
|
|
delPeerThread p = do
|
|
debug $ "delPeerThread" <+> pretty p
|
|
threads <- asks (view peerThreads)
|
|
pt <- liftIO $ atomically $ stateTVar threads (\x -> let t = HashMap.lookup p x
|
|
in (t, HashMap.delete p x))
|
|
|
|
maybe1 pt (pure ()) $ liftIO . cancel . view peerThreadAsync
|
|
|
|
newPeerThread :: (MyPeer e, MonadIO m) => Peer e -> Async () -> BlockDownloadM e m ()
|
|
newPeerThread p m = do
|
|
q <- liftIO newTQueueIO
|
|
let pt = PeerThread m q
|
|
threads <- asks (view peerThreads)
|
|
liftIO $ atomically $ modifyTVar threads $ HashMap.insert p pt
|
|
|
|
|
|
failedDownload :: forall e m . ( MyPeer e
|
|
, MonadIO m
|
|
, HasPeer e
|
|
, HasPeerLocator e (BlockDownloadM e m)
|
|
, HasStorage m
|
|
)
|
|
=> Peer e
|
|
-> Hash HbSync
|
|
-> BlockDownloadM e m ()
|
|
|
|
failedDownload p h = do
|
|
trace $ "failedDownload" <+> pretty p <+> pretty h
|
|
addDownload h
|
|
|
|
updateBlockPeerSize :: forall e m . (MyPeer e, MonadIO m)
|
|
=> Hash HbSync
|
|
-> Peer e
|
|
-> Integer
|
|
-> BlockDownloadM e m ()
|
|
|
|
updateBlockPeerSize h p s = do
|
|
tv <- asks (view blockPeers)
|
|
|
|
setBlockHasSize h
|
|
|
|
let alt = \case
|
|
Nothing -> Just $ HashMap.singleton p s
|
|
Just hm -> Just $ HashMap.insert p s hm
|
|
|
|
liftIO $ atomically $ modifyTVar tv (HashMap.alter alt h)
|
|
|
|
|
|
forKnownPeers :: forall e m . ( MonadIO m
|
|
, HasPeerLocator e m
|
|
, Sessions e (KnownPeer e) m
|
|
, HasPeer e
|
|
)
|
|
=> ( Peer e -> PeerData e -> m () ) -> m ()
|
|
forKnownPeers m = do
|
|
pl <- getPeerLocator @e
|
|
pips <- knownPeers @e pl
|
|
for_ pips $ \p -> do
|
|
pd' <- find (KnownPeerKey p) id
|
|
maybe1 pd' (pure ()) (m p)
|
|
|
|
|