# Messages and Queries

Messages and Queries are the two primary objects handled by modules. Most of the core components defined in a module, like handlers, keepers and queriers, exist to process messages and queries.

# Pre-requisite Readings

# Messages

Messages are objects whose end-goal is to trigger state-transitions. They are wrapped in transactions, which may contain one or multiple of them.

When a transaction is relayed from the underlying consensus engine to the SDK application, it is first decoded by baseapp. Then, each message contained in the transaction is extracted and routed to the appropriate module via baseapp's router so that it can be processed by the module's handler. For a more detailed explanation of the lifecycle of a transaction, click here.

Defining messages is the responsibility of module developers. Typically, they are defined in a ./internal/types/msgs.go file inside the module's folder. The message's type definition usually includes a list of parameters needed to process the message that will be provided by end-users when they want to create a new transaction containing said message.

Copy // Example of a message type definition type MsgSubmitProposal struct { Content Content `json:"content" yaml:"content"` InitialDeposit sdk.Coins `json:"initial_deposit" yaml:"initial_deposit"` Proposer sdk.AccAddress `json:"proposer" yaml:"proposer"` }

The Msg is typically accompanied by a standard constructor function, that is called from one of the module's interface. messages also need to implement the [Msg] interface:

Copy // Transactions messages must fulfill the Msg type Msg interface { // Return the message type. // Must be alphanumeric or empty. Route() string // Returns a human-readable string for the message, intended for utilization // within tags Type() string // ValidateBasic does a simple validation check that // doesn't require access to any other information. ValidateBasic() Error // Get the canonical byte representation of the Msg. GetSignBytes() []byte // Signers returns the addrs of signers that must sign. // CONTRACT: All signatures must be present to be valid. // CONTRACT: Returns addrs in some deterministic order. GetSigners() []AccAddress }

It contains the following methods:

  • Route() string: Name of the route for this message. Typically all messages in a module have the same route, which is most often the module's name.
  • Type() string: Type of the message, used primarly in events. This should return a message-specific string, typically the denomination of the message itself.
  • ValidateBasic() Error: This method is called by baseapp very early in the processing of the message (in both CheckTx and DeliverTx), in order to discard obviously invalid messages. ValidateBasic should only include stateless checks, i.e. checks that do not require access to the state. This usually consists in checking that the message's parameters are correctly formatted and valid (i.e. that the amount is strictly positive for a transfer).
  • GetSignBytes() []byte: Return the canonical byte representation of the message. Used to generate a signature.
  • GetSigners() []AccAddress: Return the list of signers. The SDK will make sure that each message contained in a transaction is signed by all the signers listed in the list returned by this method.

See an example implementation of a message from the nameservice module:

Copy // MsgSetName defines a SetName message type MsgSetName struct { Name string `json:"name"` Value string `json:"value"` Owner sdk.AccAddress `json:"owner"` } // NewMsgSetName is a constructor function for MsgSetName func NewMsgSetName(name string, value string, owner sdk.AccAddress) MsgSetName { return MsgSetName{ Name: name, Value: value, Owner: owner, } } // Route should return the name of the module func (msg MsgSetName) Route() string { return RouterKey } // Type should return the action func (msg MsgSetName) Type() string { return "set_name" } // ValidateBasic runs stateless checks on the message func (msg MsgSetName) ValidateBasic() sdk.Error { if msg.Owner.Empty() { return sdk.ErrInvalidAddress(msg.Owner.String()) } if len(msg.Name) == 0 || len(msg.Value) == 0 { return sdk.ErrUnknownRequest("Name and/or Value cannot be empty") } return nil } // GetSignBytes encodes the message for signing func (msg MsgSetName) GetSignBytes() []byte { return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(msg)) } // GetSigners defines whose signature is required func (msg MsgSetName) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.Owner} }

# Queries

A query is a request for information made by end-users of applications through an interface and processed by a full-node. A query is received by a full-node through its consensus engine and relayed to the application via the ABCI. It is then routed to the appropriate module via baseapp's queryrouter so that it can be processed by the module's querier. For a deeper look at the lifecycle of a query, click here.

Contrary to messages, there is usually no specific query object defined by module developers. Instead, the SDK takes the simpler approach of using a simple path to define each query. The path contains the query type and all the arguments needed in order to process it. For most module queries, the path should look like the following:

Copy queryCategory/queryRoute/queryType/arg1/arg2/...

where:

  • queryCategory is the category of the query, typically custom for module queries. It is used to differentiate between different kinds of queries within baseapp's Query method.
  • queryRoute is used by baseapp's queryRouter to map the query to its module. Usually, queryRoute should be the name of the module.
  • queryType is used by the module's querier to map the query to the appropriate querier function within the module.
  • args are the actual arguments needed to process the query. They are filled out by the end-user. Note that for bigger queries, you might prefer passing arguments in the Data field of the request req instead of the path.

The path for each query must be defined by the module developer in the module's command-line interface file.Overall, there are 3 mains components module developers need to implement in order to make the subset of the state defined by their module queryable:

  • A querier, to process the query once it has been routed to the module.
  • Query commands in the module's CLI file, where the path for each query is specified.
  • query return types. Typically defined in a file internal/types/querier.go, they specify the result type of each of the module's queries. These custom types must implement the String() method of fmt.Stringer.

See an example of query return types from the nameservice module:

Copy // QueryResResolve Queries Result Payload for a resolve query type QueryResResolve struct { Value string `json:"value"` } // implement fmt.Stringer func (r QueryResResolve) String() string { return r.Value } // QueryResNames Queries Result Payload for a names query type QueryResNames []string // implement fmt.Stringer func (n QueryResNames) String() string { return strings.Join(n[:], "\n") }

# Next

Learn about handlers