haskoin-node-2.4.0: test/unit/Haskoin/Node/Peer/RequestSpec.hs
module Haskoin.Node.Peer.RequestSpec (spec) where
import Haskoin
import Haskoin.Node.Peer.Request
import Haskoin.Node.Test (mkBlocks)
import Test.Hspec
import Test.Hspec.QuickCheck (prop)
import Test.QuickCheck
blocks :: [Block]
blocks = mkBlocks btcRegTest 0 btcRegTest.genesisHeader 1700000000 5
blockInv :: Block -> InvVector
blockInv b = InvVector InvWitnessBlock (headerHash b.header).get
txInv :: Tx -> InvVector
txInv tx = InvVector InvWitnessTx (txHash tx).get
-- | Feed messages to a request for the given items.
feed :: [InvVector] -> [Message] -> RequestStep
feed ivs = foldl' step (newRequest 42 ivs)
where
step (Continue r) m = stepRequest r m
step done _ = done
-- | Messages that have nothing to do with the request.
noise :: Gen Message
noise =
elements
[ MPing (Ping 1),
MPong (Pong 1),
MVerAck,
MSendHeaders,
MInv (Inv [blockInv (blocks !! 4)]),
MNotFound (NotFound [txInv (coinbaseOf (blocks !! 4))])
]
coinbaseOf :: Block -> Tx
coinbaseOf b = case b.txs of
tx : _ -> tx
[] -> error "block without transactions"
spec :: Spec
spec = describe "data request" $ do
let want = take 3 blocks
ivs = map blockInv want
done = Complete (map Right want)
it "completes when all items arrive in order" $
feed ivs (map MBlock want) `shouldBe` done
it "completes immediately when nothing is requested" $
newRequest 42 [] `shouldBe` Complete []
it "receives transactions" $ do
let txs = map coinbaseOf want
feed (map txInv txs) (map MTx txs) `shouldBe` Complete (map Left txs)
it "waits for missing items" $
case feed ivs (map MBlock (take 2 want)) of
Continue _ -> return ()
other -> expectationFailure ("Expected to continue, got " <> show other)
it "fails when items arrive out of order" $
feed ivs [MBlock (want !! 1)] `shouldBe` Failed "block out of order"
it "fails when a requested item is not found" $
feed ivs [MBlock (head' want), MNotFound (NotFound [ivs !! 2])]
`shouldBe` Failed "item not found"
it "fails when the pong arrives before all items" $
feed ivs [MBlock (head' want), MPong (Pong 42)]
`shouldBe` Failed "incomplete response"
it "ignores a pong for another ping" $
feed ivs (MPong (Pong 7) : map MBlock want) `shouldBe` done
it "does not accept a transaction for a block request" $
case feed ivs [MTx (coinbaseOf (head' want))] of
Continue _ -> return ()
other -> expectationFailure ("Expected to continue, got " <> show other)
prop "ignores unrelated messages between items" $
forAll (vectorOf 3 (listOf noise)) $ \gaps ->
let msgs = concat (zipWith (\gap b -> gap <> [MBlock b]) gaps want)
in feed ivs msgs === done
where
head' xs = case xs of
x : _ -> x
[] -> error "empty"