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.
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.Msgs:
Copy
// MsgServer is the server API for Msg service.type MsgServer interface{// Send defines a method for sending coins from one account to another account.Send(context.Context,*MsgSend)(*MsgSendResponse,error)// MultiSend defines a method for sending coins from some accounts to other accounts.MultiSend(context.Context,*MsgMultiSend)(*MsgMultiSendResponse,error)}
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:
Copy
type msgServer struct{
Keeper
}
msgServer methods can retrieve the sdk.Context from the context.Context parameter method using the sdk.UnwrapSDKContext:
Before a msgServer method is executed, the message's ValidateBasic() method has already been called. Since msg.ValidateBasic() performs only the most basic checks, this stage must perform all other validation (both stateful and stateless) to make sure the message is valid. Checks performed in the msgServer method can be more expensive and the signer is charged gas for these operations.
For example, a msgServer method for a transfer message might check that the sending account has enough funds to actually perform the transfer.
It is recommended to implement all validation checks in a separate function that passes state values as arguments. This implementation simplifies testing. As expected, expensive validation functions charge additional gas. Example:
Copy
ValidateMsgA(msg MsgA, now Time, gm GasMeter)error{if now.Before(msg.Expire){return sdkerrrors.ErrInvalidRequest.Wrap("msg expired")}
gm.ConsumeGas(1000,"signature verification")returnsignatureVerificaton(msg.Prover, msg.Data)}
Before returning, msgServer methods generally emit one or more events by using the EventManager held in the ctx. Use the new EmitTypedEvent function that uses protobuf-based event types:
Copy
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(key1, value1),
sdk.NewAttribute(key2, value2),),)
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):
Copy
package baseapp
import("context""fmt"
gogogrpc "github.com/gogo/protobuf/grpc""github.com/gogo/protobuf/proto""google.golang.org/grpc"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors")// MsgServiceRouter routes fully-qualified Msg service methods to their handler.type MsgServiceRouter struct{
interfaceRegistry codectypes.InterfaceRegistry
routes map[string]MsgServiceHandler
}var_ gogogrpc.Server =&MsgServiceRouter{}// NewMsgServiceRouter creates a new MsgServiceRouter.funcNewMsgServiceRouter()*MsgServiceRouter {return&MsgServiceRouter{
routes:map[string]MsgServiceHandler{},}}// MsgServiceHandler defines a function type which handles Msg service message.type MsgServiceHandler =func(ctx sdk.Context, req sdk.Msg)(*sdk.Result,error)// Handler returns the MsgServiceHandler for a given msg or nil if not found.func(msr *MsgServiceRouter)Handler(msg sdk.Msg) MsgServiceHandler {return msr.routes[sdk.MsgTypeURL(msg)]}// HandlerByTypeURL returns the MsgServiceHandler for a given query route path or nil// if not found.func(msr *MsgServiceRouter)HandlerByTypeURL(typeURL string) MsgServiceHandler {return msr.routes[typeURL]}// RegisterService implements the gRPC Server.RegisterService method. sd is a gRPC// service description, handler is an object which implements that gRPC service.//// This function PANICs:// - if it is called before the service `Msg`s have been registered using// RegisterInterfaces,// - or if a service is being registered twice.func(msr *MsgServiceRouter)RegisterService(sd *grpc.ServiceDesc, handler interface{}){// Adds a top-level query handler based on the gRPC service name.for_, method :=range sd.Methods {
fqMethod := fmt.Sprintf("/%s/%s", sd.ServiceName, method.MethodName)
methodHandler := method.Handler
var requestTypeName string// NOTE: This is how we pull the concrete request type for each handler for registering in the InterfaceRegistry.// This approach is maybe a bit hacky, but less hacky than reflecting on the handler object itself.// We use a no-op interceptor to avoid actually calling into the handler itself._,_=methodHandler(nil, context.Background(),func(i interface{})error{
msg, ok := i.(sdk.Msg)if!ok {// We panic here because there is no other alternative and the app cannot be initialized correctly// this should only happen if there is a problem with code generation in which case the app won't// work correctly anyway.panic(fmt.Errorf("unable to register service method %s: %T does not implement sdk.Msg", fqMethod, i))}
requestTypeName = sdk.MsgTypeURL(msg)returnnil}, noopInterceptor)// Check that the service Msg fully-qualified method name has already// been registered (via RegisterInterfaces). If the user registers a// service without registering according service Msg type, there might be// some unexpected behavior down the road. Since we can't return an error// (`Server.RegisterService` interface restriction) we panic (at startup).
reqType, err := msr.interfaceRegistry.Resolve(requestTypeName)if err !=nil|| reqType ==nil{panic(
fmt.Errorf("type_url %s has not been registered yet. "+"Before calling RegisterService, you must register all interfaces by calling the `RegisterInterfaces` "+"method on module.BasicManager. Each module should call `msgservice.RegisterMsgServiceDesc` inside its "+"`RegisterInterfaces` method with the `_Msg_serviceDesc` generated by proto-gen",
requestTypeName,),)}// Check that each service is only registered once. If a service is// registered more than once, then we should error. Since we can't// return an error (`Server.RegisterService` interface restriction) we// panic (at startup)._, found := msr.routes[requestTypeName]if found {panic(
fmt.Errorf("msg service %s has already been registered. Please make sure to only register each service once. "+"This usually means that there are conflicting modules registering the same msg service",
fqMethod,),)}
msr.routes[requestTypeName]=func(ctx sdk.Context, req sdk.Msg)(*sdk.Result,error){
ctx = ctx.WithEventManager(sdk.NewEventManager())
interceptor :=func(goCtx context.Context,_interface{},_*grpc.UnaryServerInfo, handler grpc.UnaryHandler)(interface{},error){
goCtx = context.WithValue(goCtx, sdk.SdkContextKey, ctx)returnhandler(goCtx, req)}// Call the method handler from the service description with the handler object.// We don't do any decoding here because the decoding was already done.
res, err :=methodHandler(handler, sdk.WrapSDKContext(ctx), noopDecoder, interceptor)if err !=nil{returnnil, err
}
resMsg, ok := res.(proto.Message)if!ok {returnnil, sdkerrors.Wrapf(sdkerrors.ErrInvalidType,"Expecting proto.Message, got %T", resMsg)}return sdk.WrapServiceResult(ctx, resMsg, err)}}}// SetInterfaceRegistry sets the interface registry for the router.func(msr *MsgServiceRouter)SetInterfaceRegistry(interfaceRegistry codectypes.InterfaceRegistry){
msr.interfaceRegistry = interfaceRegistry
}funcnoopDecoder(_interface{})error{returnnil}funcnoopInterceptor(_ context.Context,_interface{},_*grpc.UnaryServerInfo,_ grpc.UnaryHandler)(interface{},error){returnnil,nil}
This method takes care of marshaling the res parameter to protobuf and attaching any events on the ctx.EventManager() to the sdk.Result.
Copy
// Result is the union of ResponseFormat and ResponseCheckTx.
message Result {
option (gogoproto.goproto_getters) = false;
// Data is any data returned from message or handler execution. It MUST be
// length prefixed in order to separate data from multiple message executions.
// Deprecated. This field is still populated, but prefer msg_response instead
// because it also contains the Msg response typeURL.
bytes data = 1 [deprecated = true];
// Log contains the log information from message or handler execution.
string log = 2;
// Events contains a slice of Event objects that were emitted during message
// or handler execution.
repeated tendermint.abci.Event events = 3 [(gogoproto.nullable) = false];
// msg_responses contains the Msg handler responses type packed in Anys.
//
// Since: cosmos-sdk 0.46
repeated google.protobuf.Any msg_responses = 4;
}
This diagram shows a typical structure of a Protobuf Msg service, and how the message propagates through the module.