packages feed

haskoin-node-2.4.0: test/unit/Haskoin/Node/PeerMgr/HandshakeSpec.hs

module Haskoin.Node.PeerMgr.HandshakeSpec (spec) where

import Control.Monad (foldM)
import Data.Word (Word64)
import Haskoin
import Haskoin.Node (buildVersion, myVersion)
import Haskoin.Node.PeerMgr.Handshake
import Haskoin.Node.Test (zeroAddress)
import Test.Hspec
import Test.Hspec.QuickCheck (prop)
import Test.QuickCheck

-- | Version of a full node with nonce 7.
version :: Version
version = versionWith nodeNetwork

versionWith :: Word64 -> Version
versionWith services =
  buildVersion btcRegTest 7 0 (zeroAddress services) (zeroAddress 0) 0

-- | Run events from the start of the handshake, collecting actions.
run :: [HandshakeEvent] -> Either HandshakeError (Handshake, [HandshakeAction])
run = foldM step (AwaitingBoth, [])
  where
    step (h, acc) e = fmap (acc <>) <$> stepHandshake h e

-- | Random handshake messages: 'True' is a version, 'False' a verack.
toEvents :: [Bool] -> [HandshakeEvent]
toEvents = map (\b -> if b then GotVersion version else GotVerAck)

-- | At most one version and at most one verack, in any order: every sequence
-- a well-behaved peer can send.
validEvents :: Gen [Bool]
validEvents = do
  msgs <- sublistOf [True, False]
  elements [msgs, reverse msgs]

count :: (Eq a) => a -> [a] -> Int
count x = length . filter (== x)

spec :: Spec
spec = describe "version handshake" $ do
  it "completes when the version comes first" $
    run [GotVersion version, GotVerAck]
      `shouldBe` Right (Established version, [SendVerAck, AnnounceConnected])

  it "completes when the verack comes first" $
    run [GotVerAck, GotVersion version]
      `shouldBe` Right (Established version, [SendVerAck, AnnounceConnected])

  it "waits for the verack after the version" $
    run [GotVersion version] `shouldBe` Right (AwaitingVerAck version, [SendVerAck])

  it "rejects a repeated verack" $ do
    run [GotVerAck, GotVerAck] `shouldBe` Left DuplicateVerAck
    run [GotVersion version, GotVerAck, GotVerAck]
      `shouldBe` Left DuplicateVerAck

  it "rejects a repeated version" $ do
    run [GotVersion version, GotVersion version] `shouldBe` Left DuplicateVersion
    run [GotVersion version, GotVerAck, GotVersion version]
      `shouldBe` Left DuplicateVersion

  prop "fails exactly when a message is repeated" $ \bs ->
    let repeated = count True bs > 1 || count False bs > 1
     in either (const True) (const False) (run (toEvents bs)) === repeated

  prop "acknowledges the version once" $
    forAll validEvents $ \bs ->
      fmap (count SendVerAck . snd) (run (toEvents bs)) === Right (count True bs)

  prop "announces the peer once, when both messages arrived" $
    forAll validEvents $ \bs ->
      let both = length bs == 2
       in fmap (\(h, as) -> (isEstablished h, count AnnounceConnected as)) (run (toEvents bs))
            === Right (both, if both then 1 else 0)

  prop "knows the version of the peer once received" $
    forAll validEvents $ \bs ->
      fmap (handshakeVersion . fst) (run (toEvents bs))
        === Right (if True `elem` bs then Just version else Nothing)

  prop "knows whether the peer acknowledged our version" $
    forAll validEvents $ \bs ->
      fmap (hasVerAck . fst) (run (toEvents bs)) === Right (False `elem` bs)

  describe "version checks" $ do
    it "accepts a full node" $
      checkVersion [1, 2] version `shouldBe` Right version
    it "rejects a peer that does not serve blocks" $
      checkVersion [] (versionWith 0) `shouldBe` Left NotFullNode
    it "rejects a connection to ourselves" $
      checkVersion [7] version `shouldBe` Left ConnectedToSelf
    it "uses our protocol version" $
      version.version `shouldBe` fromIntegral myVersion