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 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ if input key is empty, or input data contains empty key.

### 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
40 changes: 39 additions & 1 deletion docs/core/proto-docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@
- [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 @@ -1798,6 +1800,42 @@ 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 |
| ----- | ---- | ----- | ----------- |
| `addresses` | [string](#string) | repeated | addresses defines the set of addresses that own a particular denomination. |
| `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 +1976,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 @@ -8255,4 +8294,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) |

29 changes: 28 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,23 @@ 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;
}

// QueryDenomOwnersResponse defines the RPC response of a DenomOwners RPC query.
message QueryDenomOwnersResponse {
// addresses defines the set of addresses that own a particular denomination.
repeated string addresses = 1;
Copy link
Member

Choose a reason for hiding this comment

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

do we want to include balances here too?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think that'd be useful too. @alexanderbez ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, I'll make this change.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done :)


// pagination defines the pagination in the response.
cosmos.base.query.v1beta1.PageResponse pagination = 2;
}
52 changes: 52 additions & 0 deletions x/bank/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,55 @@ 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 addresses []string
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
}

addresses = append(addresses, address.String())
}

return true, nil
},
)

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

return &types.QueryDenomOwnersResponse{Addresses: addresses, 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.Addresses, 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)
}