Skip to main content
Version: v0.47

Query Services

Synopsis

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.

note

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:

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. 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:

x/bank/keeper/grpc_query.go
package keeper

import (
"context"

"cosmossdk.io/math"
gogotypes "github.com/cosmos/gogoproto/types"
"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 err := sdk.ValidateDenom(req.Denom); err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}

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")
}

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 {
denom := string(key)
balance, err := UnmarshalBalanceCompat(k.cdc, value, denom)
if err != nil {
return err
}
balances = append(balances, balance)
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 := math.ZeroInt()

pageRes, err := query.Paginate(accountStore, req.Pagination, func(key, _ []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
}

// SpendableBalanceByDenom implements a gRPC query handler for retrieving an account's
// spendable balance for a specific denom.
func (k BaseKeeper) SpendableBalanceByDenom(ctx context.Context, req *types.QuerySpendableBalanceByDenomRequest) (*types.QuerySpendableBalanceByDenomResponse, 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())
}

if err := sdk.ValidateDenom(req.Denom); err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}

sdkCtx := sdk.UnwrapSDKContext(ctx)

spendable := k.SpendableCoin(sdkCtx, addr, req.Denom)

return &types.QuerySpendableBalanceByDenomResponse{Balance: &spendable}, 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 err := sdk.ValidateDenom(req.Denom); err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}

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 err := sdk.ValidateDenom(req.Denom); err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}

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 err := sdk.ValidateDenom(req.Denom); err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}

ctx := sdk.UnwrapSDKContext(goCtx)
denomPrefixStore := k.getDenomAddressPrefixStore(ctx, req.Denom)

var denomOwners []*types.DenomOwner
pageRes, err := query.FilteredPaginate(
denomPrefixStore,
req.Pagination,
func(key []byte, _ []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
}

func (k BaseKeeper) SendEnabled(goCtx context.Context, req *types.QuerySendEnabledRequest) (*types.QuerySendEnabledResponse, error) {
if req == nil {
return nil, status.Errorf(codes.InvalidArgument, "empty request")
}
ctx := sdk.UnwrapSDKContext(goCtx)
resp := &types.QuerySendEnabledResponse{}
if len(req.Denoms) > 0 {
store := ctx.KVStore(k.storeKey)
for _, denom := range req.Denoms {
if se, ok := k.getSendEnabled(store, denom); ok {
resp.SendEnabled = append(resp.SendEnabled, types.NewSendEnabled(denom, se))
}
}
} else {
store := k.getSendEnabledPrefixStore(ctx)
var err error

resp.Pagination, err = query.FilteredPaginate(
store,
req.Pagination,
func(key []byte, value []byte, accumulate bool) (bool, error) {
if accumulate {
var enabled gogotypes.BoolValue
k.cdc.MustUnmarshal(value, &enabled)

resp.SendEnabled = append(resp.SendEnabled, types.NewSendEnabled(string(key), enabled.Value))
}
return true, nil
},
)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
}

return resp, nil
}

Calling queries from the State Machine

The Cosmos SDK v0.47 introduces a new cosmos.query.v1.module_query_safe Protobuf annotation which is used to state that a query that is safe to be called from within the state machine, for example:

  • a Keeper's query function can be called from another module's Keeper,
  • ADR-033 intermodule query calls,
  • CosmWasm contracts can also directly interact with these queries.

If the module_query_safe annotation set to true, it means:

  • The query is deterministic: given a block height it will return the same response upon multiple calls, and doesn't introduce any state-machine breaking changes across SDK patch versions.
  • Gas consumption never fluctuates across calls and across patch versions.

If you are a module developer and want to use module_query_safe annotation for your own query, you have to ensure the following things:

  • the query is deterministic and won't introduce state-machine-breaking changes without coordinated upgrades
  • it has its gas tracked, to avoid the attack vector where no gas is accounted for on potentially high-computation queries.