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:
parent
6139c0466e
commit
72925fa9ee
1
buf.yaml
1
buf.yaml
@ -12,6 +12,7 @@ lint:
|
||||
- UNARY_RPC
|
||||
- COMMENT_FIELD
|
||||
- PACKAGE_DIRECTORY_MATCH
|
||||
- SERVICE_SUFFIX
|
||||
ignore:
|
||||
- third_party
|
||||
- codec/testdata
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
73
x/bank/keeper/grpc_query.go
Normal file
73
x/bank/keeper/grpc_query.go
Normal 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
|
||||
}
|
||||
110
x/bank/keeper/grpc_query_test.go
Normal file
110
x/bank/keeper/grpc_query_test.go
Normal 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)
|
||||
}
|
||||
@ -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.
|
||||
|
||||
@ -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, ¶ms); 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, ¶ms); err != nil {
|
||||
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
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
70
x/bank/types/query.proto
Normal 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];
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user