Skip to content

Commit

Permalink
allow modules to depend explicitly on other modules. (#212)
Browse files Browse the repository at this point in the history
* using deps type variable in module type to list dependencies

* Added AppEffs

* better syntax by eliminating type variable in HandlersContext

* tutorial updated

* udpdate docs symlinks

* updated tutorial sym link files

* typos

* add frontmatter for router
  • Loading branch information
martyall authored Feb 18, 2020
1 parent 25e752c commit 2c0a923
Show file tree
Hide file tree
Showing 45 changed files with 551 additions and 345 deletions.
1 change: 0 additions & 1 deletion hs-abci-docs/doc/0320-BaseApp.md

This file was deleted.

1 change: 1 addition & 0 deletions hs-abci-docs/doc/0320-Effects.md
1 change: 0 additions & 1 deletion hs-abci-docs/doc/0460-Module.md

This file was deleted.

1 change: 1 addition & 0 deletions hs-abci-docs/doc/0460-Router.md
1 change: 0 additions & 1 deletion hs-abci-docs/doc/0470-Application.md

This file was deleted.

1 change: 1 addition & 0 deletions hs-abci-docs/doc/0470-Module.md
1 change: 1 addition & 0 deletions hs-abci-docs/doc/0480-Application.md
1 change: 1 addition & 0 deletions hs-abci-docs/doc/0490-Testing.md
6 changes: 6 additions & 0 deletions hs-abci-docs/nameservice/package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -149,18 +149,24 @@ tests:
- Tutorial.Nameservice.Module
- Tutorial.Nameservice.Query
- Tutorial.Nameservice.Types
- Tutorial.Nameservice.Testing
ghc-options: -Wall -pgmL markdown-unlit
dependencies:
- aeson
- base
- data-default-class
- hs-abci-sdk
- hs-abci-server
- hs-abci-test-utils
- hs-tendermint-client
- lens
- markdown-unlit
- mtl
- nameservice
- polysemy
- polysemy-plugin
- proto3-suite
- servant
- string-conversions
- text
nameservice-test:
Expand Down
18 changes: 5 additions & 13 deletions hs-abci-docs/nameservice/src/Nameservice/Application.hs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
module Nameservice.Application
( EffR
, NameserviceModules
( NameserviceModules
, handlersContext
) where

Expand All @@ -14,28 +13,21 @@ import Tendermint.SDK.Crypto (Secp256k1)
import qualified Tendermint.SDK.Modules.Auth as A
import qualified Tendermint.SDK.Modules.Bank as B

type EffR =
N.NameserviceEffs BA.:&
B.BankEffs BA.:&
A.AuthEffs BA.:&
BA.TxEffs BA.:&
BA.BaseApp BA.CoreEffs

type NameserviceModules =
'[ N.NameserviceM EffR
, B.BankM EffR
, A.AuthM EffR
'[ N.Nameservice
, B.Bank
, A.Auth
]

handlersContext :: HandlersContext Secp256k1 NameserviceModules EffR BA.CoreEffs
handlersContext :: HandlersContext Secp256k1 NameserviceModules BA.CoreEffs
handlersContext = HandlersContext
{ signatureAlgP = Proxy @Secp256k1
, modules = nameserviceModules
, compileToCore = BA.defaultCompileToCore
, anteHandler = baseAppAnteHandler
}
where
nameserviceModules :: ModuleList NameserviceModules EffR
nameserviceModules =
N.nameserviceModule
:+ B.bankModule
Expand Down
25 changes: 10 additions & 15 deletions hs-abci-docs/nameservice/src/Nameservice/Modules/Nameservice.hs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module Nameservice.Modules.Nameservice

(
-- * Module
NameserviceM
Nameservice
, nameserviceModule
, module Nameservice.Modules.Nameservice.Keeper
, module Nameservice.Modules.Nameservice.Messages
Expand All @@ -20,23 +20,18 @@ import Nameservice.Modules.Nameservice.Query
import Nameservice.Modules.Nameservice.Router
import Nameservice.Modules.Nameservice.Types
import Polysemy (Members)
import Tendermint.SDK.Application (Module (..))
import Tendermint.SDK.BaseApp (BaseEffs,
DefaultCheckTx (..),
TxEffs)
import Tendermint.SDK.Modules.Auth (AuthEffs)
import Tendermint.SDK.Modules.Bank (BankEffs)
import Tendermint.SDK.Application (Module (..),
ModuleEffs)
import Tendermint.SDK.BaseApp (DefaultCheckTx (..))
import Tendermint.SDK.Modules.Bank (Bank)

type NameserviceM r =
Module "nameservice" MessageApi MessageApi QueryApi NameserviceEffs r

type Nameservice =
Module "nameservice" MessageApi MessageApi QueryApi NameserviceEffs '[Bank]

nameserviceModule
:: Members BaseEffs r
=> Members AuthEffs r
=> Members TxEffs r
=> Members BankEffs r
=> Members NameserviceEffs r
=> NameserviceM r
:: Members (ModuleEffs Nameservice) r
=> Nameservice r
nameserviceModule = Module
{ moduleTxDeliverer = messageHandlers
, moduleTxChecker = defaultCheckTx (Proxy :: Proxy MessageApi) (Proxy :: Proxy r)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ import Polysemy.Error (Error, mapError,
throw)
import Polysemy.Output (Output)
import qualified Tendermint.SDK.BaseApp as BaseApp
import Tendermint.SDK.Modules.Auth (AuthEffs, Coin (..),
CoinId)
import Tendermint.SDK.Modules.Auth (Coin (..), CoinId)
import Tendermint.SDK.Modules.Bank (BankEffs, burn, mint,
transfer)

Expand Down Expand Up @@ -66,7 +65,7 @@ eval = mapError BaseApp.makeAppError . evalNameservice

faucetAccount
:: Members [BaseApp.Logger, Output BaseApp.Event] r
=> Members AuthEffs r
=> Members BankEffs r
=> FaucetAccount
-> Sem r ()
faucetAccount FaucetAccount{..} = do
Expand Down Expand Up @@ -104,7 +103,7 @@ setName SetName{..} = do

deleteName
:: Members [BaseApp.Logger, Output BaseApp.Event] r
=> Members AuthEffs r
=> Members BankEffs r
=> Members NameserviceEffs r
=> DeleteName
-> Sem r ()
Expand All @@ -126,7 +125,6 @@ deleteName DeleteName{..} = do

buyName
:: Members [BaseApp.Logger, Output BaseApp.Event] r
=> Members AuthEffs r
=> Members BankEffs r
=> Members NameserviceEffs r
=> BuyName
Expand All @@ -145,7 +143,6 @@ buyName msg = do
where
buyUnclaimedName
:: Members [BaseApp.Logger, Output BaseApp.Event] r
=> Members AuthEffs r
=> Members BankEffs r
=> Members NameserviceEffs r
=> BuyName
Expand All @@ -169,7 +166,6 @@ buyName msg = do

buyClaimedName
:: Members NameserviceEffs r
=> Members AuthEffs r
=> Members BankEffs r
=> Members [BaseApp.Logger, Output BaseApp.Event] r
=> BuyName
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import Tendermint.SDK.BaseApp ((:~>), BaseEffs,
RoutingTx (..),
TxEffs, TypedMessage,
incCount, withTimer)
import Tendermint.SDK.Modules.Auth (AuthEffs)
import Tendermint.SDK.Modules.Bank (BankEffs)
import Tendermint.SDK.Types.Message (Msg (..))
import Tendermint.SDK.Types.Transaction (Tx (..))
Expand All @@ -32,7 +31,6 @@ type MessageApi =
messageHandlers
:: Members BaseEffs r
=> Members BankEffs r
=> Members AuthEffs r
=> Members TxEffs r
=> Members NameserviceEffs r
=> RouteTx MessageApi r
Expand All @@ -41,7 +39,6 @@ messageHandlers = buyNameH :<|> setNameH :<|> deleteNameH :<|> faucetH
buyNameH
:: Members BaseEffs r
=> Members TxEffs r
=> Members AuthEffs r
=> Members BankEffs r
=> Members NameserviceEffs r
=> RoutingTx BuyName
Expand All @@ -63,7 +60,7 @@ setNameH (RoutingTx Tx{txMsg=Msg{msgData}}) = do
deleteNameH
:: Members BaseEffs r
=> Members TxEffs r
=> Members AuthEffs r
=> Members BankEffs r
=> Members NameserviceEffs r
=> RoutingTx DeleteName
-> Sem r ()
Expand All @@ -72,8 +69,8 @@ deleteNameH (RoutingTx Tx{txMsg=Msg{msgData}}) = do
withTimer "delete_duration_seconds" $ deleteName msgData

faucetH
:: Members AuthEffs r
=> Members TxEffs r
:: Members TxEffs r
=> Members BankEffs r
=> Members BaseEffs r
=> RoutingTx FaucetAccount
-> Sem r ()
Expand Down
7 changes: 4 additions & 3 deletions hs-abci-docs/nameservice/tutorial/Foundations/01-Overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,16 @@ The SDK relies heavily on two abstractions to facilitate application development

The effects system is backed by a library called `polysemy` which we mentioned in the introduction. An application basically has three layers of effects

1. **Application level effects**: These are introduced by the application developer in order to customize application behavior.
1. **Application level effects**: These are introduced by the application developer in order to customize application behavior. Examples include things like `AuthEffs` from `Tendermint.SDK.Modules.Auth` that allow for manipulating accounts and throwing custom `Auth` errors.
2. **Transaction effects**: These are the effects that allow you to interpret transactions, emit events, meter gas, and handle storage requests.
3. **Base effects**: These include things like logging, metrics, exception handling, and some error handling.
4. **Core effects**: These are largely internal and used to interpet the other effects to `IO`. There are a two different core options available in the SDK (distinguished by a an in-memory versus production database), but the more advanced developer might wish to write their own.
4. **Store effects**: These are the effects that describe the possible interactions with an abstract merkelized key-value database.
5. **Core effects**: These are largely internal and used to interpet the other effects to `IO`. There are a two different core options available in the SDK (distinguished by a an in-memory versus production database), but the more advanced developer might wish to write their own.

The tutorial explains the multiple points at which you can hook your application specific effects and types into the SDK.

## Modules

The core building block of an application is a `Module`. There are some modules that ship with the SDK and make up a kind of standard library. These modules are of general utility, like dealing with things like authentication or tokens, and are considered to be safe.
The core building block of an application is a `Module`. There are some modules that ship with the SDK and make up a kind of standard library. These modules are of general utility, like dealing with things like authentication or coins, and are considered to be safe.

The most useful part of the SDK is that you are free to define your own modules, or depend on other third party modules outside the SDK. Since they all have the same type, they all easily compose into larger applications as standalone components or dependencies.
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
---
title: Foundations - SDK Effects
title: Foundations - Effects
---

# BaseEffs
# Effects Lists

There are several distinguished effects lists you should be familiar with that come up at different parts of the sdk or application development. They are often combined or stacked using the `:&` operator, which is simply concatenation of type level lists.


## BaseEffs

`BaseEffs` is a set of effects that the SDK operates with and are freely available
for an application developer to make use of in any part of their application code. They
Expand All @@ -26,7 +31,7 @@ These effects are:

The SDK does not make any assumptions about how `BaseEffs` will be interpreted at runtime, it only assumes that the developer might want use one of the provided core effects systems to interpret them. For example, the standard `CoreEffs` uses a prometheus metrics server to interpret the `Metrics` effect while `PureCoreEffs` just ignores the effect entirely.

# TxEffs
## TxEffs

`TxEffs` are the effects used to interpret transactions and are defined as

Expand All @@ -50,7 +55,7 @@ where

`TxEffs` effects are available any time during transactions and are interpreted at the time of transaction routing. It's worth noting that the interpreters take care of finalizing writes to the database when it's appropriate (i.e. during a `deliverTx` message) and not otherwise (e.g. during a `checkTx` message).

# QueryEffs
## QueryEffs

`QueryEffs` are used to interpret queries and are defined as

Expand All @@ -68,15 +73,9 @@ where

`QueryEffs` are available any time you are writing handlers for a module's query api. The SDK manages a separate connection for reading from committed (i.e. via blocks) state when `QueryEffs` are present.

# BaseApp

There is a type alias in the SDK called `BaseApp` with the definition

~~~ haskell ignore
type BaseApp core = BaseEffs :& StoreEffs :& core
~~~
## StoreEffs

where `StoreEffs` is given by
`StoreEffs` describe the possible interactions with an abstract merkelized key-value database:

~~~ haskell ignore
type StoreEffs =
Expand All @@ -88,5 +87,23 @@ type StoreEffs =
]
~~~

They are used to interpret `TxEffects` depending on what context you're in, e.g. while executing a `delierTx` versus `checkTx` message, or a `query`. Some effects are tagged with a promoted value of type `Scope`, i.e. `'Consensus` and `'QueryAndMempool`. This is because your application will keep multiple connections to the database that are used in different situations. For example, since writing to the state at a previous blocktimes is impossible, we disallow writing to the database in such instances.


# Effects Type Synonyms

There are a few effects lists that appear so frequently at different points in the SDK that they deserve synonyms:

## Effs

`Effs` is technically a type family coming from the class `Tendermint.SDK.Application.Module.Eval`. It is used to enumerate all the effects that would be needed in order to run an application defined by a `ModuleList`.

## BaseAppEffs

There is a type alias in the SDK called `BaseAppEffs` with the definition

~~~ haskell ignore
type BaseApp core = BaseEffs :& StoreEffs :& core
~~~

It sits at the bottom of any applications effects list and ultimately everything is interpreted through these effects before being run, e.g. `TxEffs` and `QueryEffs`. This effects list is pretty much the only place where the application developer needs to decide how the interpretation should be done. There are two options in the SDK, using `PureCoreEffs` or `CoreEffs` depending on whether you want to rely on an external or in-memory database.
27 changes: 21 additions & 6 deletions hs-abci-docs/nameservice/tutorial/Foundations/03-Modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,43 @@
title: Foundations - Module
---

# Modules
# Modules and Components

First a note. There is a small technical distinction betweek a `Module` and a `Component`, but we often use these words interchangable. A `Component` is simply a type synonym for a partially applied `Module`, which leaves the `r` parameter free. It's basically the type level description of a `Module` that hasn't yet been put in the context of a larger application, which is where the `r` comes in.

## Definition

A `Module` has a very specific meaning in the context of this SDK. A `Module` is something between a library and a small state machine. Here is the type:

~~~ haskell ignore
data Module (name :: Symbol) (check :: *) (deliver :: *) (query :: *) (es :: EffectRow) (r :: EffectRow) = Module
data Module (name :: Symbol) (check :: *) (deliver :: *) (query :: *) (es :: EffectRow) (deps :: [Component]) (r :: EffectRow) = Module
{ moduleTxChecker :: T.RouteTx check r
, moduleTxDeliverer :: T.RouteTx deliver r
, moduleQuerier :: Q.RouteQ query r
, moduleEval :: forall deps. Members T.TxEffs deps => forall a. Sem (es :& deps) a -> Sem deps a
, moduleEval :: forall s. (Members T.TxEffs s, Members (DependencyEffs deps) s) => forall a. Sem (es :& s) a -> Sem s a
}

~~~

where `DependencyEffs` is a type level function that gathers effect dependencies from a list of `Component`s that the module depends on:

~~~ haskell ignore

type family DependencyEffs (ms :: [Component]) :: EffectRow where
DependencyEffs '[] = '[]
DependencyEffs (Module _ _ _ _ es deps ': rest) = es :& DependencyEffs rest
DependencyEffs _ = TypeError ('Text "DependencyEffs is a partial function defined only on partially applied Modules")

~~~

where the type parameters
Let's take a look at the type parameters

- `name` is the name of the module, e.g. `"bank"`.
- `check` is the transaction router api type for `checkTx` messages.
- `deliver` is the transaction router api type for `checkTx` messages.
- `query` is the query router api type for `query` messages
- `es` is the set of effects introduced by this module.
- `deps` is the list of `Components` (i.e. Modules) that this module depends on, in the sense that the `eval` function for this module will interpret into those effects. (For example, the `BankEffs` for the `Bank` module are interpreted into `AuthEffs`)
- `r` is the global set of effects that this module will run in when part of a larger application (more on this later).

Below that line we see the fields for the `Module` data type, where
Expand All @@ -42,11 +57,11 @@ Note that in the event that a `Module` is _abstract_, meaning it doesn't have an
`Module`s are meant to be composed to create larger applications. We will see examples of this with the `Nameservice` application. The way to do this is easy, as the `ModuleList` data type allows you to simply combine them in a heterogeneous list:

~~~ haskell ignore
data ModuleList (ms :: [*]) r where
data ModuleList (ms :: [Component]) r where
NilModules :: Modules '[] r
(:+) :: Module name check deliver query es r
-> Modules ms r
-> Modules (Module name check deliver query es r ': ms) r
-> Modules (Module name check deliver query es ': ms) r
~~~

When you are ready to create your application, you simply specify a value of type `ModuleList` and some other configuration data, and the SDK will create an `App` for you.
2 changes: 2 additions & 0 deletions hs-abci-docs/nameservice/tutorial/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ The application consists of three modules:

## How to Read this Tutorial

First a warning. The `hs-abci-sdk` package is a sophisticated *framework* for building blockchain applications backed by tendermint consensus. As it is a framework, there are certain points when syntax is simplified at the expense of introducing indirection, type synonyms, and a few type families. There was a serious amount of effort to expose as little of this as possible, but alas sometimes things will be confusing and it's best to blindly follow the examples.

This tutorial is largely written as a literate haskell file to simulate developing the Nameservice app from scratch. The file structure is similar to the actual app. We will partially develop a haskell module corresponding to what you find in the app, but possibly not the whole thing. Thus whenever we depend on a haskell module in the tutorial, rather than importing from the tutorial itself we will import from the app.

The benefit of this is that we don't have to develop the entire application in this tutorial. Any breaking changes in the app will (hopefully) break the tutorial and so if you can read this, the tutorial is correct.
Expand Down
Loading

0 comments on commit 2c0a923

Please sign in to comment.