Skip to content

Commit

Permalink
Add Jormungandr client supporting getTipId
Browse files Browse the repository at this point in the history
  • Loading branch information
Anviking committed May 29, 2019
1 parent 5ee29e9 commit 35ad859
Show file tree
Hide file tree
Showing 5 changed files with 244 additions and 12 deletions.
13 changes: 13 additions & 0 deletions lib/jormungandr/cardano-wallet-jormungandr.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,21 @@ library
, bytestring
, cardano-wallet-core
, cborg
, exceptions
, http-client
, memory
, servant
, servant-client
, servant-client-core
, text
, text-class
, transformers
hs-source-dirs:
src
exposed-modules:
Cardano.Wallet.Jormungandr.Api
Cardano.Wallet.Jormungandr.Binary
Cardano.Wallet.Jormungandr.Network
Cardano.Wallet.Jormungandr.Compatibility
Cardano.Wallet.Jormungandr.Environment
Cardano.Wallet.Jormungandr.Transaction
Expand Down Expand Up @@ -104,8 +110,14 @@ test-suite integration
build-depends:
async
, base
, bytestring
, cardano-wallet-jormungandr
, cardano-wallet-core
, cardano-wallet-launcher
, http-client
, text-class
, text
, transformers
, hspec
type:
exitcode-stdio-1.0
Expand All @@ -115,3 +127,4 @@ test-suite integration
Main.hs
other-modules:
Cardano.LauncherSpec
Cardano.Wallet.Jormungandr.NetworkSpec
21 changes: 9 additions & 12 deletions lib/jormungandr/src/Cardano/Wallet/Jormungandr/Api.hs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,10 @@ module Cardano.Wallet.Jormungandr.Api
( Api
, GetBlock
, GetTipId
, GetBlockDecendantIds
, GetBlockDescendantIds
, PostSignedTx
, BlockId
, BlockId (..)
, api
, SignedTx
) where

