hbs2/hbs2-tests/test/Main.hs

262 lines
7.2 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.

{-# Language FunctionalDependencies #-}
{-# Language RankNTypes #-}
{-# Language PatternSynonyms #-}
module Main where
import HBS2.Prelude.Plated
import HBS2.Clock
import HBS2.Hash
-- import HBS2.Net.Messaging
import HBS2.Net.Proto
import HBS2.Net.Proto.BlockInfo
import HBS2.Net.Messaging.Fake
import HBS2.Net.Peer
import HBS2.Defaults
import HBS2.Storage.Simple
import HBS2.Storage.Simple.Extra
-- import Test.Tasty hiding (Timeout)
import Test.Tasty.HUnit
import Codec.Serialise
import Control.Concurrent.Async
import Data.ByteString.Lazy (ByteString)
import Data.ByteString.Lazy.Char8 qualified as B8
import Data.Foldable
import Data.Hashable
import Data.Maybe
import Data.Traversable
import Data.Word
import Lens.Micro.Platform
import Prettyprinter
import System.Directory
import System.Exit
import System.FilePath.Posix
import System.IO
import Data.Cache (Cache)
import Data.Cache qualified as Cache
debug :: (MonadIO m) => Doc ann -> m ()
debug p = liftIO $ hPrint stderr p
newtype ChunkSize = ChunkSize Word16
deriving newtype (Num,Enum,Real,Integral)
deriving stock (Eq,Ord,Show,Data,Generic)
newtype ChunkNum = ChunkNum Word16
deriving newtype (Num,Enum,Real,Integral)
deriving stock (Eq,Ord,Show,Data,Generic)
type GetBlockChunk h = forall m . MonadIO m => Hash h -> Offset -> Size -> m (Maybe ByteString)
data BlockChunksI e m =
BlockChunksI
{ blkSize :: GetBlockSize HbSync m
, blkChunk :: GetBlockChunk HbSync
, blkGetHash :: Cookie e -> m (Maybe (Hash HbSync))
, blkAcceptChunk :: (Hash HbSync, ChunkNum, ByteString) -> m ()
}
instance HasCookie e (BlockChunks e) where
type instance Cookie e = Word32
pattern BlockGetAllChunks h s <- BlockGetAllChunks_ _ h s
pattern BlockNoChunks <- BlockNoChunks_ _
pattern BlockChunk n bs <- BlockChunk_ _ n bs
pattern BlockLost <- BlockLost_ _
data BlockChunks e = BlockGetAllChunks_ (Cookie e) (Hash HbSync) ChunkSize
| BlockNoChunks_ (Cookie e)
| BlockChunk_ (Cookie e) ChunkNum ByteString
| BlockLost_ (Cookie e)
deriving stock (Generic)
instance Serialise ChunkSize
instance Serialise ChunkNum
instance Serialise (BlockChunks e)
-- instance Serialise (MyCookie e)
-- instance Serialise (Cookie e (BlockChunks e))
blockChunksProto :: forall e m . ( MonadIO m
, Response e (BlockChunks e) m
)
=> BlockChunksI e m
-> BlockChunks e
-> m ()
blockChunksProto adapter =
\case
BlockGetAllChunks h size -> deferred proto do
bsz <- blkSize adapter h
let offsets' = calcChunks (fromJust bsz) (fromIntegral size) :: [(Offset, Size)]
let offsets = zip offsets' [0..]
for_ offsets $ \((o,sz),i) -> do
chunk <- blkChunk adapter h o sz
maybe (pure ()) (response . BlockChunk_ @e c i) chunk
BlockChunk n bs -> do
-- TODO: getHashByCookie c
h <- blkGetHash adapter c
maybe1 h (response (BlockLost_ @e c)) $ \hh -> do
blkAcceptChunk adapter (hh, n, bs)
BlockNoChunks {} -> do
-- TODO: notification
pure ()
BlockLost{} -> do
pure ()
where
proto = Proxy @(BlockChunks e)
data Fake
instance HasPeer Fake where
newtype instance Peer Fake = FakePeer Word8
deriving newtype (Hashable,Num,Enum,Real,Integral)
deriving stock (Eq,Ord,Show)
instance Pretty (Peer Fake) where
pretty (FakePeer n) = parens ("peer" <+> pretty n)
instance HasProtocol Fake (BlockSize Fake) where
type instance ProtocolId (BlockSize Fake) = 1
type instance Encoded Fake = ByteString
decode = either (const Nothing) Just . deserialiseOrFail
encode = serialise
instance HasProtocol Fake (BlockChunks Fake) where
type instance ProtocolId (BlockChunks Fake) = 2
type instance Encoded Fake = ByteString
decode = either (const Nothing) Just . deserialiseOrFail
encode = serialise
main :: IO ()
main = do
hSetBuffering stderr LineBuffering
test1
-- defaultMain $
-- testGroup "root"
-- [
-- testCase "test1" test1
-- ]
runFakePeer :: forall e . e ~ Fake => EngineEnv e -> IO ()
runFakePeer env = do
let pid = fromIntegral (hash (env ^. self)) :: Word8
dir <- canonicalizePath ( ".peers" </> show pid)
createDirectoryIfMissing True dir
let opts = [ StoragePrefix dir
]
storage <- simpleStorageInit opts :: IO (SimpleStorage HbSync)
w <- async $ simpleStorageWorker storage
let size = 1024*1024
let blk = B8.concat [ fromString (take 1 $ show x)
| x <- replicate size (fromIntegral pid :: Int)
]
root <- putAsMerkle storage blk
debug $ "I'm" <+> pretty pid <+> pretty root
simpleStorageStop storage
let handleBlockInfo (p, h, sz) = do
debug $ pretty p <+> "has block" <+> pretty h <+> pretty sz
blkCookies <- Cache.newCache @(Cookie e) @(Hash HbSync) (Just defCookieTimeout)
let adapter =
BlockChunksI
{ blkSize = hasBlock storage
, blkChunk = getChunk storage
, blkGetHash = liftIO . Cache.lookup blkCookies
, blkAcceptChunk = dontHandle
}
runPeer env
[ makeResponse (blockSizeProto (hasBlock storage) handleBlockInfo)
, makeResponse (blockChunksProto adapter)
]
cancel w
pure ()
test1 :: IO ()
test1 = do
hSetBuffering stderr LineBuffering
fake <- newFakeP2P True
let peers@[p0,p1] = [0..1] :: [Peer Fake]
envs@[e0,e1] <- forM peers $ \p -> newEnv p fake
void $ race (pause (2 :: Timeout 'Seconds)) $ do
peerz <- mapM (async . runFakePeer) envs
runEngineM e0 $ do
request p1 (GetBlockSize @Fake (fromString "81JeD7LNR6Q7RYfyWBxfjJn1RsWzvegkUXae6FUNgrMZ"))
request p1 (GetBlockSize @Fake (fromString "5KP4vM6RuEX6RA1ywthBMqZV5UJDLANC17UrF6zuWdRt"))
request p0 (GetBlockSize @Fake (fromString "81JeD7LNR6Q7RYfyWBxfjJn1RsWzvegkUXae6FUNgrMZ"))
request p0 (GetBlockSize @Fake (fromString "5KP4vM6RuEX6RA1ywthBMqZV5UJDLANC17UrF6zuWdRt"))
-- request p1 (BlockGetAllChunks @Fake 0 (fromString "5KP4vM6RuEX6RA1ywthBMqZV5UJDLANC17UrF6zuWdRt"))
-- Я ЗАПРОСИЛ БЛОК
-- У МЕНЯ НЕТ КУКИ
-- МНЕ ПРИШЛИ ЧАНКИ
-- КУКИ НЕТ -> ГОВОРЮ "БЛОК ЛОСТ"
-- Q1: ЧТО ДЕЛАТЬ
-- Q1.1: КАК КУКА ПОПАДЁТ в то, где работает "adapter"
-- Q2: КАК ДЕЛАТЬ ЗАПРОСЫ
--
-- ОТСЮДА СЛЕДУЕТ: Cookie должны жить в Engine и быть там доступны
-- В монаде Response тоже должна быть кука
--
-- Как быть с тем, что кука может не поддерживаться подпротоколом?
-- Требовать HasCookie у всех?
pause ( 0.5 :: Timeout 'Seconds)
mapM_ cancel peerz
(_, e) <- waitAnyCatchCancel peerz
debug (pretty $ show e)
debug "we're done"
assertBool "success" True
exitSuccess
assertBool "failed" False