# Query Services

A Protobuf Query service processes queries. Query services are specific to the module in which they are defined, and only process queries defined within said module. They are called from BaseApp's Query method.

# Pre-requisite Readings

# Querier type

The querier type defined in the Cosmos SDK will be deprecated in favor of gRPC Services. It specifies the typical structure of a querier function:

Copy package types import ( abci "github.com/tendermint/tendermint/abci/types" ) // Querier defines a function type that a module querier must implement to handle // custom client queries. type Querier = func(ctx Context, path []string, req abci.RequestQuery) ([]byte, error)

Let us break it down:

  • The Context contains all the necessary information needed to process the query, as well as a branch of the latest state. It is primarily used by the keeper to access the state.
  • The path is an array of strings that contains the type of the query, and that can also contain query arguments. See queries for more information.
  • The req itself is primarily used to retrieve arguments if they are too large to fit in the path. This is done using the Data field of req.
  • The result in []byte returned to BaseApp, marshalled using the application's codec.

# Implementation of a module query service

# gRPC Service

When defining a Protobuf Query service, a QueryServer interface is generated for each module with all the service methods:

Copy type QueryServer interface { QueryBalance(context.Context, *QueryBalanceParams) (*types.Coin, error) QueryAllBalances(context.Context, *QueryAllBalancesParams) (*QueryAllBalancesResponse, error) }

These custom queries methods should be implemented by a module's keeper, typically in ./keeper/grpc_query.go. The first parameter of these methods is a generic context.Context, whereas querier methods generally need an instance of sdk.Context to read from the store. Therefore, the Cosmos SDK provides a function sdk.UnwrapSDKContext to retrieve the sdk.Context from the provided context.Context.

Here's an example implementation for the bank module:

