packages feed

haskoin-node-2.4.0: src/Haskoin/Node/PeerMgr/Handshake.hs

-- |
-- Module      : Haskoin.Node.PeerMgr.Handshake
-- Copyright   : (c) Haskoin Developers
-- License     : MIT
-- Maintainer  : jprupp@protonmail.ch
-- Stability   : experimental
-- Portability : POSIX
--
-- The version handshake with a peer, as an explicit state machine.
--
-- The node sends its version message as soon as a connection opens. The peer
-- must answer with its own version and acknowledge ours with a verack, in
-- either order. Only once both have arrived is the peer usable, and only then
-- does "Haskoin.Node.PeerMgr" announce it with a
-- 'Haskoin.Node.Peer.PeerConnected' event.
--
-- = Why a state machine
--
-- The handshake used to be tracked with independent flags, one for the
-- version and one for the verack, with each message handler checking the
-- flags it cared about. Every handler had to remember every rule, and a
-- combination nobody thought of, such as a second verack, would silently do
-- something. Here the states are listed once, 'stepHandshake' says what each
-- message does in each state, and a message that makes no sense in the
-- current state is an error. Unforeseen sequences are rejected instead of
-- handled by accident, and the rules can be tested exhaustively without a
-- network.
--
-- The module is pure. "Haskoin.Node.PeerMgr" stores the state of each peer
-- and carries out the actions that a transition returns.
module Haskoin.Node.PeerMgr.Handshake
  ( Handshake (..),
    HandshakeEvent (..),
    HandshakeAction (..),
    HandshakeError (..),
    stepHandshake,
    handshakeVersion,
    isEstablished,
    hasVerAck,
    checkVersion,
  )
where

import Data.Bits ((.&.))
import Data.Word (Word64)
import Haskoin (Version (..), nodeNetwork)

-- | How far the handshake with a peer has progressed.
data Handshake
  = -- | Our version was sent; nothing has been received yet.
    AwaitingBoth
  | -- | The version of the peer arrived; its verack has not.
    AwaitingVerAck !Version
  | -- | The verack of the peer arrived; its version has not.
    AwaitingVersion
  | -- | Both arrived. The peer is online.
    Established !Version
  deriving stock (Eq, Show)

-- | Handshake messages received from a peer.
data HandshakeEvent
  = -- | the peer sent its version
    GotVersion !Version
  | -- | the peer acknowledged our version
    GotVerAck
  deriving stock (Eq, Show)

-- | What the peer manager must do after a transition.
data HandshakeAction
  = -- | acknowledge the version of the peer with a verack
    SendVerAck
  | -- | publish that the peer is online
    AnnounceConnected
  deriving stock (Eq, Show)

-- | Reasons to disconnect a peer during the handshake.
data HandshakeError
  = -- | the peer sent a second version message
    DuplicateVersion
  | -- | the peer sent a second verack
    DuplicateVerAck
  | -- | the peer does not serve the full block chain
    NotFullNode
  | -- | the version carries one of our own nonces: we connected to ourselves
    ConnectedToSelf
  deriving stock (Eq, Show)

-- | Apply a message to the handshake. Returns the new state and the actions to
-- carry out, or why the peer must be disconnected.
--
-- Each message may arrive once. A second version or verack is a protocol
-- violation: well-behaved peers never send one, and accepting them would let
-- a peer announce itself as connected twice.
stepHandshake ::
  Handshake ->
  HandshakeEvent ->
  Either HandshakeError (Handshake, [HandshakeAction])
stepHandshake state event =
  case (state, event) of
    (AwaitingBoth, GotVersion v) -> Right (AwaitingVerAck v, [SendVerAck])
    (AwaitingBoth, GotVerAck) -> Right (AwaitingVersion, [])
    (AwaitingVerAck v, GotVerAck) -> Right (Established v, [AnnounceConnected])
    (AwaitingVersion, GotVersion v) ->
      Right (Established v, [SendVerAck, AnnounceConnected])
    (AwaitingVerAck _, GotVersion _) -> Left DuplicateVersion
    (AwaitingVersion, GotVerAck) -> Left DuplicateVerAck
    (Established _, GotVersion _) -> Left DuplicateVersion
    (Established _, GotVerAck) -> Left DuplicateVerAck

-- | The version of the peer, once it has arrived.
handshakeVersion :: Handshake -> Maybe Version
handshakeVersion = \case
  AwaitingVerAck v -> Just v
  Established v -> Just v
  AwaitingBoth -> Nothing
  AwaitingVersion -> Nothing

-- | Whether the handshake is complete.
isEstablished :: Handshake -> Bool
isEstablished = \case
  Established _ -> True
  _ -> False

-- | Whether the peer acknowledged our version.
hasVerAck :: Handshake -> Bool
hasVerAck = \case
  AwaitingVersion -> True
  Established _ -> True
  _ -> False

-- | Check the version of a peer before accepting it. Takes the nonces of the
-- version messages that we sent to all our peers.
--
-- A peer must serve the full chain ('nodeNetwork'), because the node syncs
-- headers and applications download blocks from it. A version carrying one of
-- our nonces means the node connected to itself, which happens when it learns
-- its own address from gossip.
checkVersion :: [Word64] -> Version -> Either HandshakeError Version
checkVersion nonces v
  | v.services .&. nodeNetwork == 0 = Left NotFullNode
  | v.nonce `elem` nonces = Left ConnectedToSelf
  | otherwise = Right v