Home

Awesome

Node Framework

Note from Alexander Granin, architector and main contributor

https://github.com/graninas/Node is the main repository of the project (Enecuum does not support it anymore). The framework is in the frozen state but it can be resumed once you'll need it and we're agree on the form of maintainance.

Intro

Node Framework allows to build network actors and blockchain protocols, console applications, work with KV database and cryptography. Current features include:

The Node project contains:

This tutorial contains more info.

Framework structure

The framework represents a set of embedded monadic languages organized hierarchically. The languages are divided to core languages responsible for common subsystems and framework languages responsible for network and actors behavior.

Core languages

Framework languages

Build, Install, Run

Install Haskell Stack

  1. Install Haskell stack

curl -sSL https://get.haskellstack.org/ | sh

  1. If needed, add the path to your profile

sudo nano ~/.profile and append export PATH=$PATH:$HOME/.local/bin at the end.

Install External dependencies

sudo apt update && sudo apt install librocksdb-dev libgd-dev libtinfo-dev -y

Build Node

  1. Clone repo:

git clone git@github.com:graninas/Node.git && cd Node

  1. Build & install

stack build

  1. Run tests (optional)

Run all tests:

stack test

Run fast tests:

stack build --test --test-arguments "-m Fast"

Run slow and unreliable tests:

stack build --test --test-arguments "-m Slow"

Sample nodes

enq-test-node-haskell is a single executable for sample nodes.

Node code sample

In this sample, two nodes interact via network sending UDP messages.

Network messages

-- Messages
newtype Ping = Ping Text deriving (Generic, ToJSON, FromJSON)
newtype Pong = Pong Int  deriving (Generic, ToJSON, FromJSON)

Server node

 -- Ping server node unique tag.
data PingServerNode = PingServerNode

-- Ping server node config.
data instance NodeConfig PingServerNode = PingServerNodeConfig
    { stopOnPing  :: Int
    , servingPort :: PortNumber
    }

-- Ping server node definition type.
instance Node PingServerNode where
    data NodeScenario PingServerNode = PingServer
    getNodeScript PingServer = pingServerNode
    getNodeTag _ = PingServerNode

-- Handling Ping messages.
acceptPing
    :: D.StateVar D.NodeStatus
    -> D.StateVar Int
    -> Int
    -> Ping
    -> D.Connection D.Udp
    -> L.NodeL ()
acceptPing status pingsCount threshold (Ping clientName) conn = do
    pings <- L.atomically $ do
        L.modifyVar pingsCount (+1)
        L.readVar pingsCount

    let done = pings + 1 >= threshold
    when done $ do
        L.close conn
        L.writeVarIO status D.NodeFinished
        L.logInfo $ "Pings threshold reached: " +|| threshold ||+ ". Finishing."

    unless done $ do
        L.send conn (Pong pings)
        L.logInfo $ "Ping #" +|| pings ||+ " accepted from " +|| clientName ||+ "."

-- Ping server definition node.
pingServerNode :: NodeConfig PingServerNode -> L.NodeDefinitionL ()
pingServerNode cfg = do
    let threshold = _stopOnPing cfg
    let port = _servingPort cfg

    pingsCount <- L.newVarIO 0
    status     <- L.newVarIO D.NodeActing

    -- Starting a separate process for serving on UDP port.
    L.serving D.Udp port $
        L.handler $ acceptPing status pingsCount threshold

    L.awaitNodeFinished' status

Client node

-- Pong client node unique tag.
data PongClientNode = PongClientNode
    deriving (Show, Generic)

-- Pong client node config.
data instance NodeConfig PongClientNode = PongClientNodeConfig
    { _clientName        :: Text
    , _pingDelay         :: Int
    , _pingServerAddress :: D.Address
    }
    deriving (Show, Generic)

-- Pong client node definition type.
instance Node PongClientNode where
    data NodeScenario PongClientNode = PongClient
        deriving (Show, Generic)
    getNodeScript _ = pongClientNode'
    getNodeTag _ = PongClientNode

-- Accepting pong responses from the server.
acceptPong :: Pong -> connection -> L.NodeL ()
acceptPong (Pong pingsCount) _ =
    L.logInfo $ "Pong accepted from server. Pings count: " <> show pingsCount

-- Sending pings to the server.
pingSending :: D.StateVar D.NodeStatus -> NodeConfig PongClientNode -> D.Connection D.Udp -> L.NodeL ()
pingSending status cfg conn = do
    L.delay $ _pingDelay cfg
    L.logInfo "Sending Ping to the server."
    eSent <- L.send conn (Ping $ _clientName cfg)
    case eSent of
        Right () -> pingSending status cfg conn
        Left _   -> do
            L.logInfo "Server is gone."
            L.close conn
            L.writeVarIO status D.NodeFinished

-- Pong client definition node.
pongClientNode :: NodeConfig PongClientNode -> L.NodeDefinitionL ()
pongClientNode cfg = do
    status <- L.newVarIO D.NodeActing

    -- Connecting to the server.
    mbConn <- L.open D.Udp (_pingServerAddress cfg) $
        L.handler acceptPong

    case mbConn of
        Nothing -> L.logError "Ping Server not found"
        Just conn -> do
            -- Forking separate process of periodical pings.
            L.process (pingSending status cfg conn)
            -- Waiting when the node is finished.
            L.awaitNodeFinished' status

Additional materials