hbs2/hbs2-peer/app/BlockHttpDownload.hs

241 lines
9.1 KiB
Haskell
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# Language UndecidableInstances #-}
{-# Language MultiWayIf #-}
module BlockHttpDownload where
import HBS2.Actors.Peer
import HBS2.Clock
import HBS2.Data.Detect
import HBS2.Data.Types.Refs
import HBS2.Defaults
import HBS2.Events
import HBS2.Hash
import HBS2.Merkle
import HBS2.Net.IP.Addr
import HBS2.Net.PeerLocator
import HBS2.Net.Proto
import HBS2.Net.Proto.Definition
import HBS2.Net.Proto.Peer
import HBS2.Net.Proto.PeerMeta
import HBS2.Net.Proto.RefLog
import HBS2.Net.Proto.Sessions
import HBS2.Prelude
import HBS2.Prelude.Plated
import HBS2.Storage
import HBS2.System.Logger.Simple
import PeerTypes
import PeerInfo
import BlockDownload
import Brains
import Control.Concurrent.Async
import Control.Concurrent.STM
import Control.Monad.Reader
import Control.Monad.Trans.Maybe
import Data.ByteString (ByteString)
import Data.ByteString.Lazy qualified as LBS
import Data.Cache qualified as Cache
import Data.Foldable hiding (find)
import Data.HashMap.Strict qualified as HashMap
import Data.IntMap (IntMap)
import Data.IntMap qualified as IntMap
import Data.IntSet qualified as IntSet
import Data.List qualified as List
import Data.Maybe
import Data.Text qualified as Text
import Data.Text.Encoding qualified as TE
import Data.Word
import Lens.Micro.Platform
import Network.HTTP.Simple (getResponseBody, httpLbs, parseRequest, getResponseStatus)
import Network.HTTP.Types.Status
import Network.Socket
import Streaming (Stream, Of)
import Streaming.Prelude qualified as S
import System.Random (randomRIO)
import System.Random.Shuffle (shuffleM)
import Text.InterpolatedString.Perl6 (qc)
blockHttpDownloadLoop :: forall e m .
( m ~ PeerM e IO
, MonadIO m
, HasProtocol e (BlockInfo e)
, Sessions e (KnownPeer e) m
, PeerSessionKey e (PeerInfo e)
, Pretty (Peer e)
, IsPeerAddr e m
-- FIXME: backlog-do-something-with-that
-- это не ревью, это надо что-то с этим
-- сделать, неудачное решение
, Block LBS.ByteString ~ LBS.ByteString
)
=> DownloadEnv e -> m ()
blockHttpDownloadLoop denv = do
e <- ask
pl <- getPeerLocator @e
pause @'Seconds 3.81
debug "I'm blockHttpDownloadLoop"
---
let streamPeers :: Stream (Of (Peer e, PeerInfo e)) m ()
streamPeers = flip fix [] \goPeers -> \case
[] -> do
pause @'Seconds 1.44
ps <- knownPeers @e pl
when (null ps) do
trace $ "No peers to use for http download"
pause @'Seconds 4
goPeers ps
peer:ps -> do
authorized <- lift $ find (KnownPeerKey peer) id <&> isJust
pinfo <- lift $ find (PeerInfoKey peer) id <&> isJust
when (authorized && pinfo) do
npi <- lift newPeerInfo
pinfo <- lift $ fetch True npi (PeerInfoKey peer) id
S.yield (peer, pinfo)
goPeers ps
let streamPeerAddrs = S.catMaybes $ streamPeers & S.mapM \(peer, pinfo) ->
(fmap (peer, pinfo, ) . join . eitherToMaybe) <$> do
r <- liftIO $ readTVarIO (_peerHttpApiAddress pinfo)
-- debug $ "streamPeerAddrs" <+> pretty peer <+> viaShow (viaShow <$> r)
pure r
let streamBlockHs = S.catMaybes $ streamPeerAddrs & S.mapM \(peer, pinfo, apiAddr) -> do
-- inq <- liftIO $ readTVarIO (_blockInQ denv)
-- TODO: change to only use blockInQ
-- do we need analog of getBlockForDownload ?
mbh <- withDownload denv $ getBlockForDownload peer
-- debug $ "streamBlockHs" <+> pretty peer <+> pretty apiAddr <+> viaShow (pretty <$> mbh)
pure $ (peer, pinfo, apiAddr, ) <$> mbh
streamBlockHs & S.mapM_ \(peer, pinfo, apiAddr, h) -> do
trace $ "Querying via http from" <+> pretty (peer, apiAddr) <+> "block" <+> pretty h
r <- liftIO $ race ( pause defBlockWaitMax )
$ do
req <- liftIO $ parseRequest [qc|http://{apiAddr}/cat/{pretty h}|]
resp <- httpLbs req
case statusCode (getResponseStatus resp) of
200 -> pure $ Just (getResponseBody resp)
_ -> pure Nothing
case r of
Right (Just block) -> do
trace $ "SUCCESS" <+> pretty peer <+> "http-download block" <+> pretty h
liftIO $ atomically $ modifyTVar (_peerHttpDownloaded pinfo) (+1)
sto <- getStorage
liftIO $ putBlock sto block
withDownload denv $ processBlock h
_ -> do
trace $ "FAIL" <+> pretty peer <+> "download block" <+> pretty h
withDownload denv $ failedDownload peer h
---
fillPeerMeta :: forall e m .
( m ~ PeerM e IO
, MonadIO m
, HasProtocol e (PeerMetaProto e)
, PeerSessionKey e (PeerInfo e)
, PeerMessaging e
, IsPeerAddr e m
, Pretty (Peer e)
, Pretty (PeerAddr e)
, EventListener e ( PeerMetaProto e) m
, e ~ L4Proto
)
=> m ()
fillPeerMeta = do
debug "I'm fillPeerMeta"
pl <- getPeerLocator @e
forever do
pause @'Seconds 5
ps <- knownPeers @e pl
debug $ "fillPeerMeta peers:" <+> pretty ps
npi <- newPeerInfo
for_ ps $ \p -> do
pinfo <- fetch True npi (PeerInfoKey p) id
mmApiAddr <- liftIO $ readTVarIO (_peerHttpApiAddress pinfo)
debug $ "Find peer meta and http address" <+> pretty p <+> "current:" <+> viaShow mmApiAddr
case mmApiAddr of
Left attemptn -> do
q <- liftIO newTQueueIO
subscribe @e (PeerMetaEventKey p) $ \case
PeerMetaEvent meta -> do
liftIO $ atomically $ writeTQueue q (Just meta)
request p (GetPeerMeta @e)
r <- liftIO $ race ( pause defGetPeerMetaTimeout )
( atomically $ do
s <- readTQueue q
void $ flushTQueue q
pure s
)
case r of
Left _ ->
liftIO $ atomically $ writeTVar (_peerHttpApiAddress pinfo) $
if attemptn < 3 then (Left (attemptn + 1)) else (Right Nothing)
Right (Just meta) -> (void . runMaybeT) do
peerMeta <- case meta of
NoMetaData -> (MaybeT . pure) Nothing
ShortMetadata t -> do
(MaybeT . pure) (parsePeerMeta t)
AnnHashRef h -> (MaybeT . pure) Nothing
liftIO $ atomically $ writeTVar (_peerMeta pinfo) (Just peerMeta)
-- 3) пробить, что есть tcp
forM_ (lookupDecode "listen-tcp" (unPeerMeta peerMeta)) \listenTCPPort -> lift do
peerTCPAddrPort <- replacePort p listenTCPPort
-- 4) выяснить, можно ли к нему открыть соединение на этот порт
-- возможно, с ним уже открыто соединение
-- или попробовать открыть или запомнить, что было открыто
-- connectPeerTCP ?
-- 5) добавить этих пиров в пекс
-- p :: Peer e <- fromPeerAddr (L4Address TCP (peerTCPAddrPort :: IPAddrPort L4Proto) :: PeerAddr e)
sendPing =<< fromPeerAddr (L4Address TCP peerTCPAddrPort)
port <- (MaybeT . pure) (lookupDecode "http-port" (unPeerMeta peerMeta))
lift do
peerHttpApiAddr <- show . pretty <$> replacePort p port
-- check peerHttpApiAddr
r <- liftIO $ race ( pause defBlockWaitMax ) do
req <- liftIO $ parseRequest [qc|http://{peerHttpApiAddr}/metadata|]
resp <- httpLbs req
case statusCode (getResponseStatus resp) of
200 -> pure True
_ -> pure False
liftIO $ atomically $ writeTVar (_peerHttpApiAddress pinfo) $ Right $
case r of
Right True -> Just peerHttpApiAddr
_ -> Nothing
_ -> do
liftIO $ atomically $ writeTVar (_peerHttpApiAddress pinfo) $ Right Nothing
_ -> pure ()
where
replacePort :: Peer e -> Word16 -> PeerM e IO (IPAddrPort e)
replacePort peer port = do
IPAddrPort (ip,_port) <- fromString @(IPAddrPort e) . show . pretty <$> toPeerAddr peer
pure $ IPAddrPort (ip,port)
lookupDecode :: (Eq k, Read v) => k -> [(k, ByteString)] -> Maybe v
lookupDecode k d =
readMay . Text.unpack . TE.decodeUtf8 =<< lookup k d