haskoin-node-0.18.0: src/Haskoin/Node.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
module Haskoin.Node
( module Haskoin.Node.Peer
, module Haskoin.Node.Manager
, module Haskoin.Node.Chain
, NodeConfig (..)
, NodeEvent (..)
, Node (..)
, withNode
, withConnection
) where
import Control.Monad (forever)
import Control.Monad.Logger (MonadLoggerIO)
import Data.Conduit.Network (appSink, appSource, clientSettings,
runTCPClient, ClientSettings)
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.Manager
import Haskoin.Node.Peer
import Network.Socket (NameInfoFlag (..), SockAddr,
getNameInfo)
import NQE (Inbox, Publisher, publish, receive,
withPublisher, withSubscription)
import Text.Read (readMaybe)
import UnliftIO (MonadUnliftIO, SomeException, catch,
liftIO, link, throwIO, withAsync)
-- | General node configuration.
data NodeConfig = NodeConfig
{ nodeConfMaxPeers :: !Int
-- ^ maximum number of connected peers allowed
, nodeConfDB :: !DB
-- ^ database handler
, nodeConfColumnFamily :: !(Maybe ColumnFamily)
-- ^ database column family
, nodeConfPeers :: ![String]
-- ^ static list of peers to connect to
, nodeConfDiscover :: !Bool
-- ^ activate peer discovery
, nodeConfNetAddr :: !NetworkAddress
-- ^ network address for the local host
, nodeConfNet :: !Network
-- ^ network constants
, nodeConfEvents :: !(Publisher NodeEvent)
-- ^ node events are sent to this publisher
, nodeConfTimeout :: !NominalDiffTime
-- ^ timeout in seconds
, nodeConfPeerMaxLife :: !NominalDiffTime
-- ^ peer disconnect after seconds
, nodeConfConnect :: !(SockAddr -> WithConnection)
}
data Node = Node { nodeManager :: !PeerManager
, nodeChain :: !Chain
}
data NodeEvent
= ChainEvent !ChainEvent
| PeerEvent !PeerEvent
| PeerMessage !Peer !Message
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
chainForwarder :: MonadLoggerIO m
=> PeerManager
-> Publisher NodeEvent
-> Inbox ChainEvent
-> m ()
chainForwarder mgr pub inbox =
forever $ receive inbox >>= \event -> do
case event of
ChainBestBlock bb ->
managerBest (nodeHeight bb) mgr
_ -> return ()
publish (ChainEvent event) pub
managerForwarder :: MonadLoggerIO m
=> Chain
-> Publisher NodeEvent
-> Inbox PeerEvent
-> m ()
managerForwarder ch pub inbox =
forever $ receive inbox >>= \event -> do
case event of
PeerConnected p ->
chainPeerConnected p ch
PeerDisconnected p ->
chainPeerDisconnected p ch
publish (PeerEvent event) pub
peerForwarder :: MonadLoggerIO m
=> Chain
-> PeerManager
-> Publisher NodeEvent
-> Inbox (Peer, Message)
-> m ()
peerForwarder ch mgr pub inbox =
forever $ receive inbox >>= \(p, msg) -> do
case msg of
MVersion v ->
managerVersion p v mgr
MVerAck ->
managerVerAck p mgr
MPing (Ping n) ->
managerPing p n mgr
MPong (Pong n) ->
managerPong p n mgr
MAddr (Addr ns) ->
managerAddrs p (map snd ns) mgr
MHeaders (Headers hs) ->
chainHeaders p (map fst hs) ch
_ -> return ()
managerTickle p mgr
publish (PeerMessage p msg) pub
-- | Launch node process in the foreground.
withNode ::
( MonadLoggerIO m
, MonadUnliftIO m
)
=> NodeConfig
-> (Node -> m a)
-> m a
withNode cfg action =
withPublisher $ \peer_pub ->
withPublisher $ \mgr_pub ->
withPublisher $ \ch_pub ->
withSubscription peer_pub $ \peer_inbox ->
withSubscription mgr_pub $ \mgr_inbox ->
withSubscription ch_pub $ \ch_inbox ->
withPeerManager (mgr_config mgr_pub peer_pub) $ \mgr ->
withChain (chain_config ch_pub) $ \ch ->
withAsync (peerForwarder ch mgr pub peer_inbox) $ \a ->
withAsync (managerForwarder ch pub mgr_inbox) $ \b ->
withAsync (chainForwarder mgr pub ch_inbox) $ \c ->
link a >> link b >> link c >>
action Node { nodeManager = mgr, nodeChain = ch }
where
pub = nodeConfEvents cfg
chain_config ch_pub =
ChainConfig
{ chainConfDB = nodeConfDB cfg
, chainConfColumnFamily = nodeConfColumnFamily cfg
, chainConfNetwork = nodeConfNet cfg
, chainConfEvents = ch_pub
, chainConfTimeout = nodeConfTimeout cfg
}
mgr_config mgr_pub peer_pub =
PeerManagerConfig
{ peerManagerMaxPeers = nodeConfMaxPeers cfg
, peerManagerPeers = nodeConfPeers cfg
, peerManagerDiscover = nodeConfDiscover cfg
, peerManagerNetAddr = nodeConfNetAddr cfg
, peerManagerNetwork = nodeConfNet cfg
, peerManagerEvents = mgr_pub
, peerManagerMaxLife = nodeConfPeerMaxLife cfg
, peerManagerTimeout = nodeConfTimeout cfg
, peerManagerConnect = nodeConfConnect cfg
, peerManagerPub = peer_pub
}