Add basic x/bank gRPC query service (#6343)

* Add basic x/bank gRPC query server

* proto lint

* Add pb.go file

* cleanup

* add separate grpc query tests

* Add request validation

* Use gRPC status errors

Co-authored-by: Alexander Bezobchuk <alexanderbez@users.noreply.github.com>
This commit is contained in:
Aaron Craelius 2020-06-05 14:10:04 -04:00 committed by GitHub
parent 6139c0466e
commit 72925fa9ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 2073 additions and 30 deletions

View File

@ -12,6 +12,7 @@ lint:
- UNARY_RPC
- COMMENT_FIELD
- PACKAGE_DIRECTORY_MATCH
- SERVICE_SUFFIX
ignore:
- third_party
- codec/testdata

View File

@ -28,7 +28,6 @@ var (
NewBaseKeeper = keeper.NewBaseKeeper
NewBaseSendKeeper = keeper.NewBaseSendKeeper
NewBaseViewKeeper = keeper.NewBaseViewKeeper
NewQuerier = keeper.NewQuerier
RegisterCodec = types.RegisterCodec
ErrNoInputs = types.ErrNoInputs
ErrNoOutputs = types.ErrNoOutputs
@ -44,8 +43,8 @@ var (
NewOutput = types.NewOutput
ValidateInputsOutputs = types.ValidateInputsOutputs
ParamKeyTable = types.ParamKeyTable
NewQueryBalanceParams = types.NewQueryBalanceParams
NewQueryAllBalancesParams = types.NewQueryAllBalancesParams
NewQueryBalanceRequest = types.NewQueryBalanceRequest
NewQueryAllBalancesRequest = types.NewQueryAllBalancesRequest
ModuleCdc = types.ModuleCdc
ParamStoreKeySendEnabled = types.ParamStoreKeySendEnabled
BalancesPrefix = types.BalancesPrefix
@ -67,8 +66,8 @@ type (
MsgMultiSend = types.MsgMultiSend
Input = types.Input
Output = types.Output
QueryBalanceParams = types.QueryBalanceParams
QueryAllBalancesParams = types.QueryAllBalancesParams
QueryBalanceRequest = types.QueryBalanceRequest
QueryAllBalancesRequest = types.QueryAllBalancesRequest
GenesisBalancesIterator = types.GenesisBalancesIterator
Keeper = keeper.Keeper
GenesisState = types.GenesisState

View File

@ -72,10 +72,10 @@ func GetBalancesCmd(cdc *codec.Codec) *cobra.Command {
denom := viper.GetString(flagDenom)
if denom == "" {
params = types.NewQueryAllBalancesParams(addr)
params = types.NewQueryAllBalancesRequest(addr)
route = fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryAllBalances)
} else {
params = types.NewQueryBalanceParams(addr, denom)
params = types.NewQueryBalanceRequest(addr, denom)
route = fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryBalance)
}

View File

@ -49,10 +49,10 @@ func QueryBalancesRequestHandlerFn(clientCtx client.Context) http.HandlerFunc {
denom := r.FormValue("denom")
if denom == "" {
params = types.NewQueryAllBalancesParams(addr)
params = types.NewQueryAllBalancesRequest(addr)
route = fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryAllBalances)
} else {
params = types.NewQueryBalanceParams(addr, denom)
params = types.NewQueryBalanceRequest(addr, denom)
route = fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryBalance)
}

View File

@ -0,0 +1,73 @@
package keeper
import (
"context"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/bank/types"
)
var _ types.QueryServer = BaseKeeper{}
// Balance implements the Query/Balance gRPC method
func (q BaseKeeper) Balance(c context.Context, req *types.QueryBalanceRequest) (*types.QueryBalanceResponse, error) {
if req == nil {
return nil, status.Errorf(codes.InvalidArgument, "empty request")
}
if len(req.Address) == 0 {
return nil, status.Errorf(codes.InvalidArgument, "invalid address")
}
if req.Denom == "" {
return nil, status.Errorf(codes.InvalidArgument, "invalid denom")
}
ctx := sdk.UnwrapSDKContext(c)
balance := q.GetBalance(ctx, req.Address, req.Denom)
return &types.QueryBalanceResponse{Balance: &balance}, nil
}
// AllBalances implements the Query/AllBalances gRPC method
func (q BaseKeeper) AllBalances(c context.Context, req *types.QueryAllBalancesRequest) (*types.QueryAllBalancesResponse, error) {
if req == nil {
return nil, status.Errorf(codes.InvalidArgument, "empty request")
}
if len(req.Address) == 0 {
return nil, status.Errorf(codes.InvalidArgument, "invalid address")
}
ctx := sdk.UnwrapSDKContext(c)
balances := q.GetAllBalances(ctx, req.Address)
return &types.QueryAllBalancesResponse{Balances: balances}, nil
}
// TotalSupply implements the Query/TotalSupply gRPC method
func (q BaseKeeper) TotalSupply(c context.Context, _ *types.QueryTotalSupplyRequest) (*types.QueryTotalSupplyResponse, error) {
ctx := sdk.UnwrapSDKContext(c)
totalSupply := q.GetSupply(ctx).GetTotal()
return &types.QueryTotalSupplyResponse{Supply: totalSupply}, nil
}
// SupplyOf implements the Query/SupplyOf gRPC method
func (q BaseKeeper) SupplyOf(c context.Context, req *types.QuerySupplyOfRequest) (*types.QuerySupplyOfResponse, error) {
if req == nil {
return nil, status.Errorf(codes.InvalidArgument, "empty request")
}
if req.Denom == "" {
return nil, status.Errorf(codes.InvalidArgument, "invalid denom")
}
ctx := sdk.UnwrapSDKContext(c)
supply := q.GetSupply(ctx).GetTotal().AmountOf(req.Denom)
return &types.QuerySupplyOfResponse{Amount: supply}, nil
}

View File

@ -0,0 +1,110 @@
package keeper_test
import (
gocontext "context"
"github.com/cosmos/cosmos-sdk/baseapp"
sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/cosmos/cosmos-sdk/x/bank"
"github.com/cosmos/cosmos-sdk/x/bank/types"
)
func (suite *IntegrationTestSuite) TestQueryBalance() {
app, ctx := suite.app, suite.ctx
_, _, addr := authtypes.KeyTestPubAddr()
queryHelper := baseapp.NewQueryServerTestHelper(ctx)
types.RegisterQueryServer(queryHelper, app.BankKeeper)
queryClient := types.NewQueryClient(queryHelper)
_, err := queryClient.Balance(gocontext.Background(), &types.QueryBalanceRequest{})
suite.Require().Error(err)
_, err = queryClient.Balance(gocontext.Background(), &types.QueryBalanceRequest{Address: addr})
suite.Require().Error(err)
req := types.NewQueryBalanceRequest(addr, fooDenom)
res, err := queryClient.Balance(gocontext.Background(), req)
suite.Require().NoError(err)
suite.Require().NotNil(res)
suite.True(res.Balance.IsZero())
origCoins := sdk.NewCoins(newFooCoin(50), newBarCoin(30))
acc := app.AccountKeeper.NewAccountWithAddress(ctx, addr)
app.AccountKeeper.SetAccount(ctx, acc)
suite.Require().NoError(app.BankKeeper.SetBalances(ctx, acc.GetAddress(), origCoins))
res, err = queryClient.Balance(gocontext.Background(), req)
suite.Require().NoError(err)
suite.Require().NotNil(res)
suite.True(res.Balance.IsEqual(newFooCoin(50)))
}
func (suite *IntegrationTestSuite) TestQueryAllBalances() {
app, ctx := suite.app, suite.ctx
_, _, addr := authtypes.KeyTestPubAddr()
queryHelper := baseapp.NewQueryServerTestHelper(ctx)
types.RegisterQueryServer(queryHelper, app.BankKeeper)
queryClient := types.NewQueryClient(queryHelper)
_, err := queryClient.AllBalances(gocontext.Background(), &types.QueryAllBalancesRequest{})
suite.Require().Error(err)
req := types.NewQueryAllBalancesRequest(addr)
res, err := queryClient.AllBalances(gocontext.Background(), req)
suite.Require().NoError(err)
suite.Require().NotNil(res)
suite.True(res.Balances.IsZero())
origCoins := sdk.NewCoins(newFooCoin(50), newBarCoin(30))
acc := app.AccountKeeper.NewAccountWithAddress(ctx, addr)
app.AccountKeeper.SetAccount(ctx, acc)
suite.Require().NoError(app.BankKeeper.SetBalances(ctx, acc.GetAddress(), origCoins))
res, err = queryClient.AllBalances(gocontext.Background(), req)
suite.Require().NoError(err)
suite.Require().NotNil(res)
suite.True(res.Balances.IsEqual(origCoins))
}
func (suite *IntegrationTestSuite) TestQueryTotalSupply() {
app, ctx := suite.app, suite.ctx
expectedTotalSupply := bank.NewSupply(sdk.NewCoins(sdk.NewInt64Coin("test", 400000000)))
app.BankKeeper.SetSupply(ctx, expectedTotalSupply)
queryHelper := baseapp.NewQueryServerTestHelper(ctx)
types.RegisterQueryServer(queryHelper, app.BankKeeper)
queryClient := types.NewQueryClient(queryHelper)
res, err := queryClient.TotalSupply(gocontext.Background(), &types.QueryTotalSupplyRequest{})
suite.Require().NoError(err)
suite.Require().NotNil(res)
suite.Require().Equal(expectedTotalSupply.Total, res.Supply)
}
func (suite *IntegrationTestSuite) TestQueryTotalSupplyOf() {
app, ctx := suite.app, suite.ctx
test1Supply := sdk.NewInt64Coin("test1", 4000000)
test2Supply := sdk.NewInt64Coin("test2", 700000000)
expectedTotalSupply := bank.NewSupply(sdk.NewCoins(test1Supply, test2Supply))
app.BankKeeper.SetSupply(ctx, expectedTotalSupply)
queryHelper := baseapp.NewQueryServerTestHelper(ctx)
types.RegisterQueryServer(queryHelper, app.BankKeeper)
queryClient := types.NewQueryClient(queryHelper)
_, err := queryClient.SupplyOf(gocontext.Background(), &types.QuerySupplyOfRequest{})
suite.Require().Error(err)
res, err := queryClient.SupplyOf(gocontext.Background(), &types.QuerySupplyOfRequest{Denom: test1Supply.Denom})
suite.Require().NoError(err)
suite.Require().NotNil(res)
suite.Require().Equal(test1Supply.Amount, res.Amount)
}

View File

@ -41,6 +41,8 @@ type Keeper interface {
UnmarshalSupply(bz []byte) (exported.SupplyI, error)
MarshalSupplyJSON(supply exported.SupplyI) ([]byte, error)
UnmarshalSupplyJSON(bz []byte) (exported.SupplyI, error)
types.QueryServer
}
// BaseKeeper manages transfers between accounts. It implements the Keeper interface.

View File

@ -33,7 +33,7 @@ func NewQuerier(k Keeper) sdk.Querier {
}
func queryBalance(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, error) {
var params types.QueryBalanceParams
var params types.QueryBalanceRequest
if err := types.ModuleCdc.UnmarshalJSON(req.Data, &params); err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
@ -50,7 +50,7 @@ func queryBalance(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, err
}
func queryAllBalance(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, error) {
var params types.QueryAllBalancesParams
var params types.QueryAllBalancesRequest
if err := types.ModuleCdc.UnmarshalJSON(req.Data, &params); err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())

View File

@ -26,7 +26,7 @@ func (suite *IntegrationTestSuite) TestQuerier_QueryBalance() {
suite.Require().NotNil(err)
suite.Require().Nil(res)
req.Data = app.Codec().MustMarshalJSON(types.NewQueryBalanceParams(addr, fooDenom))
req.Data = app.Codec().MustMarshalJSON(types.NewQueryBalanceRequest(addr, fooDenom))
res, err = querier(ctx, []string{types.QueryBalance}, req)
suite.Require().NoError(err)
suite.Require().NotNil(res)
@ -62,7 +62,7 @@ func (suite *IntegrationTestSuite) TestQuerier_QueryAllBalances() {
suite.Require().NotNil(err)
suite.Require().Nil(res)
req.Data = app.Codec().MustMarshalJSON(types.NewQueryAllBalancesParams(addr))
req.Data = app.Codec().MustMarshalJSON(types.NewQueryAllBalancesRequest(addr))
res, err = querier(ctx, []string{types.QueryAllBalances}, req)
suite.Require().NoError(err)
suite.Require().NotNil(res)

View File

@ -12,25 +12,14 @@ const (
QuerySupplyOf = "supply_of"
)
// QueryBalanceParams defines the params for querying an account balance.
type QueryBalanceParams struct {
Address sdk.AccAddress
Denom string
// NewQueryBalanceRequest creates a new instance of QueryBalanceRequest.
func NewQueryBalanceRequest(addr sdk.AccAddress, denom string) *QueryBalanceRequest {
return &QueryBalanceRequest{Address: addr, Denom: denom}
}
// NewQueryBalanceParams creates a new instance of QueryBalanceParams.
func NewQueryBalanceParams(addr sdk.AccAddress, denom string) QueryBalanceParams {
return QueryBalanceParams{Address: addr, Denom: denom}
}
// QueryAllBalancesParams defines the params for querying all account balances
type QueryAllBalancesParams struct {
Address sdk.AccAddress
}
// NewQueryAllBalancesParams creates a new instance of QueryAllBalancesParams.
func NewQueryAllBalancesParams(addr sdk.AccAddress) QueryAllBalancesParams {
return QueryAllBalancesParams{Address: addr}
// NewQueryAllBalancesRequest creates a new instance of QueryAllBalancesRequest.
func NewQueryAllBalancesRequest(addr sdk.AccAddress) *QueryAllBalancesRequest {
return &QueryAllBalancesRequest{Address: addr}
}
// QueryTotalSupply defines the params for the following queries:

1799
x/bank/types/query.pb.go Normal file

File diff suppressed because it is too large Load Diff

70
x/bank/types/query.proto Normal file
View File

@ -0,0 +1,70 @@
syntax = "proto3";
package cosmos_sdk.x.bank.v1;
import "third_party/proto/gogoproto/gogo.proto";
import "types/types.proto";
option go_package = "github.com/cosmos/cosmos-sdk/x/bank/types";
// Query provides defines the gRPC querier service
service Query {
// Balance queries the balance of a single coin for a single account
rpc Balance (QueryBalanceRequest) returns (QueryBalanceResponse) { }
// AllBalances queries the balance of all coins for a single account
rpc AllBalances (QueryAllBalancesRequest) returns (QueryAllBalancesResponse) { }
// TotalSupply queries the total supply of all coins
rpc TotalSupply (QueryTotalSupplyRequest) returns (QueryTotalSupplyResponse) { }
// SupplyOf queries the supply of a single coin
rpc SupplyOf (QuerySupplyOfRequest) returns (QuerySupplyOfResponse) { }
}
// QueryBalanceRequest is the request type for the Query/Balance RPC method
message QueryBalanceRequest {
// address is the address to query balances for
bytes address = 1 [(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"];
// denom is the coin denom to query balances for
string denom = 2;
}
// QueryBalanceResponse is the response type for the Query/Balance RPC method
message QueryBalanceResponse {
// balance is the balance of the coin
cosmos_sdk.v1.Coin balance = 1;
}
// QueryBalanceRequest is the request type for the Query/AllBalances RPC method
message QueryAllBalancesRequest {
// address is the address to query balances for
bytes address = 1 [(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"];
}
// QueryAllBalancesResponse is the response type for the Query/AllBalances RPC method
message QueryAllBalancesResponse {
// balances is the balances of the coins
repeated cosmos_sdk.v1.Coin balances = 1 [(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"];
}
// QueryTotalSupplyRequest is the request type for the Query/TotalSupply RPC method
message QueryTotalSupplyRequest { }
// QueryTotalSupplyResponse is the response type for the Query/TotalSupply RPC method
message QueryTotalSupplyResponse {
// supply is the supply of the coins
repeated cosmos_sdk.v1.Coin supply = 1 [(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"];
}
// QuerySupplyOfRequest is the request type for the Query/SupplyOf RPC method
message QuerySupplyOfRequest {
string denom = 1;
}
// QuerySupplyOfResponse is the response type for the Query/SupplyOf RPC method
message QuerySupplyOfResponse {
// amount is the supply of the coin
string amount = 1 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", (gogoproto.nullable) = false];
}