packages feed

haskoin-node-1.0.1: src/Haskoin/Node.hs

{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE NoFieldSelectors #-}

module Haskoin.Node
  ( module Haskoin.Node.Peer,
    module Haskoin.Node.PeerMgr,
    module Haskoin.Node.Chain,
    NodeConfig (..),
    NodeEvent (..),
    Node (..),
    withNode,
    withConnection,
  )
where

import Control.Monad (forever)
import Control.Monad.Cont (ContT (..), MonadCont (callCC), cont, runCont, runContT)
import Control.Monad.Logger (MonadLoggerIO)
import Control.Monad.Trans (lift)
import Data.Conduit.Network
  ( ClientSettings,
    appSink,
    appSource,
    clientSettings,
    runTCPClient,
  )
import Data.String.Conversions (cs)
import Data.Time.Clock (NominalDiffTime)
import Database.RocksDB (ColumnFamily, DB)
import Haskoin
  ( Addr (..),
    BlockNode (..),
    Headers (..),
    Message (..),
    Network,
    NetworkAddress,
    Ping (..),
    Pong (..),
  )
import Haskoin.Node.Chain
import Haskoin.Node.Peer
import Haskoin.Node.PeerMgr
import NQE
  ( Inbox,
    Publisher,
    publish,
    receive,
    withPublisher,
    withSubscription,
  )
import Network.Socket
  ( NameInfoFlag (..),
    SockAddr,
    getNameInfo,
  )
import Text.Read (readMaybe)
import UnliftIO
  ( MonadUnliftIO,
    SomeException,
    catch,
    liftIO,
    link,
    throwIO,
    withAsync,
  )

-- | General node configuration.
data NodeConfig = NodeConfig
  { -- | maximum number of connected peers allowed
    maxPeers :: !Int,
    -- | database handler
    db :: !DB,
    -- | database column family
    cf :: !(Maybe ColumnFamily),
    -- | static list of peers to connect to
    peers :: ![String],
    -- | activate peer discovery
    discover :: !Bool,
    -- | network address for the local host
    address :: !NetworkAddress,
    -- | network constants
    net :: !Network,
    -- | node events are sent to this publisher
    pub :: !(Publisher NodeEvent),
    -- | timeout in seconds
    timeout :: !NominalDiffTime,
    -- | peer disconnect after seconds
    maxPeerLife :: !NominalDiffTime,
    connect :: !(SockAddr -> WithConnection)
  }

data Node = Node
  { peerMgr :: !PeerMgr,
    chain :: !Chain
  }

data NodeEvent
  = ChainEvent !ChainEvent
  | PeerEvent !PeerEvent
  deriving (Eq)

withConnection :: SockAddr -> WithConnection
withConnection na f =
  fromSockAddr na >>= \case
    Nothing -> throwIO PeerAddressInvalid
    Just cset ->
      runTCPClient cset $ \ad ->
        f (Conduits (appSource ad) (appSink ad))

fromSockAddr ::
  (MonadUnliftIO m) => SockAddr -> m (Maybe ClientSettings)
fromSockAddr sa = go `catch` e
  where
    go = do
      (maybe_host, maybe_port) <- liftIO (getNameInfo flags True True sa)
      return $
        clientSettings
          <$> (readMaybe =<< maybe_port)
          <*> (cs <$> maybe_host)
    flags = [NI_NUMERICHOST, NI_NUMERICSERV]
    e :: (Monad m) => SomeException -> m (Maybe a)
    e _ = return Nothing

chainEvents ::
  (MonadUnliftIO m, MonadLoggerIO m) =>
  PeerMgr ->
  Inbox ChainEvent ->
  Publisher NodeEvent ->
  m ()
chainEvents mgr input output = forever $ do
  event <- receive input
  case event of
    ChainBestBlock bb ->
      peerMgrBest bb.height mgr
    _ -> return ()
  publish (ChainEvent event) output

peerEvents ::
  (MonadUnliftIO m, MonadLoggerIO m) =>
  Chain ->
  PeerMgr ->
  Inbox PeerEvent ->
  Publisher NodeEvent ->
  m ()
peerEvents ch mgr input output = forever $ do
  event <- receive input
  case event of
    PeerConnected p ->
      chainPeerConnected p ch
    PeerDisconnected p ->
      chainPeerDisconnected p ch
    PeerMessage p msg -> do
      case msg of
        MVersion v ->
          peerMgrVersion p v mgr
        MVerAck ->
          peerMgrVerAck p mgr
        MPing (Ping n) ->
          peerMgrPing p n mgr
        MPong (Pong n) ->
          peerMgrPong p n mgr
        MAddr (Addr ns) ->
          peerMgrAddrs p (map snd ns) mgr
        MHeaders (Headers hs) ->
          chainHeaders p (map fst hs) ch
        _ -> return ()
      peerMgrTickle p mgr
  publish (PeerEvent event) output

-- | Launch node process in the foreground.
withNode ::
  (MonadLoggerIO m, MonadUnliftIO m) =>
  NodeConfig ->
  (Node -> m a) ->
  m a
withNode NodeConfig {..} action = flip runContT return $ do
  peerPub <- ContT withPublisher
  peerSub <- ContT (withSubscription peerPub)
  chainPub <- ContT withPublisher
  chainSub <- ContT (withSubscription chainPub)
  let peerMgrCfg = PeerMgrConfig {pub = peerPub, ..}
  let chainCfg = ChainConfig {pub = chainPub, ..}
  chain <- ContT (withChain chainCfg)
  peerMgr <- ContT $ withPeerMgr peerMgrCfg
  lift . link =<< ContT (withAsync $ chainEvents peerMgr chainSub pub)
  lift . link =<< ContT (withAsync $ peerEvents chain peerMgr peerSub pub)
  lift $ action Node {..}