Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add DenomOwners gRPC method for x/bank #9533

Merged
merged 16 commits into from
Jun 28, 2021
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Ref: https://keepachangelog.com/en/1.0.0/

### Features

* [\#9533](https://github.com/cosmos/cosmos-sdk/pull/9533) Added a new gRPC method, `DenomOwners`, in `x/bank` to query for all account holders of a specific denomination.
alexanderbez marked this conversation as resolved.
Show resolved Hide resolved
* [\#8077](https://github.com/cosmos/cosmos-sdk/pull/8077) Added support for grpc-web, enabling browsers to communicate with a chain's gRPC server
* [\#8965](https://github.com/cosmos/cosmos-sdk/pull/8965) cosmos reflection now provides more information on the application such as: deliverable msgs, sdk.Config info etc (still in alpha stage).
* [\#8559](https://github.com/cosmos/cosmos-sdk/pull/8559) Added Protobuf compatible secp256r1 ECDSA signatures.
Expand Down
52 changes: 51 additions & 1 deletion docs/core/proto-docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,15 @@
- [GenesisState](#cosmos.bank.v1beta1.GenesisState)

- [cosmos/bank/v1beta1/query.proto](#cosmos/bank/v1beta1/query.proto)
- [DenomOwner](#cosmos.bank.v1beta1.DenomOwner)
- [QueryAllBalancesRequest](#cosmos.bank.v1beta1.QueryAllBalancesRequest)
- [QueryAllBalancesResponse](#cosmos.bank.v1beta1.QueryAllBalancesResponse)
- [QueryBalanceRequest](#cosmos.bank.v1beta1.QueryBalanceRequest)
- [QueryBalanceResponse](#cosmos.bank.v1beta1.QueryBalanceResponse)
- [QueryDenomMetadataRequest](#cosmos.bank.v1beta1.QueryDenomMetadataRequest)
- [QueryDenomMetadataResponse](#cosmos.bank.v1beta1.QueryDenomMetadataResponse)
- [QueryDenomOwnersRequest](#cosmos.bank.v1beta1.QueryDenomOwnersRequest)
- [QueryDenomOwnersResponse](#cosmos.bank.v1beta1.QueryDenomOwnersResponse)
- [QueryDenomsMetadataRequest](#cosmos.bank.v1beta1.QueryDenomsMetadataRequest)
- [QueryDenomsMetadataResponse](#cosmos.bank.v1beta1.QueryDenomsMetadataResponse)
- [QueryParamsRequest](#cosmos.bank.v1beta1.QueryParamsRequest)
Expand Down Expand Up @@ -1703,6 +1706,19 @@ GenesisState defines the bank module's genesis state.



<a name="cosmos.bank.v1beta1.DenomOwner"></a>

### DenomOwner

DenomOwner defines structure representing an account that owns or holds a
particular denominated token. It contains the account address and account
balance of the denominated token.

| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `address` | [string](#string) | | address defines the address that owns a particular denomination. |
| `balance` | [cosmos.base.v1beta1.Coin](#cosmos.base.v1beta1.Coin) | | balance is the balance of the denominated coin for an account. |

<a name="cosmos.bank.v1beta1.QueryAllBalancesRequest"></a>

### QueryAllBalancesRequest
Expand Down Expand Up @@ -1798,6 +1814,40 @@ method.



<a name="cosmos.bank.v1beta1.QueryDenomOwnersRequest"></a>

### QueryDenomOwnersRequest
QueryDenomOwnersRequest defines the request type for the DenomOwners RPC query,
which queries for a paginated set of all account holders of a particular
denomination.


| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `denom` | [string](#string) | | denom defines the coin denomination to query all account holders for. |
| `pagination` | [cosmos.base.query.v1beta1.PageRequest](#cosmos.base.query.v1beta1.PageRequest) | | pagination defines an optional pagination for the request. |






<a name="cosmos.bank.v1beta1.QueryDenomOwnersResponse"></a>

### QueryDenomOwnersResponse
QueryDenomOwnersResponse defines the RPC response of a DenomOwners RPC query.


| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `denom_owners` | [DenomOwner](#cosmos.bank.v1beta1.DenomOwner) | repeated | |
| `pagination` | [cosmos.base.query.v1beta1.PageResponse](#cosmos.base.query.v1beta1.PageResponse) | | pagination defines the pagination in the response. |






<a name="cosmos.bank.v1beta1.QueryDenomsMetadataRequest"></a>

### QueryDenomsMetadataRequest
Expand Down Expand Up @@ -1938,6 +1988,7 @@ Query defines the gRPC querier service.
| `Params` | [QueryParamsRequest](#cosmos.bank.v1beta1.QueryParamsRequest) | [QueryParamsResponse](#cosmos.bank.v1beta1.QueryParamsResponse) | Params queries the parameters of x/bank module. | GET|/cosmos/bank/v1beta1/params|
| `DenomMetadata` | [QueryDenomMetadataRequest](#cosmos.bank.v1beta1.QueryDenomMetadataRequest) | [QueryDenomMetadataResponse](#cosmos.bank.v1beta1.QueryDenomMetadataResponse) | DenomsMetadata queries the client metadata of a given coin denomination. | GET|/cosmos/bank/v1beta1/denoms_metadata/{denom}|
| `DenomsMetadata` | [QueryDenomsMetadataRequest](#cosmos.bank.v1beta1.QueryDenomsMetadataRequest) | [QueryDenomsMetadataResponse](#cosmos.bank.v1beta1.QueryDenomsMetadataResponse) | DenomsMetadata queries the client metadata for all registered coin denominations. | GET|/cosmos/bank/v1beta1/denoms_metadata|
| `DenomOwners` | [QueryDenomOwnersRequest](#cosmos.bank.v1beta1.QueryDenomOwnersRequest) | [QueryDenomOwnersResponse](#cosmos.bank.v1beta1.QueryDenomOwnersResponse) | DenomOwners queries for all account addresses that own a particular token denomination. | GET|/cosmos/bank/v1beta1/denom_owners/{denom}|

<!-- end services -->

Expand Down Expand Up @@ -8259,4 +8310,3 @@ still be used for delegating and for governance votes even while locked.
| <a name="bool" /> bool | | bool | boolean | boolean | bool | bool | boolean | TrueClass/FalseClass |
| <a name="string" /> string | A string must always contain UTF-8 encoded or 7-bit ASCII text. | string | String | str/unicode | string | string | string | String (UTF-8) |
| <a name="bytes" /> bytes | May contain any arbitrary sequence of bytes. | string | ByteString | str | []byte | ByteString | string | String (ASCII-8BIT) |

39 changes: 38 additions & 1 deletion proto/cosmos/bank/v1beta1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,17 @@ service Query {
option (google.api.http).get = "/cosmos/bank/v1beta1/denoms_metadata/{denom}";
}

// DenomsMetadata queries the client metadata for all registered coin denominations.
// DenomsMetadata queries the client metadata for all registered coin
// denominations.
rpc DenomsMetadata(QueryDenomsMetadataRequest) returns (QueryDenomsMetadataResponse) {
option (google.api.http).get = "/cosmos/bank/v1beta1/denoms_metadata";
}

// DenomOwners queries for all account addresses that own a particular token
// denomination.
rpc DenomOwners(QueryDenomOwnersRequest) returns (QueryDenomOwnersResponse) {
option (google.api.http).get = "/cosmos/bank/v1beta1/denom_owners/{denom}";
}
}

// QueryBalanceRequest is the request type for the Query/Balance RPC method.
Expand Down Expand Up @@ -157,3 +164,33 @@ message QueryDenomMetadataResponse {
// metadata describes and provides all the client information for the requested token.
Metadata metadata = 1 [(gogoproto.nullable) = false];
}

// QueryDenomOwnersRequest defines the request type for the DenomOwners RPC query,
// which queries for a paginated set of all account holders of a particular
// denomination.
message QueryDenomOwnersRequest {
// denom defines the coin denomination to query all account holders for.
string denom = 1;

// pagination defines an optional pagination for the request.
cosmos.base.query.v1beta1.PageRequest pagination = 2;
}

// DenomOwner defines structure representing an account that owns or holds a
// particular denominated token. It contains the account address and account
// balance of the denominated token.
message DenomOwner {
// address defines the address that owns a particular denomination.
string address = 1;

// balance is the balance of the denominated coin for an account.
cosmos.base.v1beta1.Coin balance = 2 [(gogoproto.nullable) = false];
}

// QueryDenomOwnersResponse defines the RPC response of a DenomOwners RPC query.
message QueryDenomOwnersResponse {
repeated DenomOwner denom_owners = 1;

// pagination defines the pagination in the response.
cosmos.base.query.v1beta1.PageResponse pagination = 2;
}
58 changes: 58 additions & 0 deletions x/bank/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,61 @@ func (k BaseKeeper) DenomMetadata(c context.Context, req *types.QueryDenomMetada
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)

store := ctx.KVStore(k.storeKey)
balancesStore := prefix.NewStore(store, types.BalancesPrefix)

var denomOwners []*types.DenomOwner
pageRes, err := query.FilteredPaginate(
balancesStore,
req.Pagination,
func(key []byte, value []byte, accumulate bool) (bool, error) {
var balance sdk.Coin
if err := k.cdc.Unmarshal(value, &balance); err != nil {
return false, err
}

if req.Denom != balance.Denom {
return false, nil
}
Comment on lines +196 to +198
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I think we need to have an index to avoid unmarshaling and doing this step

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, #9590 should address this.


if accumulate {
address, err := types.AddressFromBalancesStore(key)
if err != nil {
return false, err
}

denomOwners = append(
denomOwners,
&types.DenomOwner{
Address: address.String(),
Balance: balance,
},
)
}

return true, nil
},
)

if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}

return &types.QueryDenomOwnersResponse{DenomOwners: denomOwners, Pagination: pageRes}, nil
}
94 changes: 91 additions & 3 deletions x/bank/keeper/grpc_query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,12 @@ import (
"fmt"

"github.com/cosmos/cosmos-sdk/simapp"

minttypes "github.com/cosmos/cosmos-sdk/x/mint/types"

"github.com/cosmos/cosmos-sdk/testutil/testdata"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/query"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/cosmos/cosmos-sdk/x/bank/types"
minttypes "github.com/cosmos/cosmos-sdk/x/mint/types"
)

func (suite *IntegrationTestSuite) TestQueryBalance() {
Expand Down Expand Up @@ -305,3 +304,92 @@ func (suite *IntegrationTestSuite) QueryDenomMetadataRequest() {
})
}
}

func (suite *IntegrationTestSuite) TestGRPCDenomOwners() {
ctx := suite.ctx

authKeeper, keeper := suite.initKeepersWithmAccPerms(make(map[string]bool))
suite.Require().NoError(keeper.MintCoins(ctx, minttypes.ModuleName, initCoins))

for i := 0; i < 10; i++ {
acc := authKeeper.NewAccountWithAddress(ctx, authtypes.NewModuleAddress(fmt.Sprintf("account-%d", i)))
authKeeper.SetAccount(ctx, acc)

bal := sdk.NewCoins(sdk.NewCoin(
sdk.DefaultBondDenom,
sdk.TokensFromConsensusPower(initialPower/10, sdk.DefaultPowerReduction),
))
suite.Require().NoError(keeper.SendCoinsFromModuleToAccount(ctx, minttypes.ModuleName, acc.GetAddress(), bal))
}

testCases := map[string]struct {
req *types.QueryDenomOwnersRequest
expPass bool
numAddrs int
hasNext bool
total uint64
}{
"empty request": {
req: &types.QueryDenomOwnersRequest{},
expPass: false,
},
"invalid denom": {
req: &types.QueryDenomOwnersRequest{
Denom: "foo",
},
expPass: true,
numAddrs: 0,
hasNext: false,
total: 0,
},
"valid request - page 1": {
req: &types.QueryDenomOwnersRequest{
Denom: sdk.DefaultBondDenom,
Pagination: &query.PageRequest{
Limit: 6,
CountTotal: true,
},
},
expPass: true,
numAddrs: 6,
hasNext: true,
total: 10,
},
"valid request - page 2": {
req: &types.QueryDenomOwnersRequest{
Denom: sdk.DefaultBondDenom,
Pagination: &query.PageRequest{
Offset: 6,
Limit: 10,
CountTotal: true,
},
},
expPass: true,
numAddrs: 4,
hasNext: false,
total: 10,
},
}

for name, tc := range testCases {
suite.Run(name, func() {
resp, err := suite.queryClient.DenomOwners(gocontext.Background(), tc.req)
if tc.expPass {
suite.NoError(err)
suite.NotNil(resp)
suite.Len(resp.DenomOwners, tc.numAddrs)
suite.Equal(tc.total, resp.Pagination.Total)

if tc.hasNext {
suite.NotNil(resp.Pagination.NextKey)
} else {
suite.Nil(resp.Pagination.NextKey)
}
} else {
suite.Require().Error(err)
}
})
}

suite.Require().True(true)
}
Loading