Copy package keeper import ( "context" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/query" "github.com/cosmos/cosmos-sdk/x/bank/types" ) var _ types.QueryServer = BaseKeeper{} // Balance implements the Query/Balance gRPC method func (k BaseKeeper) Balance(ctx context.Context, req *types.QueryBalanceRequest) (*types.QueryBalanceResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request") } if req.Address == "" { return nil, status.Error(codes.InvalidArgument, "address cannot be empty") } if req.Denom == "" { return nil, status.Error(codes.InvalidArgument, "invalid denom") } sdkCtx := sdk.UnwrapSDKContext(ctx) address, err := sdk.AccAddressFromBech32(req.Address) if err != nil { return nil, status.Errorf(codes.InvalidArgument, "invalid address: %s", err.Error()) } balance := k.GetBalance(sdkCtx, address, req.Denom) return &types.QueryBalanceResponse{Balance: &balance}, nil } // AllBalances implements the Query/AllBalances gRPC method func (k BaseKeeper) AllBalances(ctx context.Context, req *types.QueryAllBalancesRequest) (*types.QueryAllBalancesResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request") } if req.Address == "" { return nil, status.Error(codes.InvalidArgument, "address cannot be empty") } addr, err := sdk.AccAddressFromBech32(req.Address) if err != nil { return nil, status.Errorf(codes.InvalidArgument, "invalid address: %s", err.Error()) } sdkCtx := sdk.UnwrapSDKContext(ctx) balances := sdk.NewCoins() accountStore := k.getAccountStore(sdkCtx, addr) pageRes, err := query.Paginate(accountStore, req.Pagination, func(key, value []byte) error { var amount sdk.Int if err := amount.Unmarshal(value); err != nil { return err } balances = append(balances, sdk.NewCoin(string(key), amount)) return nil }) if err != nil { return nil, status.Errorf(codes.InvalidArgument, "paginate: %v", err) } return &types.QueryAllBalancesResponse{Balances: balances, Pagination: pageRes}, nil } // SpendableBalances implements a gRPC query handler for retrieving an account's // spendable balances. func (k BaseKeeper) SpendableBalances(ctx context.Context, req *types.QuerySpendableBalancesRequest) (*types.QuerySpendableBalancesResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request") } addr, err := sdk.AccAddressFromBech32(req.Address) if err != nil { return nil, status.Errorf(codes.InvalidArgument, "invalid address: %s", err.Error()) } sdkCtx := sdk.UnwrapSDKContext(ctx) balances := sdk.NewCoins() accountStore := k.getAccountStore(sdkCtx, addr) zeroAmt := sdk.ZeroInt() pageRes, err := query.Paginate(accountStore, req.Pagination, func(key, value []byte) error { balances = append(balances, sdk.NewCoin(string(key), zeroAmt)) return nil }) if err != nil { return nil, status.Errorf(codes.InvalidArgument, "paginate: %v", err) } result := sdk.NewCoins() spendable := k.SpendableCoins(sdkCtx, addr) for _, c := range balances { result = append(result, sdk.NewCoin(c.Denom, spendable.AmountOf(c.Denom))) } return &types.QuerySpendableBalancesResponse{Balances: result, Pagination: pageRes}, nil } // TotalSupply implements the Query/TotalSupply gRPC method func (k BaseKeeper) TotalSupply(ctx context.Context, req *types.QueryTotalSupplyRequest) (*types.QueryTotalSupplyResponse, error) { sdkCtx := sdk.UnwrapSDKContext(ctx) totalSupply, pageRes, err := k.GetPaginatedTotalSupply(sdkCtx, req.Pagination) if err != nil { return nil, status.Error(codes.Internal, err.Error()) } return &types.QueryTotalSupplyResponse{Supply: totalSupply, Pagination: pageRes}, nil } // SupplyOf implements the Query/SupplyOf gRPC method func (k BaseKeeper) SupplyOf(c context.Context, req *types.QuerySupplyOfRequest) (*types.QuerySupplyOfResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request") } if req.Denom == "" { return nil, status.Error(codes.InvalidArgument, "invalid denom") } ctx := sdk.UnwrapSDKContext(c) supply := k.GetSupply(ctx, req.Denom) return &types.QuerySupplyOfResponse{Amount: sdk.NewCoin(req.Denom, supply.Amount)}, nil } // Params implements the gRPC service handler for querying x/bank parameters. func (k BaseKeeper) Params(ctx context.Context, req *types.QueryParamsRequest) (*types.QueryParamsResponse, error) { if req == nil { return nil, status.Errorf(codes.InvalidArgument, "empty request") } sdkCtx := sdk.UnwrapSDKContext(ctx) params := k.GetParams(sdkCtx) return &types.QueryParamsResponse{Params: params}, nil } // DenomsMetadata implements Query/DenomsMetadata gRPC method. func (k BaseKeeper) DenomsMetadata(c context.Context, req *types.QueryDenomsMetadataRequest) (*types.QueryDenomsMetadataResponse, error) { if req == nil { return nil, status.Errorf(codes.InvalidArgument, "empty request") } ctx := sdk.UnwrapSDKContext(c) store := prefix.NewStore(ctx.KVStore(k.storeKey), types.DenomMetadataPrefix) metadatas := []types.Metadata{} pageRes, err := query.Paginate(store, req.Pagination, func(_, value []byte) error { var metadata types.Metadata k.cdc.MustUnmarshal(value, &metadata) metadatas = append(metadatas, metadata) return nil }) if err != nil { return nil, status.Error(codes.Internal, err.Error()) } return &types.QueryDenomsMetadataResponse{ Metadatas: metadatas, Pagination: pageRes, }, nil } // DenomMetadata implements Query/DenomMetadata gRPC method. func (k BaseKeeper) DenomMetadata(c context.Context, req *types.QueryDenomMetadataRequest) (*types.QueryDenomMetadataResponse, error) { if req == nil { return nil, status.Errorf(codes.InvalidArgument, "empty request") } if req.Denom == "" { return nil, status.Error(codes.InvalidArgument, "invalid denom") } ctx := sdk.UnwrapSDKContext(c) metadata, found := k.GetDenomMetaData(ctx, req.Denom) if !found { return nil, status.Errorf(codes.NotFound, "client metadata for denom %s", req.Denom) } return &types.QueryDenomMetadataResponse{ Metadata: metadata, }, nil } func (k BaseKeeper) DenomOwners( goCtx context.Context, req *types.QueryDenomOwnersRequest, ) (*types.QueryDenomOwnersResponse, error) { if req == nil { return nil, status.Errorf(codes.InvalidArgument, "empty request") } if req.Denom == "" { return nil, status.Error(codes.InvalidArgument, "empty denom") } ctx := sdk.UnwrapSDKContext(goCtx) denomPrefixStore := k.getDenomAddressPrefixStore(ctx, req.Denom) var denomOwners []*types.DenomOwner pageRes, err := query.FilteredPaginate( denomPrefixStore, req.Pagination, func(key []byte, value []byte, accumulate bool) (bool, error) { if accumulate { address, _, err := types.AddressAndDenomFromBalancesStore(key) if err != nil { return false, err } denomOwners = append( denomOwners, &types.DenomOwner{ Address: address.String(), Balance: k.GetBalance(ctx, address, req.Denom), }, ) } return true, nil }, ) if err != nil { return nil, status.Error(codes.Internal, err.Error()) } return &types.QueryDenomOwnersResponse{DenomOwners: denomOwners, Pagination: pageRes}, nil }

# Legacy Queriers

Module legacy queriers are typically implemented in a ./keeper/querier.go file inside the module's folder. The module manager is used to add the module's queriers to the application's queryRouter via the NewQuerier() method. Typically, the manager's NewQuerier() method simply calls a NewQuerier() method defined in keeper/querier.go, which looks like the following:

Copy func NewQuerier(keeper Keeper) sdk.Querier { return func(ctx sdk.Context, path []string, req abci.RequestQuery) ([]byte, error) { switch path[0] { case QueryType1: return queryType1(ctx, path[1:], req, keeper) case QueryType2: return queryType2(ctx, path[1:], req, keeper) default: return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unknown %s query endpoint: %s", types.ModuleName, path[0]) } } }

This simple switch returns a querier function specific to the type of the received query. At this point of the query lifecycle, the first element of the path (path[0]) contains the type of the query. The following elements are either empty or contain arguments needed to process the query.

The querier functions themselves are pretty straightforward. They generally fetch a value or values from the state using the keeper. Then, they marshall the value(s) using the codec and return the []byte obtained as result.

For a deeper look at queriers, see this example implementation of a querier function (opens new window) from the bank module.

# Next

Learn about BeginBlocker and EndBlocker