diff --git a/hs-abci-docs/doc/98-Logging.md b/hs-abci-docs/doc/98-Logging.md index 8f2692ff..c33b4518 100644 --- a/hs-abci-docs/doc/98-Logging.md +++ b/hs-abci-docs/doc/98-Logging.md @@ -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 @@ -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 diff --git a/hs-abci-docs/nameservice/tutorial/Foundations/03-Modules.md b/hs-abci-docs/nameservice/tutorial/Foundations/03-Modules.md index c2ab122c..e8952d3e 100644 --- a/hs-abci-docs/nameservice/tutorial/Foundations/03-Modules.md +++ b/hs-abci-docs/nameservice/tutorial/Foundations/03-Modules.md @@ -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 @@ -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`) diff --git a/hs-abci-docs/nameservice/tutorial/Foundations/04-Storage.md b/hs-abci-docs/nameservice/tutorial/Foundations/04-Storage.md index 24cde7a3..6f21c1b7 100644 --- a/hs-abci-docs/nameservice/tutorial/Foundations/04-Storage.md +++ b/hs-abci-docs/nameservice/tutorial/Foundations/04-Storage.md @@ -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. diff --git a/hs-abci-docs/nameservice/tutorial/README.md b/hs-abci-docs/nameservice/tutorial/README.md index 69e3280b..db0a4066 100644 --- a/hs-abci-docs/nameservice/tutorial/README.md +++ b/hs-abci-docs/nameservice/tutorial/README.md @@ -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. diff --git a/hs-abci-docs/nameservice/tutorial/Tutorial/Nameservice/01-Overview.md b/hs-abci-docs/nameservice/tutorial/Tutorial/Nameservice/01-Overview.md index 3fc99d37..1e729f07 100644 --- a/hs-abci-docs/nameservice/tutorial/Tutorial/Nameservice/01-Overview.md +++ b/hs-abci-docs/nameservice/tutorial/Tutorial/Nameservice/01-Overview.md @@ -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. diff --git a/hs-abci-docs/nameservice/tutorial/Tutorial/Nameservice/02-Types.md b/hs-abci-docs/nameservice/tutorial/Tutorial/Nameservice/02-Types.md index 57f614dc..97447d06 100644 --- a/hs-abci-docs/nameservice/tutorial/Tutorial/Nameservice/02-Types.md +++ b/hs-abci-docs/nameservice/tutorial/Tutorial/Nameservice/02-Types.md @@ -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`. @@ -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 diff --git a/hs-abci-docs/nameservice/tutorial/Tutorial/Nameservice/03-Message.md b/hs-abci-docs/nameservice/tutorial/Tutorial/Nameservice/03-Message.md index 34d0d03f..88fc9caa 100644 --- a/hs-abci-docs/nameservice/tutorial/Tutorial/Nameservice/03-Message.md +++ b/hs-abci-docs/nameservice/tutorial/Tutorial/Nameservice/03-Message.md @@ -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. @@ -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 = @@ -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: diff --git a/hs-abci-docs/nameservice/tutorial/Tutorial/Nameservice/06-Router.md b/hs-abci-docs/nameservice/tutorial/Tutorial/Nameservice/06-Router.md index 3b2cf31d..d4398ced 100644 --- a/hs-abci-docs/nameservice/tutorial/Tutorial/Nameservice/06-Router.md +++ b/hs-abci-docs/nameservice/tutorial/Tutorial/Nameservice/06-Router.md @@ -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 (..)) ~~~ @@ -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 @@ -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 diff --git a/hs-abci-docs/nameservice/tutorial/Tutorial/Nameservice/07-Module.md b/hs-abci-docs/nameservice/tutorial/Tutorial/Nameservice/07-Module.md index 02092960..1dc55db2 100644 --- a/hs-abci-docs/nameservice/tutorial/Tutorial/Nameservice/07-Module.md +++ b/hs-abci-docs/nameservice/tutorial/Tutorial/Nameservice/07-Module.md @@ -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) diff --git a/hs-abci-docs/nameservice/tutorial/Tutorial/Nameservice/09-Testing.md b/hs-abci-docs/nameservice/tutorial/Tutorial/Nameservice/09-Testing.md index 16cd9d03..7c69003f 100644 --- a/hs-abci-docs/nameservice/tutorial/Tutorial/Nameservice/09-Testing.md +++ b/hs-abci-docs/nameservice/tutorial/Tutorial/Nameservice/09-Testing.md @@ -4,7 +4,7 @@ title: Nameservice - Testing # Testing and Client Generation -It's time to see the real benefits of including as much information as possible in the types, which goes beyond a simple guarantee that certain things won't fail at runtime. Since the `api`s for querying state and delivering transactions was specified in the type of each module, hence in the type of the application via the `ModulesList`, we are able to generate client libraries for these actions for free. This is especially useful in testing to eliminate as much boilerplate as possible, and to get compile time failures whenever an api change would break your tests. +It's time to see the real benefits of including as much information as possible in the types, which goes beyond a simple guarantee that certain things won't fail at runtime. Since the `api`s for querying state and delivering transactions were specified in the type of each module, hence in the type of the application via the `ModulesList`, we are able to generate client libraries for these actions for free. This is especially useful in testing to eliminate as much boilerplate as possible, and to get compile time failures whenever an api change would break your tests. Let's take a look at how this works in the `E2E` test suite: @@ -14,28 +14,30 @@ Let's take a look at how this works in the `E2E` test suite: module Tutorial.Nameservice.Testing where -import Control.Monad.Reader (ReaderT, runReaderT) -import Data.Default.Class (def) -import Data.Proxy -import Nameservice.Application -import qualified Nameservice.Modules.Nameservice as N -import qualified Network.Tendermint.Client as RPC -import Servant.API ((:<|>) (..)) +import Control.Monad.Reader (ReaderT, runReaderT) +import Data.Default.Class (def) +import Data.Proxy +import Nameservice.Application +import qualified Nameservice.Modules.Nameservice as N +import qualified Network.Tendermint.Client as RPC +import Servant.API ((:<|>) (..)) import qualified Tendermint.SDK.Application.Module as M -import Tendermint.SDK.BaseApp.Errors (AppError (..)) -import Tendermint.SDK.BaseApp.Query (QueryArgs (..), - QueryResult (..)) -import qualified Tendermint.SDK.Modules.Auth as Auth -import qualified Tendermint.SDK.Modules.Bank as B -import Tendermint.SDK.Types.Address (Address) -import Tendermint.Utils.Client (ClientConfig (..), - EmptyTxClient (..), - QueryClientResponse (..), - TxClientResponse (..), - TxOpts (..), HasTxClient(..), - HasQueryClient(..), - defaultClientTxOpts) -import Tendermint.Utils.ClientUtils (rpcConfig) +import Tendermint.SDK.BaseApp.Errors (AppError (..)) +import Tendermint.SDK.BaseApp.Query (QueryArgs (..), QueryResult (..)) +import qualified Tendermint.SDK.Modules.Auth as Auth +import qualified Tendermint.SDK.Modules.Bank as B +import Tendermint.SDK.Types.Address (Address) +import Tendermint.Utils.Client + (ClientConfig (..) + , EmptyTxClient (..) + , QueryClientResponse (..) + , TxClientResponse (..) + , TxOpts (..) + , HasTxClient(..) + , HasQueryClient(..) + , defaultClientTxOpts + ) +import Tendermint.Utils.ClientUtils (rpcConfig) ~~~ First let's look at how to generate a client for querying state. If you've ever used servant client, this should look familiar since the design was heavily influenced (i.e. shamelessly stolen) from there: