Application
From Modules to App
The App
type in Network.ABCI.Server
is defined as
newtype App m = App
{ unApp :: forall (t :: MessageType). Request t -> m (Response t) }
and ultimately our configuration of modules must be converted to this format. This is probably the most important part of the SDK, to provide the bridge between the list of modules - a heterogeneous list of type ModuleList
- and the actual application. The type that provides the input for this bridge is HandlersContext
:
data HandlersContext alg ms core = HandlersContext
{ signatureAlgP :: Proxy alg
, modules :: M.ModuleList ms (Effs ms core)
, beginBlocker :: Req.BeginBlock -> Sem (M.Effs ms core) ()
, endBlocker :: Req.EndBlock -> Sem (M.Effs ms core) EndBlockResult
, anteHandler :: BA.AnteHandler (Effs ms core)
, compileToCore :: forall a. Sem (BA.BaseAppEffs core) a -> Sem core a
}
where
alg
is the signature schema you would like to use for authentication (e.g. Secp256k1)ms
is the type level list of modulesr
is the global effects list for the applicationcore
is the set of core effects that are used to interpretBaseApp
toIO
.Effs
is a type family that gathers the effect dependencies forms
in the appropriate order.
We should say a few words on this compileToCore
field. The application developer has to, at the end of the day, specify how the entire effects system for the application will be interpreted to IO
. Luckily most of these decisions are abstracted away, but the one that remains is dealing with BaseApp core
. The sdk provides two default methods for two different types of core
:
defaultCompileToCore
:: forall a.
Sem (BaseApp CoreEffs) a
-> Sem CoreEffs a
defaultCompileToPureCore
:: forall a.
Sem (BaseApp PureCoreEffs) a
-> Sem PureCoreEffs a
The difference is that defaultCompileToCore
uses the IAVL store external database and also allows for metrics, where defaultCompileToPureCore
uses an in-memory db and treats all metrics operations as a no-op.
Tutorial.Nameservice.Application
module Tutorial.Nameservice.Application where
import Data.Proxy
import Nameservice.Modules.Nameservice (Nameservice, nameserviceModule)
import Network.ABCI.Server.App (App)
import Polysemy (Sem)
import Tendermint.SDK.Modules.Auth (Auth, authModule)
import Tendermint.SDK.Application (ModuleList(..), HandlersContext(..), baseAppAnteHandler, makeApp, createIOApp)
import Tendermint.SDK.BaseApp (CoreEffs, Context, defaultBeginBlocker, defaultEndBlocker, defaultCompileToCore, runCoreEffs)
import Tendermint.SDK.Crypto (Secp256k1)
import Tendermint.SDK.Modules.Bank (Bank, bankModule)
At this point we need to simply list the modules we want to use in our application. We only require that if a module is declared as a dependency by another (via the deps
type variable in the Module
type), then that dependency should be inserted below that module. For example, since Nameservice
depends on Bank
, we must list Bank
after Nameservice
. Similarly since Bank
depends on Auth
, we must list Auth
after Bank
:
type NameserviceModules =
'[ Nameservice
, Bank
, Auth
]
We're now ready to define the HandlersContext
for our application:
handlersContext :: HandlersContext Secp256k1 NameserviceModules CoreEffs
handlersContext = HandlersContext
{ signatureAlgP = Proxy @Secp256k1
, modules = nameserviceModules
, beginBlocker = defaultBeginBlocker
, endBlocker = defaultEndBlocker
, compileToCore = defaultCompileToCore
, anteHandler = baseAppAnteHandler
}
where
nameserviceModules =
nameserviceModule
:+ bankModule
:+ authModule
:+ NilModules
Finally we're able to define our application that runs in the CoreEffs
context defined in the SDK:
app :: App (Sem CoreEffs)
app = makeApp handlersContext
Since the ABCI server requires you to pass a value of type App IO
, we have one more transformation to perform to get the Sem CoreEffs
in our app. We can simple use the createIOApp
function:
makeIOApp :: Context -> App IO
makeIOApp ctx = createIOApp (runCoreEffs ctx) app