import Prelude
Expand All @@ -36,8 +35,7 @@ import Data.Proxy
import Data.Text.Encoding
( decodeUtf8 )
import Servant.API
( (:<|>)
, (:>)
( (:>)
, Accept (..)
, Capture
, Get
Expand All @@ -56,8 +54,7 @@ import qualified Servant.API.ContentTypes as Servant
api :: Proxy Api
api = Proxy

type Api =
GetBlock :<|> GetTipId :<|> GetBlockDecendantIds :<|> PostSignedTx
type Api = GetTipId


-- | Retrieve a block by its id.
Expand All @@ -68,21 +65,22 @@ type GetBlock
:> Capture "blockHeaderHash" BlockId
:> Get '[JormungandrBinary] Block

-- | Retrieve 'n' decendants of a given block, sorted from closest to
-- | Retrieve 'n' descendants of a given block, sorted from closest to
-- farthest.
--
-- There might also exist fewer than 'n' decendants.
-- There might also exist fewer than 'n' descendants.
--
-- For n=3 we might have:
--
-- > [genesis] ... -- [b] -- [b+1] -- [b+2] -- [b+3] -- ... -- [tip]
-- > \ \ \
-- > parent +--- decendants ---+
type GetBlockDecendantIds
-- > parent +--- descendants ---+
type GetBlockDescendantIds
= "api"
:> "v0"
:> "block"
:> Capture "blockId" BlockId
:> "next_id"
:> QueryParam "count" Int
:> Get '[JormungandrBinary] [BlockId]

Expand Down Expand Up @@ -130,7 +128,6 @@ instance Accept JormungandrBinary where
instance FromBinary a => MimeUnrender JormungandrBinary a where
mimeUnrender _ bs = Right $ runGet get bs


data Hex

-- | Represents data rendered to hexadecimal text.
Expand Down
131 changes: 131 additions & 0 deletions lib/jormungandr/src/Cardano/Wallet/Jormungandr/Network.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}

-- |
-- Copyright: © 2018-2019 IOHK
-- License: MIT
--
--
--
-- This module allows the wallet to retrieve blocks from a known @Jormungandr@
-- node. This is done by providing a @NetworkLayer@ with some logic building on
-- top of an underlying @Jormungandr@ HTTP client.
module Cardano.Wallet.Jormungandr.Network
( Jormungandr (..)
, mkJormungandr

-- * Re-export
, BaseUrl (..)
, newManager
, defaultManagerSettings
, Scheme (..)
) where

import Prelude

import Cardano.Wallet.Jormungandr.Api
( BlockId, GetTipId, api )
import Cardano.Wallet.Network
( ErrNetworkTip (..), ErrNetworkUnreachable (..) )
import Control.Arrow
( left )
import Control.Exception
( Exception )
import Control.Monad.Catch
( throwM )
import Control.Monad.Trans.Except
( ExceptT (..) )
import Data.Proxy
( Proxy (..) )
import Network.HTTP.Client
( Manager, defaultManagerSettings, newManager )
import Servant.Client
( BaseUrl (..), ClientM, Scheme (..), client, mkClientEnv, runClientM )
import Servant.Client.Core
( ServantError (..) )
import Servant.Links
( Link, safeLink )

-- TODO: Implement a NetworkLayer
-- -- | Constructs a network layer with the given @Jormungandr@ client.
-- mkNetworkLayer :: Monad m => Jormungandr m -> NetworkLayer m
-- mkNetworkLayer _httpBridge = NetworkLayer
-- { nextBlocks = undefined
-- , networkTip = undefined
-- , postTx = undefined
-- }
--
-- -- | Creates a jormungandr 'NetworkLayer' using the given connection
-- -- settings.
-- newNetworkLayer
-- :: Int -> IO (NetworkLayer IO)
-- newNetworkLayer port = mkNetworkLayer <$> newHttpBridge port
-- where
-- newHttpBridge = undefined

{-------------------------------------------------------------------------------
Jormungandr Client
-------------------------------------------------------------------------------}

-- | Endpoints of the jormungandr REST API.
newtype Jormungandr m = Jormungandr
{ getTipId
:: ExceptT ErrNetworkTip m BlockId
}

-- | Error while trying to get descendants
data ErrGetDescendants
= ErrGetDescendantsNetworkUnreachable ErrNetworkUnreachable
| ErrGetDescendantsParentNotFound
deriving (Show, Eq)

instance Exception ErrGetDescendants

-- | Construct a Jormungandr-client
--
-- >>> mgr <- newManager defaultManagerSettings
-- >>> j = mkJormungandr mgr (BaseUrl Http "localhost" 8080 "")
-- >>> runExceptT $ getTipId j
-- Right (BlockId (Hash {getHash = "26c640a3de09b74398c14ca0a137ec78"}))
mkJormungandr
:: Manager -> BaseUrl -> Jormungandr IO
mkJormungandr mgr baseUrl = Jormungandr
{ getTipId = ExceptT $ do
let ctx = safeLink api (Proxy @GetTipId)
run cGetTipId >>= \x ->
left ErrNetworkTipNetworkUnreachable <$> defaultHandler ctx x
}
where
run :: ClientM a -> IO (Either ServantError a)
run query = runClientM query (mkClientEnv mgr baseUrl)

defaultHandler
:: Link
-> Either ServantError a
-> IO (Either ErrNetworkUnreachable a)
defaultHandler ctx = \case
Right c -> return $ Right c

-- The node has not started yet or has exited.
-- This could be recovered from by either waiting for the node
-- initialise, or restarting the node.
Left (ConnectionError e) ->
return $ Left $ ErrNetworkUnreachable e

-- Other errors (status code, decode failure, invalid content type
-- headers). These are considered to be programming errors, so crash.
Left e -> do
throwM (ErrUnexpectedNetworkFailure ctx e)

cGetTipId = client api

data ErrUnexpectedNetworkFailure
= ErrUnexpectedNetworkFailure Link ServantError
deriving (Show)

instance Exception ErrUnexpectedNetworkFailure
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
{-# LANGUAGE LambdaCase #-}
module Cardano.Wallet.Jormungandr.NetworkSpec
( spec
) where

import Prelude

import Cardano.Launcher
( Command (..), StdStream (..), launch )
import Cardano.Wallet
( unsafeRunExceptT )
import Cardano.Wallet.Jormungandr.Api
( BlockId (..) )
import Cardano.Wallet.Jormungandr.Network
( BaseUrl (..)
, Scheme (Http)
, defaultManagerSettings
, getTipId
, mkJormungandr
, newManager
)
import Cardano.Wallet.Network
( ErrNetworkTip (..) )
import Cardano.Wallet.Primitive.Types
( Hash (..) )
import Control.Concurrent
( threadDelay )
import Control.Concurrent.Async
( async, cancel )
import Control.Monad.Trans.Except
( runExceptT )
import Test.Hspec
( Spec
, afterAll
, beforeAll
, describe
, it
, shouldBe
, shouldReturn
, shouldSatisfy
)

import qualified Data.ByteString as BS

spec :: Spec
spec = do
describe "happy paths" $ beforeAll startNode $ afterAll closeNode $ do
describe "getTipId" $ do
it "gets the id of the genesis block" $ \(_, client) -> do
(BlockId (Hash tipHash)) <- unsafeRunExceptT $ getTipId client
tipHash `shouldBe` genesisHash
(BS.length tipHash) `shouldBe` 32

describe "error paths" $ beforeAll newClient $ do
describe "getTipId" $ do
it "gets a 'ErrNetworkUnreachable' if jormungandr isn't up (1)"
$ \bridge -> do
let msg x = "Expected a ErrNetworkUnreachable' failure but got "
<> show x
let action = do
res <- runExceptT $ getTipId bridge
res `shouldSatisfy` \case
Left (ErrNetworkTipNetworkUnreachable _) -> True
_ -> error (msg res)
action `shouldReturn` ()

where
genesisHash = "&\198@\163\222\t\183C\152\193L\160\161\&7\236x\245\229\EOT\175\177\167\131\190\b\b/\174\212\177:\179"

newClient = do
threadDelay 1000000
manager <- newManager defaultManagerSettings
return $ mkJormungandr manager (BaseUrl Http "localhost" 8081 "")

startNode = do
let dir = "test/data/jormungandr"
handle <- async $ launch $ return $ Command
"jormungandr"
[ "--genesis-block", dir ++ "/block-0.bin"
, "--config", dir ++ "/node.config"
, "--secret", dir ++ "/secret.yaml"
] (return ())
Inherit
client <- newClient
return (handle, client)

closeNode (handle, _) = do
cancel handle
threadDelay 1000000
2 changes: 2 additions & 0 deletions lib/jormungandr/test/integration/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Test.Hspec
( describe, hspec )

import qualified Cardano.LauncherSpec as Launcher
import qualified Cardano.Wallet.Jormungandr.NetworkSpec as Network

main :: IO ()
main = do
Expand All @@ -20,3 +21,4 @@ main = do

hspec $ do
describe "Cardano.LauncherSpec" Launcher.spec
describe "Cardano.Wallet.Network.JormunganrSpec" Network.spec

0 comments on commit 35ad859

Please sign in to comment.