Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Minor Tutorial Edits #264

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions hs-abci-docs/doc/98-Logging.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ The `Nameservice` application has support for either scribe -- it will use Elast

## Logging to Elasticsearch

The docker network includes an `elk` image (Elasticsearch, Logstash, Kibana) for persisting and querying logs. You can read more about this stack [here](https://www.elastic.co/what-is/elk-stack). In summary `elk` is a powerful solution for hosting searchable structured logs.
The docker network includes an `elk` image (Elasticsearch, Logstash, Kibana) for persisting and querying logs. You can read more about this stack [here](https://www.elastic.co/what-is/elk-stack). In summary `elk` is a powerful solution for hosting searchable structured logs.

When logging to Elasticsearch, you can use the Kibana dashboard for creating queries and visualizations. We will cover the basics here. If you have already launched the [docker network](TODO: Where is the instructions for this?), you can view the Kibana dashboard by going to http://localhost:5601/app/kibana. You should see something like

Expand Down Expand Up @@ -38,7 +38,7 @@ The log structure is effectively a JSON object (with nesting). There are a few f
- `message_hash`: the SHA256 of the protobuf encoded bytes for the abci message that caused the logs.
- `ns` (namespace): a list of increasingly specific scopes for where the log originated. In this case, `nameservice` is the root namespace, `server` or `application` is the next scope.

Remember that the basic lifescycle of an `ABCI` message is that it first comes to the ABCI-server from tendermint, is then handed off to your application for processing, and finally the response is sent from the ABCI-server back to tendermint. In order to better track this lifecycle, we highly recommend you use the [logging middleware](https://github.com/f-o-a-m/kepler/blob/master/hs-abci-extra/src/Network/ABCI/Server/Middleware/Logger.hs). This middleware will attach the `message_type` and `message_hash` to the context for every single log that is produced, meaning that you can get a trace for a given message by simply searching its hash.
Remember that the basic lifecycle of an `ABCI` message is that it first comes to the ABCI-server from tendermint, is then handed off to your application for processing, and finally the response is sent from the ABCI-server back to tendermint. In order to better track this lifecycle, we highly recommend you use the [logging middleware](https://github.com/f-o-a-m/kepler/blob/master/hs-abci-extra/src/Network/ABCI/Server/Middleware/Logger.hs). This middleware will attach the `message_type` and `message_hash` to the context for every single log that is produced, meaning that you can get a trace for a given message by simply searching its hash.

### Querying the Logs

Expand Down
4 changes: 2 additions & 2 deletions hs-abci-docs/nameservice/tutorial/Foundations/03-Modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ title: Foundations - Module

# 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.
First a note. There is a small technical distinction between a `Module` and a `Component`, but we often use these words interchangeably. 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

Expand Down Expand Up @@ -35,7 +35,7 @@ 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.
- `deliver` is the transaction router api type for `deliverTx` 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`)
Expand Down
4 changes: 2 additions & 2 deletions hs-abci-docs/nameservice/tutorial/Foundations/04-Storage.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,14 @@ $(makeSubStore 'store "accountsMap" [t| Map Address Account|] accountsKey)
This does the following:
1. Makes a substore rooted at the `store :: Store AuthNamespace` defined above, and names this value `accountsMap`.
2. Annotates `accountsMap` with type `Map Address Account`.
3. Creates a singleton type `AccountsMapKey` which is the key to access this map directly. This key has is effectively a prefix "accountsMap".
3. Creates a singleton type `AccountsMapKey` which is the key to access this map directly. This key is effectively a prefix "accountsMap".

## Querying the store

If you wanted to query the underlying raw key-value store for the account associated to the address `0xdeadbeef`, then the actual key looks something like

~~~ haskell ignore
encodeUtf8 "auth" <> encodeUtf8 "accountsMap" <> bytesFromHex "0xdeafbeef"
encodeUtf8 "auth" <> encodeUtf8 "accountsMap" <> bytesFromHex "0xdeadbeef"
~~~

While writing apps inside the SDK you do not need to worry about the explicit prefixing since everything is taken care of for you. However, if you are querying for state via an ABCI `query` message, the `key` field that is returned in the response will contain this full path. In the above example, if you wanted to recover the address from the key, you would need to know the prefixes that were applied.
2 changes: 1 addition & 1 deletion hs-abci-docs/nameservice/tutorial/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ This tutorial should teach you:
1. How to construct application specific modules.
2. How to enable a module to receive application specific transactions.
3. How to compose modules and wire up an application.
4. How to add event logging, console logging, and other effects to module.
4. How to add event logging, console logging, and other effects to a module.
4. How to use the type system to control the capabilities of a module.

The SDK makes heavy use of the effects system brought to haskell by the [polysemy](https://hackage.haskell.org/package/polysemy-1.2.3.0) library. We're not going to explain how this library works here, there are several existing tutorials that do this already. Suffice it to say that polysemy encourages the application developer to develop modules that have well defined roles and scopes, and to prohibit certain modules from interfering with the roles and scopes of other modules unless explicitly allowed by the type system.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ The contents of these modules are roughly as follows:
- `Nameservice.Message` - Defines the message types that the module must process (if any) and their validation instances.
- `Nameservice.Query` - Defines the query server for handling state queries from clients.
- `Nameservice.Router` - Defines the transaction router for the module.
- `Namervice` Defines the module itself and re-exports any types or utils necessary for using this module as a dependency.
- `Nameservice` Defines the module itself and re-exports any types or utils necessary for using this module as a dependency.

The reason why we suggest this is that each of these haskell modules is buiding up one of the core components of our definition of a module, and it provides a nice logical split between these pieces.
The reason why we suggest this is that each of these haskell modules is building up one of the core components of our definition of a module, and it provides a nice logical split between these pieces.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ It is important to note that the database modeled by the `RawStore` effect (in t
type RawStore = Map ByteString ByteString
~~~

although the definition of `RawStore` is different than the above.
Although the definition of `RawStore` is different than the above.

The interface we give is actually a typed key value store. This means that within the scope of a module `m`, for any key type `k`, there is only one possible value type `v` associated with `k`.

Expand All @@ -25,7 +25,7 @@ balance :: Tendermint.SDK.Types.Address -> Integer

(We'll properly introduce the module `Bank` later in the walkthrough.)

This means that in the scope of the `Bank` module, the database utlity `get` function applied to a value of type `Address` will result in a value of type `Integer`. If the `Bank` module would like to store another mapping whose keys have type `Tendermint.SDK.Types.Address`, you must use a newtype instead. Otherwise you will get a compiler error.
This means that in the scope of the `Bank` module, the database utility `get` function applied to a value of type `Address` will result in a value of type `Integer`. If the `Bank` module would like to store another mapping whose keys have type `Tendermint.SDK.Types.Address`, you must use a newtype instead. Otherwise you will get a compiler error.

At the same time, you are free to define another mapping from `k -> v` in the scope of a different module. For example, you can have both the `balance` mapping described above, as well as a mapping

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ title: Nameservice - Message
Each module is ultimately a small state machine used for processing messages. Each module must define what messages it accepts, if any. Like many other types found in the SDK, this message class must implement the `HasCodec` class. We recommend using a protobuf serialization format for messages using either the `proto3-suite` or `proto-lens` libraries, though in theory you could use anything (e.g. `JSON`).

### `proto3-suite`
The advantages of using the `proto3-suite` library are that it has support for generics and that you can generate a `.proto` file from your haskell code for export to other applications. This is particularly useful when prototyping or when you have control over the message specification.
The advantages of using the `proto3-suite` library is that it has support for generics and that you can generate a `.proto` file from your haskell code for export to other applications. This is particularly useful when prototyping or when you have control over the message specification.
The disadvantage is that `proto3-suite` doesn't act as a `protoc` plugin, and instead uses it's own protobuf parser. This means that you do not have access to the full protobuf specs when parsing `.proto` files.

### `proto-lens`
The advantages of using `proto-lens` are that it can parse and generate types for pretty much any `.proto` file.
The advantages of using `proto-lens` is that it can parse and generate types for pretty much any `.proto` file.
The disadvantage is that the generated code is a bit strange, and may require you to create wrapper types to avoid depending directly on the generated code. An additional disadvantage is that you cannot generate `.proto` files from haskell code.

All in all, neither is really difficult to work with, and depending on what stage you're at in development you might chose one over the other.
Expand Down Expand Up @@ -137,7 +137,7 @@ Message validation is an important part of the transaction life cycle. When a `c
3. The message author has enough funds for the gas costs, if any.
4. The message can be successfully routed to a module without handling.

On top of this you might wish to ensure other static properties of the message, such as that the author of the message is the owner of the funds being transfered. For this we have a `ValidateMessage` class:
On top of this you might wish to ensure other static properties of the message, such as, that the author of the message is the owner of the funds being transferred. For this we have a `ValidateMessage` class:

~~~ haskell ignore
data MessageSemanticError =
Expand All @@ -149,7 +149,7 @@ class ValidateMessage msg where
validateMessage :: Msg msg -> Validation [MessageSemanticError] ()
~~~

We're using the applicative functor [`Data.Validation.Validation`](https://hackage.haskell.org/package/validation-1.1/docs/Data-Validation.html#t:Validation) to perform valdiation because it is capable of reporting all errors at once, rather than the first that occurs as in the case with something like `Either`.
We're using the applicative functor [`Data.Validation.Validation`](https://hackage.haskell.org/package/validation-1.1/docs/Data-Validation.html#t:Validation) to perform validation because it is capable of reporting all errors at once, rather than the first that occurs as in the case with something like `Either`.

Here's what the `isAuthor` check looks like, that was described above:

Expand Down
41 changes: 22 additions & 19 deletions hs-abci-docs/nameservice/tutorial/Tutorial/Nameservice/06-Router.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,25 @@ The Router is where you specify the handlers for the messages that the module ac
~~~ haskell
module Tutorial.Nameservice.Router where

import Nameservice.Modules.Nameservice.Keeper (NameserviceEffs,
buyName, deleteName,
setName)
import Nameservice.Modules.Nameservice.Messages (BuyName, DeleteName,
SetName)
import Polysemy (Members, Sem)
import Tendermint.SDK.Modules.Bank (BankEffs)
import Servant.API ((:<|>) (..))
import Tendermint.SDK.BaseApp ((:~>), BaseEffs,
Return,
RouteContext (..),
RouteTx,
RoutingTx (..),
TxEffs, TypedMessage,
incCount, withTimer)
import Tendermint.SDK.Types.Message (Msg (..))
import Tendermint.SDK.Types.Transaction (Tx (..))
import Nameservice.Modules.Nameservice.Keeper (NameserviceEffs, buyName, deleteName, setName)
import Nameservice.Modules.Nameservice.Messages (BuyName, DeleteName, SetName)
import Polysemy (Members, Sem)
import Tendermint.SDK.Modules.Bank (BankEffs)
import Servant.API ((:<|>) (..))
import Tendermint.SDK.BaseApp
((:~>)
, BaseEffs
, Return
, RouteContext (..)
, RouteTx
, RoutingTx (..)
, TxEffs
, TypedMessage
, incCount
, withTimer
)
import Tendermint.SDK.Types.Message (Msg (..))
import Tendermint.SDK.Types.Transaction (Tx (..))

~~~

Expand All @@ -49,7 +51,7 @@ Lets break it down:
- `(:~>)` is a combinator that allows us to connect a message type with a response
- `Return` is used to specify the return type.

Since there are two possible ABCI messages that the router has to accomodate, `checkTx` and `deliverTx`, the router may return different values depending on the ABCI message type. For example, it's possible that the `checkTx` does not fully mimic the transaction and simply returns `()`, while the `deliverTx` message returns a value of type `Whois`. Concretely you would write
Since there are two possible ABCI messages that the router has to accommodate, `checkTx` and `deliverTx`, the router may return different values depending on the ABCI message type. For example, it's possible that the `checkTx` does not fully mimic the transaction and simply returns `()`, while the `deliverTx` message returns a value of type `Whois`. Concretely you would write

~~~ haskell ignore
type BuyNameHandler = TypeMessage BuyName :~> Return' 'OnCheckUnit Whois
Expand All @@ -61,7 +63,8 @@ or equivalently using the alias
type BuyNameHandler = TypeMessage BuyName :~> Return Whois
~~~

Alternatively, you could write the application so that each `checkTx` ABCI message is handled in the same way as the `deliverTx` message, e.g. the both return a value of type `Whois`.
Alternatively, you could write the application so that each `checkTx` ABCI message is handled in the same way as the `deliverTx` message, e.g. the both return a value of type `Whois`.


~~~ haskell ignore
type BuyNameHandler = TypeMessage BuyName :~> Return' 'OnCheckEval Whois
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ import Nameservice.Modules.Nameservice.Keeper (NameserviceEffs, eval)
import Nameservice.Modules.Nameservice.Query (QueryApi, querier)
import Nameservice.Modules.Nameservice.Router (MessageApi, messageHandlers)
import Nameservice.Modules.Nameservice.Types (NameserviceName)
import Tendermint.SDK.Application (Module (..), ModuleEffs)
import Tendermint.SDK.Application (Module (..), ModuleEffs)
import Tendermint.SDK.BaseApp (DefaultCheckTx (..))
import Tendermint.SDK.Modules.Bank (Bank)
import Tendermint.SDK.Modules.Bank (Bank)
import Data.Proxy
import Polysemy (Members)

Expand Down
Loading