6.3 KiB
Msg
Services
A Protobuf Msg
service processes messages. Protobuf Msg
services are specific to the module in which they are defined, and only process messages defined within the said module. They are called from BaseApp
during DeliverTx
. {synopsis}
Pre-requisite Readings
- Module Manager {prereq}
- Messages and Queries {prereq}
Implementation of a module Msg
service
Each module should define a Protobuf Msg
service, which will be responsible for processing requests (implementing sdk.Msg
) and returning responses.
As further described in ADR 031, this approach has the advantage of clearly specifying return types and generating server and client code.
Protobuf generates a MsgServer
interface based on a definition of Msg
service. It is the role of the module developer to implement this interface, by implementing the state transition logic that should happen upon receival of each sdk.Msg
. As an example, here is the generated MsgServer
interface for x/bank
, which exposes two sdk.Msg
s:
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0-rc3/x/bank/types/tx.pb.go#L285-L291
When possible, the existing module's Keeper
should implement MsgServer
, otherwise a msgServer
struct that embeds the Keeper
can be created, typically in ./keeper/msg_server.go
:
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0-rc1/x/bank/keeper/msg_server.go#L14-L16
msgServer
methods can retrieve the sdk.Context
from the context.Context
parameter method using the sdk.UnwrapSDKContext
:
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0-rc1/x/bank/keeper/msg_server.go#L27-L28
sdk.Msg
processing usually follows these 2 steps:
- First, they perform stateful checks to make sure the
message
is valid. At this stage, themessage
'sValidateBasic()
method has already been called, meaning stateless checks on the message (like making sure parameters are correctly formatted) have already been performed. Checks performed in themsgServer
method can be more expensive and require access to the state. For example, amsgServer
method for atransfer
message might check that the sending account has enough funds to actually perform the transfer. To access the state, themsgServer
method needs to call thekeeper
's getter functions. - Then, if the checks are successful, the
msgServer
method calls thekeeper
's setter functions to actually perform the state transition.
Before returning, msgServer
methods generally emit one or more events via the EventManager
held in the ctx
:
ctx.EventManager().EmitEvent(
sdk.NewEvent(
eventType, // e.g. sdk.EventTypeMessage for a message, types.CustomEventType for a custom event defined in the module
sdk.NewAttribute(attributeKey, attributeValue),
),
)
These events are relayed back to the underlying consensus engine and can be used by service providers to implement services around the application. Click here to learn more about events.
The invoked msgServer
method returns a proto.Message
response and an error
. These return values are then wrapped into an *sdk.Result
or an error
using sdk.WrapServiceResult(ctx sdk.Context, res proto.Message, err error)
:
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0-rc2/baseapp/msg_service_router.go#L104-L104
This method takes care of marshaling the res
parameter to protobuf and attaching any events on the ctx.EventManager()
to the sdk.Result
.
+++ d55c1a2665/proto/cosmos/base/abci/v1beta1/abci.proto (L81-L95)
This diagram shows a typical structure of a Protobuf Msg
service, and how the message propagates through the module.
Amino LegacyMsg
s
handler
type
The handler
type defined in the Cosmos SDK will be deprecated in favor of Msg
Services.
Here is the typical structure of a handler
function:
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0-rc2/types/handler.go#L4-L4
Let us break it down:
- The
LegacyMsg
is the actual object being processed. - The
Context
contains all the necessary information needed to process themsg
, as well as a branch of the latest state. If themsg
is successfully processed, the branched version of the state contained in thectx
will be written to the main state (branch). - The
*Result
returned toBaseApp
contains (among other things) information on the execution of thehandler
and events.
Module handler
s are typically implemented in a ./handler.go
file inside the module's folder. The module manager is used to add the module's handler
s to the
application's router
via the Route()
method. Typically,
the manager's Route()
method simply constructs a Route that calls a NewHandler()
method defined in handler.go
.
+++ 228728cce2/x/gov/module.go (L143-L146)
Implementation
NewHandler
function dispatches a LegacyMsg
to appropriate handler function, usually by using a switch statement:
+++ d55c1a2665/x/bank/handler.go (L13-L29)
First, NewHandler
function sets a new EventManager
to the context to isolate events per msg
.
Then, a simple switch calls the appropriate handler
based on the LegacyMsg
type.
In this regard, handler
s functions need to be implemented for each module LegacyMsg
. This will also involve manual handler registration of LegacyMsg
types.
handler
s functions should return a *Result
and an error
.
Telemetry
New telemetry metrics can be created from msgServer
methods when handling messages.
This is an example from the x/auth/vesting
module:
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0-rc1/x/auth/vesting/msg_server.go#L73-L85
Next {hide}
Learn about query services {hide}